Skip to content

Commit 15dc525

Browse files
authored
Merge pull request #714 from pow-auth/fix-cache-invalidation
Fix bug with expired cached keys not invalidated on startup
2 parents 7c57758 + ed17a71 commit 15dc525

File tree

3 files changed

+42
-34
lines changed

3 files changed

+42
-34
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## v1.0.34 (TBA)
4+
5+
**Note:** This release contains an important security fix. It is recommended to update immediately if you are using the `Pow.Store.Backend.MnesiaCache`.
6+
7+
## Bug fixes
8+
9+
- [`Pow.Store.Backend.MnesiaCache`] Fixed bug where expired cached keys are not invalidated on startup
10+
311
## v1.0.33 (2023-09-05)
412

513
## Bug fixes

lib/pow/store/backend/mnesia_cache.ex

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -454,10 +454,9 @@ defmodule Pow.Store.Backend.MnesiaCache do
454454
:mnesia.foldl(fn
455455
{@mnesia_cache_tab, key, {_value, expire}}, invalidators when is_list(key) ->
456456
ttl = Enum.max([expire - timestamp(), 0])
457-
458-
key
459-
|> unwrap()
460-
|> append_invalidator(invalidators, ttl, config)
457+
[namespace | key] = key
458+
config = Config.put(config, :namespace, namespace)
459+
append_invalidator(key, invalidators, ttl, config)
461460

462461
# TODO: Remove by 1.1.0
463462
{@mnesia_cache_tab, key, {_key, _value, _config, expire}}, invalidators when is_binary(key) and is_number(expire) ->

test/pow/store/backend/mnesia_cache_test.exs

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ defmodule Pow.Store.Backend.MnesiaCacheTest do
2323
File.rm_rf!("tmp/mnesia")
2424
File.mkdir_p!("tmp/mnesia")
2525

26-
start(@default_config)
26+
start()
2727

2828
:ok
2929
end
@@ -34,7 +34,7 @@ defmodule Pow.Store.Backend.MnesiaCacheTest do
3434
MnesiaCache.put(@default_config, {"key", "value"})
3535
assert MnesiaCache.get(@default_config, "key") == "value"
3636

37-
restart(@default_config)
37+
restart()
3838

3939
assert MnesiaCache.get(@default_config, "key") == "value"
4040

@@ -63,7 +63,7 @@ defmodule Pow.Store.Backend.MnesiaCacheTest do
6363
assert MnesiaCache.get(@default_config, "key1") == "1"
6464
assert MnesiaCache.get(@default_config, "key2") == "2"
6565

66-
restart(@default_config)
66+
restart()
6767

6868
assert MnesiaCache.get(@default_config, "key1") == "1"
6969
assert MnesiaCache.get(@default_config, "key2") == "2"
@@ -85,51 +85,52 @@ defmodule Pow.Store.Backend.MnesiaCacheTest do
8585
end
8686

8787
test "records auto purge with persistent storage" do
88-
config = Config.put(@default_config, :ttl, 50)
88+
config_1 = Config.put(@default_config, :ttl, 50)
89+
config_2 = Config.put(config_1, :namespace, "other-namespace")
8990

90-
MnesiaCache.put(config, {"key", "value"})
91-
MnesiaCache.put(config, [{"key1", "1"}, {"key2", "2"}])
91+
MnesiaCache.put(config_1, {"key", "value"})
92+
MnesiaCache.put(config_2, [{"key1", "1"}, {"key2", "2"}])
9293
flush_process_mailbox() # Ignore sync write messages
93-
assert MnesiaCache.get(config, "key") == "value"
94-
assert MnesiaCache.get(config, "key1") == "1"
95-
assert MnesiaCache.get(config, "key2") == "2"
94+
assert MnesiaCache.get(config_1, "key") == "value"
95+
assert MnesiaCache.get(config_2, "key1") == "1"
96+
assert MnesiaCache.get(config_2, "key2") == "2"
9697
assert_receive {:mnesia_table_event, {:delete, _, _}} # Wait for TTL reached
9798
assert_receive {:mnesia_table_event, {:delete, _, _}} # Wait for TTL reached
9899
assert_receive {:mnesia_table_event, {:delete, _, _}} # Wait for TTL reached
99-
assert MnesiaCache.get(config, "key") == :not_found
100-
assert MnesiaCache.get(config, "key1") == :not_found
101-
assert MnesiaCache.get(config, "key2") == :not_found
100+
assert MnesiaCache.get(config_1, "key") == :not_found
101+
assert MnesiaCache.get(config_2, "key1") == :not_found
102+
assert MnesiaCache.get(config_2, "key2") == :not_found
102103

103104
# After restart
104-
MnesiaCache.put(config, {"key", "value"})
105-
MnesiaCache.put(config, [{"key1", "1"}, {"key2", "2"}])
105+
MnesiaCache.put(config_1, {"key", "value"})
106+
MnesiaCache.put(config_2, [{"key1", "1"}, {"key2", "2"}])
106107
flush_process_mailbox() # Ignore sync write messages
107-
restart(config)
108-
assert MnesiaCache.get(config, "key") == "value"
109-
assert MnesiaCache.get(config, "key1") == "1"
110-
assert MnesiaCache.get(config, "key2") == "2"
108+
restart()
109+
assert MnesiaCache.get(config_1, "key") == "value"
110+
assert MnesiaCache.get(config_2, "key1") == "1"
111+
assert MnesiaCache.get(config_2, "key2") == "2"
111112
assert_receive {:mnesia_table_event, {:delete, _, _}} # Wait for TTL reached
112113
assert_receive {:mnesia_table_event, {:delete, _, _}} # Wait for TTL reached
113114
assert_receive {:mnesia_table_event, {:delete, _, _}} # Wait for TTL reached
114-
assert MnesiaCache.get(config, "key") == :not_found
115-
assert MnesiaCache.get(config, "key1") == :not_found
116-
assert MnesiaCache.get(config, "key2") == :not_found
115+
assert MnesiaCache.get(config_1, "key") == :not_found
116+
assert MnesiaCache.get(config_2, "key1") == :not_found
117+
assert MnesiaCache.get(config_2, "key2") == :not_found
117118

118119
# After record expiration updated reschedules
119-
MnesiaCache.put(config, {"key", "value"})
120+
MnesiaCache.put(config_1, {"key", "value"})
120121
:mnesia.dirty_write({MnesiaCache, ["pow:test", "key"], {"value", :os.system_time(:millisecond) + 150}})
121122
flush_process_mailbox() # Ignore sync write messages
122123
assert_receive {:mnesia_system_event, {:mnesia_user, {:reschedule_invalidator, {_, _, _}}}} # Wait for reschedule event
123-
assert MnesiaCache.get(config, "key") == "value"
124+
assert MnesiaCache.get(config_1, "key") == "value"
124125
assert_receive {:mnesia_table_event, {:delete, _, _}}, 150 # Wait for TTL reached
125-
assert MnesiaCache.get(config, "key") == :not_found
126+
assert MnesiaCache.get(config_1, "key") == :not_found
126127
end
127128

128129
test "when initiated with unexpected records" do
129130
:mnesia.dirty_write({MnesiaCache, ["pow:test", "key"], :invalid_value})
130131

131132
assert CaptureLog.capture_log([format: "[$level] $message", colors: [enabled: false]], fn ->
132-
restart(@default_config)
133+
restart()
133134
end) =~ ~r/\[(warn|warning|)\] #{Regex.escape("Found an unexpected record in the mnesia cache, please delete it: [\"pow:test\", \"key\"]")}/
134135
end
135136

@@ -170,16 +171,16 @@ defmodule Pow.Store.Backend.MnesiaCacheTest do
170171
end
171172
end
172173

173-
defp start(config) do
174-
start_supervised!({MnesiaCache, config})
174+
defp start do
175+
start_supervised!(MnesiaCache)
175176
:mnesia.subscribe(:system)
176177
:mnesia.subscribe({:table, MnesiaCache, :simple})
177178
end
178179

179-
defp restart(config) do
180+
defp restart do
180181
:ok = stop_supervised(MnesiaCache)
181182
:mnesia.stop()
182-
start(config)
183+
start()
183184
end
184185

185186
describe "distributed nodes" do
@@ -773,7 +774,7 @@ defmodule Pow.Store.Backend.MnesiaCacheTest do
773774
:stopped = :mnesia.stop()
774775

775776
assert CaptureLog.capture_log([format: "[$level] $message", colors: [enabled: false]], fn ->
776-
start(@default_config)
777+
start()
777778
end) =~ ~r/\[(warn|warning|)\] #{Regex.escape("Deleting old record in the mnesia cache: \"pow:test:key1\"")}/
778779

779780
assert :mnesia.dirty_read({MnesiaCache, key}) == []

0 commit comments

Comments
 (0)