Skip to content

Commit b420b86

Browse files
authored
Merge pull request #390 from danschultzer/dont-expire-cookies
Don't delete or expire cookies
2 parents aaefb8a + 095a0f8 commit b420b86

File tree

5 files changed

+81
-75
lines changed

5 files changed

+81
-75
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* [`PowEmailConfirmation.Phoenix.ControllerCallbacks`] Now prevents user enumeration attack for `PowInvitation.Phoenix.InvitationController.create/2`
2020
* [`PowPersistentSession.Plug.Cookie`] Changed default cookie name to `persistent_session`
2121
* [`PowPersistentSession.Plug.Cookie`] Removed renewal of cookie as the token will always expire
22+
* [`PowPersistentSession.Plug.Cookie`] No longer expires invalid cookies
2223

2324
## Removed
2425

lib/extensions/persistent_session/plug/cookie.ex

Lines changed: 11 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ defmodule PowPersistentSession.Plug.Cookie do
4040
`[max_age: max_age, path: "/"]` where `:max_age` is the value defined in
4141
`:persistent_session_ttl`.
4242
43-
* `:persistent_session_cookie_expiration_timeout` - integer value in
44-
seconds for how much time should go by before cookie should expire after
45-
the token is fetched in `authenticate/2`. Defaults to 10.
46-
4743
## Custom metadata
4844
4945
You can assign a private `:pow_persistent_session_metadata` key in the conn
@@ -74,10 +70,9 @@ defmodule PowPersistentSession.Plug.Cookie do
7470
alias Pow.{Config, Operations, Plug, UUID}
7571

7672
@cookie_key "persistent_session"
77-
@cookie_expiration_timeout 10
7873

7974
@doc """
80-
Sets a persistent session cookie with an auto generated token.
75+
Sets a persistent session cookie with a randomly generated unique token.
8176
8277
The token is set as a key in the persistent session cache with the id fetched
8378
from the struct. Any existing persistent session will be deleted first with
@@ -89,8 +84,8 @@ defmodule PowPersistentSession.Plug.Cookie do
8984
value will look like:
9085
`{[id: user_id], session_metadata: [fingerprint: fingerprint]}`
9186
92-
The unique cookie id will be prepended by the `:otp_app` configuration
93-
value, if present.
87+
The unique token will be prepended by the `:otp_app` configuration value, if
88+
present.
9489
"""
9590
@spec create(Conn.t(), map(), Config.t()) :: Conn.t()
9691
def create(conn, user, config) do
@@ -138,10 +133,11 @@ defmodule PowPersistentSession.Plug.Cookie do
138133
end
139134

140135
@doc """
141-
Expires the persistent session cookie.
136+
Expires the persistent session.
142137
143-
If a persistent session cookie exists it'll be updated to expire immediately,
144-
and the token in the persistent session cache will be deleted.
138+
If a persistent session cookie exists the token in the persistent session
139+
cache will be deleted, and cookie deleted with
140+
`Plug.Conn.delete_resp_cookie/3.
145141
"""
146142
@spec delete(Conn.t(), Config.t()) :: Conn.t()
147143
def delete(conn, config) do
@@ -165,12 +161,7 @@ defmodule PowPersistentSession.Plug.Cookie do
165161
end
166162

167163
defp delete_cookie(conn, cookie_key, config) do
168-
opts =
169-
config
170-
|> cookie_opts()
171-
|> Keyword.put(:max_age, -1)
172-
173-
Conn.put_resp_cookie(conn, cookie_key, "", opts)
164+
Conn.delete_resp_cookie(conn, cookie_key, cookie_opts(config))
174165
end
175166

176167
@doc """
@@ -179,22 +170,13 @@ defmodule PowPersistentSession.Plug.Cookie do
179170
If a persistent session cookie exists, it'll fetch the credentials from the
180171
persistent session cache.
181172
182-
After the value is fetched from the cookie, it'll be updated to expire after
183-
the value of `:persistent_session_cookie_expiration_timeout` so invalid
184-
cookies will be deleted eventually. This timeout prevents immediate deletion
185-
of the cookie so in case of multiple simultaneous requests, the cache has
186-
time to update the value.
187-
188173
If credentials was fetched successfully, the token in the cache is deleted, a
189174
new session is created, and `create/2` is called to create a new persistent
190-
session cookie. This will override any expiring cookie.
175+
session cookie.
191176
192177
If a `:session_metadata` keyword list is fetched from the persistent session
193178
metadata, all the values will be merged into the private
194179
`:pow_session_metadata` key in the conn.
195-
196-
The expiration date for the cookie will be reset on each request where a user
197-
is assigned to the conn.
198180
"""
199181
@spec authenticate(Conn.t(), Config.t()) :: Conn.t()
200182
def authenticate(conn, config) do
@@ -210,16 +192,15 @@ defmodule PowPersistentSession.Plug.Cookie do
210192

