Skip to content

Instantly share code, notes, and snippets.

@topherhunt
Created October 18, 2019 15:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save topherhunt/ec1b64599e5aa15d82aa090bcc6f4ef7 to your computer and use it in GitHub Desktop.
Save topherhunt/ec1b64599e5aa15d82aa090bcc6f4ef7 to your computer and use it in GitHub Desktop.
Auth plug load_current_user using cond vs. with
# These aren't 100% parallel (they're from different apps with slightly different auth rules)
# but they're close enough to demonstrate what `with` can do, I think.
# Version 1: using `cond`
# Pro: very linear. just go down the list and pick the first truthy result.
# Con: Using `cond` maybe doesn't sufficiently emphasize how important the ordering is
# here. If you naively swapped the order of two conditions, it could wreck the security.
# Basically, in this version, I'm relying on the dev's caution and willingness to read
# through my extensive comments.
def load_current_user(conn, _opts) do
cond do
# If a user is already loaded, nothing to do
current_user_assigned?(conn) -> conn
# If no user is logged in, explicitly set current_user to nil
no_login_session?(conn) -> assign(conn, :current_user, nil)
# If the session is expired, log me out (must be before load_user_from_session!)
session_expired?(conn) -> logout!(conn)
# If we can find the user with this id, assign them
user = load_user_from_session(conn) -> set_assigned_user(conn, user)
# If no user was found by that id, the session is invalid. Log me out.
true -> logout!(conn)
end
end
# Version 2: using `with`
# Pro: I love that this pattern so clearly documents the ordering/dependency of different
# conditions, and what each failure case means.
# Con: The get_token / parse_token / get_user helpers have to be custom-made to return
# :ok/:error tuples, as opposed to my more common pattern of "return a value on success,
# or nil on failure".
def load_user_from_token(conn, _opts) do
with :ok <- ensure_no_user_assigned(conn),
{:ok, token} <- get_token(conn),
{:ok, user_id} <- parse_token(token),
{:ok, user} <- get_user(user_id) do
assign(conn, :current_user, user)
else
{:error, :user_already_assigned} -> conn
{:error, :no_auth_token} -> assign(conn, :current_user, nil)
{:error, :token_invalid} -> halt_with_invalid_token_error(conn)
{:error, :user_not_found} -> halt_with_invalid_token_error(conn)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment