diff --git a/.gitignore b/.gitignore index f744360b3..84a8fe8a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ dist* static/tmp/ static/combined/ -client_session_key.aes -cryptoid_key.bf *.hi *.o *.sqlite3 diff --git a/config/settings.yml b/config/settings.yml index f4602cd0e..658b10f55 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -26,6 +26,7 @@ job-stale-threshold: 300 notification-rate-limit: 3600 notification-collate-delay: 300 notification-expiration: 259201 +session-timeout: 7200 log-settings: log-detailed: "_env:DETAILED_LOGGING:false" @@ -41,10 +42,11 @@ auth-pw-hash: strength: 14 # Optional values with the following production defaults. -# In development, they default to true. +# In development, they default to the opposite. # reload-templates: false # mutable-static: false # skip-combining: false +# encrypt-errors: true database: user: "_env:PGUSER:uniworx" @@ -86,5 +88,4 @@ user-defaults: time-format: "%R" download-files: false -cryptoid-keyfile: "_env:CRYPTOID_KEYFILE:cryptoid_key.bf" -instance-idfile: "_env:INSTANCEID_FILE:instance" +instance-idfile: "_env:INSTANCE_ID:instance" diff --git a/messages/uniworx/de.msg b/messages/uniworx/de.msg index 898b4385a..f27bdb02b 100644 --- a/messages/uniworx/de.msg +++ b/messages/uniworx/de.msg @@ -376,7 +376,7 @@ NotificationTriggerSubmissionRated: Meine Abgabe wurde korrigiert NotificationTriggerSheetActive: Ich kann ein neues Übungsblatt herunterladen NotificationTriggerSheetSoonInactive: Ich kann ein Übungsblatt bald nicht mehr abgeben NotificationTriggerSheetInactive: Abgabefrist eines meiner Übungsblätter ist abgelaufen -NotificationCorrectionsAssigned: Mir wurden Abgaben zur Korrektur zugeteilt +NotificationTriggerCorrectionsAssigned: Mir wurden Abgaben zur Korrektur zugeteilt CorrCreate: Abgaben erstellen UnknownPseudonymWord pseudonymWord@Text: Unbekanntes Pseudonym-Wort "#{pseudonymWord}" @@ -440,4 +440,25 @@ MessageWarning: Warnung MessageInfo: Information MessageSuccess: Erfolg -InvalidLangFormat: Ungültiger Sprach-Code (RFC1766) \ No newline at end of file +InvalidLangFormat: Ungültiger Sprach-Code (RFC1766) + +ErrorResponseTitleNotFound: Ressource nicht gefunden +ErrorResponseTitleInternalError internalError@Text: Ein interner Fehler ist aufgetreten +ErrorResponseTitleInvalidArgs invalidArgs@Texts: Anfrage-Nachricht enthielt ungültige Argumente +ErrorResponseTitleNotAuthenticated: Anfrage benötigt Authentifizierung +ErrorResponseTitlePermissionDenied permissionDenied@Text: Mangelnde Authorisierung +ErrorResponseTitleBadMethod requestMethod@Method: HTTP-Methode nicht unterstützt + +UnknownErrorResponse: Ein nicht weiter klassifizierter Fehler ist aufgetreten: +ErrorResponseNotFound: Unter der von Ihrem Browser angefragten URL wurde keine Seite gefunden. +ErrorResponseNotAuthenticated: Um Zugriff auf einige Teile des Systems zu erhalten müssen Sie sich zunächst anmelden. +ErrorResponseBadMethod requestMethodText@Text: Ihr Browser kann auf mehrere verschiedene Arten versuchen mit den vom System angebotenen Ressourcen zu interagieren. Die aktuell versuchte Methode (#{requestMethodText}) wird nicht unterstützt. + +ErrorResponseEncrypted: Um keine sensiblen Daten preiszugeben wurden nähere Details verschlüsselt. Wenn Sie eine Anfrage an den Support schicken fügen Sie bitte die unten aufgeführten verschlüsselten Daten mit an. +ErrMsgCiphertext: Verschlüsselte Fehlermeldung +ErrMsgCiphertextTooShort: Verschlüsselte Daten zu kurz um valide zu sein +ErrMsgInvalidBase64 base64Err@String: Verschlüsselte Daten nicht korrekt base64url-kodiert: #{base64Err} +ErrMsgCouldNotDecodeNonce: Konnte secretbox-nonce nicht dekodieren +ErrMsgCouldNotOpenSecretbox: Konnte libsodium-secretbox nicht öffnen (Verschlüsselte Daten sind nicht authentisch) +ErrMsgCouldNotDecodePlaintext utf8Err@Text: Konnte Klartext nicht UTF8-dekodieren: #{utf8Err} +ErrMsgHeading: Fehlermeldung entschlüsseln \ No newline at end of file diff --git a/models b/models index 1398a65a5..88e88243f 100644 --- a/models +++ b/models @@ -255,4 +255,8 @@ SystemMessageTranslation language Lang content Html summary Html Maybe - UniqueSystemMessageTranslation message language \ No newline at end of file + UniqueSystemMessageTranslation message language +ClusterConfig + setting ClusterSettingsKey + value Value + Primary setting \ No newline at end of file diff --git a/package.yaml b/package.yaml index 4a48ee43d..246f6bcf3 100644 --- a/package.yaml +++ b/package.yaml @@ -48,6 +48,7 @@ dependencies: - wai - cryptonite - cryptonite-conduit +- saltine - base64-bytestring - memory - http-api-data @@ -67,6 +68,7 @@ dependencies: - cryptoids - cryptoids-class - binary +- cereal - mtl - sandi - esqueleto @@ -107,6 +109,55 @@ dependencies: - postgresql-simple - word24 - mmorph +- clientsession + +other-extensions: + - GeneralizedNewtypeDeriving + - IncoherentInstances + - OverloadedLists + - UndecidableInstances + +default-extensions: + - OverloadedStrings + - PartialTypeSignatures + - ScopedTypeVariables + - TemplateHaskell + - QuasiQuotes + - CPP + - TypeSynonymInstances + - KindSignatures + - ConstraintKinds + - ViewPatterns + - TypeOperators + - TupleSections + - TypeFamilies + - GADTs + - StandaloneDeriving + - RecordWildCards + - RankNTypes + - PatternGuards + - PatternSynonyms + - ParallelListComp + - NumDecimals + - MultiWayIf + - NamedFieldPuns + - NoImplicitPrelude + - LambdaCase + - MultiParamTypeClasses + - FlexibleContexts + - FlexibleInstances + - FunctionalDependencies + - EmptyDataDecls + - ExistentialQuantification + - DefaultSignatures + - DeriveDataTypeable + - DeriveGeneric + - DeriveLift + - DeriveFunctor + - DerivingStrategies + - DataKinds + - BinaryLiterals + - PolyKinds # The library contains all of our application code. The executable # defined below is just a thin wrapper. diff --git a/routes b/routes index 991318c6a..17a653125 100644 --- a/routes +++ b/routes @@ -36,6 +36,7 @@ /admin/test AdminTestR GET POST /admin/user/#CryptoUUIDUser AdminUserR GET /admin/user/#CryptoUUIDUser/hijack AdminHijackUserR POST +/admin/errMsg AdminErrMsgR GET POST /info VersionR GET !free /help HelpR GET POST !free diff --git a/src/Application.hs b/src/Application.hs index 9ffcf2106..3757d98f7 100644 --- a/src/Application.hs +++ b/src/Application.hs @@ -1,12 +1,5 @@ -{-# LANGUAGE NoImplicitPrelude #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE FlexibleContexts #-} {-# OPTIONS_GHC -fno-warn-orphans #-} + module Application ( getApplicationDev, getAppDevSettings , appMain @@ -67,6 +60,12 @@ import qualified Yesod.Core.Types as Yesod (Logger(..)) import qualified Data.HashMap.Strict as HashMap import Control.Lens ((&)) + +import Data.Proxy + +import qualified Data.Aeson as Aeson + +import System.Exit (exitFailure) -- Import all relevant handler modules here. -- (HPack takes care to add new modules to our cabal file nowadays.) @@ -105,8 +104,7 @@ makeFoundation appSettings@(AppSettings{..}) = do return $ Yesod.Logger loggerSet tgetter appStatic <- liftIO $ bool static staticDevel appMutableStatic appStaticDir - appCryptoIDKey <- readKeyFile appCryptoIDKeyFile - appInstanceID <- liftIO $ maybe UUID.nextRandom readInstanceIDFile appInstanceIDFile + appInstanceID <- liftIO $ maybe UUID.nextRandom (either readInstanceIDFile return) appInitialInstanceID (appJobCtl, recvChans) <- fmap unzip . atomically . replicateM appJobWorkers $ do chan <- newBroadcastTMChan @@ -120,11 +118,16 @@ makeFoundation appSettings@(AppSettings{..}) = do -- logging function. To get out of this loop, we initially create a -- temporary foundation without a real connection pool, get a log function -- from there, and then create the real foundation. - let mkFoundation appConnPool appSmtpPool = UniWorX {..} + let mkFoundation appConnPool appSmtpPool appCryptoIDKey appSessionKey appErrorMsgKey = UniWorX {..} -- The UniWorX {..} syntax is an example of record wild cards. For more -- information, see: -- https://ocharles.org.uk/blog/posts/2014-12-04-record-wildcards.html - tempFoundation = mkFoundation (error "connPool forced in tempFoundation") (error "smtpPool forced in tempFoundation") + tempFoundation = mkFoundation + (error "connPool forced in tempFoundation") + (error "smtpPool forced in tempFoundation") + (error "cryptoIDKey forced in tempFoundation") + (error "sessionKey forced in tempFoundation") + (error "errorMsgKey forced in tempFoundation") logFunc = messageLoggerSource tempFoundation appLogger flip runLoggingT logFunc $ do @@ -140,12 +143,36 @@ makeFoundation appSettings@(AppSettings{..}) = do -- Perform database migration using our application's logging settings. migrateAll `runSqlPool` sqlPool + appCryptoIDKey <- clusterSetting (Proxy :: Proxy 'ClusterCryptoIDKey) `runSqlPool` sqlPool + appSessionKey <- clusterSetting (Proxy :: Proxy 'ClusterClientSessionKey) `runSqlPool` sqlPool + appErrorMsgKey <- clusterSetting (Proxy :: Proxy 'ClusterErrorMessageKey) `runSqlPool` sqlPool - handleJobs recvChans $ mkFoundation sqlPool smtpPool + let foundation = mkFoundation sqlPool smtpPool appCryptoIDKey appSessionKey appErrorMsgKey + + handleJobs recvChans foundation -- Return the foundation - return $ mkFoundation sqlPool smtpPool + return foundation +clusterSetting :: forall key m p. + ( MonadIO m + , ClusterSetting key + , MonadLogger m + ) + => p (key :: ClusterSettingsKey) + -> ReaderT SqlBackend m (ClusterSettingValue key) +clusterSetting proxy@(knownClusterSetting -> key) = do + current' <- get (ClusterConfigKey key) + case Aeson.fromJSON . clusterConfigValue <$> current' of + Just (Aeson.Success c) -> return c + Just (Aeson.Error str) -> do + $logErrorS "clusterSetting" $ "Could not parse JSON-Value for " <> toPathPiece key <> ": " <> pack str + liftIO exitFailure + Nothing -> do + new <- initClusterSetting proxy + void . insert $ ClusterConfig key (Aeson.toJSON new) + return new + readInstanceIDFile :: MonadIO m => FilePath -> m UUID readInstanceIDFile idFile = liftIO . handle generateInstead $ LBS.readFile idFile >>= parseBS where diff --git a/src/Auth/Dummy.hs b/src/Auth/Dummy.hs index 809db8647..df4ab5e40 100644 --- a/src/Auth/Dummy.hs +++ b/src/Auth/Dummy.hs @@ -1,11 +1,3 @@ -{-# LANGUAGE NoImplicitPrelude - , RecordWildCards - , TemplateHaskell - , FlexibleContexts - , TypeFamilies - , OverloadedStrings - #-} - module Auth.Dummy ( dummyLogin , DummyMessage(..) diff --git a/src/Auth/LDAP.hs b/src/Auth/LDAP.hs index 32c185519..2b053ce05 100644 --- a/src/Auth/LDAP.hs +++ b/src/Auth/LDAP.hs @@ -1,14 +1,3 @@ -{-# LANGUAGE RecordWildCards - , OverloadedStrings - , TemplateHaskell - , ViewPatterns - , TypeFamilies - , FlexibleContexts - , FlexibleInstances - , NoImplicitPrelude - , ScopedTypeVariables - #-} - module Auth.LDAP ( campusLogin , CampusUserException(..) diff --git a/src/Auth/PWHash.hs b/src/Auth/PWHash.hs index ba7198710..3efad0d32 100644 --- a/src/Auth/PWHash.hs +++ b/src/Auth/PWHash.hs @@ -1,13 +1,3 @@ -{-# LANGUAGE NoImplicitPrelude - , QuasiQuotes - , TemplateHaskell - , ViewPatterns - , RecordWildCards - , OverloadedStrings - , FlexibleContexts - , TypeFamilies - #-} - module Auth.PWHash ( hashLogin , PWHashMessage(..) diff --git a/src/Cron.hs b/src/Cron.hs index 2620aec12..cb2d9a338 100644 --- a/src/Cron.hs +++ b/src/Cron.hs @@ -1,12 +1,3 @@ -{-# LANGUAGE NoImplicitPrelude - , RecordWildCards - , PatternGuards - , ViewPatterns - , DeriveFunctor - , TemplateHaskell - , NamedFieldPuns - #-} - module Cron ( CronNextMatch(..) , nextCronMatch diff --git a/src/Cron/Types.hs b/src/Cron/Types.hs index fa95477f0..ab3e92972 100644 --- a/src/Cron/Types.hs +++ b/src/Cron/Types.hs @@ -1,8 +1,3 @@ -{-# LANGUAGE NoImplicitPrelude - , TemplateHaskell - , DuplicateRecordFields - #-} - module Cron.Types ( Cron(..), Crontab , CronMatch(..) diff --git a/src/CryptoID.hs b/src/CryptoID.hs index 58f68171e..6d4163982 100644 --- a/src/CryptoID.hs +++ b/src/CryptoID.hs @@ -1,11 +1,3 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE NoImplicitPrelude #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-} -{-# LANGUAGE RecordWildCards, ViewPatterns, PatternGuards #-} -{-# LANGUAGE PatternSynonyms #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module CryptoID diff --git a/src/CryptoID/TH.hs b/src/CryptoID/TH.hs index 23122dadf..589c30637 100644 --- a/src/CryptoID/TH.hs +++ b/src/CryptoID/TH.hs @@ -1,7 +1,3 @@ -{-# LANGUAGE NoImplicitPrelude #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE ViewPatterns #-} - module CryptoID.TH where import ClassyPrelude diff --git a/src/Data/CaseInsensitive/Instances.hs b/src/Data/CaseInsensitive/Instances.hs index ea5253f44..214283124 100644 --- a/src/Data/CaseInsensitive/Instances.hs +++ b/src/Data/CaseInsensitive/Instances.hs @@ -1,11 +1,8 @@ -{-# LANGUAGE NoImplicitPrelude #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Data.CaseInsensitive.Instances - () where + ( + ) where import ClassyPrelude.Yesod diff --git a/src/Data/Universe/Instances/Reverse/Hashable.hs b/src/Data/Universe/Instances/Reverse/Hashable.hs index e7459f613..d264fa41f 100644 --- a/src/Data/Universe/Instances/Reverse/Hashable.hs +++ b/src/Data/Universe/Instances/Reverse/Hashable.hs @@ -1,6 +1,3 @@ -{-# LANGUAGE NoImplicitPrelude - , ScopedTypeVariables - #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Data.Universe.Instances.Reverse.Hashable diff --git a/src/Data/Universe/Instances/Reverse/JSON.hs b/src/Data/Universe/Instances/Reverse/JSON.hs index 60b7ba6ae..14c7d04fa 100644 --- a/src/Data/Universe/Instances/Reverse/JSON.hs +++ b/src/Data/Universe/Instances/Reverse/JSON.hs @@ -1,6 +1,3 @@ -{-# LANGUAGE NoImplicitPrelude - , ScopedTypeVariables - #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Data.Universe.Instances.Reverse.JSON diff --git a/src/Foundation.hs b/src/Foundation.hs index 06e945166..3f30e95b8 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -1,17 +1,4 @@ -{-# LANGUAGE NoImplicitPrelude #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell, QuasiQuotes #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE PatternSynonyms #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE PatternGuards, MultiWayIf #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE FlexibleInstances, UndecidableInstances, FlexibleContexts #-} +{-# LANGUAGE UndecidableInstances #-} module Foundation where @@ -20,6 +7,8 @@ import Database.Persist.Sql (ConnectionPool, runSqlPool) import Text.Hamlet (hamletFile) import Text.Jasmine (minifym) +import qualified Web.ClientSession as ClientSession + import Yesod.Auth.Message import Yesod.Auth.Dummy import Auth.LDAP @@ -96,6 +85,9 @@ import qualified Yesod.Auth.Message as Auth import qualified Data.Conduit.List as C +import qualified Crypto.Saltine.Core.SecretBox as SecretBox +import qualified Crypto.Saltine.Class as Saltine + instance DisplayAble b => DisplayAble (E.CryptoID a b) where display = display . ciphertext @@ -127,6 +119,8 @@ data UniWorX = UniWorX , appCryptoIDKey :: CryptoIDKey , appInstanceID :: InstanceId , appJobCtl :: [TMChan JobCtl] + , appErrorMsgKey :: SecretBox.Key + , appSessionKey :: ClientSession.Key } type SMTPPool = Pool SMTPConnection @@ -197,14 +191,8 @@ instance RenderMessage UniWorX TermIdentifier where Winter -> renderMessage' $ MsgWinterTerm year where renderMessage' = renderMessage foundation ls -instance RenderMessage UniWorX StudyFieldType where - renderMessage foundation ls = renderMessage foundation ls . \case - FieldPrimary -> MsgFieldPrimary - FieldSecondary -> MsgFieldSecondary - newtype ShortTermIdentifier = ShortTermIdentifier TermIdentifier deriving (Eq, Ord, Read, Show) - instance RenderMessage UniWorX ShortTermIdentifier where renderMessage foundation ls (ShortTermIdentifier TermIdentifier{..}) = case season of Summer -> renderMessage' $ MsgSummerTermShort year @@ -214,33 +202,12 @@ instance RenderMessage UniWorX ShortTermIdentifier where instance RenderMessage UniWorX String where renderMessage f ls str = renderMessage f ls $ Text.pack str -instance RenderMessage UniWorX SheetFileType where - renderMessage foundation ls = renderMessage foundation ls . \case - SheetExercise -> MsgSheetExercise - SheetHint -> MsgSheetHint - SheetSolution -> MsgSheetSolution - SheetMarking -> MsgSheetMarking - -instance RenderMessage UniWorX CorrectorState where - renderMessage foundation ls = renderMessage foundation ls . \case - CorrectorNormal -> MsgCorrectorNormal - CorrectorMissing -> MsgCorrectorMissing - CorrectorExcused -> MsgCorrectorExcused - - instance RenderMessage UniWorX Load where renderMessage foundation ls = renderMessage foundation ls . \case (Load {byTutorial=Nothing , byProportion=p}) -> MsgCorByProportionOnly p (Load {byTutorial=Just True , byProportion=p}) -> MsgCorByProportionIncludingTutorial p (Load {byTutorial=Just False, byProportion=p}) -> MsgCorByProportionExcludingTutorial p -instance RenderMessage UniWorX SheetType where - renderMessage foundation ls = renderMessage foundation ls . \case - Bonus{..} -> MsgSheetTypeBonus' maxPoints - Normal{..} -> MsgSheetTypeNormal' maxPoints - Pass{..} -> MsgSheetTypePass' maxPoints passingPoints - NotGraded{} -> MsgSheetTypeNotGraded' - newtype MsgLanguage = MsgLanguage Lang deriving (Eq, Ord, Show, Read) instance RenderMessage UniWorX MsgLanguage where @@ -250,24 +217,18 @@ instance RenderMessage UniWorX MsgLanguage where where mr = renderMessage foundation ls -instance RenderMessage UniWorX NotificationTrigger where - renderMessage foundation ls = renderMessage foundation ls . \case - NTSubmissionRatedGraded -> MsgNotificationTriggerSubmissionRatedGraded - NTSubmissionRated -> MsgNotificationTriggerSubmissionRated - NTSheetActive -> MsgNotificationTriggerSheetActive - NTSheetSoonInactive -> MsgNotificationTriggerSheetSoonInactive - NTSheetInactive -> MsgNotificationTriggerSheetInactive - NTCorrectionsAssigned -> MsgNotificationCorrectionsAssigned - instance RenderMessage UniWorX (UnsupportedAuthPredicate (Route UniWorX)) where renderMessage f ls (UnsupportedAuthPredicate tag route) = renderMessage f ls $ MsgUnsupportedAuthPredicate tag (show route) -instance RenderMessage UniWorX MessageClass where - renderMessage f ls = renderMessage f ls . \case - Error -> MsgMessageError - Warning -> MsgMessageWarning - Info -> MsgMessageInfo - Success -> MsgMessageSuccess +embedRenderMessage ''UniWorX ''MessageClass ("Message" <>) +embedRenderMessage ''UniWorX ''NotificationTrigger $ ("NotificationTrigger" <>) . concat . drop 1 . splitCamel +embedRenderMessage ''UniWorX ''SheetType $ \st -> "SheetType" <> st <> "'" +embedRenderMessage ''UniWorX ''StudyFieldType id +embedRenderMessage ''UniWorX ''SheetFileType id +embedRenderMessage ''UniWorX ''CorrectorState id + +newtype ErrorResponseTitle = ErrorResponseTitle ErrorResponse +embedRenderMessageVariant ''UniWorX ''ErrorResponseTitle ("ErrorResponseTitle" <>) data instance ButtonCssClass UniWorX = BCDefault | BCPrimary | BCSuccess | BCInfo | BCWarning | BCDanger | BCLink @@ -573,9 +534,9 @@ instance Yesod UniWorX where -- Store session data on the client in encrypted cookies, -- default session idle timeout is 120 minutes - makeSessionBackend _ = Just <$> defaultClientSessionBackend - 120 -- timeout in minutes - "client_session_key.aes" + makeSessionBackend UniWorX{appSessionKey,appSettings=AppSettings{appSessionTimeout}} = do + (getCachedDate, _) <- clientSessionDateCacher appSessionTimeout + return . Just $ clientSessionBackend appSessionKey getCachedDate maximumContentLength _ _ = Just $ 50 * 2^20 @@ -627,101 +588,52 @@ instance Yesod UniWorX where $logDebugS "normalizeRouteMiddleware" [st|Redirecting to #{tshow route'}|] redirectWith movedPermanently301 route' - defaultLayout widget = do - master <- getYesod - let AppSettings { appUserDefaults = UserDefaultConf{..}, .. } = appSettings master + -- Since we implement `errorHandler` ourselves we don't need `defaultMessageWidget` + defaultMessageWidget _title _body = error "defaultMessageWidget: undefined" - applySystemMessages - mmsgs <- getMessages + errorHandler err = do + mr <- getMessageRender + let + encrypted :: ToJSON a => a -> Widget -> Widget + encrypted plaintextJson plaintext = do + canDecrypt <- (== Authorized) <$> evalAccess AdminErrMsgR True + shouldEncrypt <- getsYesod $ appEncryptErrors . appSettings + errKey <- getsYesod appErrorMsgKey + if + | shouldEncrypt + , not canDecrypt -> do + nonce <- liftIO SecretBox.newNonce + let ciphertext = SecretBox.secretbox errKey nonce . Lazy.ByteString.toStrict $ encode plaintextJson + encoded = decodeUtf8 . Base64.encode $ Saltine.encode nonce <> ciphertext + formatted = Text.intercalate "\n" $ Text.chunksOf 76 encoded + [whamlet| +

_{MsgErrorResponseEncrypted} +

+                    #{formatted}
+                |]
+            | otherwise -> plaintext
+        
+        errPage = case err of
+          NotFound -> [whamlet|

_{MsgErrorResponseNotFound}|] + InternalError err -> encrypted err [whamlet|

#{err}|] + InvalidArgs errs -> [whamlet| +