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:
		
							
								
								
									
										17
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								README.md
									
									
									
									
									
								
							| @ -1,19 +1,24 @@ | ||||
| # Purr - Password Generation and Sharing | ||||
| # Purr - Password Generation and Secret Sharing | ||||
|    | ||||
| 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? | ||||
| 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. | ||||
|  | ||||
| ## 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? | ||||
| 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. | ||||
|  | ||||
| ## Why should I trust you with my passwords? | ||||
| 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. | ||||
| ## 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)!   | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| module Core.Configuration ( main ) where | ||||
|  | ||||
| import Core.Types  | ||||
| import Core.Types | ||||
|  | ||||
| import Dhall | ||||
|  | ||||
|  | ||||
| @ -2,11 +2,12 @@ module Core.HTTP ( app ) where | ||||
|  | ||||
| import Core.Types | ||||
|  | ||||
| import Core.Templates       (renderIndex) | ||||
| import Core.Templates       (renderIndex, renderStyle) | ||||
| import Feature.Sharing.HTTP as Sharing | ||||
|  | ||||
| import Data.Maybe                           (Maybe (Nothing)) | ||||
| import Network.Wai.Middleware.RequestLogger (logStdoutDev) | ||||
| import Web.Scotty.Trans  | ||||
| import Web.Scotty.Trans | ||||
|  | ||||
| app :: PurrApp () | ||||
| app = do | ||||
| @ -15,7 +16,11 @@ app = do | ||||
|  | ||||
|   -- Core Routes | ||||
|   get "/" $ do | ||||
|     html $ renderIndex | ||||
|     html $ renderIndex "/" Nothing | ||||
|  | ||||
|   get "/style.css" $ do | ||||
|     setHeader "Content-Type" "text/css" | ||||
|     text renderStyle | ||||
|  | ||||
|   -- Feature Routes | ||||
|   Sharing.routes | ||||
|  | ||||
| @ -1,16 +1,19 @@ | ||||
| {-# LANGUAGE QuasiQuotes #-} | ||||
| {-# LANGUAGE TemplateHaskell #-} | ||||
|  | ||||
| module Core.Templates ( renderIndex ) where | ||||
|  | ||||
| import qualified Data.Text.Lazy                as LT | ||||
| module Core.Templates ( renderIndex, renderStyle ) where | ||||
|  | ||||
| import Data.Text.Lazy                (Text) | ||||
| import Database.MongoDB              (Document) | ||||
| import Text.Blaze.Html.Renderer.Text (renderHtml) | ||||
| import Text.Blaze.Html | ||||
| import Text.Cassius                  (cassiusFile, renderCss) | ||||
| import Text.Hamlet                   (shamletFile) | ||||
|  | ||||
| import Prelude  | ||||
|  | ||||
| renderIndex :: LT.Text | ||||
| renderIndex = renderHtml ( $(shamletFile "./views/index.hamlet") ) | ||||
| renderIndex :: String -> Maybe String -> Text | ||||
| 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 | ||||
|  | ||||
| import           Core.Types | ||||
| import Core.Types | ||||
| import Core.Templates (renderIndex) | ||||
|  | ||||
| import           Feature.Sharing.Templates | ||||
| import qualified Feature.Sharing.Mongo as DB | ||||
| import           Feature.Sharing.Templates (renderPw) | ||||
| import qualified Feature.Sharing.Mongo     as DB | ||||
|  | ||||
| import qualified Data.Text      as T | ||||
| import qualified Data.Text.Lazy as LT | ||||
| @ -12,21 +13,21 @@ import Control.Monad.Reader (ask, lift) | ||||
| import Data.AesonBson       (aesonify) | ||||
| import Data.Bson            (Document, Field (..), Value (..), lookup) | ||||
| import Web.Scotty.Trans  | ||||
| import Prelude              hiding (id, lookup) | ||||
| import Prelude              hiding (lookup) | ||||
|  | ||||
| routes :: PurrApp () | ||||
| routes = do  | ||||
|  | ||||
|   get "/pw/:id" $ do | ||||
|     id  <- param "id" | ||||
|     doc <- DB.findByLink id | ||||
|     html $ renderPw id (pwLookup doc) | ||||
|     reqId  <- param "id" | ||||
|     doc    <- DB.findByLink reqId | ||||
|     html    $ renderIndex reqId (pwLookup doc) | ||||
|  | ||||
|   post "/pw" $ do | ||||
|     id  <- param "userLink" | ||||
|     doc <- DB.findByLink id | ||||
|     html $ renderPw id (pwLookup doc) | ||||
|     reqId  <- param "userLink" | ||||
|     doc    <- DB.findByLink reqId | ||||
|     html    $ renderPw reqId (pwLookup doc) | ||||
|  | ||||
| pwLookup :: Maybe Document -> Maybe String | ||||
| pwLookup (Just x) = (lookup "password" x) | ||||
| pwLookup _ = Nothing | ||||
| pwLookup (Just x) = lookup "password" x | ||||
| pwLookup _        = Nothing | ||||
|  | ||||
| @ -1,23 +1,23 @@ | ||||
| module Lib ( main ) where | ||||
|  | ||||
| import           Core.Types | ||||
| import qualified Core.Configuration as Configuration | ||||
| import qualified Core.HTTP          as HTTP | ||||
| import qualified Core.Mongo         as DB | ||||
| import           Core.Types | ||||
|  | ||||
| import Control.Monad.Reader (liftIO, runReaderT) | ||||
| import Database.MongoDB     (MongoContext) | ||||
| import GHC.Natural          (popCountNatural) | ||||
| import Web.Scotty.Trans     (scottyT) | ||||
| import Prelude              hiding (id) | ||||
| import Web.Scotty.Trans     (scottyT) | ||||
|  | ||||
| main :: IO () | ||||
| main = do | ||||
|   dhallConf <- liftIO Configuration.main | ||||
|   dataDB    <- liftIO $ DB.mongoSetup dhallConf | ||||
|   let config = AppConfig { res    = dhallConf | ||||
|                          , dbconn = dataDB  | ||||
|                          , dbconn = dataDB | ||||
|                          } | ||||
|   scottyT (port dhallConf) (flip runApp config) HTTP.app where | ||||
|     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 | ||||
| - bson-generic-0.0.9@sha256:ea6685daa618b2bbe6e189c33e195e812501baf42f53183eedc16f011690895a,817 | ||||
|  | ||||
| ghc-options: | ||||
|   '$everything': -haddock | ||||
|  | ||||
| # Override default flag values for local packages and extra-deps | ||||
| # 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> | ||||
|   <head> | ||||
|     <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"> | ||||
|     <link rel="stylesheet" href="/style.css"> | ||||
|  | ||||
|   <body> | ||||
|     <h1>Welcome to Purr! | ||||
|     <p #requestedPw>No password currently requested. | ||||
|     <p>Ask for the <input name="userLink" type="text"/> password | ||||
|     <button hx-post="/pw"  | ||||
|       hx-target="#requestedPw"  | ||||
|       hx-swap="outerHTML"  | ||||
|       hx-include="[name='userLink']" | ||||
|     /> | ||||
|       Get Password! | ||||
|     <pre #asciiCat .asciiCat> | ||||
|       _._     _,-'""`-._ | ||||
|       \   (,-.`._,'(       |\`-/| | ||||
|       \       `-.-' \ )-`( , o o) | ||||
|       \             `-    \`_`"'- | ||||
|     <div #content .content> | ||||
|       <div #title .title> | ||||
|         <h1> | ||||
|           <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 | ||||
|     <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 | ||||
|     <p>No password available at https://purr.eversole.co/pw/#{link} | ||||
|     <p>No secret available at /pw/#{link} | ||||
|  | ||||
		Reference in New Issue
	
	Block a user