36 Commits

Author SHA1 Message Date
233a559aaf Don't crash if the database doesn't exist 2024-09-10 20:12:46 -04:00
86278db578 load database on start 2024-09-10 20:00:24 -04:00
a872fcfd6c added password database to AppState 2024-09-10 19:03:19 -04:00
d049c40b7a handle events for password entry form 2024-09-07 20:41:43 -04:00
ffce0d6a1c render the master password form 2024-09-07 18:30:25 -04:00
2b427789d2 defined data structure for master password form 2024-09-07 16:57:20 -04:00
a3f405d9c5 make lenses for AppState 2024-09-07 15:40:46 -04:00
76d2600fcf initialize random number generator 2024-09-07 15:40:46 -04:00
11870423ed use brick 2024-09-07 15:00:17 -04:00
4b057272d3 updated copyright to reflect current year 2024-09-07 14:43:22 -04:00
3e661e3b24 use LTS 22.33 2024-09-07 14:36:48 -04:00
f65a478fb5 stripped out frontend 2024-09-07 14:26:15 -04:00
85920c26a0 minor refactor to app/Util.hs to make hlint happy 2023-05-04 14:47:20 -04:00
8be90e9822 add blank line before prompting for service name to search for 2023-05-04 14:24:55 -04:00
bb85081380 removed copyright years from individual source files
it remains intact in the README.md and package.yml files
2023-05-02 19:40:22 -04:00
f423d202ce fixed installation instructions 2023-05-02 17:45:48 -04:00
82f2c6c5fb version 0.3.1.1 2023-05-02 17:29:31 -04:00
d7da4b2924 use LTS 20.19 resolver 2023-05-02 17:27:58 -04:00
0267ce8792 removed GitHub references 2023-05-02 16:48:44 -04:00
d0d80223f7 added bug reports link 2021-11-14 03:47:53 -05:00
180af04891 updated description
The old one still pointed to GitHub.
2021-11-13 23:54:06 -05:00
a048e0ad8b moved priject to codeberg 2021-11-13 23:50:08 -05:00
d3a54f19b9 updated cabal file 2021-10-25 15:51:19 -04:00
b9d52070f1 version 0.3.1 2021-05-14 13:55:01 -04:00
6585d63385 allow user to specify temporary alternate master password 2021-05-14 13:46:52 -04:00
258ebf29fe max version for transformers package
...because I'm an idiot.
2021-05-09 14:41:20 -04:00
1d6fbb5f40 version 0.3.0.2
more dependency versions
2021-05-09 13:36:27 -04:00
08d2827613 version 0.3.0.1
- updated to latest lts
- specified dependency versions
2021-05-09 12:57:38 -04:00
4ac3d37913 version 0.3.0 2021-01-05 21:26:12 -05:00
4be38eb87a updated ChangeLog 2021-01-05 21:25:29 -05:00
807e09a5ae switched from lens package to microlens 2021-01-05 21:22:41 -05:00
c5cdde8f73 removed unnecessary import 2021-01-05 21:22:05 -05:00
d87ccc4346 updated copyright
modified in 2021
2021-01-05 21:08:41 -05:00
2d70a9e284 updated to more recent snapshot 2021-01-05 10:28:18 -05:00
97a5ff4c92 updated email address 2020-12-14 22:41:24 -05:00
df5f0a4334 updated copyright 2020-12-14 22:10:14 -05:00
31 changed files with 549 additions and 556 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,2 @@
.stack-work/
passman.cabal
*~

View File

@@ -1,5 +1,34 @@
# Changelog for passman
## current
- minor UI tweak
- minor refactoring
## 0.3.1.1
- updated documentation to on longer mention GitHub
- updated resolver to LTS 20.19
## 0.3.1
- set maximum version of transformers package
- allow user to specifiy a temporary master password to retrieve a password
## 0.3.0.2
- more dependency versions
## 0.3.0.1
- updated to latest stackage LTS
- specified versions for dependencies
## 0.3.0
- updated to more recent LTS snapshot
- use microlens instead of lens
## 0.2.1
- refactoring

View File

@@ -1,7 +1,7 @@
# passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) 2018-2024 Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -38,19 +38,14 @@ This package uses [Haskell Stack](https://haskellstack.org). Please
refer to [their
website](https://docs.haskellstack.org/en/stable/README/#how-to-install)
for instructions on installing Haskell Stack. Once you have done
this, you can simply enter the command `stack install passman` in the
terminal to install passman.
this, you can simply enter the command `stack install` in the terminal
from this directory to install passman.
## GitHub
## Codeberg
The most recent version of passman can be found on GitHub at
<https://github.com/jlamothe/passman>.
The most recent version of passman can be found on Codeberg at
<https://codeberg.org/jlamothe/passman>.
## Pull Requests
Pull requests are welcome, but should be made to the `dev` branch.
## Donations
Bitcoin donations are accepted (but not required) at:
18hqEsXCinyauDp6smPUEVuscjDdasTKvr

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -20,37 +20,15 @@ License along with this program. If not, see
-}
module Main where
module Main (main) where
import Control.Monad (mapM_)
import Control.Monad.Trans.State as S
import System.Console.HCL (Request, reqIO, runRequest)
import System.EasyFile
( createDirectoryIfMissing
, getAppUserDataDirectory
, (</>)
)
import System.Random (getStdGen)
import Brick (defaultMain)
import Control.Monad (void)
import Types
import UI
import Util
import Password.App
import Password.App.Types
main :: IO ()
main = runRequest setup >>= mapM_ (S.evalStateT mainMenu)
setup :: Request Status
setup = do
g <- reqIO getStdGen
p <- getDBPath
db <- loadFrom p
pw <- getMasterPass
return $ Status g pw p db
getDBPath :: Request FilePath
getDBPath = reqIO $ do
path <- getAppUserDataDirectory "passman"
createDirectoryIfMissing True path
return $ path </> "database.json"
main = void $ mkInitialState >>= defaultMain passmanApp
--jl

View File

