Skip to content

Commit 733a543

Browse files
authored
Merge pull request #385 from danschultzer/refactor-session
Refactor session and persistent session store handling
2 parents ff44430 + a3e76b0 commit 733a543

File tree

6 files changed

+53
-59
lines changed

6 files changed

+53
-59
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* [`Pow.Phoenix.RegistrationView`] Now renders `:password_confirmation` field instead of `:confirm_password`
1818
* [`PowEmailConfirmation.Ecto.Schema`] No longer validates if `:email` has been taken before setting `:unconfirmed_email`
1919
* [`PowEmailConfirmation.Phoenix.ControllerCallbacks`] Now prevents user enumeration attack for `PowInvitation.Phoenix.InvitationController.create/2`
20+
* [`PowPersistentSession.Plug.Cookie`] Changed default cookie name to `persistent_session`
21+
* [`PowPersistentSession.Plug.Cookie`] Removed renewal of cookie as the token will always expire
2022

2123
## Removed
2224

lib/extensions/persistent_session/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# PowPersistentSession
22

3-
This extension will permit reissuing sessions, by setting a cookie with a 30-day expiration (renewed on every request). The token in this cookie can be used exactly once to create the session. When the session has been created, the cookie will be updated with a new id.
3+
This extension can reissue sessions by setting a cookie with a token that may be used exactly once to issue the session. The cookie and token will expire after 30 days. Once the session has been issued, a new cookie and token will be set that expires after another 30 days.
44

55
## Installation
66

