Solidify design of BearerToken
This commit is contained in:
parent
710b591b4a
commit
680b674b09
@ -27,6 +27,7 @@ notification-rate-limit: 3600
|
|||||||
notification-collate-delay: 300
|
notification-collate-delay: 300
|
||||||
notification-expiration: 259201
|
notification-expiration: 259201
|
||||||
session-timeout: 7200
|
session-timeout: 7200
|
||||||
|
jwt-expiration: 604800
|
||||||
maximum-content-length: 52428800
|
maximum-content-length: 52428800
|
||||||
|
|
||||||
log-settings:
|
log-settings:
|
||||||
|
|||||||
@ -117,6 +117,8 @@ dependencies:
|
|||||||
- lattices
|
- lattices
|
||||||
- hsass
|
- hsass
|
||||||
- semigroupoids
|
- semigroupoids
|
||||||
|
- jose-jwt
|
||||||
|
- mono-traversable
|
||||||
|
|
||||||
other-extensions:
|
other-extensions:
|
||||||
- GeneralizedNewtypeDeriving
|
- GeneralizedNewtypeDeriving
|
||||||
|
|||||||
@ -101,7 +101,7 @@ mkYesodDispatch "UniWorX" resourcesUniWorX
|
|||||||
-- the place to put your migrate statements to have automatic database
|
-- the place to put your migrate statements to have automatic database
|
||||||
-- migrations handled by Yesod.
|
-- migrations handled by Yesod.
|
||||||
makeFoundation :: (MonadResource m, MonadBaseControl IO m) => AppSettings -> m UniWorX
|
makeFoundation :: (MonadResource m, MonadBaseControl IO m) => AppSettings -> m UniWorX
|
||||||
makeFoundation appSettings@AppSettings{..} = do
|
makeFoundation appSettings'@AppSettings{..} = do
|
||||||
-- Some basic initializations: HTTP connection manager, logger, and static
|
-- Some basic initializations: HTTP connection manager, logger, and static
|
||||||
-- subsite.
|
-- subsite.
|
||||||
appHttpManager <- newManager
|
appHttpManager <- newManager
|
||||||
@ -141,7 +141,7 @@ makeFoundation appSettings@AppSettings{..} = do
|
|||||||
-- logging function. To get out of this loop, we initially create a
|
-- logging function. To get out of this loop, we initially create a
|
||||||
-- temporary foundation without a real connection pool, get a log function
|
-- temporary foundation without a real connection pool, get a log function
|
||||||
-- from there, and then create the real foundation.
|
-- from there, and then create the real foundation.
|
||||||
let mkFoundation appConnPool appSmtpPool appLdapPool appCryptoIDKey appSessionKey appSecretBoxKey appWidgetMemcached = UniWorX {..}
|
let mkFoundation appConnPool appSmtpPool appLdapPool appCryptoIDKey appSessionKey appSecretBoxKey appWidgetMemcached appJSONWebKeySet = UniWorX {..}
|
||||||
-- The UniWorX {..} syntax is an example of record wild cards. For more
|
-- The UniWorX {..} syntax is an example of record wild cards. For more
|
||||||
-- information, see:
|
-- information, see:
|
||||||
-- https://ocharles.org.uk/blog/posts/2014-12-04-record-wildcards.html
|
-- https://ocharles.org.uk/blog/posts/2014-12-04-record-wildcards.html
|
||||||
@ -153,13 +153,14 @@ makeFoundation appSettings@AppSettings{..} = do
|
|||||||
(error "sessionKey forced in tempFoundation")
|
(error "sessionKey forced in tempFoundation")
|
||||||
(error "secretBoxKey forced in tempFoundation")
|
(error "secretBoxKey forced in tempFoundation")
|
||||||
(error "widgetMemcached forced in tempFoundation")
|
(error "widgetMemcached forced in tempFoundation")
|
||||||
|
(error "JSONWebKeySet forced in tempFoundation")
|
||||||
logFunc loc src lvl str = do
|
logFunc loc src lvl str = do
|
||||||
f <- messageLoggerSource tempFoundation <$> readTVarIO (snd appLogger)
|
f <- messageLoggerSource tempFoundation <$> readTVarIO (snd appLogger)
|
||||||
f loc src lvl str
|
f loc src lvl str
|
||||||
|
|
||||||
flip runLoggingT logFunc $ do
|
flip runLoggingT logFunc $ do
|
||||||
$logDebugS "InstanceID" $ UUID.toText appInstanceID
|
$logDebugS "InstanceID" $ UUID.toText appInstanceID
|
||||||
-- logDebugS "Configuration" $ tshow appSettings
|
-- logDebugS "Configuration" $ tshow appSettings'
|
||||||
|
|
||||||
smtpPool <- traverse createSmtpPool appSmtpConf
|
smtpPool <- traverse createSmtpPool appSmtpConf
|
||||||
|
|
||||||
@ -177,8 +178,9 @@ makeFoundation appSettings@AppSettings{..} = do
|
|||||||
appCryptoIDKey <- clusterSetting (Proxy :: Proxy 'ClusterCryptoIDKey) `runSqlPool` sqlPool
|
appCryptoIDKey <- clusterSetting (Proxy :: Proxy 'ClusterCryptoIDKey) `runSqlPool` sqlPool
|
||||||
appSessionKey <- clusterSetting (Proxy :: Proxy 'ClusterClientSessionKey) `runSqlPool` sqlPool
|
appSessionKey <- clusterSetting (Proxy :: Proxy 'ClusterClientSessionKey) `runSqlPool` sqlPool
|
||||||
appSecretBoxKey <- clusterSetting (Proxy :: Proxy 'ClusterSecretBoxKey) `runSqlPool` sqlPool
|
appSecretBoxKey <- clusterSetting (Proxy :: Proxy 'ClusterSecretBoxKey) `runSqlPool` sqlPool
|
||||||
|
appJSONWebKeySet <- clusterSetting (Proxy :: Proxy 'ClusterJSONWebKeySet) `runSqlPool` sqlPool
|
||||||
|
|
||||||
let foundation = mkFoundation sqlPool smtpPool ldapPool appCryptoIDKey appSessionKey appSecretBoxKey appWidgetMemcached
|
let foundation = mkFoundation sqlPool smtpPool ldapPool appCryptoIDKey appSessionKey appSecretBoxKey appWidgetMemcached appJSONWebKeySet
|
||||||
|
|
||||||
handleJobs foundation
|
handleJobs foundation
|
||||||
|
|
||||||
@ -265,7 +267,7 @@ makeLogWare app = do
|
|||||||
logger <- readTVarIO . snd $ appLogger app
|
logger <- readTVarIO . snd $ appLogger app
|
||||||
logWare <- mkRequestLogger def
|
logWare <- mkRequestLogger def
|
||||||
{ outputFormat = bool
|
{ outputFormat = bool
|
||||||
(Apache . bool FromSocket FromHeader . appIpFromHeader $ appSettings app)
|
(Apache . bool FromSocket FromHeader $ app ^. _appIpFromHeader)
|
||||||
(Detailed True)
|
(Detailed True)
|
||||||
logDetailed
|
logDetailed
|
||||||
, destination = Logger $ loggerSet logger
|
, destination = Logger $ loggerSet logger
|
||||||
@ -287,8 +289,8 @@ makeLogWare app = do
|
|||||||
-- | Warp settings for the given foundation value.
|
-- | Warp settings for the given foundation value.
|
||||||
warpSettings :: UniWorX -> Settings
|
warpSettings :: UniWorX -> Settings
|
||||||
warpSettings foundation = defaultSettings
|
warpSettings foundation = defaultSettings
|
||||||
& setPort (appPort $ appSettings foundation)
|
& setPort (foundation ^. _appPort)
|
||||||
& setHost (appHost $ appSettings foundation)
|
& setHost (foundation ^. _appHost)
|
||||||
& setOnException (\_req e ->
|
& setOnException (\_req e ->
|
||||||
when (defaultShouldDisplayException e) $ do
|
when (defaultShouldDisplayException e) $ do
|
||||||
logger <- readTVarIO . snd $ appLogger foundation
|
logger <- readTVarIO . snd $ appLogger foundation
|
||||||
@ -384,6 +386,6 @@ addPWEntry :: User
|
|||||||
-> Text {-^ Password -}
|
-> Text {-^ Password -}
|
||||||
-> IO ()
|
-> IO ()
|
||||||
addPWEntry User{ userAuthentication = _, ..} (Text.encodeUtf8 -> pw) = db $ do
|
addPWEntry User{ userAuthentication = _, ..} (Text.encodeUtf8 -> pw) = db $ do
|
||||||
PWHashConf{..} <- getsYesod $ appAuthPWHash . appSettings
|
PWHashConf{..} <- getsYesod $ view _appAuthPWHash
|
||||||
(AuthPWHash . Text.decodeUtf8 -> userAuthentication) <- liftIO $ makePasswordWith pwHashAlgorithm pw pwHashStrength
|
(AuthPWHash . Text.decodeUtf8 -> userAuthentication) <- liftIO $ makePasswordWith pwHashAlgorithm pw pwHashStrength
|
||||||
void $ insert User{..}
|
void $ insert User{..}
|
||||||
|
|||||||
@ -159,7 +159,7 @@ campusUser conf@LdapConf{..} pool Creds{..} = liftIO . (`catches` errHandlers) $
|
|||||||
]
|
]
|
||||||
|
|
||||||
-- ldapConfig :: UniWorX -> LDAPConfig
|
-- ldapConfig :: UniWorX -> LDAPConfig
|
||||||
-- ldapConfig _app@(appSettings -> settings) = LDAPConfig
|
-- ldapConfig _app@(appSettings' -> settings) = LDAPConfig
|
||||||
-- { usernameFilter = \u -> principalName <> "=" <> u
|
-- { usernameFilter = \u -> principalName <> "=" <> u
|
||||||
-- , identifierModifier
|
-- , identifierModifier
|
||||||
-- , ldapUri = appLDAPURI settings
|
-- , ldapUri = appLDAPURI settings
|
||||||
|
|||||||
@ -29,6 +29,11 @@ import Data.Aeson (ToJSON(..), ToJSONKey(..), ToJSONKeyFunction(..), FromJSON(..
|
|||||||
import Data.Aeson.Encoding (text)
|
import Data.Aeson.Encoding (text)
|
||||||
|
|
||||||
|
|
||||||
|
instance {-# OVERLAPPING #-} MonadThrow m => MonadCrypto (ReaderT CryptoIDKey m) where
|
||||||
|
type MonadCryptoKey (ReaderT CryptoIDKey m) = CryptoIDKey
|
||||||
|
cryptoIDKey f = ask >>= f
|
||||||
|
|
||||||
|
|
||||||
-- Generates CryptoUUID... and CryptoFileName... Datatypes
|
-- Generates CryptoUUID... and CryptoFileName... Datatypes
|
||||||
decCryptoIDs [ ''SubmissionId
|
decCryptoIDs [ ''SubmissionId
|
||||||
, ''FileId
|
, ''FileId
|
||||||
|
|||||||
14
src/Data/Aeson/Types/Instances.hs
Normal file
14
src/Data/Aeson/Types/Instances.hs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
|
module Data.Aeson.Types.Instances
|
||||||
|
(
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
|
||||||
|
import Data.Aeson.Types (Parser)
|
||||||
|
import Control.Monad.Catch
|
||||||
|
|
||||||
|
|
||||||
|
instance MonadThrow Parser where
|
||||||
|
throwM = fail . show
|
||||||
20
src/Data/NonNull/Instances.hs
Normal file
20
src/Data/NonNull/Instances.hs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
|
module Data.NonNull.Instances
|
||||||
|
(
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
|
||||||
|
import Data.Aeson
|
||||||
|
|
||||||
|
|
||||||
|
instance ToJSON a => ToJSON (NonNull a) where
|
||||||
|
toJSON = toJSON . toNullable
|
||||||
|
|
||||||
|
instance (FromJSON a, MonoFoldable a) => FromJSON (NonNull a) where
|
||||||
|
parseJSON = parseJSON >=> maybe (fail "Expected non-empty structure") return . fromNullable
|
||||||
|
|
||||||
|
|
||||||
|
instance Hashable a => Hashable (NonNull a) where
|
||||||
|
hashWithSalt s = hashWithSalt s . toNullable
|
||||||
@ -77,6 +77,7 @@ import qualified Yesod.Auth.Message as Auth
|
|||||||
import qualified Data.Conduit.List as C
|
import qualified Data.Conduit.List as C
|
||||||
|
|
||||||
import qualified Crypto.Saltine.Core.SecretBox as SecretBox
|
import qualified Crypto.Saltine.Core.SecretBox as SecretBox
|
||||||
|
import qualified Jose.Jwk as Jose
|
||||||
|
|
||||||
import qualified Database.Memcached.Binary.IO as Memcached
|
import qualified Database.Memcached.Binary.IO as Memcached
|
||||||
import Data.Bits (Bits(zeroBits))
|
import Data.Bits (Bits(zeroBits))
|
||||||
@ -96,6 +97,8 @@ instance DisplayAble TermId where
|
|||||||
instance DisplayAble SchoolId where
|
instance DisplayAble SchoolId where
|
||||||
display = CI.original . unSchoolKey
|
display = CI.original . unSchoolKey
|
||||||
|
|
||||||
|
type SMTPPool = Pool SMTPConnection
|
||||||
|
|
||||||
-- infixl 9 :$:
|
-- infixl 9 :$:
|
||||||
-- pattern a :$: b = a b
|
-- pattern a :$: b = a b
|
||||||
|
|
||||||
@ -104,7 +107,7 @@ instance DisplayAble SchoolId where
|
|||||||
-- starts running, such as database connections. Every handler will have
|
-- starts running, such as database connections. Every handler will have
|
||||||
-- access to the data present here.
|
-- access to the data present here.
|
||||||
data UniWorX = UniWorX
|
data UniWorX = UniWorX
|
||||||
{ appSettings :: AppSettings
|
{ appSettings' :: AppSettings
|
||||||
, appStatic :: EmbeddedStatic -- ^ Settings for static file serving.
|
, appStatic :: EmbeddedStatic -- ^ Settings for static file serving.
|
||||||
, appConnPool :: ConnectionPool -- ^ Database connection pool.
|
, appConnPool :: ConnectionPool -- ^ Database connection pool.
|
||||||
, appSmtpPool :: Maybe SMTPPool
|
, appSmtpPool :: Maybe SMTPPool
|
||||||
@ -119,9 +122,16 @@ data UniWorX = UniWorX
|
|||||||
, appCronThread :: TMVar (ReleaseKey, ThreadId)
|
, appCronThread :: TMVar (ReleaseKey, ThreadId)
|
||||||
, appSessionKey :: ClientSession.Key
|
, appSessionKey :: ClientSession.Key
|
||||||
, appSecretBoxKey :: SecretBox.Key
|
, appSecretBoxKey :: SecretBox.Key
|
||||||
|
, appJSONWebKeySet :: Jose.JwkSet
|
||||||
}
|
}
|
||||||
|
|
||||||
type SMTPPool = Pool SMTPConnection
|
makeLenses_ ''UniWorX
|
||||||
|
instance HasInstanceID UniWorX InstanceId where
|
||||||
|
instanceID = _appInstanceID
|
||||||
|
instance HasJSONWebKeySet UniWorX Jose.JwkSet where
|
||||||
|
jsonWebKeySet = _appJSONWebKeySet
|
||||||
|
instance HasAppSettings UniWorX where
|
||||||
|
appSettings = _appSettings'
|
||||||
|
|
||||||
-- This is where we define all of the routes in our application. For a full
|
-- This is where we define all of the routes in our application. For a full
|
||||||
-- explanation of the syntax, please see:
|
-- explanation of the syntax, please see:
|
||||||
@ -463,7 +473,7 @@ tagAccessPredicate AuthNoEscalation = APDB $ \mAuthId route _ -> case route of
|
|||||||
tagAccessPredicate AuthDeprecated = APHandler $ \_ r _ -> do
|
tagAccessPredicate AuthDeprecated = APHandler $ \_ r _ -> do
|
||||||
$logWarnS "AccessControl" ("deprecated route: " <> tshow r)
|
$logWarnS "AccessControl" ("deprecated route: " <> tshow r)
|
||||||
addMessageI Error MsgDeprecatedRoute
|
addMessageI Error MsgDeprecatedRoute
|
||||||
allow <- appAllowDeprecated . appSettings <$> getYesod
|
allow <- view _appAllowDeprecated
|
||||||
return $ bool (Unauthorized "Deprecated Route") Authorized allow
|
return $ bool (Unauthorized "Deprecated Route") Authorized allow
|
||||||
tagAccessPredicate AuthDevelopment = APHandler $ \_ r _ -> do
|
tagAccessPredicate AuthDevelopment = APHandler $ \_ r _ -> do
|
||||||
$logWarnS "AccessControl" ("route in development: " <> tshow r)
|
$logWarnS "AccessControl" ("route in development: " <> tshow r)
|
||||||
@ -809,17 +819,17 @@ instance Yesod UniWorX where
|
|||||||
-- Controls the base of generated URLs. For more information on modifying,
|
-- Controls the base of generated URLs. For more information on modifying,
|
||||||
-- see: https://github.com/yesodweb/yesod/wiki/Overriding-approot
|
-- see: https://github.com/yesodweb/yesod/wiki/Overriding-approot
|
||||||
approot = ApprootRequest $ \app req ->
|
approot = ApprootRequest $ \app req ->
|
||||||
case appRoot $ appSettings app of
|
case app ^. _appRoot of
|
||||||
Nothing -> getApprootText guessApproot app req
|
Nothing -> getApprootText guessApproot app req
|
||||||
Just root -> root
|
Just root -> root
|
||||||
|
|
||||||
-- Store session data on the client in encrypted cookies,
|
-- Store session data on the client in encrypted cookies,
|
||||||
-- default session idle timeout is 120 minutes
|
-- default session idle timeout is 120 minutes
|
||||||
makeSessionBackend UniWorX{appSessionKey,appSettings=AppSettings{appSessionTimeout}} = do
|
makeSessionBackend app = do
|
||||||
(getCachedDate, _) <- clientSessionDateCacher appSessionTimeout
|
(getCachedDate, _) <- clientSessionDateCacher (app ^. _appSessionTimeout)
|
||||||
return . Just $ clientSessionBackend appSessionKey getCachedDate
|
return . Just $ clientSessionBackend (app ^. _appSessionKey) getCachedDate
|
||||||
|
|
||||||
maximumContentLength UniWorX{appSettings=AppSettings{appMaximumContentLength}} _ = appMaximumContentLength
|
maximumContentLength app _ = app ^. _appMaximumContentLength
|
||||||
|
|
||||||
-- Yesod Middleware allows you to run code before and after each handler function.
|
-- Yesod Middleware allows you to run code before and after each handler function.
|
||||||
-- The defaultYesodMiddleware adds the response header "Vary: Accept, Accept-Language" and performs authorization checks.
|
-- The defaultYesodMiddleware adds the response header "Vary: Accept, Accept-Language" and performs authorization checks.
|
||||||
@ -878,7 +888,7 @@ instance Yesod UniWorX where
|
|||||||
encrypted :: ToJSON a => a -> Widget -> Widget
|
encrypted :: ToJSON a => a -> Widget -> Widget
|
||||||
encrypted plaintextJson plaintext = do
|
encrypted plaintextJson plaintext = do
|
||||||
canDecrypt <- (== Authorized) <$> evalAccess AdminErrMsgR True
|
canDecrypt <- (== Authorized) <$> evalAccess AdminErrMsgR True
|
||||||
shouldEncrypt <- getsYesod $ appEncryptErrors . appSettings
|
shouldEncrypt <- view _appEncryptErrors
|
||||||
if
|
if
|
||||||
| shouldEncrypt
|
| shouldEncrypt
|
||||||
, not canDecrypt -> do
|
, not canDecrypt -> do
|
||||||
@ -919,8 +929,8 @@ instance Yesod UniWorX where
|
|||||||
isAuthorized = evalAccess
|
isAuthorized = evalAccess
|
||||||
|
|
||||||
addStaticContent ext _mime content = do
|
addStaticContent ext _mime content = do
|
||||||
UniWorX{appWidgetMemcached, appSettings} <- getYesod
|
UniWorX{appWidgetMemcached, appSettings'} <- getYesod
|
||||||
for ((,) <$> appWidgetMemcached <*> appWidgetMemcachedConf appSettings) $ \(mConn, WidgetMemcachedConf{ widgetMemcachedConnectInfo = _, .. }) -> do
|
for ((,) <$> appWidgetMemcached <*> appWidgetMemcachedConf appSettings') $ \(mConn, WidgetMemcachedConf{ widgetMemcachedConnectInfo = _, .. }) -> do
|
||||||
let expiry = (maybe 0 ceiling widgetMemcachedExpiry)
|
let expiry = (maybe 0 ceiling widgetMemcachedExpiry)
|
||||||
touch = liftIO $ Memcached.touch expiry (encodeUtf8 $ pack fileName) mConn
|
touch = liftIO $ Memcached.touch expiry (encodeUtf8 $ pack fileName) mConn
|
||||||
add = liftIO $ Memcached.add zeroBits expiry (encodeUtf8 $ pack fileName) content mConn
|
add = liftIO $ Memcached.add zeroBits expiry (encodeUtf8 $ pack fileName) content mConn
|
||||||
@ -971,8 +981,7 @@ siteLayout = siteLayout' . Just
|
|||||||
siteLayout' :: Maybe Widget -- ^ Optionally override `pageHeading`
|
siteLayout' :: Maybe Widget -- ^ Optionally override `pageHeading`
|
||||||
-> Widget -> Handler Html
|
-> Widget -> Handler Html
|
||||||
siteLayout' headingOverride widget = do
|
siteLayout' headingOverride widget = do
|
||||||
master <- getYesod
|
AppSettings { appUserDefaults = UserDefaultConf{..}, .. } <- view appSettings
|
||||||
let AppSettings { appUserDefaults = UserDefaultConf{..}, .. } = appSettings master
|
|
||||||
|
|
||||||
isModal <- hasCustomHeader HeaderIsModal
|
isModal <- hasCustomHeader HeaderIsModal
|
||||||
|
|
||||||
@ -2081,7 +2090,7 @@ instance YesodAuth UniWorX where
|
|||||||
_other -> return res
|
_other -> return res
|
||||||
|
|
||||||
$logDebugS "auth" $ tshow Creds{..}
|
$logDebugS "auth" $ tshow Creds{..}
|
||||||
UniWorX{ appSettings = AppSettings{ appUserDefaults = UserDefaultConf{..}, ..}, .. } <- getYesod
|
UniWorX{ appSettings' = AppSettings{ appUserDefaults = UserDefaultConf{..}, ..}, .. } <- getYesod
|
||||||
|
|
||||||
flip catches excHandlers $ case (,) <$> appLdapConf <*> appLdapPool of
|
flip catches excHandlers $ case (,) <$> appLdapConf <*> appLdapPool of
|
||||||
Just (ldapConf, ldapPool) -> fmap (either id id) . runExceptT $ do
|
Just (ldapConf, ldapPool) -> fmap (either id id) . runExceptT $ do
|
||||||
@ -2200,7 +2209,7 @@ instance YesodAuth UniWorX where
|
|||||||
where
|
where
|
||||||
insertMaybe key val = get key >>= maybe (insert_ val) (\_ -> return ())
|
insertMaybe key val = get key >>= maybe (insert_ val) (\_ -> return ())
|
||||||
|
|
||||||
authPlugins (UniWorX{ appSettings = AppSettings{..}, appLdapPool }) = catMaybes
|
authPlugins (UniWorX{ appSettings' = AppSettings{..}, appLdapPool }) = catMaybes
|
||||||
[ campusLogin <$> appLdapConf <*> appLdapPool
|
[ campusLogin <$> appLdapConf <*> appLdapPool
|
||||||
, Just . hashLogin $ pwHashAlgorithm appAuthPWHash
|
, Just . hashLogin $ pwHashAlgorithm appAuthPWHash
|
||||||
, dummyLogin <$ guard appAuthDummyLogin
|
, dummyLogin <$ guard appAuthDummyLogin
|
||||||
@ -2225,9 +2234,9 @@ unsafeHandler f h = do
|
|||||||
|
|
||||||
|
|
||||||
instance YesodMail UniWorX where
|
instance YesodMail UniWorX where
|
||||||
defaultFromAddress = getsYesod $ appMailFrom . appSettings
|
defaultFromAddress = getsYesod $ view _appMailFrom
|
||||||
mailObjectIdDomain = getsYesod $ appMailObjectDomain . appSettings
|
mailObjectIdDomain = getsYesod $ view _appMailObjectDomain
|
||||||
mailVerp = getsYesod $ appMailVerp . appSettings
|
mailVerp = getsYesod $ view _appMailVerp
|
||||||
mailDateTZ = return appTZ
|
mailDateTZ = return appTZ
|
||||||
mailSmtp act = do
|
mailSmtp act = do
|
||||||
pool <- maybe (throwM MailNotAvailable) return =<< getsYesod appSmtpPool
|
pool <- maybe (throwM MailNotAvailable) return =<< getsYesod appSmtpPool
|
||||||
|
|||||||
@ -4,6 +4,8 @@ module Handler.Utils
|
|||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
|
||||||
|
import Utils.Lens
|
||||||
|
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
-- import qualified Data.Set (Set)
|
-- import qualified Data.Set (Set)
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
@ -40,7 +42,7 @@ downloadFiles = do
|
|||||||
case mauth of
|
case mauth of
|
||||||
Just (Entity _ User{..}) -> return userDownloadFiles
|
Just (Entity _ User{..}) -> return userDownloadFiles
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
AppSettings{ appUserDefaults = UserDefaultConf{..} } <- getsYesod appSettings
|
UserDefaultConf{..} <- getsYesod $ view _appUserDefaults
|
||||||
return userDefaultDownloadFiles
|
return userDefaultDownloadFiles
|
||||||
|
|
||||||
tidFromText :: Text -> Maybe TermId
|
tidFromText :: Text -> Maybe TermId
|
||||||
|
|||||||
@ -13,6 +13,8 @@ module Handler.Utils.DateTime
|
|||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
|
||||||
|
import Utils.Lens
|
||||||
|
|
||||||
import Data.Time.Zones
|
import Data.Time.Zones
|
||||||
import qualified Data.Time.Zones as TZ
|
import qualified Data.Time.Zones as TZ
|
||||||
|
|
||||||
@ -83,7 +85,7 @@ getTimeLocale = getTimeLocale' <$> languages
|
|||||||
getDateTimeFormat :: (MonadHandler m, HandlerSite m ~ UniWorX) => SelDateTimeFormat -> m DateTimeFormat
|
getDateTimeFormat :: (MonadHandler m, HandlerSite m ~ UniWorX) => SelDateTimeFormat -> m DateTimeFormat
|
||||||
getDateTimeFormat sel = do
|
getDateTimeFormat sel = do
|
||||||
mauth <- liftHandlerT maybeAuth
|
mauth <- liftHandlerT maybeAuth
|
||||||
AppSettings{ appUserDefaults = UserDefaultConf{..} } <- getsYesod appSettings
|
UserDefaultConf{..} <- getsYesod $ view _appUserDefaults
|
||||||
let
|
let
|
||||||
fmt
|
fmt
|
||||||
| Just (Entity _ User{..}) <- mauth
|
| Just (Entity _ User{..}) <- mauth
|
||||||
@ -182,4 +184,4 @@ weeksToAdd old new = loop 0 old
|
|||||||
where
|
where
|
||||||
loop n t
|
loop n t
|
||||||
| t > new = n
|
| t > new = n
|
||||||
| otherwise = loop (succ n) (addOneWeek t)
|
| otherwise = loop (succ n) (addOneWeek t)
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import GHC.Exts as Import (IsList)
|
|||||||
import Data.Hashable as Import
|
import Data.Hashable as Import
|
||||||
import Data.List.NonEmpty as Import (NonEmpty(..))
|
import Data.List.NonEmpty as Import (NonEmpty(..))
|
||||||
import Data.List.NonEmpty.Instances as Import ()
|
import Data.List.NonEmpty.Instances as Import ()
|
||||||
|
import Data.NonNull.Instances as Import ()
|
||||||
import Data.Text.Encoding.Error as Import(UnicodeException(..))
|
import Data.Text.Encoding.Error as Import(UnicodeException(..))
|
||||||
import Data.Semigroup as Import (Semigroup)
|
import Data.Semigroup as Import (Semigroup)
|
||||||
import Data.Monoid as Import (Last(..), First(..))
|
import Data.Monoid as Import (Last(..), First(..))
|
||||||
@ -56,6 +57,7 @@ import Control.Monad.Trans.Resource as Import (ReleaseKey)
|
|||||||
|
|
||||||
import Network.Mail.Mime.Instances as Import ()
|
import Network.Mail.Mime.Instances as Import ()
|
||||||
import Yesod.Core.Instances as Import ()
|
import Yesod.Core.Instances as Import ()
|
||||||
|
import Data.Aeson.Types.Instances as Import ()
|
||||||
|
|
||||||
import Ldap.Client.Pool as Import
|
import Ldap.Client.Pool as Import
|
||||||
|
|
||||||
@ -67,6 +69,8 @@ import Numeric.Natural.Instances as Import ()
|
|||||||
import System.Random as Import (Random)
|
import System.Random as Import (Random)
|
||||||
import Control.Monad.Random.Class as Import (MonadRandom(..))
|
import Control.Monad.Random.Class as Import (MonadRandom(..))
|
||||||
|
|
||||||
|
import Jose.Jwt.Instances as Import ()
|
||||||
|
|
||||||
|
|
||||||
import Control.Monad.Trans.RWS (RWST)
|
import Control.Monad.Trans.RWS (RWST)
|
||||||
|
|
||||||
|
|||||||
17
src/Jobs.hs
17
src/Jobs.hs
@ -6,6 +6,7 @@ module Jobs
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
import Utils.Lens
|
||||||
|
|
||||||
import Jobs.Types as Types hiding (JobCtl(JobCtlQueue))
|
import Jobs.Types as Types hiding (JobCtl(JobCtlQueue))
|
||||||
import Jobs.Types (JobCtl(JobCtlQueue))
|
import Jobs.Types (JobCtl(JobCtlQueue))
|
||||||
@ -77,7 +78,7 @@ handleJobs :: ( MonadResource m
|
|||||||
-- Uses `unsafeHandler`, as per documentation all HTTP-related fields of state/environment are meaningless placeholders.
|
-- Uses `unsafeHandler`, as per documentation all HTTP-related fields of state/environment are meaningless placeholders.
|
||||||
-- Handling commands in `HandlerT` provides us with the facilities to render urls, unifies logging, provides a value of the foundation type, ...
|
-- Handling commands in `HandlerT` provides us with the facilities to render urls, unifies logging, provides a value of the foundation type, ...
|
||||||
handleJobs foundation@UniWorX{..} = do
|
handleJobs foundation@UniWorX{..} = do
|
||||||
let num = appJobWorkers appSettings
|
let num = foundation ^. _appJobWorkers
|
||||||
|
|
||||||
jobCrontab <- liftIO $ newTMVarIO HashMap.empty
|
jobCrontab <- liftIO $ newTMVarIO HashMap.empty
|
||||||
jobConfirm <- liftIO $ newTVarIO HashMap.empty
|
jobConfirm <- liftIO $ newTVarIO HashMap.empty
|
||||||
@ -135,7 +136,7 @@ execCrontab = evalStateT go HashMap.empty
|
|||||||
runConduit $ transPipe lift (selectSource [] []) .| C.mapM_ merge
|
runConduit $ transPipe lift (selectSource [] []) .| C.mapM_ merge
|
||||||
|
|
||||||
refT <- liftIO getCurrentTime
|
refT <- liftIO getCurrentTime
|
||||||
settings <- getsYesod appSettings
|
settings <- getsYesod appSettings'
|
||||||
currentState <- mapStateT (mapReaderT $ liftIO . atomically) $ do
|
currentState <- mapStateT (mapReaderT $ liftIO . atomically) $ do
|
||||||
crontab' <- liftBase . tryReadTMVar =<< asks jobCrontab
|
crontab' <- liftBase . tryReadTMVar =<< asks jobCrontab
|
||||||
case crontab' of
|
case crontab' of
|
||||||
@ -157,7 +158,7 @@ execCrontab = evalStateT go HashMap.empty
|
|||||||
| ((==) `on` HashMap.lookup jobCtl) newCrontab currentCrontab
|
| ((==) `on` HashMap.lookup jobCtl) newCrontab currentCrontab
|
||||||
-> do
|
-> do
|
||||||
now <- liftIO $ getCurrentTime
|
now <- liftIO $ getCurrentTime
|
||||||
instanceID <- getsYesod appInstanceID
|
instanceID' <- getsYesod appInstanceID
|
||||||
State.modify $ HashMap.alter (Just . ($ Max now) . maybe id (<>)) jobCtl
|
State.modify $ HashMap.alter (Just . ($ Max now) . maybe id (<>)) jobCtl
|
||||||
case jobCtl of
|
case jobCtl of
|
||||||
JobCtlQueue job -> do
|
JobCtlQueue job -> do
|
||||||
@ -166,7 +167,7 @@ execCrontab = evalStateT go HashMap.empty
|
|||||||
CronLastExec
|
CronLastExec
|
||||||
{ cronLastExecJob = toJSON job
|
{ cronLastExecJob = toJSON job
|
||||||
, cronLastExecTime = now
|
, cronLastExecTime = now
|
||||||
, cronLastExecInstance = instanceID
|
, cronLastExecInstance = instanceID'
|
||||||
}
|
}
|
||||||
[ CronLastExecTime =. now ]
|
[ CronLastExecTime =. now ]
|
||||||
lift . lift $ queueDBJob job
|
lift . lift $ queueDBJob job
|
||||||
@ -285,21 +286,21 @@ jLocked jId act = do
|
|||||||
let
|
let
|
||||||
lock = runDB . setSerializable $ do
|
lock = runDB . setSerializable $ do
|
||||||
qj@QueuedJob{..} <- maybe (throwM $ JNonexistant jId) return =<< get jId
|
qj@QueuedJob{..} <- maybe (throwM $ JNonexistant jId) return =<< get jId
|
||||||
instanceID <- getsYesod appInstanceID
|
instanceID' <- getsYesod $ view instanceID
|
||||||
threshold <- getsYesod $ appJobStaleThreshold . appSettings
|
threshold <- getsYesod $ view _appJobStaleThreshold
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
hadStale <- maybeT (return False) $ do
|
hadStale <- maybeT (return False) $ do
|
||||||
lockTime <- MaybeT $ return queuedJobLockTime
|
lockTime <- MaybeT $ return queuedJobLockTime
|
||||||
lockInstance <- MaybeT $ return queuedJobLockInstance
|
lockInstance <- MaybeT $ return queuedJobLockInstance
|
||||||
if
|
if
|
||||||
| lockInstance == instanceID
|
| lockInstance == instanceID'
|
||||||
, diffUTCTime now lockTime >= threshold
|
, diffUTCTime now lockTime >= threshold
|
||||||
-> return True
|
-> return True
|
||||||
| otherwise
|
| otherwise
|
||||||
-> throwM $ JLocked jId lockInstance lockTime
|
-> throwM $ JLocked jId lockInstance lockTime
|
||||||
when hadStale .
|
when hadStale .
|
||||||
$logWarnS "Jobs" $ "Ignored stale lock: " <> tshow qj
|
$logWarnS "Jobs" $ "Ignored stale lock: " <> tshow qj
|
||||||
val <- updateGet jId [ QueuedJobLockInstance =. Just instanceID
|
val <- updateGet jId [ QueuedJobLockInstance =. Just instanceID'
|
||||||
, QueuedJobLockTime =. Just now
|
, QueuedJobLockTime =. Just now
|
||||||
]
|
]
|
||||||
liftIO . atomically $ writeTVar hasLock True
|
liftIO . atomically $ writeTVar hasLock True
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import qualified Data.Conduit.List as C
|
|||||||
determineCrontab :: DB (Crontab JobCtl)
|
determineCrontab :: DB (Crontab JobCtl)
|
||||||
-- ^ Extract all future jobs from the database (sheet deadlines, ...)
|
-- ^ Extract all future jobs from the database (sheet deadlines, ...)
|
||||||
determineCrontab = execWriterT $ do
|
determineCrontab = execWriterT $ do
|
||||||
AppSettings{..} <- getsYesod appSettings
|
AppSettings{..} <- getsYesod appSettings'
|
||||||
|
|
||||||
case appJobFlushInterval of
|
case appJobFlushInterval of
|
||||||
Just interval -> tell $ HashMap.singleton
|
Just interval -> tell $ HashMap.singleton
|
||||||
|
|||||||
@ -20,7 +20,7 @@ dispatchJobHelpRequest :: Either (Maybe Address) UserId
|
|||||||
-> Maybe Text -- ^ Referer
|
-> Maybe Text -- ^ Referer
|
||||||
-> Handler ()
|
-> Handler ()
|
||||||
dispatchJobHelpRequest jSender jRequestTime jHelpRequest jReferer = do
|
dispatchJobHelpRequest jSender jRequestTime jHelpRequest jReferer = do
|
||||||
supportAddress <- getsYesod $ appMailSupport . appSettings
|
supportAddress <- view _appMailSupport
|
||||||
userInfo <- bitraverse return (runDB . getEntity) jSender
|
userInfo <- bitraverse return (runDB . getEntity) jSender
|
||||||
let userAddress = either
|
let userAddress = either
|
||||||
id
|
id
|
||||||
|
|||||||
18
src/Jose/Jwt/Instances.hs
Normal file
18
src/Jose/Jwt/Instances.hs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
|
module Jose.Jwt.Instances
|
||||||
|
(
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude.Yesod
|
||||||
|
|
||||||
|
import Jose.Jwt
|
||||||
|
|
||||||
|
|
||||||
|
instance PathPiece Jwt where
|
||||||
|
toPathPiece (Jwt bytes) = decodeUtf8 bytes
|
||||||
|
fromPathPiece = Just . Jwt . encodeUtf8
|
||||||
|
|
||||||
|
deriving instance Typeable JwtError
|
||||||
|
|
||||||
|
instance Exception JwtError
|
||||||
@ -2,7 +2,6 @@ module Model.Migration.Types where
|
|||||||
|
|
||||||
import ClassyPrelude.Yesod
|
import ClassyPrelude.Yesod
|
||||||
import Data.Aeson.TH (deriveJSON, defaultOptions)
|
import Data.Aeson.TH (deriveJSON, defaultOptions)
|
||||||
import Database.Persist.Sql
|
|
||||||
|
|
||||||
import qualified Model as Current
|
import qualified Model as Current
|
||||||
import qualified Model.Types.JSON as Current
|
import qualified Model.Types.JSON as Current
|
||||||
|
|||||||
158
src/Model/Token.hs
Normal file
158
src/Model/Token.hs
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
|
||||||
|
module Model.Token
|
||||||
|
( BearerToken(..)
|
||||||
|
, bearerToken
|
||||||
|
, encodeToken, decodeToken
|
||||||
|
, tokenToJSON, tokenParseJSON
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude.Yesod
|
||||||
|
import Model
|
||||||
|
import Settings
|
||||||
|
import Utils.Lens hiding ((.=))
|
||||||
|
|
||||||
|
import Yesod.Auth (AuthId)
|
||||||
|
|
||||||
|
-- import qualified Jose.Jwa as Jose
|
||||||
|
import Jose.Jwk (JwkSet)
|
||||||
|
-- import qualified Jose.Jwk as Jose
|
||||||
|
import Jose.Jwt (Jwt, JwtError, IntDate(..))
|
||||||
|
import qualified Jose.Jwt as Jose
|
||||||
|
|
||||||
|
import Jose.Jwt.Instances ()
|
||||||
|
import Data.Aeson.Types.Instances ()
|
||||||
|
|
||||||
|
import qualified Crypto.Random as Crypto (MonadRandom)
|
||||||
|
|
||||||
|
import Data.HashSet (HashSet)
|
||||||
|
|
||||||
|
import qualified Data.HashMap.Strict as HashMap
|
||||||
|
|
||||||
|
import Data.Aeson.Types (Parser, (.:?), (.:))
|
||||||
|
import qualified Data.Aeson as JSON
|
||||||
|
import qualified Data.Aeson.Types as JSON
|
||||||
|
|
||||||
|
import CryptoID
|
||||||
|
|
||||||
|
import Data.Time.Clock
|
||||||
|
import Data.Time.Clock.POSIX
|
||||||
|
|
||||||
|
import Control.Monad.Random (MonadRandom(..))
|
||||||
|
|
||||||
|
|
||||||
|
data BearerToken site = BearerToken
|
||||||
|
{ tokenIdentifier :: TokenId
|
||||||
|
, tokenAuthority :: AuthId site
|
||||||
|
, tokenRoutes :: Maybe (HashSet (Route site))
|
||||||
|
, tokenAddAuth :: AuthCNF
|
||||||
|
, tokenIssuedAt :: UTCTime
|
||||||
|
, tokenIssuedBy :: InstanceId
|
||||||
|
, tokenExpiresAt
|
||||||
|
, tokenStartsAt :: Maybe UTCTime
|
||||||
|
} deriving (Generic, Typeable)
|
||||||
|
|
||||||
|
deriving instance (Eq (AuthId site), Eq (Route site)) => Eq (BearerToken site)
|
||||||
|
deriving instance (Read (AuthId site), Eq (Route site), Hashable (Route site), Read (Route site)) => Read (BearerToken site)
|
||||||
|
deriving instance (Show (AuthId site), Show (Route site)) => Show (BearerToken site)
|
||||||
|
|
||||||
|
tokenToJSON :: forall m.
|
||||||
|
( MonadHandler m
|
||||||
|
, HasCryptoUUID (AuthId (HandlerSite m)) m
|
||||||
|
, RenderRoute (HandlerSite m)
|
||||||
|
) => BearerToken (HandlerSite m) -> m Value
|
||||||
|
tokenToJSON BearerToken{..} = do
|
||||||
|
cID <- encrypt tokenAuthority :: m (CryptoUUID (AuthId (HandlerSite m)))
|
||||||
|
let stdPayload = Jose.JwtClaims
|
||||||
|
{ jwtIss = Just $ toPathPiece tokenIssuedBy
|
||||||
|
, jwtSub = Nothing
|
||||||
|
, jwtAud = Nothing
|
||||||
|
, jwtExp = IntDate . utcTimeToPOSIXSeconds <$> tokenExpiresAt
|
||||||
|
, jwtNbf = IntDate . utcTimeToPOSIXSeconds <$> tokenStartsAt
|
||||||
|
, jwtIat = Just . IntDate $ utcTimeToPOSIXSeconds tokenIssuedAt
|
||||||
|
, jwtJti = Just $ toPathPiece tokenIdentifier
|
||||||
|
}
|
||||||
|
return . JSON.object $
|
||||||
|
[ "authority" .= cID
|
||||||
|
, "routes" .= tokenRoutes
|
||||||
|
, "add-auth" .= tokenAddAuth
|
||||||
|
] ++ let JSON.Object hm = toJSON stdPayload in HashMap.toList hm
|
||||||
|
|
||||||
|
tokenParseJSON :: forall site.
|
||||||
|
( HasCryptoUUID (AuthId site) (ReaderT CryptoIDKey Parser)
|
||||||
|
, ParseRoute site
|
||||||
|
, Hashable (Route site)
|
||||||
|
)
|
||||||
|
=> Value
|
||||||
|
-> ReaderT CryptoIDKey Parser (BearerToken site)
|
||||||
|
tokenParseJSON v@(Object o) = do
|
||||||
|
tokenAuthority' <- lift (o .: "authority") :: ReaderT CryptoIDKey Parser (CryptoUUID (AuthId site))
|
||||||
|
tokenAuthority <- decrypt tokenAuthority'
|
||||||
|
|
||||||
|
tokenRoutes <- lift $ o .:? "routes"
|
||||||
|
tokenAddAuth <- lift $ o .: "add-auth"
|
||||||
|
Jose.JwtClaims{..} <- lift $ parseJSON v
|
||||||
|
|
||||||
|
let unIntDate (IntDate posix) = posixSecondsToUTCTime posix
|
||||||
|
|
||||||
|
Just tokenIssuedBy <- return $ jwtIss >>= fromPathPiece
|
||||||
|
Just tokenIdentifier <- return $ jwtJti >>= fromPathPiece
|
||||||
|
Just tokenIssuedAt <- return $ unIntDate <$> jwtIat
|
||||||
|
let tokenExpiresAt = unIntDate <$> jwtExp
|
||||||
|
tokenStartsAt = unIntDate <$> jwtNbf
|
||||||
|
|
||||||
|
return BearerToken{..}
|
||||||
|
tokenParseJSON v = lift $ JSON.typeMismatch "BearerToken" v
|
||||||
|
|
||||||
|
|
||||||
|
bearerToken :: forall m.
|
||||||
|
( MonadHandler m
|
||||||
|
, HasInstanceID (HandlerSite m) InstanceId
|
||||||
|
, HasCryptoUUID (AuthId (HandlerSite m)) m
|
||||||
|
, HasAppSettings (HandlerSite m)
|
||||||
|
)
|
||||||
|
=> AuthId (HandlerSite m)
|
||||||
|
-> Maybe (HashSet (Route (HandlerSite m)))
|
||||||
|
-> AuthCNF
|
||||||
|
-> Maybe (Maybe UTCTime) -- ^ @Nothing@ determines default expiry time automatically
|
||||||
|
-> Maybe UTCTime -- ^ @Nothing@ means token starts to be valid immediately
|
||||||
|
-> m (BearerToken (HandlerSite m))
|
||||||
|
bearerToken tokenAuthority tokenRoutes tokenAddAuth mTokenExpiresAt tokenStartsAt = do
|
||||||
|
tokenIdentifier <- liftIO $ getRandom
|
||||||
|
tokenIssuedAt <- liftIO $ getCurrentTime
|
||||||
|
tokenIssuedBy <- getsYesod $ view instanceID
|
||||||
|
|
||||||
|
defaultExpiration <- getsYesod $ view _appJWTExpiration
|
||||||
|
|
||||||
|
let tokenExpiresAt
|
||||||
|
| Just t <- mTokenExpiresAt
|
||||||
|
= t
|
||||||
|
| Just tDiff <- defaultExpiration
|
||||||
|
= Just $ tDiff `addUTCTime` fromMaybe tokenIssuedAt tokenStartsAt
|
||||||
|
| otherwise
|
||||||
|
= Nothing
|
||||||
|
|
||||||
|
return BearerToken{..}
|
||||||
|
|
||||||
|
|
||||||
|
encodeToken :: forall m.
|
||||||
|
( Crypto.MonadRandom m
|
||||||
|
, MonadHandler m
|
||||||
|
, HasJSONWebKeySet (HandlerSite m) JwkSet
|
||||||
|
, HasInstanceID (HandlerSite m) InstanceId
|
||||||
|
, HasCryptoUUID (AuthId (HandlerSite m)) m
|
||||||
|
, RenderRoute (HandlerSite m)
|
||||||
|
)
|
||||||
|
=> BearerToken (HandlerSite m) -> m Jwt
|
||||||
|
encodeToken token = do
|
||||||
|
_payload <- tokenToJSON token
|
||||||
|
error "Not implemented"
|
||||||
|
|
||||||
|
decodeToken :: forall m.
|
||||||
|
( Crypto.MonadRandom m
|
||||||
|
, MonadHandler m
|
||||||
|
, HasJSONWebKeySet (HandlerSite m) JwkSet
|
||||||
|
, HasCryptoUUID (AuthId (HandlerSite m)) m
|
||||||
|
)
|
||||||
|
=> Jwt -> m (Either JwtError (BearerToken (HandlerSite m)))
|
||||||
|
decodeToken = error "Not implemented"
|
||||||
@ -27,6 +27,8 @@ import Data.Universe.Helpers
|
|||||||
import Data.UUID.Types (UUID)
|
import Data.UUID.Types (UUID)
|
||||||
import qualified Data.UUID.Types as UUID
|
import qualified Data.UUID.Types as UUID
|
||||||
|
|
||||||
|
import Data.NonNull.Instances ()
|
||||||
|
|
||||||
import Data.Default
|
import Data.Default
|
||||||
|
|
||||||
import Text.Read (readMaybe)
|
import Text.Read (readMaybe)
|
||||||
@ -54,7 +56,7 @@ import qualified Data.Aeson as Aeson
|
|||||||
import qualified Data.Aeson.Types as Aeson
|
import qualified Data.Aeson.Types as Aeson
|
||||||
import Data.Aeson (FromJSON(..), ToJSON(..), FromJSONKey(..), ToJSONKey(..), FromJSONKeyFunction(..), withText, withObject, Value())
|
import Data.Aeson (FromJSON(..), ToJSON(..), FromJSONKey(..), ToJSONKey(..), FromJSONKeyFunction(..), withText, withObject, Value())
|
||||||
import Data.Aeson.Types (toJSONKeyText)
|
import Data.Aeson.Types (toJSONKeyText)
|
||||||
import Data.Aeson.TH (deriveJSON, defaultOptions, Options(..), SumEncoding(..))
|
import Data.Aeson.TH (deriveJSON, defaultOptions, Options(..), SumEncoding(..), mkToJSON, mkParseJSON)
|
||||||
|
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Generics.Deriving.Monoid (memptydefault, mappenddefault)
|
import Generics.Deriving.Monoid (memptydefault, mappenddefault)
|
||||||
@ -79,7 +81,7 @@ import Model.Types.Wordlist
|
|||||||
import Data.Text.Metrics (damerauLevenshtein)
|
import Data.Text.Metrics (damerauLevenshtein)
|
||||||
|
|
||||||
import Data.Binary (Binary)
|
import Data.Binary (Binary)
|
||||||
|
|
||||||
|
|
||||||
instance PathPiece UUID where
|
instance PathPiece UUID where
|
||||||
fromPathPiece = UUID.fromString . unpack
|
fromPathPiece = UUID.fromString . unpack
|
||||||
@ -731,7 +733,7 @@ data AuthTag -- sortiert nach gewünschter Reihenfolge auf /authpreds, d.h. Prä
|
|||||||
| AuthDeprecated
|
| AuthDeprecated
|
||||||
| AuthDevelopment
|
| AuthDevelopment
|
||||||
| AuthFree
|
| AuthFree
|
||||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
|
||||||
|
|
||||||
instance Universe AuthTag
|
instance Universe AuthTag
|
||||||
instance Finite AuthTag
|
instance Finite AuthTag
|
||||||
@ -774,6 +776,40 @@ instance FromJSON AuthTagActive where
|
|||||||
derivePersistFieldJSON ''AuthTagActive
|
derivePersistFieldJSON ''AuthTagActive
|
||||||
|
|
||||||
|
|
||||||
|
data PredLiteral a = PLVariable { plVar :: a } | PLNegated { plVar :: a }
|
||||||
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
|
||||||
|
instance Hashable a => Hashable (PredLiteral a)
|
||||||
|
deriveJSON defaultOptions
|
||||||
|
{ constructorTagModifier = camelToPathPiece' 1
|
||||||
|
, sumEncoding = ObjectWithSingleField
|
||||||
|
, unwrapUnaryRecords = True
|
||||||
|
} ''PredLiteral
|
||||||
|
|
||||||
|
|
||||||
|
newtype PredCNF a = PredCNF (Set (NonNull (Set (PredLiteral a))))
|
||||||
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
deriving newtype (Semigroup, Monoid)
|
||||||
|
|
||||||
|
newtype PredDNF a = PredDNF (Set (NonNull (Set (PredLiteral a))))
|
||||||
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
deriving newtype (Semigroup, Monoid)
|
||||||
|
|
||||||
|
$(return [])
|
||||||
|
|
||||||
|
instance (Ord a, ToJSON a) => ToJSON (PredCNF a) where
|
||||||
|
toJSON = $(mkToJSON predNFAesonOptions ''PredCNF)
|
||||||
|
instance (Ord a, FromJSON a) => FromJSON (PredCNF a) where
|
||||||
|
parseJSON = $(mkParseJSON predNFAesonOptions ''PredCNF)
|
||||||
|
|
||||||
|
instance (Ord a, ToJSON a) => ToJSON (PredDNF a) where
|
||||||
|
toJSON = $(mkToJSON predNFAesonOptions ''PredDNF)
|
||||||
|
instance (Ord a, FromJSON a) => FromJSON (PredDNF a) where
|
||||||
|
parseJSON = $(mkParseJSON predNFAesonOptions ''PredDNF)
|
||||||
|
|
||||||
|
type AuthCNF = PredCNF AuthTag
|
||||||
|
type AuthDNF = PredDNF AuthTag
|
||||||
|
|
||||||
|
|
||||||
data LecturerType = CourseLecturer | CourseAssistant
|
data LecturerType = CourseLecturer | CourseAssistant
|
||||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||||
@ -801,4 +837,5 @@ type UserEmail = CI Email
|
|||||||
|
|
||||||
type PWHashAlgorithm = ByteString -> PWStore.Salt -> Int -> ByteString
|
type PWHashAlgorithm = ByteString -> PWStore.Salt -> Int -> ByteString
|
||||||
type InstanceId = UUID
|
type InstanceId = UUID
|
||||||
|
type TokenId = UUID
|
||||||
type TermCandidateIncidence = UUID
|
type TermCandidateIncidence = UUID
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
module Model.Types.JSON
|
module Model.Types.JSON
|
||||||
( derivePersistFieldJSON
|
( derivePersistFieldJSON
|
||||||
|
, predNFAesonOptions
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import ClassyPrelude.Yesod hiding (derivePersistFieldJSON)
|
import ClassyPrelude.Yesod hiding (derivePersistFieldJSON)
|
||||||
@ -9,11 +10,13 @@ import Database.Persist.Sql
|
|||||||
import qualified Data.ByteString.Lazy as LBS
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
import qualified Data.Text.Encoding as Text
|
import qualified Data.Text.Encoding as Text
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import Data.Aeson as JSON
|
||||||
|
|
||||||
import Language.Haskell.TH
|
import Language.Haskell.TH
|
||||||
import Language.Haskell.TH.Datatype
|
import Language.Haskell.TH.Datatype
|
||||||
|
|
||||||
|
import Utils.PathPiece
|
||||||
|
|
||||||
|
|
||||||
derivePersistFieldJSON :: Name -> DecsQ
|
derivePersistFieldJSON :: Name -> DecsQ
|
||||||
derivePersistFieldJSON tName = do
|
derivePersistFieldJSON tName = do
|
||||||
@ -28,10 +31,10 @@ derivePersistFieldJSON tName = do
|
|||||||
| otherwise = cxt [[t|PersistField|] `appT` t]
|
| otherwise = cxt [[t|PersistField|] `appT` t]
|
||||||
sequence
|
sequence
|
||||||
[ instanceD iCxt ([t|PersistField|] `appT` t)
|
[ instanceD iCxt ([t|PersistField|] `appT` t)
|
||||||
[ funD (mkName "toPersistValue")
|
[ funD 'toPersistValue
|
||||||
[ clause [] (normalB [e|PersistDbSpecific . LBS.toStrict . JSON.encode|]) []
|
[ clause [] (normalB [e|PersistDbSpecific . LBS.toStrict . JSON.encode|]) []
|
||||||
]
|
]
|
||||||
, funD (mkName "fromPersistValue")
|
, funD 'fromPersistValue
|
||||||
[ do
|
[ do
|
||||||
bs <- newName "bs"
|
bs <- newName "bs"
|
||||||
clause [[p|PersistDbSpecific $(varP bs)|]] (normalB [e|first pack $ JSON.eitherDecodeStrict' $(varE bs)|]) []
|
clause [[p|PersistDbSpecific $(varP bs)|]] (normalB [e|first pack $ JSON.eitherDecodeStrict' $(varE bs)|]) []
|
||||||
@ -45,8 +48,20 @@ derivePersistFieldJSON tName = do
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
, instanceD sqlCxt ([t|PersistFieldSql|] `appT` t)
|
, instanceD sqlCxt ([t|PersistFieldSql|] `appT` t)
|
||||||
[ funD (mkName "sqlType")
|
[ funD 'sqlType
|
||||||
[ clause [wildP] (normalB [e|SqlOther "jsonb"|]) []
|
[ clause [wildP] (normalB [e|SqlOther "jsonb"|]) []
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
predNFAesonOptions :: Options
|
||||||
|
-- ^ Needed for JSON instances of `predCNF` and `predDNF`
|
||||||
|
--
|
||||||
|
-- Moved to this module due to stage restriction
|
||||||
|
predNFAesonOptions = defaultOptions
|
||||||
|
{ constructorTagModifier = camelToPathPiece' 1
|
||||||
|
, sumEncoding = ObjectWithSingleField
|
||||||
|
, tagSingleConstructors = True
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -100,6 +100,7 @@ data AppSettings = AppSettings
|
|||||||
, appNotificationExpiration :: NominalDiffTime
|
, appNotificationExpiration :: NominalDiffTime
|
||||||
, appSessionTimeout :: NominalDiffTime
|
, appSessionTimeout :: NominalDiffTime
|
||||||
, appMaximumContentLength :: Maybe Word64
|
, appMaximumContentLength :: Maybe Word64
|
||||||
|
, appJWTExpiration :: Maybe NominalDiffTime
|
||||||
|
|
||||||
, appInitialLogSettings :: LogSettings
|
, appInitialLogSettings :: LogSettings
|
||||||
|
|
||||||
@ -352,6 +353,7 @@ instance FromJSON AppSettings where
|
|||||||
appNotificationRateLimit <- o .: "notification-rate-limit"
|
appNotificationRateLimit <- o .: "notification-rate-limit"
|
||||||
appNotificationCollateDelay <- o .: "notification-collate-delay"
|
appNotificationCollateDelay <- o .: "notification-collate-delay"
|
||||||
appNotificationExpiration <- o .: "notification-expiration"
|
appNotificationExpiration <- o .: "notification-expiration"
|
||||||
|
appJWTExpiration <- o .:? "jwt-expiration"
|
||||||
|
|
||||||
appSessionTimeout <- o .: "session-timeout"
|
appSessionTimeout <- o .: "session-timeout"
|
||||||
|
|
||||||
@ -379,6 +381,8 @@ instance FromJSON AppSettings where
|
|||||||
|
|
||||||
return AppSettings {..}
|
return AppSettings {..}
|
||||||
|
|
||||||
|
makeClassy_ ''AppSettings
|
||||||
|
|
||||||
-- | Settings for 'widgetFile', such as which template languages to support and
|
-- | Settings for 'widgetFile', such as which template languages to support and
|
||||||
-- default Hamlet settings.
|
-- default Hamlet settings.
|
||||||
--
|
--
|
||||||
|
|||||||
@ -32,11 +32,16 @@ import qualified Data.Binary as Binary
|
|||||||
import qualified Data.Serialize as Serialize
|
import qualified Data.Serialize as Serialize
|
||||||
import qualified Data.ByteString.Base64.URL as Base64
|
import qualified Data.ByteString.Base64.URL as Base64
|
||||||
|
|
||||||
|
import qualified Jose.Jwa as Jose
|
||||||
|
import qualified Jose.Jwk as Jose
|
||||||
|
import qualified Jose.Jwt as Jose
|
||||||
|
|
||||||
|
|
||||||
data ClusterSettingsKey
|
data ClusterSettingsKey
|
||||||
= ClusterCryptoIDKey
|
= ClusterCryptoIDKey
|
||||||
| ClusterClientSessionKey
|
| ClusterClientSessionKey
|
||||||
| ClusterSecretBoxKey
|
| ClusterSecretBoxKey
|
||||||
|
| ClusterJSONWebKeySet
|
||||||
deriving (Eq, Ord, Enum, Bounded, Show, Read)
|
deriving (Eq, Ord, Enum, Bounded, Show, Read)
|
||||||
|
|
||||||
instance Universe ClusterSettingsKey
|
instance Universe ClusterSettingsKey
|
||||||
@ -120,3 +125,12 @@ instance FromJSON SecretBox.Key where
|
|||||||
parseJSON = Aeson.withText "Key" $ \t -> do
|
parseJSON = Aeson.withText "Key" $ \t -> do
|
||||||
bytes <- either fail return . Base64.decode $ encodeUtf8 t
|
bytes <- either fail return . Base64.decode $ encodeUtf8 t
|
||||||
maybe (fail "Could not parse key") return $ Saltine.decode bytes
|
maybe (fail "Could not parse key") return $ Saltine.decode bytes
|
||||||
|
|
||||||
|
|
||||||
|
instance ClusterSetting 'ClusterJSONWebKeySet where
|
||||||
|
type ClusterSettingValue 'ClusterJSONWebKeySet = Jose.JwkSet
|
||||||
|
initClusterSetting _ = liftIO $ do
|
||||||
|
now <- getCurrentTime
|
||||||
|
jwkSig <- Jose.generateSymmetricKey 32 (Jose.UTCKeyId now) Jose.Sig (Just $ Jose.Signed Jose.HS256)
|
||||||
|
return $ Jose.JwkSet [jwkSig]
|
||||||
|
knownClusterSetting _ = ClusterJSONWebKeySet
|
||||||
|
|||||||
@ -93,4 +93,12 @@ makeLenses_ ''StudyTermCandidate
|
|||||||
|
|
||||||
-- makeClassy_ ''Load
|
-- makeClassy_ ''Load
|
||||||
|
|
||||||
|
--------------------------
|
||||||
|
-- Fields for `UniWorX` --
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
class HasInstanceID s a | s -> a where
|
||||||
|
instanceID :: Lens' s a
|
||||||
|
|
||||||
|
class HasJSONWebKeySet s a | s -> a where
|
||||||
|
jsonWebKeySet :: Lens' s a
|
||||||
|
|||||||
Reference in New Issue
Block a user