Read encrypted secret entries from the database and decode/unencrypt appropriately, add max duration/view count entries for the database and frontend interface, update README to remove graceful JS degradation goal, remove a few wordlist entries
This commit is contained in:
parent
478384aae9
commit
ca73ed7982
@ -18,7 +18,6 @@ You shouldn't! This is [free and open-source software](https://git.eversole.co/J
|
||||
## Project Goals
|
||||
1. Provide a minimal and clean interface for generating and sharing passwords.
|
||||
2. Maintain a clean and organized codebase that can be extended to include more utilities than originally anticipated.
|
||||
3. Aim for graceful degradation when JavaScript isn't enabled.
|
||||
|
||||
## Development & Support
|
||||
Please send me an [email](mailto:james@eversole.co) or join the [Support Chat](openpgp4fpr://FEB27223219E8DB3203225350462EA0901FE08F7#a=james%40eversole.co&g=Purr%20Support&x=RbVs8iQCVnf&i=-FuzUDK_RM1&s=KgeGtFFJtkq) in [DeltaChat](https://delta.chat)!
|
||||
|
@ -15,7 +15,10 @@ main db = do
|
||||
"CREATE TABLE IF NOT EXISTS pws\
|
||||
\ (link TEXT PRIMARY KEY,\
|
||||
\ secret TEXT,\
|
||||
\ date DATETIME DEFAULT CURRENT_TIMESTAMP)"
|
||||
\ date DATETIME DEFAULT CURRENT_TIMESTAMP,\
|
||||
\ life INT,\
|
||||
\ views INT,\
|
||||
\ maxViews INT)"
|
||||
close conn
|
||||
|
||||
dbPath :: PurrAction String
|
||||
|
@ -175,7 +175,6 @@ bread
|
||||
break
|
||||
breast
|
||||
breath
|
||||
breed
|
||||
brick
|
||||
bridge
|
||||
brief
|
||||
@ -820,9 +819,7 @@ media
|
||||
medium
|
||||
meet
|
||||
melt
|
||||
member
|
||||
memory
|
||||
mental
|
||||
menu
|
||||
mere
|
||||
merely
|
||||
@ -1035,7 +1032,6 @@ profit
|
||||
prompt
|
||||
proof
|
||||
proper
|
||||
proud
|
||||
prove
|
||||
public
|
||||
pull
|
||||
|
@ -26,10 +26,12 @@ routes = do
|
||||
post "/pw" $ do
|
||||
reqId <- param "userLink"
|
||||
res <- findByLink reqId
|
||||
html $ renderPw reqId (secret <$> res)
|
||||
html $ renderPw reqId res
|
||||
|
||||
post "/new" $ do
|
||||
reqSecret <- param "newSec"
|
||||
reqDur <- param "newSecDuration"
|
||||
reqViews <- param "newSecViews"
|
||||
link <- genLink
|
||||
insertNewSecret reqSecret (T.pack link)
|
||||
insertNewSecret reqSecret reqDur (T.pack link) reqViews
|
||||
html $ renderPw link (Just reqSecret)
|
||||
|
@ -9,7 +9,6 @@ import Crypto.Simple.CBC (encrypt, decrypt)
|
||||
import Data.Maybe (listToMaybe)
|
||||
import Data.Time.Clock.POSIX (getPOSIXTime)
|
||||
import Database.SQLite.Simple
|
||||
import Database.SQLite.Simple.FromRow
|
||||
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
@ -17,28 +16,44 @@ import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as ET
|
||||
import qualified Data.Text.Lazy as LT
|
||||
|
||||
findByLink :: String -> PurrAction (Maybe SecretEntry)
|
||||
findByLink :: String -> PurrAction (Maybe T.Text)
|
||||
findByLink link = do
|
||||
db <- dbPath
|
||||
conn <- liftIO $ open db
|
||||
res <- liftIO $ query conn "SELECT * from pws WHERE link = ?" (Only link)
|
||||
db <- dbPath
|
||||
key <- encKey
|
||||
conn <- liftIO $ open db
|
||||
res <- liftIO $ query conn "SELECT * from pws WHERE link = ?" (Only link)
|
||||
liftIO $ close conn
|
||||
return $ listToMaybe res
|
||||
readEncryptedSecret key res
|
||||
|
||||
insertNewSecret :: T.Text -> T.Text -> PurrAction ()
|
||||
insertNewSecret sec link = do
|
||||
insertNewSecret :: T.Text -> Integer -> T.Text -> Integer -> PurrAction ()
|
||||
insertNewSecret sec life link maxViews = do
|
||||
db <- dbPath
|
||||
key <- encKey
|
||||
encSec <- liftIO $ encrypt (B.pack key) (ET.encodeUtf8 sec)
|
||||
encSec <- liftIO $ encryptSecret key sec
|
||||
conn <- liftIO $ open db
|
||||
time <- liftIO $ epochTime
|
||||
liftIO $ execute conn
|
||||
"INSERT INTO pws (link, secret, date) VALUES (?, ?, ?)"
|
||||
(SecretEntry link (encodeSecret encSec) time)
|
||||
liftIO $ close conn
|
||||
liftIO $ execute conn
|
||||
"INSERT INTO pws (link, secret, date, life, views, maxViews) VALUES (?, ?, ?, ?, ?, ?)"
|
||||
(SecretEntry link (encodeSecret encSec) time life 0 maxViews)
|
||||
liftIO $ close conn
|
||||
|
||||
epochTime :: IO Integer
|
||||
epochTime = fmap round getPOSIXTime
|
||||
readEncryptedSecret :: String -> [SecretEntry] -> PurrAction (Maybe T.Text)
|
||||
readEncryptedSecret key sec = do
|
||||
decKey <- liftIO
|
||||
(sequence $ decryptSecret key <$> decodeSecret <$> listToMaybe sec)
|
||||
return (ET.decodeLatin1 <$> decKey)
|
||||
|
||||
encodeSecret :: B.ByteString -> T.Text
|
||||
encodeSecret b = ET.decodeUtf8 $ B64.encode b
|
||||
|
||||
decodeSecret :: SecretEntry -> B.ByteString
|
||||
decodeSecret s = B64.decodeLenient $ ET.encodeUtf8 (secret s)
|
||||
|
||||
encryptSecret :: String -> T.Text -> IO B.ByteString
|
||||
encryptSecret k s = encrypt (B.pack k) (ET.encodeUtf8 s)
|
||||
|
||||
decryptSecret :: String -> B.ByteString -> IO B.ByteString
|
||||
decryptSecret k b = decrypt (B.pack k) b
|
||||
|
||||
epochTime :: IO Integer
|
||||
epochTime = fmap round getPOSIXTime
|
||||
|
@ -10,9 +10,12 @@ import Database.SQLite.Simple.FromRow
|
||||
import qualified Data.Text as T
|
||||
|
||||
data SecretEntry = SecretEntry
|
||||
{ link :: T.Text
|
||||
, secret :: T.Text
|
||||
, date :: Integer
|
||||
{ link :: T.Text
|
||||
, secret :: T.Text
|
||||
, date :: Integer
|
||||
, life :: Integer
|
||||
, views :: Integer
|
||||
, maxViews :: Integer
|
||||
} deriving (Show, Generic)
|
||||
|
||||
instance FromRow SecretEntry where
|
||||
|
@ -56,7 +56,7 @@ a
|
||||
outline: none
|
||||
color: #{colorOne}
|
||||
background: #{colorTwo}
|
||||
margin: 0.5em 0
|
||||
margin: 0.5em 0 1em 0
|
||||
border-style: none none solid none
|
||||
padding: 0.4em 0
|
||||
box-sizing: border-box
|
||||
@ -75,6 +75,33 @@ a
|
||||
color: #{colorThree}
|
||||
opacity: 0.5
|
||||
|
||||
.numberInput
|
||||
text-align: center
|
||||
font-weight: 400
|
||||
font-size: 1em
|
||||
width: 10%
|
||||
outline: none
|
||||
color: #{colorOne}
|
||||
background: #{colorTwo}
|
||||
margin: 0.5em 0
|
||||
border-style: none none solid none
|
||||
padding: 0.4em 0
|
||||
box-sizing: border-box
|
||||
-webkit-box-sizing: border-box
|
||||
-moz-box-sizing: border-box
|
||||
-webkit-transition: all 0.1s ease-in-out
|
||||
-moz-transition: all 0.1s ease-in-out
|
||||
-ms-transition: all 0.1s ease-in-out
|
||||
-o-transition: all 0.1s ease-in-out
|
||||
transition: all 0.1s ease-in-out
|
||||
|
||||
.numberInput:focus
|
||||
border-bottom: 0.2em solid #{colorThree};
|
||||
|
||||
.numberInput:focus::placeholder
|
||||
color: #{colorThree}
|
||||
opacity: 0.5
|
||||
|
||||
.pwResult
|
||||
font-size: 1.5em
|
||||
color: #{colorFour}
|
||||
@ -109,6 +136,9 @@ a
|
||||
width: 95%
|
||||
text-align: center
|
||||
|
||||
.numberInput
|
||||
width: 25%
|
||||
|
||||
.title
|
||||
margin: 8% auto 2% auto
|
||||
font-size: 3em
|
||||
|
@ -36,14 +36,33 @@ $doctype 5
|
||||
<p>
|
||||
<input .mainInput
|
||||
name="newSec"
|
||||
type="text"
|
||||
type="text"
|
||||
placeholder="Enter a Secret to Share"
|
||||
/>
|
||||
<br />
|
||||
Valid for:
|
||||
<br />
|
||||
<input .numberInput
|
||||
name="newSecDuration"
|
||||
type="number"
|
||||
min="1"
|
||||
max="90"
|
||||
value="20"
|
||||
onkeyup="if (value < 1 || value > 90) { value = 0 }"
|
||||
/> days
|
||||
<input .numberInput
|
||||
name="newSecViews"
|
||||
type="number"
|
||||
min="1"
|
||||
max="60"
|
||||
value="20"
|
||||
onkeyup="if (value < 1 || value > 60) { value = 0 }"
|
||||
/> views
|
||||
<button .mainButton
|
||||
hx-post="/new"
|
||||
hx-target="#requestedPw"
|
||||
hx-swap="outerHTML"
|
||||
hx-include="[name='newSec']"
|
||||
hx-include="[id='shareNew']"
|
||||
/>
|
||||
Share Secret
|
||||
<img class="htmx-indicator" src="/loading.svg" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user