@@ -1,52 +0,0 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE TemplateHaskell #-}
module Types (Status (Status), gen, dbPath, masterPass, database) where
import Control.Lens (makeLenses, set, (^.))
import System.Random (RandomGen (next, split), StdGen)
import Password
data Status = Status
{ _gen :: StdGen
, _masterPass :: String
, _dbPath :: FilePath
, _database :: PWDatabase
}
makeLenses ''Status
instance RandomGen Status where
next s = (x, s') where
(x, g') = next g
s' = set gen g' s
g = s^.gen
split s = (s1, s2) where
s1 = set gen g1 s
s2 = set gen g2 s
(g1, g2) = split g
g = s^.gen
--jl

271
app/UI.hs
View File

@@ -1,271 +0,0 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE LambdaCase #-}
module UI (getMasterPass, mainMenu) where
import Control.Lens (over, set, view, (^.))
import Control.Monad (when)
import Control.Monad.Trans.Class (lift)
import qualified Control.Monad.Trans.State as S
import System.Console.HCL
( Request
, prompt
, reqDefault
, reqFail
, reqIf
, reqInt
, reqIO
, reqPassword
, reqResp
, required
, runRequest
)
import Password
import Types
import Util
getMasterPass :: Request String
getMasterPass = do
p1 <- required $ prompt "master password: " reqPassword
p2 <- required $ prompt "confirm master password: " reqPassword
if p1 /= p2
then do
reqIO $ putStrLn "The passwords you entered do not match."
reqFail
else return p1
mainMenu :: S.StateT Status IO ()
mainMenu =
menu "Main Menu"
[ ( "view/edit a password", viewEditMenu )
, ( "add a password", addPassword )
, ( "change master password", changeMasterPass )
, ( "save manually", save >> mainMenu )
, ( "lock session", lockSession )
, ( "quit", quit )
]
addPassword :: S.StateT Status IO ()
addPassword = do
pass <- S.gets (^.masterPass)
lift (runRequest $ prompt "confirm master password: " reqPassword)
>>= \case
Nothing -> mainMenu
Just chkPass
| pass == chkPass -> addPassword'
| otherwise -> do
lift $ putStrLn "Incorrect master password."
mainMenu
addPassword' :: S.StateT Status IO ()
addPassword' = do
svc <- req $ prompt "service name: " reqResp
ifServExists svc
(do
edit <- req (confirm $
"The service already exists in the database.\n" ++
"Would you like to edit it?")
if edit
then servMenu svc
else mainMenu)
(do
d <- buildData
setService svc d
showPass svc
servMenu svc)
viewEditMenu :: S.StateT Status IO ()
viewEditMenu = menu "View/Edit Password"
[ ( "search services", searchServ )
, ( "list services", listServ )
, ( "cancel", mainMenu )
]
changeMasterPass :: S.StateT Status IO ()
changeMasterPass = do
req (confirm $
"\nWARNING: Changing your master password will change all of your saved passwords.\n" ++
"Are you sure you would like to proceed?") >>= flip when
(do
oldP <- S.gets $ view masterPass
newP <- req $ reqDefault getMasterPass oldP
S.modify $ set masterPass newP)
mainMenu
lockSession :: S.StateT Status IO ()
lockSession = do
lift $ putStrLn "\nThe session is locked."
pass <- S.gets $ view masterPass
x <- req $ prompt "master password: " reqPassword
if x == pass
then mainMenu
else lockSession
quit :: S.StateT Status IO ()
quit = save
buildData :: S.StateT Status IO PWData
buildData = do
d <- run newPWData
req $ reqIf (confirm "Would you like to change the password policy?")
(do
let p = d^.pwPolicy
p' <- reqDefault (editPolicy p) p
return $ set pwPolicy p' d)
(return d)
searchServ :: S.StateT Status IO ()
searchServ = do
svc <- req $ prompt "service name: " reqResp
db <- S.gets $ view database
case pwSearch svc db of
[] -> do
lift $ putStrLn "\nThe service could not be found in the database."
mainMenu
[x] -> servMenu x
xs -> selectServ xs
listServ :: S.StateT Status IO ()
listServ = S.gets (view database) >>= selectServ . pwSearch ""
selectServ :: [String] -> S.StateT Status IO ()
selectServ xs = menu "Select Service" $
map (\x -> (x, servMenu x)) xs ++
[("(cancel)", mainMenu)]
servMenu :: String -> S.StateT Status IO ()
servMenu x = menu x
[ ( "show password", showPass x >> servMenu x )
, ( "edit password", editPassMenu x )
, ( "remove service", removeServ x )
, ( "rename service", renameServ x )
, ( "back", mainMenu )
]
editPassMenu :: String -> S.StateT Status IO ()
editPassMenu x = menu (x ++ " : Edit Password")
[ ( "generate new password", changeSalt x )
, ( "edit password policy", doEditPolicy x )
, ( "back", servMenu x )
]
removeServ :: String -> S.StateT Status IO ()
removeServ x = do
go <- req $ confirm $
"Are you sure you want to delete the password for " ++ x ++ "?"
if go
then do
removeServ' x
mainMenu
else servMenu x
removeServ' :: String -> S.StateT Status IO ()
removeServ' = S.modify . over database . pwRemoveService
renameServ :: String -> S.StateT Status IO ()
renameServ x = do
y <- req $ prompt "new service name: " reqResp
if x == y
then servMenu x
else ifServExists y
(do
overwrite <- req $ confirm $
y ++ " already exists.\n" ++
"Would you like to overwrite it?"
if overwrite
then renameServ' x y
else servMenu x)
(renameServ' x y)
renameServ' :: String -> String -> S.StateT Status IO ()
renameServ' x y = withService x mainMenu $ \d -> do
removeServ' x
setService y d
servMenu y
changeSalt :: String -> S.StateT Status IO ()
changeSalt x = withService x mainMenu $ \d -> do
salt <- run newPWSalt
setService x $ set pwSalt salt d
showPass x
editPassMenu x
doEditPolicy :: String -> S.StateT Status IO ()
doEditPolicy x = withService x mainMenu $ \d -> do
let p = d^.pwPolicy
p' <- req $ reqDefault (editPolicy p) p
setService x $ set pwPolicy p' d
showPass x
editPassMenu x
showPass :: String -> S.StateT Status IO ()
showPass x = do
lift $ putStrLn ""
withService x
(lift $ putStrLn "The service could not be found in the database.") $
\d -> do
mp <- S.gets $ view masterPass
lift $ putStrLn $ case pwGenerate mp d of
Nothing -> "The password data were not valid."
Just pw -> "password for " ++ x ++ ": " ++ pw
-- TODO: refactor this monstrosity
editPolicy :: PWPolicy -> Request PWPolicy
editPolicy policy = do
p <-
edit "length" (policy^.pwLength) pwLength policy >>=
edit "min upper case" (policy^.pwUpper) pwUpper >>=
edit "min lower case" (policy^.pwLower) pwLower >>=
edit "min digits" (policy^.pwDigits) pwDigits >>=
special
if validatePWPolicy p
then return p
else do
reqIO $ putStrLn $
"\nThe password policy you entered is invalid\n." ++
"It will not be changed."
reqFail
where
edit l v t p = do
v' <- reqDefault
(prompt ("new " ++ l ++ " (default " ++ show v ++ "): ") reqInt) v
return $ set t v' p
special p = do
reqIO $ putStrLn $ "Special characters are currently " ++
(case p^.pwSpecial of
Nothing -> "not "
Just _ -> "") ++ "allowed."
reqIf (confirm "Would you like to allow special characters?")
(case p^.pwSpecial of
Nothing -> do
x <- required $ prompt "min special chars: " reqInt
return $ set pwSpecial (Just x) p
Just x -> edit "min special chars" x (pwSpecial.traverse) p)
(return $ set pwSpecial Nothing p)
--jl

View File

@@ -1,117 +0,0 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-}
module Util
( menu
, run
, withService
, ifServExists
, setService
, req
, tryReq
, confirm
, loadFrom
, save
) where
import Control.Lens (over, view)
import Control.Monad (join)
import Control.Monad.Trans.Class (lift)
import qualified Control.Monad.Trans.State as S
import Data.Aeson (decodeFileStrict, encodeFile)
import Data.Maybe (fromJust, fromMaybe)
import System.Console.HCL
( Request
, prompt
, reqAgree
, reqChar
, reqDefault
, reqIO
, reqMenu
, required
, runRequest
)
import Password
import Types
menu
:: String
-> [(String, S.StateT Status IO a)]
-> S.StateT Status IO a
menu title = reqState . prompt ("\n*** " ++ title ++ " ***") .
reqMenu . map menuItem
menuItem :: (String, a) -> (String, Request a)
menuItem (str, x) = (str, return x)
reqState :: Request (S.StateT s IO a) -> S.StateT s IO a
reqState = join . req
run :: Monad m => (s -> (a, s)) -> S.StateT s m a
run f = S.StateT $ return . f
withService
:: String
-> S.StateT Status IO a
-> (PWData -> S.StateT Status IO a)
-> S.StateT Status IO a
withService srv fb act = do
db <- S.gets $ view database
maybe fb act $ pwGetService srv db
ifServExists
:: String
-> S.StateT Status IO a
-> S.StateT Status IO a
-> S.StateT Status IO a
ifServExists s x y = do
db <- S.gets $ view database
if pwHasService s db
then x
else y
setService :: String -> PWData -> S.StateT Status IO ()
setService k = S.modify . over database . pwSetService k
req :: Request a -> S.StateT s IO a
req = lift . fmap fromJust . runRequest . required
tryReq :: Request a -> S.StateT s IO (Maybe a)
tryReq = lift . runRequest
confirm :: String -> Request Bool
confirm x = prompt (x ++ " (y/n): ") $ reqAgree Nothing $ fmap return reqChar
loadFrom :: FilePath -> Request PWDatabase
loadFrom path = reqDefault
(reqIO (decodeFileStrict path))
(Just newPWDatabase) >>= (return . fromMaybe newPWDatabase)
save :: S.StateT Status IO ()
save = do
path <- S.gets $ view dbPath
db <- S.gets $ view database
lift $ encodeFile path db
--jl

View File

@@ -1,10 +1,11 @@
name: passman
version: 0.2.1
github: "jlamothe/passman"
license: LGPL-3
version: 0.3.1.1
license: LGPL-3.0-or-later
author: "Jonathan Lamothe"
maintainer: "jlamothe1980@gmail.com"
copyright: "(C) 2018, 2019 Jonathan Lamothe"
maintainer: "jonathan@jlamothe.net"
copyright: "(C) 2018-2024 Jonathan Lamothe"
homepage: https://codeberg.org/jlamothe/passman
bug-reports: https://codeberg.org/jlamothe/passman/issues
extra-source-files:
- README.md
@@ -17,15 +18,21 @@ category: Security
# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
description: Please see the README on GitHub at <https://github.com/jlamothe/passman#readme>
description: a simple password manager - see README.md for details
dependencies:
- base >= 4.7 && < 5
- aeson
- bytestring
- containers
- lens
- random
- aeson >= 2.1.2.1 && < 2.2
- bytestring >= 0.11.4.0 && < 0.12
- containers >= 0.6.2.1 && < 0.7
- microlens >= 0.4.11.2 && < 0.5
- microlens-th >= 0.4.3.6 && < 0.5
- microlens-mtl >= 0.2.0.3 && < 0.3
- random >=1.2.1.1 && < 1.3
- brick >= 2.1.1 && < 2.2
- vty >= 6.1 && < 6.2
- mtl >= 2.3.1 && < 2.4
- easy-file >= 0.2.5 && < 0.3
ghc-options:
- -Wall
@@ -33,10 +40,10 @@ ghc-options:
library:
source-dirs: src
dependencies:
- base16-bytestring
- base64-bytestring
- SHA
- text
- base16-bytestring >= 1.0.2.0 && < 1.1
- base64-bytestring >= 1.2.1.0 && < 1.3
- SHA >= 1.6.4.4 && < 1.7
- text >= 2.0.2 && < 2.1
executables:
passman:
@@ -48,9 +55,6 @@ executables:
- -with-rtsopts=-N
dependencies:
- passman
- easy-file >= 0.2.2 && < 0.3
- HCL >= 1.7.1 && < 2
- transformers
tests:
passman-test:

123
passman.cabal Normal file
View File

@@ -0,0 +1,123 @@
cabal-version: 2.2
-- This file has been generated from package.yaml by hpack version 0.37.0.
--
-- see: https://github.com/sol/hpack
--
-- hash: d18a8e1efd32ff2d20b0b1f5ac8186be5242411bd72a6a017fd9f97d401a9836
name: passman
version: 0.3.1.1
synopsis: a simple password manager
description: a simple password manager - see README.md for details
category: Security
homepage: https://codeberg.org/jlamothe/passman
bug-reports: https://codeberg.org/jlamothe/passman/issues
author: Jonathan Lamothe
maintainer: jonathan@jlamothe.net
copyright: (C) 2018-2024 Jonathan Lamothe
license: LGPL-3.0-or-later
license-file: LICENSE
build-type: Simple
extra-source-files:
README.md
ChangeLog.md
library
exposed-modules:
Password
Password.App
Password.App.Draw
Password.App.Event
Password.App.Types
other-modules:
Paths_passman
autogen-modules:
Paths_passman
hs-source-dirs:
src
ghc-options: -Wall
build-depends:
SHA >=1.6.4.4 && <1.7
, aeson >=2.1.2.1 && <2.2
, base >=4.7 && <5
, base16-bytestring >=1.0.2.0 && <1.1
, base64-bytestring >=1.2.1.0 && <1.3
, brick >=2.1.1 && <2.2
, bytestring >=0.11.4.0 && <0.12
, containers >=0.6.2.1 && <0.7
, easy-file >=0.2.5 && <0.3
, microlens >=0.4.11.2 && <0.5
, microlens-mtl >=0.2.0.3 && <0.3
, microlens-th >=0.4.3.6 && <0.5
, mtl >=2.3.1 && <2.4
, random >=1.2.1.1 && <1.3
, text >=2.0.2 && <2.1
, vty ==6.1.*
default-language: Haskell2010
executable passman
main-is: Main.hs
other-modules:
Paths_passman
autogen-modules:
Paths_passman
hs-source-dirs:
app
ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
build-depends:
aeson >=2.1.2.1 && <2.2
, base >=4.7 && <5
, brick >=2.1.1 && <2.2
, bytestring >=0.11.4.0 && <0.12
, containers >=0.6.2.1 && <0.7
, easy-file >=0.2.5 && <0.3
, microlens >=0.4.11.2 && <0.5
, microlens-mtl >=0.2.0.3 && <0.3
, microlens-th >=0.4.3.6 && <0.5
, mtl >=2.3.1 && <2.4
, passman
, random >=1.2.1.1 && <1.3
, vty ==6.1.*
default-language: Haskell2010
test-suite passman-test
type: exitcode-stdio-1.0
main-is: Spec.hs
other-modules:
Spec.JSON
Spec.NewPWData
Spec.NewPWDatabase
Spec.NewPWPolicy
Spec.NewPWSalt
Spec.PWGenerate
Spec.PWGetService
Spec.PWHasService
Spec.PWRemoveService
Spec.PWSearch
Spec.PWSetService
Spec.ValidatePWData
Spec.ValidatePWDatabase
Spec.ValidatePWPolicy
Paths_passman
autogen-modules:
Paths_passman
hs-source-dirs:
test
ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
build-depends:
HUnit
, aeson >=2.1.2.1 && <2.2
, base >=4.7 && <5
, brick >=2.1.1 && <2.2
, bytestring >=0.11.4.0 && <0.12
, containers >=0.6.2.1 && <0.7
, easy-file >=0.2.5 && <0.3
, microlens >=0.4.11.2 && <0.5
, microlens-mtl >=0.2.0.3 && <0.3
, microlens-th >=0.4.3.6 && <0.5
, mtl >=2.3.1 && <2.4
, passman
, random >=1.2.1.1 && <1.3
, vty ==6.1.*
default-language: Haskell2010

View File

@@ -2,9 +2,9 @@
Module: Password
Description: a simple password manager
Copyright: (C) 2018, 2019 Jonathan Lamothe
Copyright: (C) Jonathan Lamothe
License: LGPLv3 (or later)
Maintainer: jlamothe1980@gmail.com
Maintainer: jonathan@jlamothe.net
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -47,7 +47,6 @@ module Password (
pwHasService, pwSetService, pwGetService, pwRemoveService, pwSearch
) where
import Control.Lens (makeLenses, over, set, to, (^.))
import Data.Aeson
( FromJSON (parseJSON)
, ToJSON (toJSON)
@@ -64,9 +63,12 @@ import qualified Data.ByteString.Base16.Lazy as B16
import qualified Data.ByteString.Base64.Lazy as B64
import Data.Char (isUpper, isLower, isDigit, isAlphaNum, toLower)
import Data.Digest.Pure.SHA
import Data.Either (fromRight)
import qualified Data.Map as M
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import Lens.Micro (over, set, to, (^.))
import Lens.Micro.TH (makeLenses)
import System.Random (RandomGen, randoms, split)
-- | a mapping of service names to password data
@@ -340,7 +342,7 @@ mkSeed :: String -> PWData ->B.ByteString
mkSeed pw d = toUTF8 pw `B.append` (d^.pwSalt.to runPWSalt)
mkHash :: B.ByteString -> B.ByteString
mkHash = fst . B16.decode . toUTF8 . show . sha256
mkHash = fromRight "" . B16.decode . toUTF8 . show . sha256
nextPolicy :: Char -> PWPolicy -> PWPolicy
nextPolicy x p = over pwLength pred $

48
src/Password/App.hs Normal file
View File

@@ -0,0 +1,48 @@
{-|
Module: Password.App
Description: the application frontend
Copyright: (C) Jonathan Lamothe
License: LGPLv3 (or later)
Maintainer: jonathan@jlamothe.net
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-}
module Password.App (passmanApp) where
import Brick
( App (..)
, attrMap
, showFirstCursor
, style
)
import Password.App.Draw
import Password.App.Event
import Password.App.Types
-- | The main application
passmanApp :: App AppState () ResName
passmanApp = App
{ appDraw = drawFunc
, appChooseCursor = showFirstCursor
, appHandleEvent = eventHandler
, appStartEvent = loadDatabase
, appAttrMap = const $ attrMap (style 0) []
}
--jl

46
src/Password/App/Draw.hs Normal file
View File

@@ -0,0 +1,46 @@
{-|
Module: Password.App.Draw
Description: widget drawing functions
Copyright: (C) Jonathan Lamothe
License: LGPLv3 (or later)
Maintainer: jonathan@jlamothe.net
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-}
module Password.App.Draw (drawFunc) where
import Brick (Widget, txt, vBox)
import Brick.Forms (renderForm)
import Lens.Micro ((^.))
import Password.App.Types
-- | Renders the application view
drawFunc :: AppState -> [Widget ResName]
drawFunc s = case s^.appMode of
InitMode is -> drawPassForm is
drawPassForm :: InitState -> [Widget ResName]
drawPassForm is =
[ vBox
[ renderForm $ is^.setPassForm
, txt $ is^.spfError
]
]
--jl

106
src/Password/App/Event.hs Normal file
View File

@@ -0,0 +1,106 @@
{-|
Module: Password.App.Event
Description: event handling functions
Copyright: (C) Jonathan Lamothe
License: LGPLv3 (or later)
Maintainer: jonathan@jlamothe.net
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE LambdaCase, OverloadedStrings #-}
module Password.App.Event (eventHandler, loadDatabase) where
import Brick (BrickEvent (VtyEvent), EventM, halt)
import Brick.Forms (handleFormEvent)
import Brick.Keybindings
( Binding
, KeyDispatcher
, ctrl
, handleKey
, keyDispatcher
, keyEvents
, newKeyConfig
, onEvent
)
import Control.Monad (unless)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.State.Class (gets, put)
import Data.Aeson (decodeFileStrict)
import Graphics.Vty.Input.Events (Event (EvKey))
import Lens.Micro ((^.))
import Lens.Micro.Mtl (zoom)
import System.EasyFile
( createDirectoryIfMissing
, doesFileExist
, getAppUserDataDirectory
, (</>)
)
import Password.App.Types
dbFile :: String
dbFile = "database.json"
data KEventID = QuitKE deriving (Eq, Ord, Show)
-- | The main event handler
eventHandler :: BrickEvent ResName () -> EventM ResName AppState ()
eventHandler e@(VtyEvent (EvKey k m)) = do
disp <- gets getKeyDispatcher
handleKey disp k m >>= flip unless (fallbackHandler e)
eventHandler e = fallbackHandler e
loadDatabase :: EventM ResName AppState ()
loadDatabase = zoom database $ liftIO
( do
dir <- mkAppDir
let fn = dir </> dbFile
doesFileExist fn >>= \case
True -> decodeFileStrict fn
False -> return Nothing
) >>= mapM_ put
fallbackHandler :: BrickEvent ResName () -> EventM ResName AppState ()
fallbackHandler e = gets (^.appMode) >>= \case
InitMode _ -> zoom (appMode.initState.setPassForm) $
handleFormEvent e
getKeyDispatcher
:: AppState
-> KeyDispatcher KEventID (EventM ResName AppState)
getKeyDispatcher s = either (error "can't build dispatcher") id $
keyDispatcher conf handlers
where
conf = newKeyConfig ke bs []
ke = keyEvents []
bs = keyBindingsFor s
handlers =
[ onEvent QuitKE "Quit Application" halt
]
keyBindingsFor :: AppState -> [(KEventID, [Binding])]
keyBindingsFor = const [(QuitKE, [ctrl 'c'])]
mkAppDir :: IO FilePath
mkAppDir = do
path <- getAppUserDataDirectory "passman"
createDirectoryIfMissing True path
return path
--jl

111
src/Password/App/Types.hs Normal file
View File

@@ -0,0 +1,111 @@
{-|
Module: Password.App.Types
Description: data types used by the application
Copyright: (C) Jonathan Lamothe
License: LGPLv3 (or later)
Maintainer: jonathan@jlamothe.net
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}
module Password.App.Types (
-- * Types
AppState (..),
AppMode (..),
InitState (..),
ResName (..),
-- * Lenses
-- ** AppState
randGen,
database,
appMode,
-- ** AppMode
initState,
-- ** InitState
setPassForm,
spfError,
-- * Constructors
mkInitialState,
) where
import Brick (txt, (<+>))
import Brick.Forms (Form, editPasswordField, newForm, (@@=))
import Control.Monad.IO.Class (MonadIO)
import Data.Text (Text)
import Lens.Micro (_1, _2)
import Lens.Micro.TH (makeLenses)
import System.Random (StdGen, initStdGen)
import Password
-- | The application state
data AppState = AppState
{ _randGen :: StdGen
-- ^ The random number generator
, _database :: PWDatabase
-- ^ The password database
, _appMode :: AppMode
-- ^ The current operating mode
}
-- | The applicaiton's mode
newtype AppMode = InitMode
{ _initState :: InitState
-- ^ Initialization state
}
-- | Application initialization state
data InitState = InitState
{ _setPassForm :: Form (Text, Text) () ResName
-- ^ password form
, _spfError :: Text
-- ^ error message
}
-- | Resource identifier
data ResName
= PassField
| ConfField
deriving (Eq, Ord, Show)
concat <$> mapM makeLenses
[ ''AppState
, ''AppMode
, ''InitState
]
-- | Builds an initial state
mkInitialState :: MonadIO m => m AppState
mkInitialState = AppState
<$> initStdGen
<*> return newPWDatabase
<*> return (InitMode newInitState)
-- | New `InitState` value
newInitState :: InitState
newInitState = InitState
( newForm
[ (txt "Master password: " <+>) @@= editPasswordField _1 PassField
, (txt "Confirm password: " <+>) @@= editPasswordField _2 ConfField
]
("", "")
)
""
--jl

View File

@@ -17,7 +17,7 @@
#
# resolver: ./custom-snapshot.yaml
# resolver: https://example.com/snapshots/2018-01-01.yaml
resolver: lts-12.21
resolver: lts-22.33
# User packages to be built.
# Various formats can be used as shown in the example below.
@@ -37,8 +37,7 @@ packages:
# Dependency packages to be pulled from upstream that are not in the resolver
# using the same syntax as the packages field.
# (e.g., acme-missiles-0.3)
extra-deps:
- HCL-1.7.1@sha256:7bc617fbc9ba4b1f9c10d9b3e195042c1f031629f86d08253eec87660492d646
# extra-deps:
# Override default flag values for local packages and extra-deps
# flags: {}

View File

@@ -3,17 +3,10 @@
# For more information, please see the documentation at:
# https://docs.haskellstack.org/en/stable/lock_files
packages:
- completed:
hackage: HCL-1.7.1@sha256:7bc617fbc9ba4b1f9c10d9b3e195042c1f031629f86d08253eec87660492d646,1627
pantry-tree:
size: 1223
sha256: 5dd9d6b52e85caae6e47d8686be92873e00de14791c4e2c2753492ff288454fe
original:
hackage: HCL-1.7.1@sha256:7bc617fbc9ba4b1f9c10d9b3e195042c1f031629f86d08253eec87660492d646
packages: []
snapshots:
- completed:
size: 508406
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/12/21.yaml
sha256: 609dd00c32f59e11bb333b9113d9d2e54269627de1268cbb3cc576af8c7b6237
original: lts-12.21
sha256: 098936027eaa1ef14e2b8eb39d9933a973894bb70a68684a1bbf00730249879b
size: 720001
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/33.yaml
original: lts-22.33

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -22,7 +22,7 @@ License along with this program. If not, see
module Spec.NewPWData (tests) where
import Control.Lens ((^.))
import Lens.Micro ((^.))
import System.Random (mkStdGen, StdGen)
import Test.HUnit (Test (..), (~?=))

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -22,7 +22,7 @@ License along with this program. If not, see
module Spec.NewPWPolicy (tests) where
import Control.Lens ((^.))
import Lens.Micro ((^.))
import Test.HUnit (Test(..), (~?=))
import Password

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -22,8 +22,8 @@ License along with this program. If not, see
module Spec.PWGenerate (tests) where
import Control.Lens (set, (^.))
import Data.Maybe (fromJust)
import Lens.Micro (set, (^.))
import System.Random (mkStdGen, StdGen)
import Test.HUnit
(Test (..)

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -22,8 +22,8 @@ License along with this program. If not, see
module Spec.ValidatePWData (tests) where
import Control.Lens (set)
import qualified Data.ByteString.Lazy as B
import Lens.Micro (set)
import System.Random (mkStdGen, StdGen)
import Test.HUnit (Test (..), (~?=))

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -22,8 +22,8 @@ License along with this program. If not, see
module Spec.ValidatePWDatabase (tests) where
import Control.Lens (set)
import qualified Data.Map as M
import Lens.Micro (set)
import System.Random (mkStdGen, StdGen)
import Test.HUnit (Test (..), (~?=))

View File

@@ -1,8 +1,8 @@
{-
passman
Copyright (C) 2018, 2019 Jonathan Lamothe
<jlamothe1980@gmail.com>
Copyright (C) Jonathan Lamothe
<jonathan@jlamothe.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -22,7 +22,7 @@ License along with this program. If not, see
module Spec.ValidatePWPolicy (tests) where
import Control.Lens (set)
import Lens.Micro (set)
import Test.HUnit (Test(..), (~?=))
import Password