passman/app/UI.hs

273 lines
7.6 KiB
Haskell
Raw Normal View History

2018-12-26 14:38:42 -05:00
{-
passman
2021-01-05 21:08:41 -05:00
Copyright (C) 2018-2021 Jonathan Lamothe
2020-12-14 22:41:24 -05:00
<jonathan@jlamothe.net>
2018-12-26 14:38:42 -05:00
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 #-}
2018-12-26 14:38:42 -05:00
module UI (getMasterPass, mainMenu) where
import Control.Monad (when)
import Control.Monad.Trans.Class (lift)
import qualified Control.Monad.Trans.State as S
import Lens.Micro (over, set, (^.))
import Lens.Micro.Extras (view)
2018-12-26 14:38:42 -05:00
import System.Console.HCL
( Request
, prompt
, reqDefault
, reqFail
, reqIf
, reqInt
, reqIO
, reqPassword
, reqResp
, required
, runRequest
2018-12-26 14:38:42 -05:00
)
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."
2018-12-26 14:38:42 -05:00
reqFail
else return p1
mainMenu :: S.StateT Status IO ()
mainMenu =
menu "Main Menu"
2020-03-20 17:36:04 -04:00
[ ( "view/edit a password", viewEditMenu )
, ( "add a password", addPassword )
2018-12-26 14:38:42 -05:00
, ( "change master password", changeMasterPass )
2019-01-01 21:02:52 -05:00
, ( "save manually", save >> mainMenu )
2018-12-26 14:38:42 -05:00
, ( "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
2018-12-26 14:38:42 -05:00
svc <- req $ prompt "service name: " reqResp
2018-12-31 15:27:36 -05:00
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
2018-12-31 15:27:36 -05:00
else mainMenu)
(do
d <- buildData
setService svc d
showPass svc
2018-12-31 15:27:36 -05:00
servMenu svc)
2018-12-26 14:38:42 -05:00
viewEditMenu :: S.StateT Status IO ()
viewEditMenu = menu "View/Edit Password"
[ ( "search services", searchServ )
, ( "list services", listServ )
, ( "cancel", mainMenu )
]
2018-12-27 14:26:44 -05:00
changeMasterPass :: S.StateT Status IO ()
changeMasterPass = do
2019-01-01 21:15:09 -05:00
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)
2018-12-27 14:26:44 -05:00
mainMenu
lockSession :: S.StateT Status IO ()
lockSession = do
lift $ putStrLn "\nThe session is locked."
2018-12-27 14:26:44 -05:00
pass <- S.gets $ view masterPass
x <- req $ prompt "master password: " reqPassword
if x == pass
then mainMenu
else lockSession
2018-12-27 14:26:44 -05:00
quit :: S.StateT Status IO ()
2018-12-29 20:15:59 -05:00
quit = save
2018-12-27 14:26:44 -05:00
buildData :: S.StateT Status IO PWData
buildData = do
d <- run newPWData
req $ reqIf (confirm "Would you like to change the password policy?")
2018-12-27 14:26:44 -05:00
(do
let p = d^.pwPolicy
p' <- reqDefault (editPolicy p) p
return $ set pwPolicy p' d)
2018-12-27 14:26:44 -05:00
(return d)
2018-12-26 14:38:42 -05:00
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."
2018-12-26 14:38:42 -05:00
mainMenu
2018-12-26 15:03:01 -05:00
[x] -> servMenu x
2018-12-26 14:38:42 -05:00
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" $
2018-12-26 15:03:01 -05:00
map (\x -> (x, servMenu x)) xs ++
2018-12-26 14:38:42 -05:00
[("(cancel)", mainMenu)]
2018-12-26 15:03:01 -05:00
servMenu :: String -> S.StateT Status IO ()
servMenu x = menu x
2018-12-31 14:29:44 -05:00
[ ( "show password", showPass x >> servMenu x )
, ( "edit password", editPassMenu x )
, ( "remove service", removeServ x )
2018-12-31 15:27:36 -05:00
, ( "rename service", renameServ x )
2018-12-31 14:29:44 -05:00
, ( "back", mainMenu )
2018-12-26 15:03:01 -05:00
]
editPassMenu :: String -> S.StateT Status IO ()
editPassMenu x = menu (x ++ " : Edit Password")
2018-12-27 15:56:25 -05:00
[ ( "generate new password", changeSalt x )
, ( "edit password policy", doEditPolicy x )
, ( "back", servMenu x )
2018-12-26 14:38:42 -05:00
]
2018-12-31 14:29:44 -05:00
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
2018-12-31 15:27:36 -05:00
removeServ' x
2018-12-31 14:29:44 -05:00
mainMenu
else servMenu x
2018-12-31 15:27:36 -05:00
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
2018-12-26 15:43:13 -05:00
changeSalt :: String -> S.StateT Status IO ()
2018-12-27 15:56:25 -05:00
changeSalt x = withService x mainMenu $ \d -> do
salt <- run newPWSalt
setService x $ set pwSalt salt d
2018-12-27 15:56:25 -05:00
showPass x
editPassMenu x
2018-12-27 15:56:25 -05:00
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
2018-12-27 15:56:25 -05:00
editPassMenu x
2018-12-26 15:43:13 -05:00
2018-12-26 14:38:42 -05:00
showPass :: String -> S.StateT Status IO ()
showPass x = do
lift $ putStrLn ""
2018-12-27 15:56:25 -05:00
withService x
(lift $ putStrLn "The service could not be found in the database.") $
2018-12-27 15:56:25 -05:00
\d -> do
mp <- S.gets $ view masterPass
lift $ putStrLn $ case pwGenerate mp d of
Nothing -> "The password data were not valid."
2018-12-26 14:38:42 -05:00
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
2018-12-26 14:38:42 -05:00
where
edit l v t p = do
v' <- reqDefault
2018-12-26 14:38:42 -05:00
(prompt ("new " ++ l ++ " (default " ++ show v ++ "): ") reqInt) v
return $ set t v' p
2018-12-26 14:38:42 -05:00
special p = do
reqIO $ putStrLn $ "Special characters are currently " ++
2018-12-26 14:38:42 -05:00
(case p^.pwSpecial of
Nothing -> "not "
Just _ -> "") ++ "allowed."
reqIf (confirm "Would you like to allow special characters?")
2018-12-26 14:38:42 -05:00
(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