Updated README with project goals, started prototyping frontend and added route for primary css dist, added reasonable functionality for requesting a password directly via link as well as patching index DOM when requesting HTML stub from /pw
This commit is contained in:
parent
7274560398
commit
f1b18f3b47
17
README.md
17
README.md
@ -1,19 +1,24 @@
|
|||||||
# Purr - Password Generation and Sharing
|
# Purr - Password Generation and Secret Sharing
|
||||||
![Big Purr and Sploot](https://eversole.co/Purr-Small.png "Purr!")
|
![Big Purr and Sploot](https://eversole.co/Purr-Small.png "Purr!")
|
||||||
Purr is a work-in-progress web application offering customizable password generation and time-limited link sharing features.
|
Purr is a work-in-progress web application offering customizable password generation and time-limited sharing of secrets.
|
||||||
|
|
||||||
## What problems does Purr solve?
|
## What problems does Purr solve?
|
||||||
1. Generating sufficiently memorable but secure passwords for use with accounts that don't offer better authentication methods.
|
1. Generating sufficiently memorable but secure passwords for use with accounts that don't offer better authentication methods.
|
||||||
2. Sharing passwords or other text secrets with others via email/chat/etc., without disclosing the password itself. As passwords expire after a predefined period, the email/chat history where the information was shared don't become a purr-manent (sorry, **permanent**) vulnerability.
|
2. Sharing text secrets with others without disclosing the secret in the message itself. As secrets expire after a predefined period, the email/chat history where the information was shared don't become a purr-manent (sorry, **permanent**) vulnerability.
|
||||||
3. Being really cute compared to the competition.
|
3. Being really cute compared to the competition.
|
||||||
|
|
||||||
|
## Why should I trust you with my secrets?
|
||||||
|
You shouldn't! This is [free and open-source software](https://git.eversole.co/James/Purr/src/branch/main/LICENSE) which you can run on your own hardware. Instructions for deployment are coming soon.
|
||||||
|
|
||||||
## Tech Stack?
|
## Tech Stack?
|
||||||
1. [Haskell](https://www.haskell.org) and [Scotty](https://github.com/scotty-web/scotty) backend.
|
1. [Haskell](https://www.haskell.org) and [Scotty](https://github.com/scotty-web/scotty) backend.
|
||||||
2. [HTMX](https://github.com/bigskysoftware/htmx) and [Alpine.js](https://github.com/alpinejs/alpine) for the frontend.
|
2. [HTMX](https://github.com/bigskysoftware/htmx) for the frontend.
|
||||||
3. [MongoDB](https://github.com/mongodb/mongo) database.
|
3. [MongoDB](https://github.com/mongodb/mongo) database.
|
||||||
|
|
||||||
## Why should I trust you with my passwords?
|
## Project Goals
|
||||||
You shouldn't! This is [free and open-source software](https://git.eversole.co/James/Purr/src/branch/main/LICENSE) which you can run on your own hardware. Instructions for deployment are coming soon.
|
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
|
## 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)!
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module Core.Configuration ( main ) where
|
module Core.Configuration ( main ) where
|
||||||
|
|
||||||
import Core.Types
|
import Core.Types
|
||||||
|
|
||||||
import Dhall
|
import Dhall
|
||||||
|
|
||||||
|
@ -2,11 +2,12 @@ module Core.HTTP ( app ) where
|
|||||||
|
|
||||||
import Core.Types
|
import Core.Types
|
||||||
|
|
||||||
import Core.Templates (renderIndex)
|
import Core.Templates (renderIndex, renderStyle)
|
||||||
import Feature.Sharing.HTTP as Sharing
|
import Feature.Sharing.HTTP as Sharing
|
||||||
|
|
||||||
|
import Data.Maybe (Maybe (Nothing))
|
||||||
import Network.Wai.Middleware.RequestLogger (logStdoutDev)
|
import Network.Wai.Middleware.RequestLogger (logStdoutDev)
|
||||||
import Web.Scotty.Trans
|
import Web.Scotty.Trans
|
||||||
|
|
||||||
app :: PurrApp ()
|
app :: PurrApp ()
|
||||||
app = do
|
app = do
|
||||||
@ -15,7 +16,11 @@ app = do
|
|||||||
|
|
||||||
-- Core Routes
|
-- Core Routes
|
||||||
get "/" $ do
|
get "/" $ do
|
||||||
html $ renderIndex
|
html $ renderIndex "/" Nothing
|
||||||
|
|
||||||
|
get "/style.css" $ do
|
||||||
|
setHeader "Content-Type" "text/css"
|
||||||
|
text renderStyle
|
||||||
|
|
||||||
-- Feature Routes
|
-- Feature Routes
|
||||||
Sharing.routes
|
Sharing.routes
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
{-# LANGUAGE QuasiQuotes #-}
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
|
||||||
module Core.Templates ( renderIndex ) where
|
module Core.Templates ( renderIndex, renderStyle ) where
|
||||||
|
|
||||||
import qualified Data.Text.Lazy as LT
|
|
||||||
|
|
||||||
|
import Data.Text.Lazy (Text)
|
||||||
import Database.MongoDB (Document)
|
import Database.MongoDB (Document)
|
||||||
import Text.Blaze.Html.Renderer.Text (renderHtml)
|
import Text.Blaze.Html.Renderer.Text (renderHtml)
|
||||||
import Text.Blaze.Html
|
import Text.Blaze.Html
|
||||||
|
import Text.Cassius (cassiusFile, renderCss)
|
||||||
import Text.Hamlet (shamletFile)
|
import Text.Hamlet (shamletFile)
|
||||||
|
|
||||||
import Prelude
|
import Prelude
|
||||||
|
|
||||||
renderIndex :: LT.Text
|
renderIndex :: String -> Maybe String -> Text
|
||||||
renderIndex = renderHtml ( $(shamletFile "./views/index.hamlet") )
|
renderIndex link password = renderHtml ( $(shamletFile "./views/index.hamlet") )
|
||||||
|
|
||||||
|
renderStyle :: Text
|
||||||
|
renderStyle = renderCss ( $(cassiusFile "./views/cassius/style.cassius") "/style.css" )
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
module Feature.Sharing.HTTP ( routes ) where
|
module Feature.Sharing.HTTP ( routes ) where
|
||||||
|
|
||||||
import Core.Types
|
import Core.Types
|
||||||
|
import Core.Templates (renderIndex)
|
||||||
|
|
||||||
import Feature.Sharing.Templates
|
import Feature.Sharing.Templates (renderPw)
|
||||||
import qualified Feature.Sharing.Mongo as DB
|
import qualified Feature.Sharing.Mongo as DB
|
||||||
|
|
||||||
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
|
||||||
@ -12,21 +13,21 @@ import Control.Monad.Reader (ask, lift)
|
|||||||
import Data.AesonBson (aesonify)
|
import Data.AesonBson (aesonify)
|
||||||
import Data.Bson (Document, Field (..), Value (..), lookup)
|
import Data.Bson (Document, Field (..), Value (..), lookup)
|
||||||
import Web.Scotty.Trans
|
import Web.Scotty.Trans
|
||||||
import Prelude hiding (id, lookup)
|
import Prelude hiding (lookup)
|
||||||
|
|
||||||
routes :: PurrApp ()
|
routes :: PurrApp ()
|
||||||
routes = do
|
routes = do
|
||||||
|
|
||||||
get "/pw/:id" $ do
|
get "/pw/:id" $ do
|
||||||
id <- param "id"
|
reqId <- param "id"
|
||||||
doc <- DB.findByLink id
|
doc <- DB.findByLink reqId
|
||||||
html $ renderPw id (pwLookup doc)
|
html $ renderIndex reqId (pwLookup doc)
|
||||||
|
|
||||||
post "/pw" $ do
|
post "/pw" $ do
|
||||||
id <- param "userLink"
|
reqId <- param "userLink"
|
||||||
doc <- DB.findByLink id
|
doc <- DB.findByLink reqId
|
||||||
html $ renderPw id (pwLookup doc)
|
html $ renderPw reqId (pwLookup doc)
|
||||||
|
|
||||||
pwLookup :: Maybe Document -> Maybe String
|
pwLookup :: Maybe Document -> Maybe String
|
||||||
pwLookup (Just x) = (lookup "password" x)
|
pwLookup (Just x) = lookup "password" x
|
||||||
pwLookup _ = Nothing
|
pwLookup _ = Nothing
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
module Lib ( main ) where
|
module Lib ( main ) where
|
||||||
|
|
||||||
import Core.Types
|
|
||||||
import qualified Core.Configuration as Configuration
|
import qualified Core.Configuration as Configuration
|
||||||
import qualified Core.HTTP as HTTP
|
import qualified Core.HTTP as HTTP
|
||||||
import qualified Core.Mongo as DB
|
import qualified Core.Mongo as DB
|
||||||
|
import Core.Types
|
||||||
|
|
||||||
import Control.Monad.Reader (liftIO, runReaderT)
|
import Control.Monad.Reader (liftIO, runReaderT)
|
||||||
import Database.MongoDB (MongoContext)
|
import Database.MongoDB (MongoContext)
|
||||||
import GHC.Natural (popCountNatural)
|
import GHC.Natural (popCountNatural)
|
||||||
import Web.Scotty.Trans (scottyT)
|
|
||||||
import Prelude hiding (id)
|
import Prelude hiding (id)
|
||||||
|
import Web.Scotty.Trans (scottyT)
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
dhallConf <- liftIO Configuration.main
|
dhallConf <- liftIO Configuration.main
|
||||||
dataDB <- liftIO $ DB.mongoSetup dhallConf
|
dataDB <- liftIO $ DB.mongoSetup dhallConf
|
||||||
let config = AppConfig { res = dhallConf
|
let config = AppConfig { res = dhallConf
|
||||||
, dbconn = dataDB
|
, dbconn = dataDB
|
||||||
}
|
}
|
||||||
scottyT (port dhallConf) (flip runApp config) HTTP.app where
|
scottyT (port dhallConf) (flip runApp config) HTTP.app where
|
||||||
runApp :: ConfigM a -> AppConfig -> IO a
|
runApp :: ConfigM a -> AppConfig -> IO a
|
||||||
runApp m c = runReaderT (runConfigM m) c
|
runApp m = runReaderT (runConfigM m)
|
||||||
|
@ -39,6 +39,9 @@ extra-deps:
|
|||||||
- AesonBson-0.4.1@sha256:30a4ecb39e8da94dc1e1e8945eb0d4e33a833ae4342841b3c87c56b5918a90a1,1398
|
- AesonBson-0.4.1@sha256:30a4ecb39e8da94dc1e1e8945eb0d4e33a833ae4342841b3c87c56b5918a90a1,1398
|
||||||
- bson-generic-0.0.9@sha256:ea6685daa618b2bbe6e189c33e195e812501baf42f53183eedc16f011690895a,817
|
- bson-generic-0.0.9@sha256:ea6685daa618b2bbe6e189c33e195e812501baf42f53183eedc16f011690895a,817
|
||||||
|
|
||||||
|
ghc-options:
|
||||||
|
'$everything': -haddock
|
||||||
|
|
||||||
# Override default flag values for local packages and extra-deps
|
# Override default flag values for local packages and extra-deps
|
||||||
# flags: {}
|
# flags: {}
|
||||||
|
|
||||||
|
106
views/cassius/style.cassius
Normal file
106
views/cassius/style.cassius
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
@colorOne: #222323
|
||||||
|
@colorTwo: #f0f6f0
|
||||||
|
@colorThree: #6D92AD
|
||||||
|
@colorFour: #435F5D
|
||||||
|
|
||||||
|
html
|
||||||
|
background-color: #{colorTwo}
|
||||||
|
color: #{colorOne}
|
||||||
|
|
||||||
|
body
|
||||||
|
font-family: Courier
|
||||||
|
font-size: 20px
|
||||||
|
text-align: left
|
||||||
|
|
||||||
|
p
|
||||||
|
margin: 0.4em 0 0.4em 0
|
||||||
|
|
||||||
|
::placeholder
|
||||||
|
color: #{colorOne}
|
||||||
|
opacity: 1
|
||||||
|
|
||||||
|
.asciiCat
|
||||||
|
margin: 2% 3% 0 0
|
||||||
|
font-size: 1.2vw
|
||||||
|
color: #{colorFour}
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
.content
|
||||||
|
margin: 0 15% 0 15%
|
||||||
|
|
||||||
|
.emptyReq
|
||||||
|
height: 1%
|
||||||
|
|
||||||
|
.mainButton
|
||||||
|
padding: 0.75em 1.75em
|
||||||
|
background-color: #{colorThree}
|
||||||
|
color: #{colorTwo}
|
||||||
|
border-style: none
|
||||||
|
text-decoration: none
|
||||||
|
|
||||||
|
.mainInput
|
||||||
|
font-weight: 400
|
||||||
|
font-size: 1em
|
||||||
|
width: 50%
|
||||||
|
outline: none
|
||||||
|
color: #{colorOne}
|
||||||
|
background: #{colorTwo}
|
||||||
|
margin: 1em 0
|
||||||
|
border-style: none none none 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
|
||||||
|
|
||||||
|
.mainInput:focus
|
||||||
|
border-bottom: 0.2em solid #{colorThree};
|
||||||
|
|
||||||
|
.mainInput:focus::placeholder
|
||||||
|
color: #{colorThree}
|
||||||
|
opacity: 0.5
|
||||||
|
|
||||||
|
.pwResult
|
||||||
|
font-size: 1.5em
|
||||||
|
color: #{colorFour}
|
||||||
|
|
||||||
|
.pwUtils
|
||||||
|
width: 75%
|
||||||
|
|
||||||
|
.shareNew
|
||||||
|
margin-bottom: 2em
|
||||||
|
|
||||||
|
.title
|
||||||
|
font-size: 2em
|
||||||
|
color: #{colorOne}
|
||||||
|
text-decoration: none
|
||||||
|
|
||||||
|
.titleLink
|
||||||
|
all: unset
|
||||||
|
cursor: pointer
|
||||||
|
|
||||||
|
.title h1
|
||||||
|
margin: 0.1em 0 0.3em 0
|
||||||
|
|
||||||
|
@media only screen and (max-width : 768px)
|
||||||
|
body
|
||||||
|
text-align: center
|
||||||
|
font-size: 16px
|
||||||
|
|
||||||
|
.asciiCat
|
||||||
|
display: none
|
||||||
|
|
||||||
|
.mainInput
|
||||||
|
width: 95%
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
.title
|
||||||
|
margin: 8% auto
|
||||||
|
font-size: 3em
|
||||||
|
|
||||||
|
.pwUtils
|
||||||
|
width: 100%
|
@ -1,14 +1,58 @@
|
|||||||
|
$doctype 5
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Purr
|
<title>Purr
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<script src="https://unpkg.com/htmx.org@1.7.0" integrity="sha384-EzBXYPt0/T6gxNp0nuPtLkmRpmDBbjg6WmCUZRLXBBwYYmwAUxzlSGej0ARHX0Bo" crossorigin="anonymous">
|
<script src="https://unpkg.com/htmx.org@1.7.0" integrity="sha384-EzBXYPt0/T6gxNp0nuPtLkmRpmDBbjg6WmCUZRLXBBwYYmwAUxzlSGej0ARHX0Bo" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1>Welcome to Purr!
|
<pre #asciiCat .asciiCat>
|
||||||
<p #requestedPw>No password currently requested.
|
_._ _,-'""`-._
|
||||||
<p>Ask for the <input name="userLink" type="text"/> password
|
\ (,-.`._,'( |\`-/|
|
||||||
<button hx-post="/pw"
|
\ `-.-' \ )-`( , o o)
|
||||||
hx-target="#requestedPw"
|
\ `- \`_`"'-
|
||||||
hx-swap="outerHTML"
|
<div #content .content>
|
||||||
hx-include="[name='userLink']"
|
<div #title .title>
|
||||||
/>
|
<h1>
|
||||||
Get Password!
|
<a #titleLink .titleLink href="/">Purr
|
||||||
|
<div #pwUtils .pwUtils>
|
||||||
|
<div #requestedPw .requestedPw>
|
||||||
|
$maybe pw <- password
|
||||||
|
<p>Here's the secret for /pw/#{link}:
|
||||||
|
<h2 .pwResult>#{pw}
|
||||||
|
$nothing
|
||||||
|
$if (link == "/")
|
||||||
|
<p .emptyReq>
|
||||||
|
$else
|
||||||
|
<p>No secret available at /pw/#{link}
|
||||||
|
|
||||||
|
<div #shareNew .shareNew>
|
||||||
|
<p>
|
||||||
|
<input .mainInput
|
||||||
|
name="newPw"
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter a Secret to Share"
|
||||||
|
/>
|
||||||
|
<button .mainButton
|
||||||
|
hx-post="/newpw"
|
||||||
|
hx-target="#requestedPw"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-include="[name='newPw']"
|
||||||
|
/>
|
||||||
|
Share Secret
|
||||||
|
|
||||||
|
<div #requestNew .requestNew>
|
||||||
|
<p>
|
||||||
|
<input .mainInput
|
||||||
|
name="userLink"
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter a Link to View Secret"
|
||||||
|
/>
|
||||||
|
<button .mainButton
|
||||||
|
hx-post="/pw"
|
||||||
|
hx-target="#requestedPw"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-include="[name='userLink']"
|
||||||
|
/>
|
||||||
|
Get Secret
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<div #requestedPw>
|
<div #requestedPw .requestedPw>
|
||||||
$maybe pw <- password
|
$maybe pw <- password
|
||||||
<p>Here's the password for https://purr.eversole.co/pw/#{link}: #{pw}
|
<p>Here's the secret for /pw/#{link}:
|
||||||
|
<h2 .pwResult>#{pw}
|
||||||
$nothing
|
$nothing
|
||||||
<p>No password available at https://purr.eversole.co/pw/#{link}
|
<p>No secret available at /pw/#{link}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user