Skip to content

Commit

Permalink
Merge pull request #5 from tofran/session-extending-improvements
Browse files Browse the repository at this point in the history
Session extending improvements
  • Loading branch information
tofran committed May 9, 2023
2 parents 4d0766d + b0dfbaf commit 1473e5b
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 103 deletions.
1 change: 0 additions & 1 deletion demo/lib/ecto_sessions_demo_web/auth_pipelines.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ defmodule EctoSessionsDemoWeb.AuthPipelines do
defp get_session_and_current_user(auth_token) do
with session when not is_nil(session) <- Sessions.get_session(auth_token: auth_token),
user when not is_nil(session) <- Accounts.get_user(session.user_id) do
session = Sessions.refresh_session(session)
{session, user}
else
_ ->
Expand Down
47 changes: 41 additions & 6 deletions lib/ecto_sessions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,13 @@ defmodule EctoSessions do
def get_session(filters, options \\ []) when is_list(filters) do
get_sessions_query(filters, options)
|> @repo.one(prefix: unquote(prefix))
|> maybe_extend_session(options)
end

def get_session!(filters, options \\ []) do
get_sessions_query(filters, options)
|> @repo.one!(prefix: unquote(prefix))
|> maybe_extend_session(options)
end

def list_sessions(filters, options \\ []) do
Expand All @@ -174,7 +176,7 @@ defmodule EctoSessions do
|> @repo.all(prefix: unquote(prefix))
end

def refresh_session(session) do
def extend_session(session) do
Session.changeset(session)
|> update_session!()
end
Expand Down Expand Up @@ -210,6 +212,30 @@ defmodule EctoSessions do

delete_count
end

defp maybe_extend_session({:ok, session}, options) do
{
:ok,
maybe_extend_session(session, options)
}
end

defp maybe_extend_session(%Session{} = session, options) do
should_extend_session =
Keyword.get(
options,
:should_extend_session,
Config.get_auto_extend_session()
)

if should_extend_session do
extend_session(session)
end

session
end

defp maybe_extend_session(result, options), do: result
end
end

Expand Down Expand Up @@ -262,11 +288,20 @@ defmodule EctoSessions do

@doc """
Retrieves a session from the database.
Options:
- `preload`: ecto preloads, see `get_sessions_query/1`.
- `auto_extend_session`: override the default `auto_extend_session` config
(`Config.get_auto_extend_session()`). `true` will perform session extending,
`false` to prevent this behaviour.
"""
@callback get_session(filters :: any, options :: list) :: {atom, Ecto.Schema.t()}

@doc """
Retrieves a session from the database using `Repo.one!`
Retrieves a session from the database using `Ecto.Repo.one!`
See `get_session/2` for more information.
"""
@callback get_session!(filters :: any, options :: list) :: Ecto.Schema.t()

Expand All @@ -283,20 +318,20 @@ defmodule EctoSessions do
@doc """
Given a session, ensure `expires_at` is updated according to the `EctoSessions.Config`.
"""
@callback refresh_session(Ecto.Schema.t()) :: Ecto.Schema.t()
@callback extend_session(Ecto.Schema.t()) :: Ecto.Schema.t()

@doc """
Deletes the session using `Repo.delete`.
Deletes the session using `Ecto.Repo.delete`.
"""
@callback delete_session(Ecto.Schema.t()) :: {atom, any}

@doc """
Deletes the session using `Repo.delete!`.
Deletes the session using `Ecto.Repo.delete!`.
"""
@callback delete_session!(Ecto.Schema.t()) :: any

@doc """
Updates a session using `Repo.update!`.
Updates a session using `Ecto.Repo.update!`.
"""
@callback update_session!(Ecto.Changeset.t()) :: Ecto.Schema.t()

