packages feed

prodapi-userauth-0.1.0.0: src/Prod/UserAuth/JWT.hs

{-# LANGUAGE StrictData #-}

module Prod.UserAuth.JWT (
    UserAuthInfo (..),
    authUserId,
    makeLoggedInCookie,
    module Web.JWT,
)
where

import Data.Aeson (FromJSON, Result (..), Value (Number), fromJSON)
import qualified Data.Map.Strict as Map
import Data.Text (Text)
import Data.Time.Clock.POSIX
import Prod.UserAuth.Base
import Prod.UserAuth.Runtime
import Web.JWT

data UserAuthInfo = UserAuthInfo (Maybe (JWT VerifiedJWT))

-- | Extract an decodes a JSON value from a UserAuthInfo containing a VerifiedJWT.
authClaim :: (FromJSON a) => UserAuthInfo -> Text -> Maybe a
authClaim (UserAuthInfo mjwt) key = do
    jwt <- mjwt
    obj <- Map.lookup key (unClaimsMap . unregisteredClaims $ claims jwt)
    case fromJSON obj of
        Error _ -> Nothing
        Success x -> Just x

-- | Extract a JSON value from a UserAuthInfo containing a VerifiedJWT.
authClaimValue :: UserAuthInfo -> Text -> Maybe Value
authClaimValue (UserAuthInfo mjwt) key = do
    jwt <- mjwt
    Map.lookup key (unClaimsMap . unregisteredClaims $ claims jwt)

-- | Extracts the UserId from a UserAuthInfo containing VerifiedJWT.
authUserId :: UserAuthInfo -> Maybe UserId
authUserId uai = do
    val <- authClaim uai "user-id"
    case val of
        (Number nid) -> pure (truncate nid :: UserId)
        _ -> Nothing

{- | Encodes a JWT cookie for a given UserId.

The runtime allows augmenting the JWT with extra claims.
-}
makeLoggedInCookie :: Runtime a -> UserId -> IO (Either ErrorMessage LoggedInCookie)
makeLoggedInCookie runtime uid = do
    now <- numericDate <$> getPOSIXTime
    case now of
        Nothing -> pure $ Left "could not build a valid issued-at"
        Just iat -> do
            extras <- augmentLoggedInCookieClaims runtime uid
            case extras of
                Left err -> pure $ Left err
                Right vals -> do
                    traceJWTSigned runtime uid vals
                    pure $ Right $ adapt iat vals
  where
    baseClaims = baseClaimsSet runtime
    adapt iat extras = do
        let claims = Map.fromList $ mconcat [[("user-id", (Number $ fromIntegral uid))], extras]
        LoggedInCookie $
            encodeSigned
                (hmacSecret $ secretstring runtime)
                mempty
                ( baseClaims
                    { iss = stringOrURI "jwt-app"
                    , iat = Just iat
                    , unregisteredClaims = ClaimsMap claims
                    }
                )