packages feed

shomei-core-0.2.0.0: src/Shomei/Session/RefreshToken/Store.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}

-- | The refresh-token-store port: persisting and rotating refresh tokens, including standalone
-- family/session/user revocation operations. Authentication reuse detection uses the
-- session-scoped transactional unit of work; OAuth revocation still uses the family operation.
module Shomei.Session.RefreshToken.Store
  ( RefreshTokenStore (..),
    createRefreshToken,
    findRefreshTokenByHash,
    markRefreshTokenUsed,
    revokeRefreshTokenFamily,
    revokeSessionRefreshTokens,
    revokeAllUserRefreshTokens,
  )
where

import Effectful (Dispatch (..), DispatchOf, Eff, Effect, (:>))
import Effectful.Dispatch.Dynamic (send)
import Shomei.Id (RefreshTokenId, SessionId, UserId)
import Shomei.Prelude
import Shomei.Session.RefreshToken.Domain (NewRefreshToken, PersistedRefreshToken, RefreshTokenHash)

data RefreshTokenStore :: Effect where
  CreateRefreshToken :: NewRefreshToken -> RefreshTokenStore m PersistedRefreshToken
  FindRefreshTokenByHash :: RefreshTokenHash -> RefreshTokenStore m (Maybe PersistedRefreshToken)
  -- | Transition a token @active → used@ as one atomic compare-and-swap. 'True' means this
  -- call performed the transition; 'False' means the token was no longer @active@ — someone
  -- else spent it first, which 'Shomei.Session.Authentication.Workflow.refresh' treats as reuse.
  MarkRefreshTokenUsed :: RefreshTokenId -> UTCTime -> RefreshTokenStore m Bool
  RevokeRefreshTokenFamily :: RefreshTokenId -> UTCTime -> RefreshTokenStore m ()
  RevokeSessionRefreshTokens :: SessionId -> UTCTime -> RefreshTokenStore m ()
  RevokeAllUserRefreshTokens :: UserId -> UTCTime -> RefreshTokenStore m ()

type instance DispatchOf RefreshTokenStore = Dynamic

createRefreshToken :: (RefreshTokenStore :> es) => NewRefreshToken -> Eff es PersistedRefreshToken
createRefreshToken = send . CreateRefreshToken

findRefreshTokenByHash :: (RefreshTokenStore :> es) => RefreshTokenHash -> Eff es (Maybe PersistedRefreshToken)
findRefreshTokenByHash = send . FindRefreshTokenByHash

markRefreshTokenUsed :: (RefreshTokenStore :> es) => RefreshTokenId -> UTCTime -> Eff es Bool
markRefreshTokenUsed i t = send (MarkRefreshTokenUsed i t)

revokeRefreshTokenFamily :: (RefreshTokenStore :> es) => RefreshTokenId -> UTCTime -> Eff es ()
revokeRefreshTokenFamily i t = send (RevokeRefreshTokenFamily i t)

revokeSessionRefreshTokens :: (RefreshTokenStore :> es) => SessionId -> UTCTime -> Eff es ()
revokeSessionRefreshTokens s t = send (RevokeSessionRefreshTokens s t)

revokeAllUserRefreshTokens :: (RefreshTokenStore :> es) => UserId -> UTCTime -> Eff es ()
revokeAllUserRefreshTokens u t = send (RevokeAllUserRefreshTokens u t)