polysemy-account-api-0.2.0.0: lib/Polysemy/Account/Api/Interpreter/Jwt.hs
{-# options_haddock prune #-}
{-# options_ghc -fno-warn-orphans #-}
-- | Interpreters for 'Jwt'
module Polysemy.Account.Api.Interpreter.Jwt where
import Conc (interpretAtomic)
import qualified Crypto.JOSE as JOSE
import Crypto.JOSE (JWK, KeyMaterialGenParam (OKPGenParam), OKPCrv (Ed25519), genJWK)
import Polysemy.Db (DbError, InitDbError)
import Polysemy.Hasql (Database, interpretAtomicStateDb, interpretTable)
import Servant.Auth.JWT (ToJWT)
import Servant.Auth.Server (FromJWT, JWTSettings, defaultJWTSettings, makeJWT)
import Sqel (tableName)
import Sqel.Data.TableSchema (TableSchema)
import Sqel.Names (named)
import Sqel.PgType (tableSchema)
import qualified Sqel.Prim as Sqel
import Polysemy.Account.Api.Effect.Jwt (GenJwk (GenJwk), Jwt (..), genJwk)
import Polysemy.Account.Data.AuthToken (AuthToken (AuthToken))
import Polysemy.Account.Data.AuthedAccount (AuthedAccount)
instance (FromJSON i, FromJSON p) => FromJWT (AuthedAccount i p) where
instance (ToJSON i, ToJSON p) => ToJWT (AuthedAccount i p) where
generateKey ::
Member (Embed IO) r =>
Sem r JWK
generateKey =
embed (genJWK (OKPGenParam Ed25519))
generateAndStoreKey ::
Members [AtomicState (Maybe JWK), Embed IO] r =>
Sem r JWK
generateAndStoreKey = do
k <- embed (genJWK (OKPGenParam Ed25519))
k <$ atomicPut (Just k)
-- | Interpret 'GenJwk' using 'Ed25519'.
interpretGenJwk ::
Member (Embed IO) r =>
InterpreterFor GenJwk r
interpretGenJwk =
interpret \ GenJwk -> generateKey
key ::
Members [AtomicState (Maybe JWK), Embed IO] r =>
Sem r JWK
key =
maybe generateAndStoreKey pure =<< atomicGet
settings ::
Members [AtomicState (Maybe JWK), Embed IO] r =>
Sem r JWTSettings
settings =
defaultJWTSettings <$> key
authToken ::
Member (Error Text) r =>
Either JOSE.Error LByteString ->
Sem r AuthToken
authToken = \case
Right bytes ->
pure (AuthToken (decodeUtf8 bytes))
Left err ->
throw (show err)
-- | Interpret 'Jwt' by storing the key in 'AtomicState', generating it on the fly if absent.
--
-- Generates 'Ed25519' keys.
--
-- Errors originating from the token generator are critical.
interpretJwtState ::
Members [GenJwk, AtomicState (Maybe JWK), Error Text, Embed IO] r =>
ToJWT a =>
InterpreterFor (Jwt a) r
interpretJwtState =
interpret \case
Key ->
genJwk
Settings ->
settings
MakeToken a -> do
sett <- settings
authToken =<< embed (makeJWT a sett Nothing)
-- | Interpret 'Jwt' by storing the key in 'AtomicState' in memory.
interpretJwt ::
∀ a r .
Members [Error Text, Embed IO] r =>
ToJWT a =>
InterpreterFor (Jwt a) r
interpretJwt =
interpretAtomic Nothing .
interpretGenJwk .
interpretJwtState .
raiseUnder2
settingsPersistent ::
Member (AtomicState JWK) r =>
Sem r JWTSettings
settingsPersistent =
defaultJWTSettings <$> atomicGet
-- | Interpret 'Jwt' by storing the key in 'AtomicState', requiring the key to be present from the start.
-- This is intended to be used with a database backing the 'AtomicState', the key being generated when starting the app.
--
-- Generates 'Ed25519' keys.
--
-- Errors originating from the token generator are critical.
interpretJwtPersistent ::
∀ a e r .
Members [AtomicState JWK !! e, Error Text, Embed IO] r =>
ToJWT a =>
InterpreterFor (Jwt a !! e) r
interpretJwtPersistent =
interpretResumable \case
Key ->
restop atomicGet
Settings ->
restop settingsPersistent
MakeToken a -> do
sett <- restop settingsPersistent
authToken =<< embed (makeJWT a sett Nothing)
-- | Interpret 'Jwt' using 'interpretJwtPersistent' and interpret 'AtomicState' as a PostgreSQL table using
-- @polysemy-hasql@, generating the JWK when it is not found in the database.
interpretJwtDb ::
∀ a r .
Members [Database !! DbError, Error InitDbError, Error Text, Log, Mask, Resource, Race, Embed IO] r =>
ToJWT a =>
InterpreterFor (Jwt a !! DbError) r
interpretJwtDb =
interpretGenJwk .
interpretTable ts .
interpretAtomicStateDb ts genJwk .
interpretJwtPersistent .
insertAt @1
where
ts :: TableSchema JWK
ts = tableSchema (tableName "jwk" (named @"payload" Sqel.json))