lib/extensions/persistent_session/plug/cookie.ex

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ defmodule PowPersistentSession.Plug.Cookie do
22
@moduledoc """
33
This plug will handle persistent user sessions with cookies.
44
5-
By default, the cookie will expire after 30 days. The cookie expiration will
6-
be renewed on every request where a user is assigned to the conn. The token
7-
in the cookie can only be used once to create a session.
5+
The cookie and token will expire after 30 days. The token in the cookie can
6+
only be used once to create a session.
87
98
If an assigned private `:pow_session_metadata` key exists in the conn with a
109
keyword list containing a `:fingerprint` key, that fingerprint value will be
@@ -30,8 +29,8 @@ defmodule PowPersistentSession.Plug.Cookie do
3029
* `:cache_store_backend` - see `PowPersistentSession.Plug.Base`
3130
3231
* `:persistent_session_cookie_key` - session key name. This defaults to
33-
"persistent_session_cookie". If `:otp_app` is used it'll automatically
34-
prepend the key with the `:otp_app` value.
32+
"persistent_session". If `:otp_app` is used it'll automatically prepend
33+
the key with the `:otp_app` value.
3534
3635
* `:persistent_session_ttl` - used for both backend store and max age for
3736
cookie. See `PowPersistentSession.Plug.Base` for more.
@@ -74,7 +73,7 @@ defmodule PowPersistentSession.Plug.Cookie do
7473
alias Plug.Conn
7574
alias Pow.{Config, Operations, Plug, UUID}
7675

77-
@cookie_key "persistent_session_cookie"
76+
@cookie_key "persistent_session"
7877
@cookie_expiration_timeout 10
7978

8079
@doc """
@@ -146,6 +145,7 @@ defmodule PowPersistentSession.Plug.Cookie do
146145
"""
147146
@spec delete(Conn.t(), Config.t()) :: Conn.t()
148147
def delete(conn, config) do
148+
conn = Conn.fetch_cookies(conn)
149149
cookie_key = cookie_key(config)
150150

151151
case conn.req_cookies[cookie_key] do
@@ -203,7 +203,6 @@ defmodule PowPersistentSession.Plug.Cookie do
203203
conn
204204
|> Conn.fetch_cookies()
205205
|> maybe_authenticate(user, config)
206-
|> maybe_renew(config)
207206
end
208207

209208
defp maybe_authenticate(conn, nil, config) do
@@ -315,26 +314,6 @@ defmodule PowPersistentSession.Plug.Cookie do
315314
end
316315
end
317316

318-
defp maybe_renew(conn, config) do
319-
cookie_key = cookie_key(config)
320-
321-
with user when not is_nil(user) <- Plug.current_user(conn, config),
322-
nil <- conn.resp_cookies[cookie_key] do
323-
renew(conn, cookie_key, config)
324-
else
325-
_ -> conn
326-
end
327-
end
328-
329-
defp renew(conn, cookie_key, config) do
330-
opts = cookie_opts(config)
331-
332-
case conn.req_cookies[cookie_key] do
333-
nil -> conn
334-
value -> Conn.put_resp_cookie(conn, cookie_key, value, opts)
335-
end
336-
end
337-
338317
defp cookie_id(config) do
339318
uuid = UUID.generate()
340319

lib/pow/plug/session.ex

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,7 @@ defmodule Pow.Plug.Session do
130130
@spec fetch(Conn.t(), Config.t()) :: {Conn.t(), map() | nil}
131131
def fetch(conn, config) do
132132
{store, store_config} = store(config)
133-
conn = Conn.fetch_session(conn)
134-
key = Conn.get_session(conn, session_key(config))
133+
key = client_store_fetch(conn, config)
135134

136135
{key, store.get(store_config, key)}
137136
|> convert_old_session_value()
@@ -159,21 +158,18 @@ defmodule Pow.Plug.Session do
159158
@impl true
160159
@spec create(Conn.t(), map(), Config.t()) :: {Conn.t(), map()}
161160
def create(conn, user, config) do
162-
conn = Conn.fetch_session(conn)
163161
{store, store_config} = store(config)
164162
metadata = Map.get(conn.private, :pow_session_metadata, [])
165163
{user, metadata} = session_value(user, metadata)
166164
key = session_id(config)
167-
session_key = session_key(config)
168165

169166
store.put(store_config, key, {user, metadata})
170167

171168
conn =
172169
conn
173170
|> delete(config)
174171
|> Conn.put_private(:pow_session_metadata, metadata)
175-
|> Conn.put_session(session_key, key)
176-
|> Conn.configure_session(renew: true)
172+
|> client_store_put(key, config)
177173

178174
{conn, user}
179175
end
@@ -199,14 +195,12 @@ defmodule Pow.Plug.Session do
199195
@impl true
200196
@spec delete(Conn.t(), Config.t()) :: Conn.t()
201197
def delete(conn, config) do
202-
conn = Conn.fetch_session(conn)
203-
key = Conn.get_session(conn, session_key(config))
198+
key = client_store_fetch(conn, config)
204199
{store, store_config} = store(config)
205-
session_key = session_key(config)
206200

207201
store.delete(store_config, key)
208202

209-
Conn.delete_session(conn, session_key)
203+
client_store_delete(conn, config)
210204
end
211205

212206
# TODO: Remove by 1.1.0
@@ -267,4 +261,23 @@ defmodule Pow.Plug.Session do
267261
end
268262

269263
defp timestamp, do: :os.system_time(:millisecond)
264+
265+
defp client_store_fetch(conn, config) do
266+
conn
267+
|> Conn.fetch_session()
268+
|> Conn.get_session(session_key(config))
269+
end
270+
271+
defp client_store_put(conn, value, config) do
272+
conn
273+
|> Conn.fetch_session()
274+
|> Conn.put_session(session_key(config), value)
275+
|> Conn.configure_session(renew: true)
276+
end
277+
278+
defp client_store_delete(conn, config) do
279+
conn
280+
|> Conn.fetch_session()
281+
|> Conn.delete_session(session_key(config))
282+
end
270283
end

test/extensions/persistent_session/phoenix/controllers/controller_callbacks_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule PowPersistentSession.Phoenix.ControllerCallbacksTest do
55

66
@valid_params %{"email" => "[email protected]", "password" => "secret1234"}
77
@max_age Integer.floor_div(:timer.hours(30) * 24, 1000)
8-
@cookie_key "persistent_session_cookie"
8+
@cookie_key "persistent_session"
99

1010
describe "Pow.Phoenix.SessionController.create/2" do
1111
test "generates cookie", %{conn: conn, ets: ets} do

test/extensions/persistent_session/plug/cookie_test.exs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
88
alias PowPersistentSession.{Plug.Cookie, Store.PersistentSessionCache}
99
alias PowPersistentSession.Test.Users.User
1010

11+
@cookie_key "persistent_session"
1112
@max_age Integer.floor_div(:timer.hours(24) * 30, 1000)
1213
@custom_cookie_opts [domain: "domain.com", max_age: 1, path: "/path", http_only: false, secure: true, extra: "SameSite=Lax"]
1314

@@ -26,7 +27,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
2627
{:ok, %{conn: conn, config: config, ets: ets}}
2728
end
2829

29-
defp store_persistent(conn, ets, id, value, cookie_key \\ "persistent_session_cookie") do
30+
defp store_persistent(conn, ets, id, value, cookie_key \\ @cookie_key) do
3031
PersistentSessionCache.put([backend: ets], id, value)
3132
persistent_cookie(conn, cookie_key, id)
3233
end
@@ -41,7 +42,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
4142
expected_config = [mod: Session, plug: Session] ++ config
4243

4344
assert {Cookie, ^expected_config} = conn.private[:pow_persistent_session]
44-
refute conn.resp_cookies["persistent_session_cookie"]
45+
refute conn.resp_cookies[@cookie_key]
4546
end
4647

4748
test "call/2 assigns user from cookie", %{conn: conn, ets: ets} do
@@ -53,7 +54,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
5354
|> Cookie.call(Cookie.init([]))
5455

5556
assert Plug.current_user(conn) == user
56-
assert %{value: new_id, max_age: @max_age, path: "/"} = conn.resp_cookies["persistent_session_cookie"]
57+
assert %{value: new_id, max_age: @max_age, path: "/"} = conn.resp_cookies[@cookie_key]
5758
refute new_id == id
5859
assert PersistentSessionCache.get([backend: ets], id) == :not_found
5960
assert PersistentSessionCache.get([backend: ets], new_id) == {[id: 1], []}
@@ -68,7 +69,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
6869
|> Cookie.call(Cookie.init([]))
6970

7071
assert Plug.current_user(conn) == user
71-
assert %{value: new_id, max_age: @max_age, path: "/"} = conn.resp_cookies["persistent_session_cookie"]
72+
assert %{value: new_id, max_age: @max_age, path: "/"} = conn.resp_cookies[@cookie_key]
7273
refute new_id == id
7374
assert PersistentSessionCache.get([backend: ets], id) == :not_found
7475
assert PersistentSessionCache.get([backend: ets], new_id) == {[id: 1], session_metadata: [fingerprint: "fingerprint"]}
@@ -85,7 +86,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
8586
|> Cookie.call(Cookie.init([]))
8687

8788
assert Plug.current_user(conn) == user
88-
assert %{value: id, max_age: @max_age, path: "/"} = conn.resp_cookies["persistent_session_cookie"]
89+
assert %{value: id, max_age: @max_age, path: "/"} = conn.resp_cookies[@cookie_key]
8990
assert PersistentSessionCache.get([backend: ets], id) == {[id: 1], session_metadata: [fingerprint: "new_fingerprint", b: 2, a: 2]}
9091
assert [inserted_at: _, b: 2, a: 3, fingerprint: "new_fingerprint"] = conn.private[:pow_session_metadata]
9192
end
@@ -97,11 +98,11 @@ defmodule PowPersistentSession.Plug.CookieTest do
9798
|> ConnHelpers.conn("/")
9899
|> ConnHelpers.init_session()
99100
|> Session.call(config ++ [otp_app: :test_app])
100-
|> store_persistent(ets, "test_app_test", {[id: user.id], []}, "test_app_persistent_session_cookie")
101+
|> store_persistent(ets, "test_app_test", {[id: user.id], []}, "test_app_" <> @cookie_key)
101102
|> Cookie.call(Cookie.init(config))
102103

103104
assert Plug.current_user(conn) == user
104-
assert %{value: new_id, max_age: @max_age, path: "/"} = conn.resp_cookies["test_app_persistent_session_cookie"]
105+
assert %{value: new_id, max_age: @max_age, path: "/"} = conn.resp_cookies["test_app_" <> @cookie_key]
105106
assert String.starts_with?(new_id, "test_app")
106107
assert PersistentSessionCache.get([backend: ets], new_id) == {[id: 1], []}
107108
end
@@ -115,8 +116,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
115116
|> Plug.assign_current_user(:user, [])
116117
|> Cookie.call(Cookie.init([]))
117118

118-
assert %{value: new_id, max_age: @max_age, path: "/"} = conn.resp_cookies["persistent_session_cookie"]
119-
assert new_id == id
119+
refute conn.resp_cookies[@cookie_key]
120120
assert PersistentSessionCache.get([backend: ets], id) == {[id: 1], []}
121121
end
122122

@@ -129,29 +129,29 @@ defmodule PowPersistentSession.Plug.CookieTest do
129129
|> Cookie.call(Cookie.init([]))
130130

131131
refute Plug.current_user(conn)
132-
assert conn.resp_cookies["persistent_session_cookie"] == %{max_age: 10, path: "/", value: "test"}
132+
assert conn.resp_cookies[@cookie_key] == %{max_age: 10, path: "/", value: "test"}
133133
assert PersistentSessionCache.get([backend: ets], id) == :not_found
134134
end
135135

136136
test "call/2 when persistent session cache doesn't have credentials", %{conn: conn} do
137137
conn =
138138
conn
139-
|> persistent_cookie("persistent_session_cookie", "test")
139+
|> persistent_cookie(@cookie_key, "test")
140140
|> Cookie.call(Cookie.init([]))
141141

142142
refute Plug.current_user(conn)
143-
assert conn.resp_cookies["persistent_session_cookie"] == %{max_age: 10, path: "/", value: "test"}
143+
assert conn.resp_cookies[@cookie_key] == %{max_age: 10, path: "/", value: "test"}
144144
end
145145

146146
test "call/2 when persistent session cache doesn't have credentials with custom cookie options", %{conn: conn, config: config} do
147147
config = Keyword.merge(config, persistent_session_cookie_opts: @custom_cookie_opts, persistent_session_cookie_expiration_timeout: 20)
148148
conn =
149149
conn
150-
|> persistent_cookie("persistent_session_cookie", "test")
150+
|> persistent_cookie(@cookie_key, "test")
151151
|> Cookie.call(Cookie.init(config))
152152

153153
refute Plug.current_user(conn)
154-
assert conn.resp_cookies["persistent_session_cookie"] == %{
154+
assert conn.resp_cookies[@cookie_key] == %{
155155
domain: "domain.com",
156156
extra: "SameSite=Lax",
157157
http_only: false,
@@ -183,7 +183,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
183183
|> Cookie.call(Cookie.init([]))
184184

185185
assert Plug.current_user(conn) == user
186-
assert %{value: new_id, max_age: @max_age, path: "/"} = conn.resp_cookies["persistent_session_cookie"]
186+
assert %{value: new_id, max_age: @max_age, path: "/"} = conn.resp_cookies[@cookie_key]
187187
refute new_id == id
188188
assert PersistentSessionCache.get([backend: ets], id) == :not_found
189189
assert PersistentSessionCache.get([backend: ets], new_id) == {[id: 1], []}
@@ -199,7 +199,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
199199
|> Cookie.call(Cookie.init([]))
200200

201201
assert Plug.current_user(conn) == user
202-
assert %{value: new_id, max_age: @max_age, path: "/"} = conn.resp_cookies["persistent_session_cookie"]
202+
assert %{value: new_id, max_age: @max_age, path: "/"} = conn.resp_cookies[@cookie_key]
203203
refute new_id == id
204204
assert PersistentSessionCache.get([backend: ets], id) == :not_found
205205
assert PersistentSessionCache.get([backend: ets], new_id) == {[id: 1], session_metadata: [fingerprint: "fingerprint"]}
@@ -213,7 +213,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
213213
assert_received {:ets, :put, [{_key, _value} | _rest], config}
214214
assert config[:ttl] == 1000
215215

216-
assert %{max_age: 1, path: "/"} = conn.resp_cookies["persistent_session_cookie"]
216+
assert %{max_age: 1, path: "/"} = conn.resp_cookies[@cookie_key]
217217
end
218218

219219
test "create/3 with custom cookie options", %{conn: conn, config: config} do
@@ -227,7 +227,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
227227
max_age: 1,
228228
path: "/path",
229229
secure: true
230-
} = conn.resp_cookies["persistent_session_cookie"]
230+
} = conn.resp_cookies[@cookie_key]
231231
end
232232

233233
test "create/3 deletes previous persistent session", %{conn: conn, config: config, ets: ets} do
@@ -265,7 +265,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
265265
|> Cookie.delete(config)
266266

267267
refute Plug.current_user(conn)
268-
assert conn.resp_cookies["persistent_session_cookie"] == %{max_age: -1, path: "/", value: ""}
268+
assert conn.resp_cookies[@cookie_key] == %{max_age: -1, path: "/", value: ""}
269269
assert PersistentSessionCache.get([backend: ets], id) == :not_found
270270
end
271271

@@ -278,7 +278,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
278278
|> Cookie.delete(config)
279279

280280
refute Plug.current_user(conn)
281-
assert conn.resp_cookies["persistent_session_cookie"] == %{
281+
assert conn.resp_cookies[@cookie_key] == %{
282282
domain: "domain.com",
283283
extra: "SameSite=Lax",
284284
http_only: false,

0 commit comments

Comments
 (0)