211193
case conn.req_cookies[cookie_key] do
212194
nil -> conn
213-
key_id -> do_authenticate(conn, cookie_key, key_id, config)
195+
key_id -> do_authenticate(conn, key_id, config)
214196
end
215197
end
216198
defp maybe_authenticate(conn, _user, _config), do: conn
217199

218-
defp do_authenticate(conn, cookie_key, key_id, config) do
200+
defp do_authenticate(conn, key_id, config) do
219201
{store, store_config} = store(config)
220202
res = store.get(store_config, key_id)
221203
plug = Plug.get_plug(config)
222-
conn = expire_cookie(conn, cookie_key, key_id, config)
223204

224205
case res do
225206
:not_found ->
@@ -232,17 +213,6 @@ defmodule PowPersistentSession.Plug.Cookie do
232213
end
233214
end
234215

235-
defp expire_cookie(conn, cookie_key, key_id, config) do
236-
max_age = Config.get(config, :persistent_session_cookie_expiration_timeout, @cookie_expiration_timeout)
237-
opts =
238-
config
239-
|> cookie_opts()
240-
|> Keyword.put(:max_age, max_age)
241-
242-
Conn.put_resp_cookie(conn, cookie_key, key_id, opts)
243-
end
244-
245-
246216
defp fetch_and_auth_user(conn, {clauses, metadata}, plug, config) do
247217
clauses
248218
|> filter_invalid!()

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ defmodule PowPersistentSession.Phoenix.ControllerCallbacksTest do
3232
end
3333

3434
describe "Pow.Phoenix.SessionController.delete/2" do
35-
test "generates cookie", %{conn: conn, ets: ets} do
35+
test "expires cookie", %{conn: conn, ets: ets} do
3636
conn = post conn, Routes.pow_session_path(conn, :create, %{"user" => @valid_params})
37-
%{value: id} = conn.resp_cookies[@cookie_key]
37+
38+
assert %{value: id} = conn.resp_cookies[@cookie_key]
39+
3840
conn = delete conn, Routes.pow_session_path(conn, :delete)
3941

40-
assert %{max_age: -1, path: "/", value: ""} = conn.resp_cookies[@cookie_key]
42+
assert conn.resp_cookies[@cookie_key] == %{max_age: 0, path: "/", universal_time: {{1970, 1, 1}, {0, 0, 0}}}
4143
assert PersistentSessionCache.get([backend: ets], id) == :not_found
4244
end
4345
end

test/extensions/persistent_session/plug/cookie_test.exs

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,29 @@ defmodule PowPersistentSession.Plug.CookieTest do
6060
assert PersistentSessionCache.get([backend: ets], new_id) == {[id: 1], []}
6161
end
6262

63+
test "call/2 assigns user from cookie and doesn't expire with simultanous request", %{conn: conn, ets: ets} do
64+
user = %User{id: 1}
65+
id = "test"
66+
opts = Cookie.init([])
67+
conn = store_persistent(conn, ets, id, {[id: user.id], []})
68+
69+
first_conn =
70+
conn
71+
|> Cookie.call(opts)
72+
|> Conn.resp(200, "")
73+
74+
assert Plug.current_user(first_conn) == user
75+
assert %{value: _id, max_age: @max_age, path: "/"} = first_conn.resp_cookies[@cookie_key]
76+
77+
second_conn =
78+
conn
79+
|> Cookie.call(opts)
80+
|> Conn.resp(200, "")
81+
82+
refute Plug.current_user(second_conn) == user
83+
refute second_conn.resp_cookies[@cookie_key]
84+
end
85+
6386
test "call/2 assigns user from cookie passing fingerprint to the session metadata", %{conn: conn, ets: ets} do
6487
user = %User{id: 1}
6588
id = "test"
@@ -129,7 +152,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
129152
|> Cookie.call(Cookie.init([]))
130153

131154
refute Plug.current_user(conn)
132-
assert conn.resp_cookies[@cookie_key] == %{max_age: 10, path: "/", value: "test"}
155+
refute conn.resp_cookies[@cookie_key]
133156
assert PersistentSessionCache.get([backend: ets], id) == :not_found
134157
end
135158

@@ -140,26 +163,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
140163
|> Cookie.call(Cookie.init([]))
141164

142165
refute Plug.current_user(conn)
143-
assert conn.resp_cookies[@cookie_key] == %{max_age: 10, path: "/", value: "test"}
144-
end
145-
146-
test "call/2 when persistent session cache doesn't have credentials with custom cookie options", %{conn: conn, config: config} do
147-
config = Keyword.merge(config, persistent_session_cookie_opts: @custom_cookie_opts, persistent_session_cookie_expiration_timeout: 20)
148-
conn =
149-
conn
150-
|> persistent_cookie(@cookie_key, "test")
151-
|> Cookie.call(Cookie.init(config))
152-
153-
refute Plug.current_user(conn)
154-
assert conn.resp_cookies[@cookie_key] == %{
155-
domain: "domain.com",
156-
extra: "SameSite=Lax",
157-
http_only: false,
158-
max_age: 20,
159-
path: "/path",
160-
secure: true,
161-
value: "test"
162-
}
166+
refute conn.resp_cookies[@cookie_key]
163167
end
164168

165169
test "call/2 with invalid stored clauses", %{conn: conn, ets: ets} do
@@ -265,7 +269,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
265269
|> Cookie.delete(config)
266270

267271
refute Plug.current_user(conn)
268-
assert conn.resp_cookies[@cookie_key] == %{max_age: -1, path: "/", value: ""}
272+
assert conn.resp_cookies[@cookie_key] == %{max_age: 0, path: "/", universal_time: {{1970, 1, 1}, {0, 0, 0}}}
269273
assert PersistentSessionCache.get([backend: ets], id) == :not_found
270274
end
271275

@@ -278,15 +282,7 @@ defmodule PowPersistentSession.Plug.CookieTest do
278282
|> Cookie.delete(config)
279283

280284
refute Plug.current_user(conn)
281-
assert conn.resp_cookies[@cookie_key] == %{
282-
domain: "domain.com",
283-
extra: "SameSite=Lax",
284-
http_only: false,
285-
max_age: -1,
286-
path: "/path",
287-
secure: true,
288-
value: ""
289-
}
285+
assert conn.resp_cookies[@cookie_key] == %{max_age: 0, universal_time: {{1970, 1, 1}, {0, 0, 0}}, path: "/path", domain: "domain.com", extra: "SameSite=Lax", http_only: false, secure: true}
290286
assert PersistentSessionCache.get([backend: ets], id) == :not_found
291287
end
292288
end

test/pow/plug/session_test.exs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,43 @@ defmodule Pow.Plug.SessionTest do
120120
assert conn.private[:pow_session_metadata][:fingerprint] == "fingerprint"
121121
end
122122

123+
test "call/2 creates new session when :session_renewal_ttl reached and doesn't delete with simultanous request", %{conn: new_conn} do
124+
ttl = 100
125+
id = "token"
126+
config = Keyword.put(@default_opts, :session_ttl_renewal, ttl)
127+
stale_timestamp = :os.system_time(:millisecond) - ttl - 1
128+
opts = Session.init(config)
129+
130+
CredentialsCache.put(@store_config, id, {@user, inserted_at: stale_timestamp})
131+
132+
conn =
133+
new_conn
134+
|> Conn.fetch_session()
135+
|> Conn.put_session(config[:session_key], id)
136+
|> Conn.send_resp(200, "")
137+
138+
conn = Test.recycle_cookies(new_conn, conn)
139+
140+
first_conn =
141+
conn
142+
|> Session.call(opts)
143+
|> Conn.send_resp(200, "")
144+
145+
assert Plug.current_user(first_conn) == @user
146+
assert %{value: _id} = first_conn.resp_cookies["foobar"]
147+
assert new_id = first_conn.private[:plug_session][config[:session_key]]
148+
refute new_id == id
149+
assert {@user, _metadata} = CredentialsCache.get(@store_config, new_id)
150+
151+
second_conn =
152+
conn
153+
|> Session.call(opts)
154+
|> Conn.send_resp(200, "")
155+
156+
refute second_conn.resp_cookies["foobar"]
157+
refute second_conn.private[:plug_session]
158+
end
159+
123160
test "call/2 with prepended `:otp_app` session key", %{conn: conn} do
124161
CredentialsCache.put(@store_config, "token", {@user, inserted_at: :os.system_time(:millisecond)})
125162

0 commit comments

Comments
 (0)