Refactor excessive do notation. Complete oldschool and gibberish generators. Add frontend functionality for selecting from multiple generator styles. Add config requirement for admin email. Add admin email to header.

This commit is contained in:
James Eversole 2022-08-07 16:22:09 -05:00
parent 3c4c4f8d30
commit 2a4787fc84
16 changed files with 197 additions and 159 deletions

View File

@ -12,4 +12,5 @@
, dbFile = "data/Purr.sqlite" , dbFile = "data/Purr.sqlite"
, dbKey = "REPLACEME!!!!!" , dbKey = "REPLACEME!!!!!"
, linkLength = +24 , linkLength = +24
, adminEmail = "james@eversole.co"
} }

View File

@ -6,6 +6,7 @@ import Core.Templates (renderIndex, renderStyle)
import Feature.Sharing.HTTP as Sharing import Feature.Sharing.HTTP as Sharing
import Feature.Generation.HTTP as Generation import Feature.Generation.HTTP as Generation
import Control.Monad.Reader (ask, lift)
import Data.Maybe (Maybe (Nothing)) import Data.Maybe (Maybe (Nothing))
import Network.Wai.Middleware.RequestLogger (logStdoutDev) import Network.Wai.Middleware.RequestLogger (logStdoutDev)
import Network.Wai.Middleware.Static import Network.Wai.Middleware.Static
@ -19,7 +20,8 @@ app = do
-- Core Routes -- Core Routes
get "/" $ do get "/" $ do
html $ renderIndex "/" conf <- lift ask
html $ renderIndex "/" (adminEmail conf)
get "/style.css" $ do get "/style.css" $ do
setHeader "Content-Type" "text/css" setHeader "Content-Type" "text/css"

View File

@ -22,11 +22,10 @@ main db = do
close conn close conn
dbPath :: PurrAction String dbPath :: PurrAction String
dbPath = do dbPath = lift ask >>= (\a -> return $ dbFile a)
conf <- lift ask
return $ dbFile conf
encKey :: PurrAction String encKey :: PurrAction String
encKey = do encKey = lift ask >>= (\a -> return $ dbKey a)
conf <- lift ask
return $ dbKey conf confLinkLength :: PurrAction Int
confLinkLength = lift ask >>= (\a -> return $ linkLength a)

View File

@ -13,8 +13,8 @@ import qualified Data.Text.Lazy as LT
import Prelude import Prelude
renderIndex :: String -> LT.Text renderIndex :: String -> String -> LT.Text
renderIndex link = renderHtml ( $(shamletFile "./views/index.hamlet") ) renderIndex link email = renderHtml ( $(shamletFile "./views/index.hamlet") )
where where
hsUserLink = hxVals "userLink" link hsUserLink = hxVals "userLink" link

View File

@ -22,4 +22,5 @@ data DhallConfig = DhallConfig
, dbFile :: String , dbFile :: String
, dbKey :: String , dbKey :: String
, linkLength :: Int , linkLength :: Int
, adminEmail :: String
} deriving (Generic, Show) } deriving (Generic, Show)

View File

@ -3,7 +3,11 @@ module Feature.Generation.HTTP ( routes ) where
import Core.Types import Core.Types
import Core.Templates (renderIndex) import Core.Templates (renderIndex)
import Feature.Generation.Passwords (suggestedScheme) import Feature.Generation.Passwords
( suggestedScheme
, xkcd
, oldschool
, gibberish )
import Feature.Generation.Templates (renderGen) import Feature.Generation.Templates (renderGen)
import qualified Data.Text as T import qualified Data.Text as T
@ -18,5 +22,7 @@ routes :: PurrApp ()
routes = do routes = do
get "/gen" $ do get "/gen" $ do
genPw <- liftIO $ suggestedScheme 24 genXkcd <- liftIO $ xkcd
html $ renderGen genPw genOldschool <- liftIO $ oldschool
genGibberish <- liftIO $ gibberish 12
html $ renderGen genXkcd genOldschool genGibberish

View File

@ -1,43 +1,21 @@
module Feature.Generation.Links ( genLink ) where module Feature.Generation.Links ( genLink ) where
import Core.Types import Core.Types
import Feature.Generation.Shared (rIndex, validChars) import Feature.Generation.Shared (rIndex, rChar, validChars)
import Control.Monad.Reader (ask, lift, liftIO) import Control.Monad.Reader (ask, lift)
import Data.Char (toLower, toUpper) import Data.List (singleton)
import System.IO
import System.Random
-- Generates a string containing randomly generated and capitalized newtype GenLink = GenLink' String
deriving (Eq, Ord)
instance Show GenLink where
show (GenLink' a) = a
-- Generates a string containing romly generated and capitalized
-- characters. The number of characters used is defined in the global config.dhall. -- characters. The number of characters used is defined in the global config.dhall.
genLink :: PurrAction String genLink :: Int -> IO GenLink
genLink = do genLink linkLength = genLink' linkLength (return "")
linkLength <- confLinkLength
genLink' linkLength ""
where where
genLink' 0 cs = return cs genLink' 0 cs = GenLink' <$> cs
genLink' d cs = do genLink' d cs = genLink' (d - 1) (cs <> (singleton <$> rChar))
res <- liftIO $ randChar
fin <- liftIO $ randCapitalization res
genLink' (d - 1) (cs <> (fin:[]))
randChar :: IO Char
randChar = rIndex validChars
randCapitalization :: Char -> IO Char
randCapitalization c = do
capRand <- randomRIO (0,1)
return $ go capRand c
where
go :: Int -> Char -> Char
go r c
| r == 0 = toLower c
| r == 1 = toUpper c
| otherwise = c
-- Helper function in our PurrAction monad transformer stack to read
-- the desired length for generated links.
confLinkLength :: PurrAction Int
confLinkLength = do
conf <- lift ask
return $ linkLength conf

View File

@ -1,47 +1,75 @@
module Feature.Generation.Passwords where module Feature.Generation.Passwords
( Password
, suggestedScheme
, xkcd
, oldschool
, gibberish
) where
import Core.Types import Core.Types
import Feature.Generation.Shared (rIndex, validChars) import Feature.Generation.Shared
( camelCase
, rCharSym
, rIndex
, validChars
, validNumbers
, validSymbols
)
import Control.Monad.Reader (ask, lift, liftIO)
import Data.Char (toLower, toUpper)
import Data.List (singleton) import Data.List (singleton)
import System.IO
import System.Random
camelCase :: [Char] -> [Char] newtype Password = Password' String
camelCase [] = [] deriving (Eq, Ord)
camelCase x = toUpper (head x) : map toLower (tail x)
-- suggestedScheme and its helpers xkcd, oldschool, and gibberish are TODO. instance Show Password where
suggestedScheme :: Int -> IO String show (Password' a) = a
suggestedScheme :: Int -> IO Password
suggestedScheme i suggestedScheme i
| i > 23 = xkcd i | i > 17 = xkcd
| i > 12 = oldschool i | i > 12 = oldschool
| otherwise = gibberish i | otherwise = gibberish i
xkcd :: Int -> IO String xkcd :: IO Password
xkcd i = do xkcd = do
wOne <- randomCamel wOne <- rCamel
wTwo <- randomCamel wTwo <- rCamel
wThree <- randomCamel wThree <- rCamel
wFour <- randomCamel wFour <- rCamel
return $ wOne <> wTwo <> wThree <> wFour return $ Password' (wOne <> wTwo <> wThree <> wFour)
oldschool :: Int -> IO String oldschool :: IO Password
oldschool i = do oldschool = do
wOne <- randomWord wOne <- rCamel
wTwo <- randomWord wTwo <- rCamel
return $ wOne <> wTwo nOne <- rNum
nTwo <- rNum
nThr <- rNum
nFou <- rNum
sOne <- rSym
return
$ Password' (wOne <> wTwo
<> show nOne <> show nTwo <> show nThr <> show nFou
<> pure sOne)
gibberish :: Int -> IO String gibberish :: Int -> IO Password
gibberish i = return "mf98sgs7bgg%#" gibberish i = go i (return "")
where
go :: Int -> IO String -> IO Password
go 0 s = Password' <$> s
go i s = go (i - 1) (s <> (singleton <$> rCharSym))
rNum :: IO Int
rNum = rIndex validNumbers
rWord :: IO String
rWord = wordList >>= rIndex
rSym :: IO Char
rSym = rIndex validSymbols
rCamel :: IO String
rCamel = camelCase <$> rWord
wordList :: IO [String] wordList :: IO [String]
wordList = fmap lines (readFile "./src/Feature/Generation/wordlist.txt") wordList = fmap lines (readFile "./src/Feature/Generation/wordlist.txt")
randomWord :: IO String
randomWord = wordList >>= rIndex
randomCamel :: IO String
randomCamel = camelCase <$> randomWord

View File

@ -1,20 +1,37 @@
module Feature.Generation.Shared where module Feature.Generation.Shared where
import Data.Char (intToDigit, toLower, toUpper)
import System.Random (randomRIO) import System.Random (randomRIO)
rIndex :: [a] -> IO a rIndex :: [a] -> IO a
rIndex arr = do rIndex arr = randomRIO (0, length arr - 1)
i <- randomRIO (0, length arr - 1) >>= (\i -> return $ arr !! i)
return $ arr !! i
rCap :: Char -> IO Char
rCap c = rIndex [True,False] >>= (\r -> return $ rCap' r c)
where
rCap' :: Bool -> Char -> Char
rCap' True c = toUpper c
rCap' False c = toLower c
rChar :: IO Char
rChar = rIndex validChars >>= rCap
rCharSym :: IO Char
rCharSym = rIndex (validChars <> validSymbols)>>= rCap
camelCase :: [Char] -> [Char]
camelCase [] = []
camelCase x = toUpper (head x) : map toLower (tail x)
-- Defines the valid range of characters to be used when generating.
-- This consists of all lowercase Latin alphabet characters and the
-- numbers 1 through 9.
validChars :: [Char] validChars :: [Char]
validChars = validLetters <> validNumbers validChars = validLetters <> fmap intToDigit validNumbers
validNumbers :: [Char] validNumbers :: [Int]
validNumbers = ['1'..'9'] validNumbers = [1..9]
validLetters :: [Char] validLetters :: [Char]
validLetters = ['a'..'z'] validLetters = ['a'..'z']
validSymbols :: [Char]
validSymbols = ['!', '@', '#', '$', '%', '^', '&', '*']

View File

@ -4,6 +4,7 @@
module Feature.Generation.Templates ( renderGen ) where module Feature.Generation.Templates ( renderGen ) where
import Core.Templates (hxVals) import Core.Templates (hxVals)
import Feature.Generation.Passwords (Password)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Lazy as LT import qualified Data.Text.Lazy as LT
@ -14,7 +15,5 @@ import Text.Hamlet (shamletFile)
import Prelude import Prelude
renderGen :: String -> LT.Text renderGen :: Password -> Password -> Password -> LT.Text
renderGen genPw = renderHtml ( $(shamletFile "./views/gen.hamlet") ) renderGen genXkcd genOldschool genGibberish = renderHtml ( $(shamletFile "./views/gen.hamlet") )
where
hsGeneratedSharing = hxVals "newSec" genPw

View File

@ -654,7 +654,6 @@ humour
hungry hungry
hunt hunt
hurry hurry
hurt
idea idea
ideal ideal
ignore ignore
@ -668,10 +667,7 @@ income
indeed indeed
index index
indoor indoor
infect
inform inform
injure
injury
inner inner
insect insect
insert insert
@ -862,14 +858,12 @@ move
movie movie
moving moving
much much
murder
muscle muscle
museum museum
music music
must must
myself myself
nail nail
naked
name name
narrow narrow
nation nation
@ -1242,7 +1236,6 @@ single
sink sink
site site
size size
skill
skin skin
skirt skirt
sleep sleep

View File

@ -1,5 +1,6 @@
module Feature.Sharing.HTTP ( routes ) where module Feature.Sharing.HTTP ( routes ) where
import Core.SQLite (confLinkLength)
import Core.Types import Core.Types
import Core.Templates (renderIndex) import Core.Templates (renderIndex)
@ -21,7 +22,8 @@ routes = do
get "/pw/:id" $ do get "/pw/:id" $ do
reqId <- param "id" reqId <- param "id"
html $ renderIndex reqId conf <- lift ask
html $ renderIndex reqId (adminEmail conf)
post "/pw" $ do post "/pw" $ do
reqId <- param "userLink" reqId <- param "userLink"
@ -32,6 +34,7 @@ routes = do
reqSecret <- param "newSec" reqSecret <- param "newSec"
reqDur <- param "newSecDuration" reqDur <- param "newSecDuration"
reqViews <- param "newSecViews" reqViews <- param "newSecViews"
link <- genLink cLength <- confLinkLength
insertNewSecret reqSecret reqDur (T.pack link) reqViews link <- liftIO $ genLink cLength
html $ renderPw link (Just reqSecret) insertNewSecret reqSecret reqDur (T.pack $ show link) reqViews
html $ renderPw (show link) (Just reqSecret)

View File

@ -2,6 +2,7 @@ module Feature.Sharing.SQLite where
import Core.Types import Core.Types
import Core.SQLite import Core.SQLite
import Feature.Generation.Passwords (Password)
import Feature.Sharing.Types import Feature.Sharing.Types
import Control.Monad.Reader (ask, lift, liftIO) import Control.Monad.Reader (ask, lift, liftIO)

View File

@ -13,6 +13,9 @@ body
font-size: 20px font-size: 20px
text-align: left text-align: left
header
text-align: right
h2 h2
font-family: monaco, Consolas, monospace font-family: monaco, Consolas, monospace
text-transform: uppercase text-transform: uppercase
@ -38,18 +41,6 @@ a
.title h1 .title h1
margin: 0.1em 0 0.1em 0 margin: 0.1em 0 0.1em 0
.footer
position: absolute
text-align: right
bottom: 0
width: 75%
height: 2.5em
.footer a
all: unset
cursor: pointer
color: #{colorThree}
.htmx-indicator .htmx-indicator
display: none display: none
@ -57,6 +48,9 @@ a
color: #{colorOne} color: #{colorOne}
opacity: 1 opacity: 1
.generators
margin: 0 0 0.5em 0
.logo .logo
margin: 4% 3% 0 0 margin: 4% 3% 0 0
font-size: 1.2vw font-size: 1.2vw
@ -142,6 +136,9 @@ a
color: #{colorThree} color: #{colorThree}
opacity: 0.5 opacity: 0.5
.genNew .numberInput
margin: 0 0 0.5em 0
.genResult .genResult
color: #{colorFour} color: #{colorFour}
@ -178,6 +175,9 @@ a
.genButton .genButton
width: 80% width: 80%
.generators
text-align: left
.mainInput .mainInput
width: 95% width: 95%
@ -188,7 +188,7 @@ a
width: 25% width: 25%
.title .title
margin: 8% auto 2% auto margin: 4% auto 2% auto
font-size: 3em font-size: 3em
.title h1 .title h1

View File

@ -1,41 +1,50 @@
<div #generators .column> <div #generators .column>
<h2>Generators <h2>Generators
<h3 .genResult>#{genPw} <h3>Random, Classic, XKCD
<button .genButton <form id="genForm">
hx-get="/gen" <div .generators">
hx-target="#generators" <input type="radio" id="gibberish" name="newSec" value="#{show genGibberish}">
hx-swap="outerHTML" <label for="gibberish" .genResult>#{show genGibberish}
/> <br />
Generate New <input type="radio" id="oldschool" name="newSec" value="#{show genOldschool}">
<br /> <label for="oldschool" .genResult>#{show genOldschool}
<br />
<button .genButton <input type="radio" id="xkcd" name="newSec" value="#{show genXkcd}">
hx-post="/new" <label for="xkcd" .genResult>#{show genXkcd}
hx-target="#requestedPw" <button .genButton
hx-swap="outerHTML" hx-get="/gen"
hx-include="[id='generators']" hx-target="#generators"
*{hsGeneratedSharing} hx-swap="outerHTML"
/> />
Share Password Generate New
<img class="htmx-indicator" src="/loading.svg" />
<div .validForm>
Valid for:
<br /> <br />
<input .numberInput
name="newSecDuration" <div .validForm>
type="number" <h3>Share Generated Password
min="1" Valid for:
max="90" <br />
value="20" <input .numberInput
onkeyup="if (value < 1 || value > 90) { value = 0 }" name="newSecDuration"
/> days type="number"
<input .numberInput min="1"
name="newSecViews" max="90"
type="number" value="20"
min="1" onkeyup="if (value < 1 || value > 90) { value = 0 }"
max="60" /> days
value="20" <input .numberInput
onkeyup="if (value < 1 || value > 60) { value = 0 }" name="newSecViews"
/> views type="number"
min="1"
max="60"
value="20"
onkeyup="if (value < 1 || value > 60) { value = 0 }"
/> views
<button .genButton
hx-post="/new"
hx-target="#requestedPw"
hx-swap="outerHTML"
hx-include="[name='newSec']"
/>
Share Password
<img class="htmx-indicator" src="/loading.svg" />

View File

@ -9,6 +9,11 @@ $doctype 5
<body> <body>
<header>
<a href="https://git.eversole.co/James/Purr">
Made with &#9829;
| <a href="mailto:#{email}">Contact
<div #title .title> <div #title .title>
<h1> <h1>
<a #titleLink .titleLink href="/">Purr <a #titleLink .titleLink href="/">Purr
@ -91,7 +96,3 @@ $doctype 5
hx-swap="outerHTML" hx-swap="outerHTML"
/> />
Load Generators Load Generators
<footer .footer #footer>
<a href="https://git.eversole.co/James/Purr">
Made With &#9829;