• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / supavisor / 19370957114

14 Nov 2025 04:30PM UTC coverage: 62.682% (+1.4%) from 61.246%
19370957114

Pull #744

github

web-flow
Merge fd252a012 into 0224a24c8
Pull Request #744: fix(defrag): improve statems, caching, logs, circuit breaking

592 of 785 new or added lines in 22 files covered. (75.41%)

18 existing lines in 5 files now uncovered.

1809 of 2886 relevant lines covered (62.68%)

4508.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.14
/lib/supavisor/secret_cache.ex
1
defmodule Supavisor.SecretCache do
2
  @moduledoc """
3
  Manages caching of authentication secrets for database users.
4

5
  Two types of secrets are cached with different purposes:
6

7
  1. Validation secrets: used by ClientHandler to validate incoming client authentication
8
  2. Upstream secrets: used by DbHandler to authenticate TO the upstream database
9

10
  ## Cache Bypass
11

12
  Certain users can be configured to bypass validation secret caching via the
13
  `CACHE_BYPASS_USERS` environment variable (comma-separated list of usernames).
14

15
  For bypass users:
16
  - Validation secrets are never cached (always fetched fresh from the database)
17
  - Upstream auth secrets are still cached (required for database connections)
18
  - Useful for users with temporary passwords or frequently changing credentials
19
  """
20

21
  require Logger
22

23
  @default_secrets_ttl :timer.hours(24)
24

25
  @doc """
26
  Gets validation secrets for validating incoming client authentication requests.
27
  """
28
  def get_validation_secrets(tenant, user) do
29
    Logger.info("get_validation_secrets(#{tenant}, #{user})")
7✔
30

31
    case Cachex.get(Supavisor.Cache, {:secrets_for_validation, tenant, user}) do
7✔
32
      {:ok, {:cached, {method, secrets_fn}}} ->
5✔
33
        {:ok, {method, secrets_fn}}
34

35
      _other ->
2✔
36
        {:error, :not_found}
37
    end
38
  end
39

40
  @doc """
41
  Fetches validation secrets with cache support and fallback function.
42

43
  Uses Cachex.fetch which provides mutex guarantees to avoid multiple concurrent
44
  fetches. For bypass users, always calls the fetch function directly.
45
  """
46
  def fetch_validation_secrets(tenant, user, fetch_fn) do
47
    Logger.info("fetch_validation_secrets(#{tenant}, #{user})")
496✔
48

49
    if should_bypass_cache?(user) do
496✔
50
      fetch_fn.()
4✔
51
    else
52
      cache_key = {:secrets_for_validation, tenant, user}
492✔
53

54
      cachex_fetch_fn = fn _key ->
492✔
55
        case fetch_fn.() do
24✔
56
          {:ok, {method, secrets_fn} = secrets} ->
57
            put_validation_secrets(tenant, user, method, secrets_fn)
24✔
58
            {:commit, {:cached, secrets}, ttl: @default_secrets_ttl}
24✔
59

NEW
60
          {:error, _} = resp ->
×
61
            {:ignore, resp}
62
        end
63
      end
64

65
      case Cachex.fetch(Supavisor.Cache, cache_key, cachex_fetch_fn) do
492✔
66
        {:ok, {:cached, value}} -> {:ok, value}
468✔
67
        {:commit, {:cached, value}, _opts} -> {:ok, value}
24✔
NEW
68
        {:ignore, resp} -> resp
×
69
      end
70
    end
71
  end
72

73
  @doc """
74
  Gets auth secrets to authenticate to the upstream database.
75
  """
76
  def get_upstream_auth_secrets(tenant, user) do
77
    Logger.info("get_upstream_auth_secrets(#{tenant}, #{user})")
310✔
78

79
    case Cachex.get(Supavisor.Cache, {:secrets_for_upstream_auth, tenant, user}) do
310✔
80
      {:ok, {:cached, {method, secrets_fn}}} ->
309✔
81
        {:ok, {method, secrets_fn}}
82

83
      _other ->
1✔
84
        {:error, :not_found}
85
    end
86
  end
87

88
  @doc """
89
  Caches validation secrets.
90

91
  For users in the cache bypass list, this function does nothing (no caching occurs).
92
  """
93
  def put_validation_secrets(tenant, user, method, secrets_fn) do
94
    Logger.info("put_validation_secrets(#{tenant}, #{user})")
63✔
95

96
    if should_bypass_cache?(user) do
