Adding current_user_id to metadata
aovertus opened this issue ยท 1 comments
Hi @smpallen99,
First of all, thanks for your work with coherence & ex_admin! Its pretty awesome ๐.
I'm new to Elixir/Phoenix, in my current project I've added the current_user_id to web phoenix log in order to track user actions.
defmodule MyAppWeb.Plug.LogAuthenticatedUser do
require Logger
alias Plug.Conn
@behaviour Plug
def init(_opts), do: nil
def call(conn, _req_id_header) do
conn
|> get_user_id
|> set_user_id
end
defp get_user_id(conn) do
%{ current_user: current_user } = conn.assigns
case current_user do
%MyAppWeb.Coherence.User{} -> {conn, Integer.to_string(current_user.id)}
_ -> {conn, ""}
end
end
defp set_user_id({conn, user_id}) do
Logger.metadata(user_id: user_id)
Conn.put_resp_header(conn, "current_user_id", user_id)
end
end
pipeline :protected do
...
plug Coherence.Authentication.Session, protected: true
plug MyAppWeb.Plug.LogAuthenticatedUser
end
I was wondering if this is something we could add to the coherence library.
Thanks for your consideration
@aovertus Thanks for the feedback!!
This is the first I've seen for a request like this. I'm reluctant to add something specialize like this unless there is a demand from multiple users. If it were added to coherence, I would probably use the same approach as you did here and add it as a plug. However, it would need to be more generic with configuration like the field name for the response header.
Here is a version that is configurable, will not throw exceptions it called from :browser pipeline, or accidently called before the coherence auth plug.
defmodule MyAppWeb.Plug.LogAuthenticatedUser do
@moduledoc """
Plug to log authenticated user's id.
## Examples
defmodule MyAppWeb.Router do
pipeline :protected do
# ...
plug Coherence.Authentication.Session, protected: true
plug MyAppWeb.Plug.LogAuthentictedUser
# or override the field name
plug MyAppWeb.Plug.LogAuthentictedUser, headner_field_name: "user_id"
# or disable it with
plug MyAppWeb.Plug.LogAuthentictedUser, headner_field_name: false
# or set :current_user_id_field in The projects config
plug MyAppWeb.Plug.LogAuthentictedUser
"""
require Logger
alias Plug.Conn
@behaviour Plug
# this needs to be bound at compile time, otherwise it will fail in a
# production release where mix is not available.
@app Mix.Project.config[:app]
def init(opts) do
# set the header field name. Use the value provided in opts when the plug is
# called, otherwise try the :current_user_id_field value from the project config,
# otherwise use "current_user_id"
%{
header_field_name: Keyword.get(
opts, :header_field_name, Application.get_env(@app, :current_user_field,
"current_user_id"),
}
end
def call(conn, opts) do
conn
|> get_user_id
|> set_user_id(opts)
end
defp get_user_id(conn) do
# note that the following will throw an exception is :current_user is not assigned
# %{ current_user: current_user } = conn.assigns
# so its safer to do a case on conn.assigns and match on %{current_user: current_user}
# even better, use the current_user/1 helper that take into account changing the
# current_user assigns key.
case Coherence.current_user(conn) do
nil -> {conn, nil}
current_user ->
# simple to_string here will handle the case where id is integer, binary, or nil
{conn, to_string(current_user.id)}
end
end
# probably not needed, but just in case its set to an empty string
defp set_user_id({conn, _user_id}, %{header_field_name: ""}) do
conn
end
# don't try to set the user_id if its nil. Note that to_string of nil is ""
defp set_user_id({conn, nil_or_empty, _opts) when nil_or_empty in [nil, ""] do
conn
end
# valid user_id and header_field_name
defp set_user_id({conn, user_id}, %{header_field_name: field_name}) when is_binary(field_name) do
Logger.metadata(user_id: user_id)
Conn.put_resp_header(conn, field_name, user_id)
end
# catch all
defp set_user_id({conn, _}, _) do
conn
end
end
Closing this for now. If others are interested, please reopen and submit a PR (with tests and docs).