Compare commits

...

10 Commits

12 changed files with 156 additions and 88 deletions

View File

@ -1,4 +1,6 @@
Copyright 2022 James Eversole (james@eversole.co)
ISC LICENSE
Copyright James Eversole (james@eversole.co)
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

View File

@ -1,7 +1,7 @@
cabal-version: 1.12
name: Purr
version: 0.3.0
version: 0.5.0
description: https://git.eversole.co/Purr
author: James Eversole
maintainer: james@eversole.co
@ -16,8 +16,7 @@ extra-source-files:
executable Purr
main-is: Main.hs
hs-source-dirs:
app
, src
src
default-extensions:
ConstraintKinds
DeriveGeneric
@ -64,5 +63,4 @@ executable Purr
Feature.Sharing.HTTP
Feature.Sharing.SQLite
Feature.Sharing.Templates
Lib
default-language: Haskell2010

51
README
View File

@ -1,51 +0,0 @@
purr
-----
https://purr.eversole.co
a work-in-progress web application offering customizable password generation
and time-limited sharing of secrets.
TECH STACK
- Haskell and Scotty backend
- HTMX frontend
- SQLite database
GOALS
- Generate sufficiently memorable but secure passwords for use with accounts
that don't offer better authentication methods.
- Share text secrets with others without disclosing the secret in the
message itself.
- Provide a minimal and clean interface for generating and sharing passwords.
- Maintain a clean and organized codebase that can be extended to include more
utilities than originally anticipated.
- Be really cute compared to the competition.
WHY TRUST YOU?
You shouldn't. This is free and open-source software which you can run on your
own hardware.
DEPLOYMENT
Only Nix build instructions targeting containers are provided below,
but this project can be built and run without containers or Nix using Cabal.
- Clone this repository
- Build the container image (with flakes enabled): `nix build .#purr-container`
- Load the container image
- podman load -i result
- Use the provided docker stack example to deploy the container if desired
- docker stack deploy -c docker-stack.yml purr
DEVELOPMENT & SUPPORT
Per the permissive ISC license, you are free to do what you wish with this software. I hold
no liability for any defects and no guarantees are made to its usability.
Copyright James Eversole (james@eversole.co)

79
README.md Normal file
View File

@ -0,0 +1,79 @@
# purr
[purr.eversole.co](https://purr.eversole.co)
a simple web application offering customizable password generation
and time-limited sharing of secrets.
## TECH STACK
- [Haskell](https://www.haskell.org)
- [Scotty](https://hackage.haskell.org/package/scotty)
- [HTMX](https://htmx.org)
- [SQLite](https://www.sqlite.org)
## GOALS
- Provide a minimal and clean interface for generating and sharing passwords.
- Generate sufficiently memorable but secure passwords for use with accounts
that don't offer better authentication methods.
- Share text secrets with others without disclosing the secret in the
message itself.
- Maintain a clean and organized codebase that can be extended to include more
utilities than originally anticipated.
## WHY TRUST PURR.EVERSOLE.CO?
You shouldn't. This is free and open-source software which you can run on your
own hardware!
## DEPLOYMENT
Only Nix build instructions are provided below.
### No Containers
1) Clone this repository
2) Build the application (with flakes enabled): `nix build '.#'`
3) Set the environment variables
- File: `cp examples/.env.example ./.env; $EDITOR ./.env`
- If you want to set them in a different way, you already know how.
4) Run the application: `./result/bin/Purr`
### Containers
1) Clone this repository
2) Build the container image (with flakes enabled): `nix build .#purr-container`
3) Load the container image: `podman load -i result`
4) Pick option 5, 6, or use your favorite process to manage containers and ENV
5) NixOS configuration:
```
virtualisation.oci-containers.containers.purr = {
image = "purr";
ports = [ "${PURR_EXTERNAL_PORT}:3000" ];
volumes = [
"/PATH/TO/PURR/data:/app/data"
];
environment = {
PURRNOFILE = "true";
ENVIRONMENT = "production";
APPLICATIONHOST = "localhost";
APPLICATIONPORT = "3000";
DATADIR = "/app/";
LINKLENGTH = "24";
ADMINEMAIL = "${YOUR_EMAIL}";
};
};
```
6) Docker Stack
1) Copy the docker-stack.yml example and edit as needed.
1) `cp examples/docker-stack.yml ./; $EDITOR ./docker-stack.yml`
2) Set environment variables:
- `cp examples/.env.example ./.env; $EDITOR ./.env`
- Or set them in the docker-stack.yml environment declaration.
3) Deploy: `docker stack deploy -c docker-stack.yml purr`
## DEVELOPMENT & SUPPORT
Per the permissive ISC license, you are free to do what you wish with this
software. No guarantees are made to its usability, security, or functionality.
Copyright James Eversole (james@eversole.co)

View File

@ -1,7 +0,0 @@
module Main where
import Prelude
import qualified Lib
main :: IO ()
main = Lib.main

File diff suppressed because one or more lines are too long

View File