Expand Down
161 changes: 83 additions & 78 deletions lib/ecto_sessions/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,55 +10,84 @@ defmodule EctoSessions.Config do
hashing_algorithm: :sha512,
secret_salt: "my-unique-secret",
session_ttl: 60 * 60 * 24,
refresh_session_ttl: 60 * 60 * 12
extend_session_stale_time: 60 * 60 * 6,
auto_extend_session: true
```
## Configuration options
### `auth_token_length`
Returns the length of the session auth token.
The length of the session auth token (from `auth_token_length` application env).
Defaults to `64`. Can be changed at any time, applies for new sessions only.
### `hashing_algorithm`
Returns the hashing algorithm to use. Can be one of the following:
- `:sha256` the default;
- `:sha`
- `:sha224`
- `:sha256`
- `:sha384`
- `:sha512`
- `:sha3_224`
- `:sha3_256`
- `:sha3_384`
- `:sha3_512`
- `:blake2b`
- `:blake2s`
- `:ripemd160`
- `nil` to not hash, and store tokens in plaintext;
The hashing algorithm to use (from `hashing_algorithm` application env).
Can be one of the following:
- `:sha`
- `:sha224`
- `:sha256` (the default)
- `:sha384`
- `:sha512`
- `:sha3_224`
- `:sha3_256`
- `:sha3_384`
- `:sha3_512`
- `:blake2b`
- `:blake2s`
- `:ripemd160`
- `nil` to not hash, and store tokens in plaintext;
See [erlang's crypto `hash_algorithm()`](https://www.erlang.org/doc/man/crypto.html#type-hash_algorithm)
for more information.
### hashing_algorithm
### `secret_salt`
Optional *secret salt*, commonly known as *pepper* to be added to the
The Optional *secret salt*, commonly known as *pepper* to be added to the
auth token before hashing.
Runtime configuration.
**Once changed, invalidates all sessions, as lookup is no longer possible.**
Can only be set if `hashing_algorithm` is not `nil`.
Set to `nil` to not salt auth_tokens. Defaults to `nil`.
### `session_ttl`
How many seconds since the creation a session should last.
Defaults to 7 days (`60 * 60 * 24 * 7`).
For how many should the session be valid. Both since its creation or when extended.
Runtime configuration from `session_ttl`, defaults to 7 days (`60 * 60 * 24 * 7`).
### `extend_session_stale_time`
The number of seconds from the `session_ttl` to consider the session as needing to
be extended, it is a threshold, to keep the value unchanged.
This prevents constant update of the session `expires_at`.
When this threshold has been met, the Session's `expires_at` will be updated to _now_ plus the
`session_ttl`.
Set to `nil` to prevent session extending, and `0` to extend it every time.
Session extending is attempted (if enabled), when:
- Calling `EctoSessions.get_session` or `EctoSessions.get_session!` when the config
`auto_extend_session` is `true`.
- Calling `EctoSessions.get_session` or `EctoSessions.get_session!` and passing
the option `:should_extend_session` as `true` (overrides the default).
- Manually calling `EctoSessions.extend_session`.
- Manual update passing tru `Session.changeset` is called.
### `refresh_session_ttl`
Runtime configuration. Defaults to 1 day (`60 * 60 * 24`).
Must be lower than `session_ttl`.
The number of seconds that should be added to the session expires at when
calling `Session.changeset()`.
`nil` to prevent this behaviour.
Defaults to 7 days (`60 * 60 * 24 * 7`).
### `auto_extend_session`
The default value for the `:should_extend_session` option, used when not explicitly passed to
`EctoSessions.get_session` and `EctoSessions.get_session!`.
When `true`, session extending is attempted automatically after retrieving a single session.
Set to `false` to prevent this behaviour.
Runtime configuration, defaults to `true`.
Should only be set if `extend_session_stale_time` is not `nil`.
See `extend_session_stale_time` above for more information.
"""

Expand All @@ -68,65 +97,17 @@ defmodule EctoSessions.Config do
quote do
@ecto_sessions_module unquote(ecto_sessions_module)

@doc """
Returns the length of the session auth token (from `auth_token_length` application env).
Defaults to `64`. Can be changed at any time, applies for new sessions only.
"""
def get_auth_token_length(), do: get_env(:auth_token_length, 64)

@doc """
Returns the hashing algorithm to use (from `hashing_algorithm` application env).
Can be one of the following:
- `:sha256` the default;
- `:sha`
- `:sha224`
- `:sha256`
- `:sha384`
- `:sha512`
- `:sha3_224`
- `:sha3_256`
- `:sha3_384`
- `:sha3_512`
- `:blake2b`
- `:blake2s`
- `:ripemd160`
- `nil` to not hash, and store tokens in plaintext;
See [erlang's crypto `hash_algorithm()`](https://www.erlang.org/doc/man/crypto.html#type-hash_algorithm)
for more information.
"""
def get_hashing_algorithm(), do: get_env(:hashing_algorithm, :sha256)

@doc """
Optional *secret salt*, commonly known as *pepper* to be added to the
auth token before hashing. Runtime configuration.
**Once changed, invalidates all sessions, as lookup is no longer possible.**
Can only be set if `hashing_algorithm` is not `nil`.
Set to `nil` to not salt auth_tokens. Defaults to `nil`.
"""
def get_secret_salt(), do: get_env(:secret_salt)

@doc """
How many seconds since the creation a session should last.
Runtime configuration from `session_ttl`, defaults to 7 days (`60 * 60 * 24 * 7`).
"""
def get_session_ttl(), do: get_env(:session_ttl, 60 * 60 * 24 * 7)

@doc """
The number of seconds from the `session_ttl` to consider the session as needing to
be refreshed. This prevents constant update of the session `expires_at`.
Set to `nil` to prevent session refreshing, and `0` to refresh it every time.
Session is refreshed by any update that calls `Session.changeset` and manually
calling `refresh_session`.
def get_extend_session_stale_time(), do: get_env(:extend_session_stale_time, 60 * 60 * 24)

Runtime configuration from `refresh_session_ttl`, defaults to 1 day (`60 * 60 * 24`).
Must be lower than `session_ttl`.
TODO: RENAME this is not really a TTL,
it is **a threshold, in seconds to keep the value unchanged**.
"""
def get_refresh_session_ttl(), do: get_env(:refresh_session_ttl, 60 * 60 * 24)
def get_auto_extend_session(), do: get_env(:auto_extend_session, true)

defp get_env(key, default \\ nil) do
{:ok, application} = :application.get_application(unquote(__CALLER__.module))
Expand All @@ -141,9 +122,33 @@ defmodule EctoSessions.Config do
end
end

@doc """
Returns the config `auth_token_length`.
"""
@callback get_auth_token_length() :: non_neg_integer()

@doc """
Returns the config `hashing_algorithm`.
"""
@callback get_hashing_algorithm() :: atom()

@doc """
Returns the config `secret_salt`.
"""
@callback get_secret_salt() :: binary() | nil

@doc """
Returns the config `session_ttl`.
"""
@callback get_session_ttl() :: non_neg_integer()
@callback get_refresh_session_ttl() :: non_neg_integer()

@doc """
Returns the config `extend_session_stale_time`.
"""
@callback get_extend_session_stale_time() :: non_neg_integer()

@doc """
Returns the config `auto_extend_session`.
"""
@callback get_auto_extend_session() :: boolean()
end
6 changes: 3 additions & 3 deletions lib/ecto_sessions/session.ex
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,11 @@ defmodule EctoSessions.Session do
end

def get_new_expires_at(current_expires_at) do
case Config.get_refresh_session_ttl() do
case Config.get_extend_session_stale_time() do
nil ->
current_expires_at

refresh_session_ttl ->
extend_session_stale_time ->
proposed_expired_at =
DateTime.add(
DateTime.utc_now(),
Expand All @@ -170,7 +170,7 @@ defmodule EctoSessions.Session do
expired_at_threshold =
DateTime.add(
current_expires_at,
refresh_session_ttl,
extend_session_stale_time,
:second
)

Expand Down
18 changes: 9 additions & 9 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
%{
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
"earmark_parser": {:hex, :earmark_parser, "1.4.30", "0b938aa5b9bafd455056440cdaa2a79197ca5e693830b4a982beada840513c5f", [:mix], [], "hexpm", "3b5385c2d36b0473d0b206927b841343d25adb14f95f0110062506b300cd5a1b"},
"ecto": {:hex, :ecto, "3.7.2", "44c034f88e1980754983cc4400585970b4206841f6f3780967a65a9150ef09a8", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a600da5772d1c31abbf06f3e4a1ffb150e74ed3e2aa92ff3cee95901657a874e"},
"ecto_sql": {:hex, :ecto_sql, "3.7.2", "55c60aa3a06168912abf145c6df38b0295c34118c3624cf7a6977cd6ce043081", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0 or ~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c218ea62f305dcaef0b915fb56583195e7b91c91dcfb006ba1f669bfacbff2a"},
"ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"},
"db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"earmark_parser": {:hex, :earmark_parser, "1.4.32", "fa739a0ecfa34493de19426681b23f6814573faee95dfd4b4aafe15a7b5b32c6", [:mix], [], "hexpm", "b8b0dd77d60373e77a3d7e8afa598f325e49e8663a51bcc2b88ef41838cca755"},
"ecto": {:hex, :ecto, "3.10.1", "c6757101880e90acc6125b095853176a02da8f1afe056f91f1f90b80c9389822", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2ac4255f1601bdf7ac74c0ed971102c6829dc158719b94bd30041bbad77f87a"},
"ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"},
"ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
"telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"},
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
}

0 comments on commit 1473e5b

Please sign in to comment.