Skip to content

Instantly share code, notes, and snippets.

@jorbs
Last active September 22, 2021 20:31
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 jorbs/41c764e934e32350e6bbed3c344725ba to your computer and use it in GitHub Desktop.
Save jorbs/41c764e934e32350e6bbed3c344725ba to your computer and use it in GitHub Desktop.
Postgres Session Store Plug
defmodule MyAppWeb.Plugs.Session.Postgres do
@behaviour Plug.Session.Store
alias MyApp.Repo
alias MyApp.UserSession
@impl true
def init(opts) do
%{session_max_age: Keyword.fetch!(opts, :session_max_age)}
end
@impl true
def get(_conn, nil, _opts), do: {nil, %{}}
def get(_conn, "", _opts), do: {nil, %{}}
def get(_conn, sid, _opts) do
user_session = Repo.get_by(UserSession, sid: sid)
if user_session && !check_and_expire_session(user_session) do
{sid, %{"current_user_id" => user_session.user_id}}
else
{nil, %{}}
end
end
@impl true
def put(_conn, nil, data, %{session_max_age: max_age}) do
user_id = data["current_user_id"]
sid = generate_sid()
if user_id do
expires_at = DateTime.utc_now() |> DateTime.add(max_age) |> DateTime.truncate(:second)
user_session =
case Repo.get_by(UserSession, user_id: user_id) do
nil -> %UserSession{user_id: user_id}
user_session -> user_session
end
user_session
|> UserSession.changeset(%{sid: sid, expires_at: expires_at})
|> Repo.insert_or_update!()
end
sid
end
def put(_conn, sid, data, _opts) do
user_id = data["current_user_id"]
if user_id do
user_session = Repo.get_by(UserSession, sid: sid, user_id: user_id)
if check_and_expire_session(user_session) do
generate_sid()
else
user_session.sid
end
else
generate_sid()
end
end
@impl true
def delete(_conn, sid, _opts) do
UserSession
|> Repo.get_by(sid: sid)
|> Repo.delete()
:ok
end
defp generate_sid() do
Base.encode64(:crypto.strong_rand_bytes(96))
end
defp check_and_expire_session(session) do
is_expired? = DateTime.compare(session.expires_at, DateTime.utc_now()) == :lt
if is_expired? do
Repo.delete(session)
end
is_expired?
end
end
defmodule MyApp.UserSession do
use Ecto.Schema
import Ecto.Changeset
schema "user_sessions" do
field :expires_at, :utc_datetime
field :sid, :string
field :user_id, :id
timestamps()
end
@doc false
def changeset(user_session, attrs) do
user_session
|> cast(attrs, [:sid, :expires_at])
|> validate_required([:sid, :expires_at])
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment