From 310dfd47ebac115dd258ca6c02a2cf5041096147 Mon Sep 17 00:00:00 2001 From: Martin Potier Date: Thu, 16 May 2019 09:03:35 +0300 Subject: [PATCH] Adding password manager for my own good --- lib/KeyBindings.hs | 4 ++++ lib/Password.hs | 56 ++++++++++++++++++++++++++++++++++++++++++++++ xmonad.hs | 1 + 3 files changed, 61 insertions(+) create mode 100644 lib/Password.hs diff --git a/lib/KeyBindings.hs b/lib/KeyBindings.hs index 1ad45c2..04d1f17 100644 --- a/lib/KeyBindings.hs +++ b/lib/KeyBindings.hs @@ -24,6 +24,9 @@ import qualified Solarized as S import qualified Scratchpad as R import qualified XMonad.StackSet as W +-- Custom (in libs) +import Password (passPrompt) + modify :: XConfig l -> XConfig l modify conf = conf { modMask = mod4Mask -- Use the "Win" key for the mod key @@ -57,6 +60,7 @@ modify conf = conf , ("M-p n", switchProjectPrompt promptConfig) , ("M-p r", renameProjectPrompt promptConfig) , ("M-p s", shellPrompt promptConfig) + , ("M-d", passPrompt promptConfig) -- Scratchpads , ("M-p p", namedScratchpadAction R.pads "htop") ] diff --git a/lib/Password.hs b/lib/Password.hs new file mode 100644 index 0000000..f6fda22 --- /dev/null +++ b/lib/Password.hs @@ -0,0 +1,56 @@ +-- Initially from +-- https://github.com/MasseR/xmonad-masser/blob/master/src/XMonad/Password.hs + +module Password (passGeneratePrompt, passPrompt) where + +import Control.Monad.Trans (liftIO) +import Data.Function (on) +import Data.List (foldl', scanl', sort, sortBy) +import System.Directory (getHomeDirectory) +import System.FilePath.Posix (dropExtension, takeExtension, ()) +import System.Posix.Env (getEnv) +import XMonad.Core +import XMonad.Prompt +import XMonad.Util.Run (runProcessWithInput) + +newtype Pass = Pass { passLabel :: String } + +-- Rosetta code levenshtein +levenshtein :: String -> String -> Int +levenshtein s1 s2 = last $ foldl' transform [0..length s1] s2 + where + transform [] _ = [] + transform ns@(n:ns1) c = scanl' calc (n+1) $ zip3 s1 ns ns1 + where + calc z (c1, x, y) = minimum [y+1, z+1, x + (fromEnum (c1 /= c) * 2)] + +instance XPrompt Pass where + showXPrompt p = passLabel p <> ": " + commandToComplete _ = id + nextCompletion _ = getNextCompletion + +passGeneratePrompt :: XPConfig -> X () +passGeneratePrompt _ = return () -- Not implemented + +passPrompt :: XPConfig -> X () +passPrompt = mkPassPrompt "Select password" selectPassword + +mkPassPrompt :: String -> (String -> X ()) -> XPConfig -> X () +mkPassPrompt label f conf = do + -- I'm just sorting here, but could use some kind of fuzzy matching instead, but it requires a bit more effort + passwords <- sort <$> liftIO getPasswords + -- Other change, use infixof instead of prefixof + mkXPrompt (Pass label) conf (\input -> pure (sortBy (compare `on` levenshtein input) . filter (consumes input) $ passwords)) f + where + consumes [] _ = True -- everything consumed + consumes (_:_) [] = False -- all not consumed + consumes (a:xs) (a':ys) | a == a' = consumes xs ys + | otherwise = consumes (a:xs) ys + getPasswords = do + passwordStoreDir <- ( "pass") <$> getHomeDirectory + files <- runProcessWithInput "find" [ passwordStoreDir, "-type", "f", "-name", "*.gpg", "-printf", "%p\n"] [] + return . lines $ files + +selectPassword :: String -> X () +selectPassword pass = spawn $ "gpg --decrypt " ++ pass ++ " | /home/eeva/.nix-profile/bin/copy" + diff --git a/xmonad.hs b/xmonad.hs index 666c051..9e3dccc 100755 --- a/xmonad.hs +++ b/xmonad.hs @@ -17,6 +17,7 @@ import XMonad.Layout.ResizableTile (ResizableTall(..)) import XMonad.Layout.Spacing import XMonad.Layout.ToggleLayouts (toggleLayouts) + import XMonad.Util.EZConfig import qualified Solarized as S