packages feed

shomei-core-0.2.0.0: src/Shomei/ServiceAccount/Store.hs

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

-- | The service-account port (EP-4): the @shomei_service_accounts@ table behind the OAuth2
-- @client_credentials@ grant.
--
-- Lookup is by @client_id@ (the public TypeID text), because that is what an OAuth2 client
-- presents. Mutations are by 'ServiceAccountDbId', because that is what an administrator holds
-- after a create or a list.
module Shomei.ServiceAccount.Store
  ( ServiceAccountStore (..),
    createServiceAccount,
    findServiceAccountByClientId,
    listServiceAccounts,
    rotateServiceAccountSecret,
    revokeServiceAccount,
  )
where

import Effectful (Dispatch (..), DispatchOf, Eff, Effect, (:>))
import Effectful.Dispatch.Dynamic (send)
import Shomei.Id (ServiceAccountDbId)
import Shomei.Prelude
import Shomei.ServiceAccount.Domain (NewServiceAccount, ServiceAccount)

data ServiceAccountStore :: Effect where
  CreateServiceAccount :: NewServiceAccount -> ServiceAccountStore m ServiceAccount
  -- | The authentication lookup. Returns revoked accounts too: refusing a revoked credential
  -- is the workflow's job, and it must be indistinguishable from a wrong secret.
  FindServiceAccountByClientId :: Text -> ServiceAccountStore m (Maybe ServiceAccount)
  -- | The whole table, newest first. Deployments have few service accounts; no paging.
  ListServiceAccounts :: ServiceAccountStore m [ServiceAccount]
  -- | Replace the secret hash and stamp @rotated_at@. Takes the /new hash/, never a plaintext:
  -- the secret is generated and shown once by the caller.
  RotateServiceAccountSecret :: ServiceAccountDbId -> Text -> UTCTime -> ServiceAccountStore m ()
  RevokeServiceAccount :: ServiceAccountDbId -> UTCTime -> ServiceAccountStore m ()

type instance DispatchOf ServiceAccountStore = Dynamic

createServiceAccount :: (ServiceAccountStore :> es) => NewServiceAccount -> Eff es ServiceAccount
createServiceAccount = send . CreateServiceAccount

findServiceAccountByClientId :: (ServiceAccountStore :> es) => Text -> Eff es (Maybe ServiceAccount)
findServiceAccountByClientId = send . FindServiceAccountByClientId

listServiceAccounts :: (ServiceAccountStore :> es) => Eff es [ServiceAccount]
listServiceAccounts = send ListServiceAccounts

rotateServiceAccountSecret :: (ServiceAccountStore :> es) => ServiceAccountDbId -> Text -> UTCTime -> Eff es ()
rotateServiceAccountSecret sid h t = send (RotateServiceAccountSecret sid h t)

revokeServiceAccount :: (ServiceAccountStore :> es) => ServiceAccountDbId -> UTCTime -> Eff es ()
revokeServiceAccount sid t = send (RevokeServiceAccount sid t)