@ -1,21 +1,48 @@
module Core.Configuration where
module Core.Configuration ( adminEmail, appPort
, confLinkLength, dataPath, dbPath
, encKey, getRuntimeEnvironment
, keyFileInit, init) where
import qualified Data.ByteString as B
import Control.Monad (mapM)
import Configuration.Dotenv
import Core.Types
import Crypto.Saltine.Core.SecretBox (newKey)
import Crypto.Saltine.Class (encode)
import Configuration.Dotenv
import Prelude hiding (init)
import System.Directory (doesFileExist)
import System.Environment (getEnv, lookupEnv)
-- Make the dotenv file configuration available if PURRNOFILE is not present
main :: IO ()
main = do
envFile <- lookupEnv "PURRNOFILE"
case envFile of
Nothing -> loadFile defaultConfig
_ -> putStrLn "Not using dotenv file"
-- Load environment variables from dotenv file if required
init :: IO ()
init = do
reqEnvLookup <- getRequiredEnv
if (Nothing `elem` reqEnvLookup)
then checkEnvFile requiredEnvVars
else pure ()
where
getRequiredEnv :: IO [Maybe String]
getRequiredEnv = mapM (\s -> lookupEnv s) requiredEnvVars
checkEnvFile :: [String] -> IO ()
checkEnvFile requiredEnv = do
dotEnvExists <- doesFileExist "./.env"
if dotEnvExists
then do
loadFile defaultConfig
fromEnvFile <- getRequiredEnv
if (Nothing `elem` fromEnvFile)
then error $ missingEnvMsg requiredEnv
else pure ()
else error $ "Cannot find .env file in application directory.\n"
++ missingEnvMsg requiredEnv
missingEnvMsg :: [String] -> String
missingEnvMsg required =
"Missing required environment variable(s).\n"
++ "All required environment variables:\n"
++ unlines required
-- Check if an encryption key exists on the filesystem and create one if not
keyFileInit :: IO ()
@ -39,6 +66,9 @@ encKey = do
adminEmail :: IO String
adminEmail = getEnv "ADMINEMAIL"
getRuntimeEnvironment :: IO String
getRuntimeEnvironment = getEnv "ENVIRONMENT"
appPort :: IO String
appPort = getEnv "APPLICATIONPORT"
@ -50,3 +80,8 @@ dbPath = "data/Purr.sqlite"
confLinkLength :: IO String
confLinkLength = getEnv "LINKLENGTH"
requiredEnvVars :: [String]
requiredEnvVars = [ "ADMINEMAIL", "APPLICATIONHOST", "APPLICATIONPORT"
, "DATADIR", "ENVIRONMENT", "LINKLENGTH"
]

View File

@ -1,22 +1,28 @@
module Core.HTTP ( app ) where
import Core.Configuration (adminEmail, confLinkLength)
import Core.Configuration ( adminEmail
, confLinkLength
, getRuntimeEnvironment)
import Core.Types
import Core.Templates (renderIndex, renderStyle)
import Feature.Generation.HTTP as Generation
import Feature.Sharing.HTTP as Sharing
import Control.Monad (void)
import Control.Monad.Trans (liftIO)
import Data.Maybe (Maybe (Nothing))
import Network.Wai.Middleware.RequestLogger (logStdoutDev)
import Network.Wai.Middleware.RequestLogger (logStdout, logStdoutDev)
import Network.Wai.Middleware.Static
import Web.Scotty
app :: PurrApp ()
app = do
app :: String -> PurrApp ()
app env = do
-- Middleware that are processed on every request
middleware logStdoutDev
case env of
"production" -> middleware logStdout
"prod" -> middleware logStdout
_ -> middleware logStdoutDev
middleware $ staticPolicy (noDots >-> addBase "data/assets/public")
-- Core Routes

View File

@ -6,12 +6,13 @@ import Core.Types
import Data.ByteString as B
import Database.SQLite.Simple
import Database.SQLite.Simple.FromRow
import Prelude hiding (init)
import qualified Data.Text as T
-- Set up SQLite database table when Purr starts if it doesn't already exist
main :: IO ()
main = do
init :: IO ()
init = do
conn <- open dbPath
execute_ conn
"CREATE TABLE IF NOT EXISTS pws\

View File

@ -1,4 +1,4 @@
module Lib ( main ) where
module Main ( main ) where
import qualified Core.Configuration as Configuration
import qualified Core.HTTP as HTTP
@ -16,16 +16,16 @@ main = do
-- Initialize the RNG used for sodium encryption (Saltine library)
sodiumInit
{- Initialize our dotenv configuration which reads from a .env configuration
file unless the PURRNOFILE env var exists already. -}
Configuration.main
if any required variables are missing from the provided environment -}
Configuration.init
{- Initialize the encryption key file if it doesn't
exist yet or use the existing key -}
Configuration.keyFileInit
{- Initialize our database by ensuring the SQLite file exists
and has tables setup as the application expects -}
DB.main
DB.init
{- Get the configured port to run on and start the Scotty webserver app
defined in HTTP.app -}
appPortStr <- Configuration.appPort
let appPort = read appPortStr :: Int
scotty appPort HTTP.app
port <- Configuration.appPort
env <- Configuration.getRuntimeEnvironment
scotty (read port) (HTTP.app env)

View File

@ -70,6 +70,7 @@ input[type=number]
padding: 1em 0.5em 0.5em 1.5em
background-color: #{colorTwo}
box-shadow: 8px 8px 12px #ccc
overflow-x: scroll
.generators .numberInput
background-color: #{colorTwo}
@ -276,6 +277,9 @@ input[type=number]
.mainButton
width: 80%
.generators
font-size: 12px
.genButton
width: 80%

View File

@ -4,14 +4,14 @@ $doctype 5
<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">
<script src="/htmx.js" integrity="sha384-wS5l5IKJBvK6sPTKa2WZ1js3d947pvWXbPJ1OmWfEuxLgeHcEbjUUA5i9V5ZkpCw">
<script src="/copyButtons.js" integrity="sha384-eNQZr7QWPQmi/EWi4lVVFOavm+Eibmh7iDvDptgE0j5fI3xycLssbDBZbKphi8pk">
<link rel="stylesheet" href="/style.css">
<body>
<header>
<a href="https://git.eversole.co/purr">
<a href="https://git.eversole.co">
made with &#9829;
|
<a href="mailto:#{email}">contact