packages feed

paseto-0.1.0.0: src/Crypto/Paseto/Token/Build.hs

{-# LANGUAGE DataKinds #-}

module Crypto.Paseto.Token.Build
  ( BuildTokenParams (..)
  , getDefaultBuildTokenParams
  , V3LocalBuildError (..)
  , renderV3LocalBuildError
  , buildTokenV3Local
  , V3PublicBuildError (..)
  , renderV3PublicBuildError
  , buildTokenV3Public
  , buildTokenV4Local
  , buildTokenV4Public
  ) where

import Control.Monad.Except ( ExceptT )
import Control.Monad.Trans.Except.Extra ( firstExceptT )
import Crypto.Paseto.Keys ( SigningKey (..), SymmetricKey (..) )
import Crypto.Paseto.Mode ( Purpose (..), Version (..) )
import qualified Crypto.Paseto.Protocol.V3 as V3
import qualified Crypto.Paseto.Protocol.V4 as V4
import Crypto.Paseto.Token ( Footer, ImplicitAssertion, Token (..) )
import Crypto.Paseto.Token.Claim
  ( Claim (..), Expiration (..), IssuedAt (..), NotBefore (..) )
import Crypto.Paseto.Token.Claims ( Claims )
import qualified Crypto.Paseto.Token.Claims as Claims
import Data.Text ( Text )
import Data.Time.Clock ( addUTCTime, getCurrentTime, secondsToNominalDiffTime )
import Prelude hiding ( exp )

-- | Parameters for building a PASETO token.
data BuildTokenParams = BuildTokenParams
  { btpClaims :: !Claims
  , btpFooter :: !(Maybe Footer)
  , btpImplicitAssertion :: !(Maybe ImplicitAssertion)
  } deriving stock (Show, Eq)

-- | Get parameters for building a PASETO token which includes the
-- [recommended default claims](https://github.com/paseto-standard/paseto-spec/blob/af79f25908227555404e7462ccdd8ce106049469/docs/02-Implementation-Guide/05-API-UX.md#secure-defaults).
--
-- This includes the following default claims:
--
-- * An 'ExpirationClaim' of 1 hour from the current system time.
-- * An 'IssuedAtClaim' of the current system time.
-- * A 'NotBeforeClaim' of the current system time.
--
-- The default 'Footer' and 'ImplicitAssertion' is 'Nothing'.
getDefaultBuildTokenParams :: IO BuildTokenParams
getDefaultBuildTokenParams = do
  now <- getCurrentTime
  let hourInSeconds = 3600
      exp = ExpirationClaim . Expiration $ addUTCTime (secondsToNominalDiffTime hourInSeconds) now
      iat = IssuedAtClaim (IssuedAt now)
      nbf = NotBeforeClaim (NotBefore now)
  pure BuildTokenParams
    { btpClaims = Claims.fromList [exp, iat, nbf]
    , btpFooter = Nothing
    , btpImplicitAssertion = Nothing
    }

-- | Error building a version 3 local PASETO token.
newtype V3LocalBuildError
  = -- | Encryption error.
    V3LocalBuildEncryptionError V3.EncryptionError
  deriving stock (Show, Eq)

-- | Render a 'V3LocalBuildError' as 'Text'.
renderV3LocalBuildError :: V3LocalBuildError -> Text
renderV3LocalBuildError err =
  case err of
    V3LocalBuildEncryptionError e -> V3.renderEncryptionError e

-- | Build a version 3 local token.
buildTokenV3Local :: BuildTokenParams -> SymmetricKey V3 -> ExceptT V3LocalBuildError IO (Token V3 Local)
buildTokenV3Local btp k =
  firstExceptT V3LocalBuildEncryptionError $
    V3.encrypt k btpClaims btpFooter btpImplicitAssertion
  where
    BuildTokenParams
      { btpClaims
      , btpFooter
      , btpImplicitAssertion
      } = btp

-- | Error building a version 3 public PASETO token.
newtype V3PublicBuildError
  = -- | Cryptographic signing error.
    V3PublicBuildSigningError V3.SigningError
  deriving stock (Show, Eq)

-- | Render a 'V3PublicBuildError' as 'Text'.
renderV3PublicBuildError :: V3PublicBuildError -> Text
renderV3PublicBuildError err =
  case err of
    V3PublicBuildSigningError e -> V3.renderSigningError e

-- | Build a version 3 public token.
buildTokenV3Public :: BuildTokenParams -> SigningKey V3 -> ExceptT V3PublicBuildError IO (Token V3 Public)
buildTokenV3Public btp sk =
  firstExceptT V3PublicBuildSigningError $
    V3.sign sk btpClaims btpFooter btpImplicitAssertion
  where
    BuildTokenParams
      { btpClaims
      , btpFooter
      , btpImplicitAssertion
      } = btp

-- | Build a version 4 local token.
buildTokenV4Local :: BuildTokenParams -> SymmetricKey V4 -> IO (Token V4 Local)
buildTokenV4Local btp k = V4.encrypt k btpClaims btpFooter btpImplicitAssertion
  where
    BuildTokenParams
      { btpClaims
      , btpFooter
      , btpImplicitAssertion
      } = btp

-- | Build a version 4 public token.
buildTokenV4Public :: BuildTokenParams -> SigningKey V4 -> Token V4 Public
buildTokenV4Public btp sk = V4.sign sk btpClaims btpFooter btpImplicitAssertion
  where
    BuildTokenParams
      { btpClaims
      , btpFooter
      , btpImplicitAssertion
      } = btp