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
|
## Project Goals
|
||||||
1. Provide a minimal and clean interface for generating and sharing passwords.
|
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.
|
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
|
## 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)!
|
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\
|
"CREATE TABLE IF NOT EXISTS pws\
|
||||||
\ (link TEXT PRIMARY KEY,\
|
\ (link TEXT PRIMARY KEY,\
|
||||||
\ secret TEXT,\
|
\ secret TEXT,\
|
||||||
\ date DATETIME DEFAULT CURRENT_TIMESTAMP)"
|
\ date DATETIME DEFAULT CURRENT_TIMESTAMP,\
|
||||||
|
\ life INT,\
|
||||||
|
\ views INT,\
|
||||||
|
\ maxViews INT)"
|
||||||
close conn
|
close conn
|
||||||
|
|
||||||
dbPath :: PurrAction String
|
dbPath :: PurrAction String
|
||||||
|
@ -175,7 +175,6 @@ bread
|
|||||||
break
|
break
|
||||||
breast
|
breast
|
||||||
breath
|
breath
|
||||||
breed
|
|
||||||
brick
|
brick
|
||||||
bridge
|
bridge
|
||||||
brief
|
brief
|
||||||
@ -820,9 +819,7 @@ media
|
|||||||
medium
|
medium
|
||||||
meet
|
meet
|
||||||
melt
|
melt
|
||||||
member
|
|
||||||
memory
|
memory
|
||||||
mental
|
|
||||||
menu
|
menu
|
||||||
mere
|
mere
|
||||||
merely
|
merely
|
||||||
@ -1035,7 +1032,6 @@ profit
|
|||||||
prompt
|
prompt
|
||||||
proof
|
proof
|
||||||
proper
|
proper
|
||||||
proud
|
|
||||||
prove
|
prove
|
||||||
public
|
public
|
||||||
pull
|
pull
|
||||||
|
@ -26,10 +26,12 @@ routes = do
|
|||||||
post "/pw" $ do
|
post "/pw" $ do
|
||||||
reqId <- param "userLink"
|
reqId <- param "userLink"
|
||||||
res <- findByLink reqId
|
res <- findByLink reqId
|
||||||
html $ renderPw reqId (secret <$> res)
|
html $ renderPw reqId res
|
||||||
|
|
||||||
post "/new" $ do
|
post "/new" $ do
|
||||||
reqSecret <- param "newSec"
|
reqSecret <- param "newSec"
|
||||||
|
reqDur <- param "newSecDuration"
|
||||||
|
reqViews <- param "newSecViews"
|
||||||
link <- genLink
|
link <- genLink
|
||||||
insertNewSecret reqSecret (T.pack link)
|
insertNewSecret reqSecret reqDur (T.pack link) reqViews
|
||||||
html $ renderPw link (Just reqSecret)
|
html $ renderPw link (Just reqSecret)
|
||||||
|
@ -9,7 +9,6 @@ import Crypto.Simple.CBC (encrypt, decrypt)
|
|||||||
import Data.Maybe (listToMaybe)
|
import Data.Maybe (listToMaybe)
|
||||||
import Data.Time.Clock.POSIX (getPOSIXTime)
|
import Data.Time.Clock.POSIX (getPOSIXTime)
|
||||||
import Database.SQLite.Simple
|
import Database.SQLite.Simple
|
||||||
import Database.SQLite.Simple.FromRow
|
|
||||||
|
|
||||||
import qualified Data.ByteString.Base64 as B64
|
import qualified Data.ByteString.Base64 as B64
|
||||||
import qualified Data.ByteString.Char8 as B
|
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.Encoding as ET
|
||||||
import qualified Data.Text.Lazy as LT
|
import qualified Data.Text.Lazy as LT
|
||||||
|
|
||||||
findByLink :: String -> PurrAction (Maybe SecretEntry)
|
findByLink :: String -> PurrAction (Maybe T.Text)
|
||||||
findByLink link = do
|
findByLink link = do
|
||||||
db <- dbPath
|
db <- dbPath
|
||||||
conn <- liftIO $ open db
|
key <- encKey
|
||||||
res <- liftIO $ query conn "SELECT * from pws WHERE link = ?" (Only link)
|
conn <- liftIO $ open db
|
||||||
|
res <- liftIO $ query conn "SELECT * from pws WHERE link = ?" (Only link)
|
||||||
liftIO $ close conn
|
liftIO $ close conn
|
||||||
return $ listToMaybe res
|
readEncryptedSecret key res
|
||||||
|
|
||||||
insertNewSecret :: T.Text -> T.Text -> PurrAction ()
|
insertNewSecret :: T.Text -> Integer -> T.Text -> Integer -> PurrAction ()
|
||||||
insertNewSecret sec link = do
|
insertNewSecret sec life link maxViews = do
|
||||||
db <- dbPath
|
db <- dbPath
|
||||||
key <- encKey
|
key <- encKey
|
||||||
encSec <- liftIO $ encrypt (B.pack key) (ET.encodeUtf8 sec)
|
encSec <- liftIO $ encryptSecret key sec
|
||||||
conn <- liftIO $ open db
|
conn <- liftIO $ open db
|
||||||
time <- liftIO $ epochTime
|
time <- liftIO $ epochTime
|
||||||
liftIO $ execute conn
|
liftIO $ execute conn
|
||||||
"INSERT INTO pws (link, secret, date) VALUES (?, ?, ?)"
|
"INSERT INTO pws (link, secret, date, life, views, maxViews) VALUES (?, ?, ?, ?, ?, ?)"
|
||||||
(SecretEntry link (encodeSecret encSec) time)
|
(SecretEntry link (encodeSecret encSec) time life 0 maxViews)
|
||||||
liftIO $ close conn
|
liftIO $ close conn
|
||||||
|
|
||||||
epochTime :: IO Integer
|
readEncryptedSecret :: String -> [SecretEntry] -> PurrAction (Maybe T.Text)
|
||||||
epochTime = fmap round getPOSIXTime
|
readEncryptedSecret key sec = do
|
||||||
|
decKey <- liftIO
|
||||||
|
(sequence $ decryptSecret key <$> decodeSecret <$> listToMaybe sec)
|
||||||
|
return (ET.decodeLatin1 <$> decKey)
|
||||||
|
|
||||||
encodeSecret :: B.ByteString -> T.Text
|
encodeSecret :: B.ByteString -> T.Text
|
||||||
encodeSecret b = ET.decodeUtf8 $ B64.encode b
|
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
|
import qualified Data.Text as T
|
||||||
|
|
||||||
data SecretEntry = SecretEntry
|
data SecretEntry = SecretEntry
|
||||||
{ link :: T.Text
|
{ link :: T.Text
|
||||||
, secret :: T.Text
|
, secret :: T.Text
|
||||||
, date :: Integer
|
, date :: Integer
|
||||||
|
, life :: Integer
|
||||||
|
, views :: Integer
|
||||||
|
, maxViews :: Integer
|
||||||
} deriving (Show, Generic)
|
} deriving (Show, Generic)
|
||||||
|
|
||||||
instance FromRow SecretEntry where
|
instance FromRow SecretEntry where
|
||||||
|
@ -56,7 +56,7 @@ a
|
|||||||
outline: none
|
outline: none
|
||||||
color: #{colorOne}
|
color: #{colorOne}
|
||||||
background: #{colorTwo}
|
background: #{colorTwo}
|
||||||
margin: 0.5em 0
|
margin: 0.5em 0 1em 0
|
||||||
border-style: none none solid none
|
border-style: none none solid none
|
||||||
padding: 0.4em 0
|
padding: 0.4em 0
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
@ -75,6 +75,33 @@ a
|
|||||||
color: #{colorThree}
|
color: #{colorThree}
|
||||||
opacity: 0.5
|
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
|
.pwResult
|
||||||
font-size: 1.5em
|
font-size: 1.5em
|
||||||
color: #{colorFour}
|
color: #{colorFour}
|
||||||
@ -109,6 +136,9 @@ a
|
|||||||
width: 95%
|
width: 95%
|
||||||
text-align: center
|
text-align: center
|
||||||
|
|
||||||
|
.numberInput
|
||||||
|
width: 25%
|
||||||
|
|
||||||
.title
|
.title
|
||||||
margin: 8% auto 2% auto
|
margin: 8% auto 2% auto
|
||||||
font-size: 3em
|
font-size: 3em
|
||||||
|
@ -36,14 +36,33 @@ $doctype 5
|
|||||||
<p>
|
<p>
|
||||||
<input .mainInput
|
<input .mainInput
|
||||||
name="newSec"
|
name="newSec"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Enter a Secret to Share"
|
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
|
<button .mainButton
|
||||||
hx-post="/new"
|
hx-post="/new"
|
||||||
hx-target="#requestedPw"
|
hx-target="#requestedPw"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-include="[name='newSec']"
|
hx-include="[id='shareNew']"
|
||||||
/>
|
/>
|
||||||
Share Secret
|
Share Secret
|
||||||
<img class="htmx-indicator" src="/loading.svg" />
|
<img class="htmx-indicator" src="/loading.svg" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user