Skip to content
Snippets Groups Projects
Verified Commit 8ff7ce43 authored by jan.hamal.dvorak's avatar jan.hamal.dvorak
Browse files

Drop Hikaru.Config and use Praha.Config instead

parent 76e4fe27
Branches
No related tags found
No related merge requests found
...@@ -81,6 +81,7 @@ common common ...@@ -81,6 +81,7 @@ common common
, memory >=0.15 && <0.16 , memory >=0.15 && <0.16
, mtl >=2.2 && <2.3 , mtl >=2.2 && <2.3
, praha >=0.1 && <0.2 , praha >=0.1 && <0.2
, praha-config >=0.1 && <0.2
, resourcet >=1.2 && <1.3 , resourcet >=1.2 && <1.3
, text >=1.2 && <1.3 , text >=1.2 && <1.3
, time >=1.9 && <1.12 , time >=1.9 && <1.12
...@@ -98,7 +99,6 @@ library ...@@ -98,7 +99,6 @@ library
Hikaru Hikaru
Hikaru.Develop Hikaru.Develop
Hikaru.Action Hikaru.Action
Hikaru.Config
Hikaru.CSRF Hikaru.CSRF
Hikaru.Dispatch Hikaru.Dispatch
Hikaru.Widget Hikaru.Widget
......
...@@ -14,7 +14,6 @@ module Hikaru ...@@ -14,7 +14,6 @@ module Hikaru
( (
-- * Exported Modules -- * Exported Modules
module Hikaru.Action module Hikaru.Action
, module Hikaru.Config
, module Hikaru.CSRF , module Hikaru.CSRF
, module Hikaru.Develop , module Hikaru.Develop
, module Hikaru.Dispatch , module Hikaru.Dispatch
...@@ -27,7 +26,6 @@ module Hikaru ...@@ -27,7 +26,6 @@ module Hikaru
) )
where where
import Hikaru.Action import Hikaru.Action
import Hikaru.Config
import Hikaru.CSRF import Hikaru.CSRF
import Hikaru.Develop import Hikaru.Develop
import Hikaru.Dispatch import Hikaru.Dispatch
......
...@@ -97,10 +97,6 @@ module Hikaru.Action ...@@ -97,10 +97,6 @@ module Hikaru.Action
, dropCache , dropCache
, dropCaches , dropCaches
-- ** Configuration
, getConfigMaybe
, getConfigDefault
-- ** Finalizing -- ** Finalizing
, registerFinalizer , registerFinalizer
...@@ -127,7 +123,6 @@ where ...@@ -127,7 +123,6 @@ where
import Data.Binary.Builder import Data.Binary.Builder
import Data.Dynamic import Data.Dynamic
import Data.List (deleteBy, lookup, map, filter) import Data.List (deleteBy, lookup, map, filter)
import Hikaru.Config
import Hikaru.Media import Hikaru.Media
import Hikaru.Types import Hikaru.Types
import Lucid import Lucid
...@@ -204,7 +199,6 @@ where ...@@ -204,7 +199,6 @@ where
data ActionEnv data ActionEnv
= ActionEnv = ActionEnv
{ aeRequest :: Request { aeRequest :: Request
, aeConfig :: Config
, aeBody :: IORef RequestBody , aeBody :: IORef RequestBody
, aeRespStatus :: IORef Status , aeRespStatus :: IORef Status
, aeRespHeaders :: IORef ResponseHeaders , aeRespHeaders :: IORef ResponseHeaders
...@@ -226,9 +220,9 @@ where ...@@ -226,9 +220,9 @@ where
-- --
-- Whole operation is bracketed to ensure all finalizers are run. -- Whole operation is bracketed to ensure all finalizers are run.
-- --
respond :: Config -> (ActionEnv -> IO ()) -> Application respond :: (ActionEnv -> IO ()) -> Application
respond cfg run req resp = do respond run req resp = do
env <- makeActionEnv cfg req env <- makeActionEnv req
bracket_ (return ()) (finalize env) do bracket_ (return ()) (finalize env) do
_ <- run env _ <- run env
...@@ -278,10 +272,9 @@ where ...@@ -278,10 +272,9 @@ where
-- | -- |
-- Create an initial action environment to handle given 'Request'. -- Create an initial action environment to handle given 'Request'.
-- --
makeActionEnv :: Config -> Request -> IO ActionEnv makeActionEnv :: Request -> IO ActionEnv
makeActionEnv cfg req = do makeActionEnv req = do
aeRequest <- pure req aeRequest <- pure req
aeConfig <- pure cfg
aeBody <- newIORef BodyUnparsed aeBody <- newIORef BodyUnparsed
aeRespStatus <- newIORef status200 aeRespStatus <- newIORef status200
aeRespHeaders <- newIORef [] aeRespHeaders <- newIORef []
...@@ -1239,27 +1232,6 @@ where ...@@ -1239,27 +1232,6 @@ where
modifyActionField aeCache (const Map.empty) modifyActionField aeCache (const Map.empty)
-- Configuration -----------------------------------------------------------
-- |
-- Lookup a configuration value.
--
getConfigMaybe :: (MonadAction m, Param a) => Text -> m (Maybe a)
getConfigMaybe name = do
ActionEnv{aeConfig} <- getActionEnv
return $ fromParam =<< Map.lookup name aeConfig
-- |
-- Lookup a configuration value with a fallback to return if not found.
--
getConfigDefault :: (MonadAction m, Param a) => Text -> a -> m a
getConfigDefault name value = do
ActionEnv{aeConfig} <- getActionEnv
return $ fromMaybe value $ fromParam =<< Map.lookup name aeConfig
-- Finalizing -------------------------------------------------------------- -- Finalizing --------------------------------------------------------------
......
...@@ -16,24 +16,29 @@ module Hikaru.CSRF ...@@ -16,24 +16,29 @@ module Hikaru.CSRF
) )
where where
import Praha import Praha
import Praha.Config.Environment
import Crypto.Hash import Crypto.Hash
import Crypto.MAC.HMAC import Crypto.MAC.HMAC
import Crypto.Random.Entropy
import Data.Text (splitOn) import Data.Text (splitOn)
import Data.Time.Clock.POSIX (getPOSIXTime) import Data.Time.Clock.POSIX (getPOSIXTime)
import Data.ByteArray.Encoding
import Hikaru.Action import Hikaru.Action
-- | -- |
-- Generate an anti-CSRF token to be used with forms. -- Generate an anti-CSRF token to be used with forms.
-- --
-- Uses the @CSRF_SECRET@ configuration key. -- Uses the @HIKARU_SECRET@ configuration key.
-- --
generateToken :: (MonadAction m) => m Text generateToken :: (MonadAction m) => m Text
generateToken = do generateToken = do
now <- getTimestamp now <- getTimestamp
secret <- getConfigDefault "CSRF_SECRET" "" secret <- getSecret
let signature = sign now secret let signature = sign now secret
in return $ mconcat [ tshow now, ":", signature ] in return $ mconcat [ tshow now, ":", signature ]
...@@ -71,6 +76,21 @@ where ...@@ -71,6 +76,21 @@ where
getTimestamp = round <$> liftIO getPOSIXTime getTimestamp = round <$> liftIO getPOSIXTime
-- |
-- Use the secret from environment or put there an actual one.
--
getSecret :: (MonadIO m) => m Text
getSecret = do
maybeSecret <- getConfigMaybe "HIKARU_SECRET"
case maybeSecret of
Just secret -> return secret
Nothing -> do
secret <- generateSecret 16
setConfig "HIKARU_SECRET" secret
return secret
sign :: Int64 -> Text -> Text sign :: Int64 -> Text -> Text
sign timestamp secret = tshow $ hmacGetDigest digest sign timestamp secret = tshow $ hmacGetDigest digest
where where
...@@ -79,4 +99,17 @@ where ...@@ -79,4 +99,17 @@ where
timeBytes = cs (show timestamp) :: ByteString timeBytes = cs (show timestamp) :: ByteString
-- |
-- Generate a random base64-encoded secret of given length
-- (in decoded bytes).
--
generateSecret :: (MonadIO m) => Int -> m Text
generateSecret n = do
(bstr :: ByteString) <- liftIO $ getEntropy n
let (bstr64 :: ByteString) = convertToBase Base64 bstr
in return (cs bstr64)
-- vim:set ft=haskell sw=2 ts=2 et: -- vim:set ft=haskell sw=2 ts=2 et:
--
-- Module : Hikaru.Config
-- Copyright : Jan Hamal Dvořák
-- License : AGPL-3.0-or-later
--
-- Maintainer : mordae@anilinux.org
-- Stability : unstable
-- Portability : non-portable (ghc)
--
-- This module provides means to read configuration from environment and files.
--
-- Example:
--
-- @
-- base <- 'configDefault'
-- file <- 'configFromFile' \"site.env\"
-- env <- 'configFromEnv'
--
-- let cfg = env <> file <> base
-- @
module Hikaru.Config
( Config
, configFromEnv
, configFromFile
, configDefault
-- * Reading Config
, configGet
, configGetMaybe
, configGetDefault
-- * Secrets
, generateSecret
)
where
import Praha
import Hikaru.Types
import UnliftIO.Environment
import Crypto.Random.Entropy
import Data.ByteArray.Encoding
import Data.Text (lines, strip, drop, span, isPrefixOf)
import Data.Text.IO (readFile)
import Data.List (map)
import qualified Data.Map as Map
-- |
-- Website configuration to be passed to the action.
--
type Config = Map.Map Text Text
-- |
-- Read configuration from program environment.
--
configFromEnv :: (MonadIO m) => m Config
configFromEnv = Map.fromList <$> map conv <$> getEnvironment
where conv (k, v) = (cs k, cs v)
-- |
-- Read configuration from file.
--
-- To read configuration from a file and then update it from the environment
-- use "<>" like this:
--
-- @
-- def <- 'configFromFile' \"site.env\"
-- env <- 'configFromEnv'
--
-- let cfg = env <> def
-- @
--
-- Configuration file format is approximately this:
--
-- @
-- # How many seconds before forms need to be reloaded?
-- CSRF_VALIDITY = 3600
--
-- # Secret key to protect against CSRF. Don't tell anyone!
-- CSRF_SECRET = Ain9eec8aighoiri
-- @
--
configFromFile :: (MonadIO m) => FilePath -> m Config
configFromFile path = Map.fromList <$> parseFile <$> contents
where
contents = liftIO (readFile path)
parseFile = mapMaybe parseLine . lines
parseLine = fmap tidy . fmap parseKV . reject . strip
parseKV = fmap (drop 1) . span (/= '=')
reject = guarded (not . isPrefixOf "#") <=< guarded ("" /=)
tidy (k, v) = (strip k, strip v)
-- |
-- Generate default configuration with keys required by Hikaru.
--
-- This includes @CSRF_SECRET@ which should be set to something persistent,
-- but if it's not, it must be set to a random value upon startup at least.
--
configDefault :: (MonadIO m) => m Config
configDefault = do
secret <- generateSecret 16
return $ Map.fromList [ ("CSRF_SECRET", secret)
, ("CSRF_VALIDITY", "86400")
]
-- |
-- Obtain value of a configuration key or raise an error.
-- You really should not use this function, you know?
--
configGet :: (Param a) => Text -> Config -> a
configGet name cfg = case configGetMaybe name cfg of
Just value -> value
Nothing -> error (cs name <> " not set!")
-- |
-- Try to obtain value of a configuration key.
--
configGetMaybe :: (Param a) => Text -> Config -> Maybe a
configGetMaybe name cfg = fromParam =<< Map.lookup name cfg
-- |
-- Obtain value of a configuration key or the provided default.
--
configGetDefault :: (Param a) => Text -> a -> Config -> a
configGetDefault name value = fromMaybe value . configGetMaybe name
-- |
-- Generate a random base64-encoded secret of given length
-- (in decoded bytes).
--
generateSecret :: (MonadIO m) => Int -> m Text
generateSecret n = do
(bstr :: ByteString) <- liftIO $ getEntropy n
let (bstr64 :: ByteString) = convertToBase Base64 bstr
in return (cs bstr64)
-- vim:set ft=haskell sw=2 ts=2 et:
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment