feat: JWT cache implementation based on sieve algorithm (#4084)

Changes:

1. Refactoring and some cleanup of JWT handling code:
* Instead of caching AuthResult cache decoded claims (which signature was verified). Validating claims and determining role is done after cache lookup
* Cleaned up API so that usage of it is simplified: lookupJwtCache cache key >>= parseClaims configJwtAud time
* Handling of JwtCacheState initialization and updates of configuration is encapsulated in Auth.JwtCache module

2. Generic high performance (hopefully) scalable, dynamically resizeable cache implementation based on stm, stm-hamt and sieve algorithm. It also integrates with PostgREST measurements infrastructure providing usage stats (ie. hit ratio, evictions count)
This commit is contained in:
Michal Kleczek
2025-07-29 18:51:41 -05:00
committed by GitHub
parent ac155a9391
commit 77ff11de95
35 changed files with 664 additions and 203 deletions
+12 -10
View File
@@ -15,15 +15,15 @@ import PostgREST.SchemaCache (querySchemaCache)
import Protolude hiding (toList, toS)
import SpecHelper
import qualified PostgREST.AppState as AppState
import qualified PostgREST.Auth.JwtCache as JwtCache
import qualified PostgREST.Logger as Logger
import qualified PostgREST.Metrics as Metrics
import qualified PostgREST.AppState as AppState
import qualified PostgREST.Logger as Logger
import qualified PostgREST.Metrics as Metrics
import qualified Feature.Auth.AsymmetricJwtSpec
import qualified Feature.Auth.AudienceJwtSecretSpec
import qualified Feature.Auth.AuthSpec
import qualified Feature.Auth.BinaryJwtSecretSpec
import qualified Feature.Auth.JwtCacheSpec
import qualified Feature.Auth.NoAnonSpec
import qualified Feature.Auth.NoJwtSecretSpec
import qualified Feature.ConcurrentSpec
@@ -85,24 +85,23 @@ main = do
-- cached schema cache so most tests run fast
baseSchemaCache <- loadSCache pool testCfg
sockets <- AppState.initSockets testCfg
jwtCacheState <- JwtCache.init
loggerState <- Logger.init
metricsState <- Metrics.init (configDbPoolSize testCfg)
let
initApp sCache config = do
appState <- AppState.initWithPool sockets pool config jwtCacheState loggerState metricsState (const $ pure ())
initApp sCache st config = do
appState <- AppState.initWithPool sockets pool config loggerState metricsState (Metrics.observationMetrics metricsState)
AppState.putPgVersion appState actualPgVersion
AppState.putSchemaCache appState (Just sCache)
return ((), postgrest (configLogLevel config) appState (pure ()))
return (st, postgrest (configLogLevel config) appState (pure ()))
-- For tests that run with the same schema cache
app = initApp baseSchemaCache
app = initApp baseSchemaCache ()
-- For tests that run with a different SchemaCache (depends on configSchemas)
appDbs config = do
customSchemaCache <- loadSCache pool config
initApp customSchemaCache config
initApp customSchemaCache () config
let withApp = app testCfg
maxRowsApp = app testMaxRowsCfg
@@ -276,6 +275,9 @@ main = do
before pgSafeUpdateApp $
describe "Feature.Query.PgSafeUpdateSpec.spec" Feature.Query.PgSafeUpdateSpec.spec
before (initApp baseSchemaCache metricsState testCfgJwtCache) $
describe "Feature.Auth.JwtCacheSpec" Feature.Auth.JwtCacheSpec.spec
where
loadSCache pool conf =
either (panic.show) id <$> P.use pool (HT.transaction HT.ReadCommitted HT.Read $ querySchemaCache conf)