63✔
97
      :ok
98
    else
99
      secrets_map = secrets_fn.()
60✔
100

101
      validation_secrets_fn = fn ->
60✔
102
        Map.delete(secrets_map, :client_key)
1,672✔
103
      end
104

105
      validation_key = {:secrets_for_validation, tenant, user}
60✔
106
      validation_value = {method, validation_secrets_fn}
60✔
107

108
      Cachex.put(Supavisor.Cache, validation_key, {:cached, validation_value},
60✔
109
        ttl: @default_secrets_ttl
110
      )
111
    end
112
  end
113

114
  @doc """
115
  Caches upstream auth secrets with the specified TTL.
116
  """
117
  def put_upstream_auth_secrets(
118
        tenant,
119
        user,
120
        method,
121
        secrets_with_client_key_fn,
122
        upstream_secrets_ttl
123
      ) do
124
    Logger.info("put_upstream_auth_secrets(#{tenant}, #{user})")
644✔
125
    upstream_auth_key = {:secrets_for_upstream_auth, tenant, user}
644✔
126
    upstream_auth_value = {method, secrets_with_client_key_fn}
644✔
127

128
    cache_opts =
644✔
129
      case upstream_secrets_ttl do
130
        :infinity -> []
643✔
131
        ttl_ms -> [ttl: ttl_ms]
1✔
132
      end
133

134
    Cachex.put(Supavisor.Cache, upstream_auth_key, {:cached, upstream_auth_value}, cache_opts)
644✔
135
  end
136

137
  @doc """
138
  Caches both validation and upstream auth secrets.
139
  """
140
  def put_both(tenant, user, method, secrets_with_client_key_fn, upstream_secrets_ttl) do
141
    Logger.info("put_both(#{tenant}, #{user})")
36✔
142
    put_validation_secrets(tenant, user, method, secrets_with_client_key_fn)
36✔
143

144
    put_upstream_auth_secrets(
36✔
145
      tenant,
146
      user,
147
      method,
148
      secrets_with_client_key_fn,
149
      upstream_secrets_ttl
150
    )
151
  end
152

153
  @doc """
154
  Caches validation secrets only if missing.
155
  """
156
  def put_validation_secrets_if_missing(tenant, user, method, secrets_fn) do
NEW
157
    Logger.info("put_validation_secrets_if_missing(#{tenant}, #{user})")
×
NEW
158
    validation_key = {:secrets_for_validation, tenant, user}
×
159

NEW
160
    case Cachex.get(Supavisor.Cache, validation_key) do
×
NEW
161
      {:ok, {:cached, _value}} ->
×
162
        :ok
163

164
      _other ->
NEW
165
        put_validation_secrets(tenant, user, method, secrets_fn)
×
166
    end
167
  end
168

169
  @doc """
170
  Short-term cache indicating that cached validation secrets were checked against
171
  the upstream database recently.
172
  """
173
  def put_check(tenant, user, method, secrets_fn) do
NEW
174
    key = {:secrets_check, tenant, user}
×
NEW
175
    value = {method, secrets_fn}
×
NEW
176
    Cachex.put(Supavisor.Cache, key, {:cached, value}, ttl: 5_000)
×
177
  end
178

179
  @doc """
180
  Clean upstream secrets
181
  """
182
  def clean_upstream_secrets(tenant, user) do
183
    Logger.info("clean_upstream_secrets(#{tenant}, #{user})")
2✔
184
    Cachex.del(Supavisor.Cache, {:secrets_for_upstream_auth, tenant, user})
2✔
185
  end
186

187
  @doc """
188
  Invalidates all cached secrets for a tenant/user across the cluster.
189
  """
190
  def invalidate(tenant, user) do
191
    Logger.info("invalidate(#{tenant}, #{user})")
1✔
192

193
    :erpc.multicast([node() | Node.list()], fn ->
1✔
194
      Cachex.del(Supavisor.Cache, {:secrets_for_validation, tenant, user})
1✔
195
      Cachex.del(Supavisor.Cache, {:secrets_for_upstream_auth, tenant, user})
1✔
196
      Cachex.del(Supavisor.Cache, {:secrets_check, tenant, user})
1✔
197
    end)
198
  end
199

200
  @doc false
201
  defp should_bypass_cache?(user) do
202
    bypass_users = Application.get_env(:supavisor, :cache_bypass_users, [])
559✔
203
    user in bypass_users
559✔
204
  end
205
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc