----------------------------------------------------------------------------- -- | -- Module : XMonad.Prompt.Pass -- Copyright : (c) 2014 Igor Babuschkin, Antoine R. Dumont -- License : BSD3-style (see LICENSE) -- -- Maintainer : Antoine R. Dumont -- Stability : unstable -- Portability : unportable -- -- This module provides 3 to ease passwords manipulation (generate, read, remove): -- -- - one to lookup passwords in the password-storage. -- -- - one to generate a password for a given password label that the user inputs. -- -- - one to delete a stored password for a given password label that the user inputs. -- -- All those prompts benefit from the completion system provided by the module . -- -- The password store is setuped through an environment variable PASSWORD_STORE_DIR. -- If this is set, use the content of the variable. -- Otherwise, the password store is located on user's home @$HOME\/.password-store@. -- -- -- Source: -- -- - The password storage implementation is . -- -- - Inspired from -- ----------------------------------------------------------------------------- module XMonad.Prompt.Pass ( -- * Usages -- $usages passPrompt , passGeneratePrompt , passRemovePrompt ) where import Control.Monad (liftM) import XMonad.Core import XMonad.Prompt ( XPrompt , showXPrompt , commandToComplete , nextCompletion , getNextCompletion , XPConfig , mkXPrompt , mkComplFunFromList) import System.Directory (getHomeDirectory) import System.FilePath (takeExtension, dropExtension, combine) import System.Posix.Env (getEnv) import XMonad.Util.Run (runProcessWithInput) -- $usages -- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: -- -- > import XMonad.Prompt.Pass -- -- Then add a keybinding for 'passPrompt', 'passGeneratePrompt' or 'passRemovePrompt': -- -- > , ((modMask x , xK_p) , passPrompt xpconfig) -- > , ((modMask x .|. controlMask, xK_p) , passGeneratePrompt xpconfig) -- > , ((modMask x .|. controlMask .|. shiftMask, xK_p), passRemovePrompt xpconfig) -- -- For detailed instructions on: -- -- - editing your key bindings, see "XMonad.Doc.Extending#Editing_key_bindings". -- -- - how to setup the password storage, see -- type PromptLabel = String data Pass = Pass PromptLabel instance XPrompt Pass where showXPrompt (Pass prompt) = prompt ++ ": " commandToComplete _ c = c nextCompletion _ = getNextCompletion -- | Default password store folder in $HOME/.password-store -- passwordStoreFolderDefault :: String -> String passwordStoreFolderDefault home = combine home ".password-store" -- | Compute the password store's location. -- Use the PASSWORD_STORE_DIR environment variable to set the password store. -- If empty, return the password store located in user's home. -- passwordStoreFolder :: IO String passwordStoreFolder = getEnv "PASSWORD_STORE_DIR" >>= computePasswordStoreDir where computePasswordStoreDir Nothing = liftM passwordStoreFolderDefault getHomeDirectory computePasswordStoreDir (Just storeDir) = return storeDir -- | A pass prompt factory -- mkPassPrompt :: PromptLabel -> (String -> X ()) -> XPConfig -> X () mkPassPrompt promptLabel passwordFunction xpconfig = do passwords <- io (passwordStoreFolder >>= getPasswords) mkXPrompt (Pass promptLabel) xpconfig (mkComplFunFromList passwords) passwordFunction -- | A prompt to retrieve a password from a given entry. -- passPrompt :: XPConfig -> X () passPrompt = mkPassPrompt "Select password" selectPassword -- | A prompt to generate a password for a given entry. -- This can be used to override an already stored entry. -- (Beware that no confirmation is asked) -- passGeneratePrompt :: XPConfig -> X () passGeneratePrompt = mkPassPrompt "Generate password" generatePassword -- | A prompt to remove a password for a given entry. -- (Beware that no confirmation is asked) -- passRemovePrompt :: XPConfig -> X () passRemovePrompt = mkPassPrompt "Remove password" removePassword -- | Select a password. -- selectPassword :: String -> X () selectPassword passLabel = spawn $ "pass --clip " ++ passLabel -- | Generate a 30 characters password for a given entry. -- If the entry already exists, it is updated with a new password. -- generatePassword :: String -> X () generatePassword passLabel = spawn $ "pass generate --force " ++ passLabel ++ " 30" -- | Remove a password stored for a given entry. -- removePassword :: String -> X () removePassword passLabel = spawn $ "pass rm --force " ++ passLabel -- | Retrieve the list of passwords from the password storage 'passwordStoreDir getPasswords :: FilePath -> IO [String] getPasswords passwordStoreDir = do files <- runProcessWithInput "find" [ passwordStoreDir, "-type", "f", "-name", "*.gpg", "-printf", "%P\n"] [] return $ map removeGpgExtension $ lines files removeGpgExtension :: String -> String removeGpgExtension file | takeExtension file == ".gpg" = dropExtension file | otherwise = file