diff --git a/scat.cabal b/scat.cabal index cbe8642..1de06cf 100644 --- a/scat.cabal +++ b/scat.cabal @@ -10,7 +10,7 @@ name: scat -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change -version: 0.2.1.0 +version: 1.0.0.0 -- A short (one-line) description of the package. synopsis: Generates unique passwords for various websites from a single password. @@ -63,5 +63,5 @@ executable scat other-modules: Scat.Builder, Scat.Schemas, Scat.Options, Scat.Utils.Permutation, Paths_scat -- Other library packages from which modules are imported. - build-depends: base ==4.5.*, scrypt ==0.3.*, bytestring ==0.9.*, optparse-applicative ==0.5.*, mtl ==2.1.*, vector ==0.10.* + build-depends: base ==4.5.*, scrypt ==0.3.*, bytestring ==0.9.*, optparse-applicative ==0.5.*, mtl ==2.1.*, vector ==0.10.*, ansi-terminal ==0.6.* \ No newline at end of file diff --git a/src/Scat.hs b/src/Scat.hs index 557c17f..5542515 100644 --- a/src/Scat.hs +++ b/src/Scat.hs @@ -9,6 +9,7 @@ import Data.ByteString (unpack) import qualified Data.ByteString.Char8 as C import System.IO import System.Exit +import System.Console.ANSI import Control.Exception import Control.Monad.Reader import Crypto.Scrypt @@ -17,19 +18,34 @@ import Scat.Schemas import Scat.Builder import Scat.Options --- | Generates the seed integer given a key and a password. +-- | Generates the seed integer given a service, a password and a code. scatter :: ByteString -> ByteString -> ByteString -> Integer scatter k pw c = foldr (\ n s -> fromIntegral n + 256 * s) 0 $ - unpack $ unHash $ scrypt params (Salt k) (Pass $ pw <> c) - where - Just params = scryptParams 14 8 50 + unpack $ unHash $ scrypt params (Salt k) (Pass $ pw <> c) + where + Just params = scryptParams 14 8 50 -- | Main type of the program. type Scat a = ReaderT Options IO a +-- | Input visibility. +data Visibility = Shown | Hidden | Erased + +-- | Should the input be echoed? +shouldShow :: Visibility -> Bool +shouldShow Shown = True +shouldShow Hidden = False +shouldShow Erased = True + +-- | Should the input be erased afterwards? +shouldErase :: Visibility -> Bool +shouldErase Shown = False +shouldErase Hidden = False +shouldErase Erased = True + {- | Generates a password, given a input password, - a key (category, website, etc.), - and a password `Schema`. + a service name (category, website, etc.), + a code, and a password `Schema`. The parameters are specified as command line arguments. The password can be read from @stdin@ if not already provided. -} @@ -39,7 +55,7 @@ main = getOptions >>= runReaderT scat -- | Main program. scat :: Scat () scat = do - k <- getKey + k <- getService s <- getSchema pw <- getPassword c <- getCode @@ -69,32 +85,41 @@ getPassword = do -- Retrieve the password from the arguments. Just st -> return $ C.pack st where - getPass = askPassword False "Password: " + getPass = prompt Hidden "Password: " getPassConfirm = do - a <- askPassword False "Password: " - b <- askPassword False "Confirm: " + a <- prompt Hidden "Password: " + b <- prompt Hidden "Confirm: " if a == b then return a else do printVerbose "Passwords do not match, please retry.\n" getPassConfirm --- | Ask a password on the command line, with the specified prompt. -askPassword :: Bool -> String -> Scat ByteString -askPassword echo str = do +-- | Ask a for input on the command line, with the specified prompt. +prompt :: Visibility -> String -> Scat ByteString +prompt vis str = do printVerbose str old <- liftIO $ hGetEcho stdin pw <- liftIO $ bracket_ - (hSetEcho stdin echo) + (hSetEcho stdin $ shouldShow vis) (hSetEcho stdin old) C.getLine - unless echo $ printVerbose "\n" + when (shouldErase vis) $ liftIO $ do + cursorUpLine 1 + cursorForward $ length str + clearFromCursorToScreenEnd + cursorDownLine 1 + unless (shouldShow vis) $ printVerbose "\n" return pw --- | Gets the key. -getKey :: Scat ByteString -getKey = fmap (C.pack . key) ask +-- | Gets the service. +getService :: Scat ByteString +getService = do + mk <- fmap service ask + case mk of + Just k -> return $ C.pack k + Nothing -> prompt Shown "Service: " -- | Gets the code. getCode :: Scat ByteString @@ -105,7 +130,7 @@ getCode = do mc <- fmap code ask case mc of Just st -> return $ C.pack st - Nothing -> askPassword True "Code: " + Nothing -> prompt Erased "Code: " else return "" -- | Gets the schema to generate the new password. diff --git a/src/Scat/Options.hs b/src/Scat/Options.hs index a296803..63f1a23 100644 --- a/src/Scat/Options.hs +++ b/src/Scat/Options.hs @@ -7,7 +7,7 @@ module Scat.Options -- * Accessors , password - , key + , service , useCode , code , schema @@ -19,43 +19,33 @@ module Scat.Options ) where import Data.Monoid -import Data.Maybe (isJust) import Options.Applicative -- | All program options. data Options = Options { password :: Maybe String -- ^ Password, optionally provided. - , key :: String - -- ^ Key or category for the password. - , useCode_ :: Bool + , service :: Maybe String + -- ^ Service for which to generate the password. + , useCode :: Bool -- ^ Indicates if extra code should be used. , code :: Maybe String - -- ^ Extra code. Activates code usage. + -- ^ Extra code. , schema :: String -- ^ Name of the schema to use. - , verbose_ :: Bool + , verbose :: Bool -- ^ Verbosity. If false, do not print anything but the generated password. , confirm :: Bool - -- ^ Indicates if the password must be confirmed. Activates verbosity. + -- ^ Indicates if the password must be confirmed. } --- | Indicates if extra code should be used. -useCode :: Options -> Bool -useCode opts = useCode_ opts || isJust (code opts) - -{- | Verbosity. If false, do not print anything but the generated password. - True when @--verbose@ or @--confirmation@ are specified. -} -verbose :: Options -> Bool -verbose opts = verbose_ opts || confirm opts - -- | Parses the arguments from the command line. getOptions :: IO Options getOptions = execParser opts where opts = info (helper <*> options) (fullDesc - <> progDesc "Safely generate passwords derived from a unique password." + <> progDesc "Safely generate passwords derived from a unique password and code." <> header "scat - a password scatterer") -- | Option parser. @@ -66,18 +56,18 @@ options = Options <> long "password" <> help "The password" <> metavar "PASSWORD")) - <*> strOption - (short 'k' - <> long "key" - <> help "Key associated (website, email address, ...) (mandatory)" - <> metavar "KEY") - <*> switch - (short 'x' - <> long "extra" - <> help "Indicates extra code should be used.") + <*> optional (strOption + (short 'S' + <> long "service" + <> help "Service associated (website, email address, ...)" + <> metavar "SERVICE")) + <*> flag True False + (long "nocode" + <> help "Indicates that extra code should be not be used") <*> optional - (strOption (long "code" - <> help "Extra code." + (strOption (short 'x' + <> long "code" + <> help "The extra code to use" <> metavar "CODE")) <*> strOption (short 's' @@ -86,10 +76,9 @@ options = Options <> metavar "SCHEMA" <> value "safe" <> showDefault) - <*> switch - (short 'v' - <> long "verbose" - <> help "Prints instructions and information") + <*> flag True False + (long "silent" + <> help "Do not print anything but the generated password") <*> switch (short 'c' <> long "confirmation"