diff --git a/Readme.md b/Readme.md index bd0c71b..9b821e4 100644 --- a/Readme.md +++ b/Readme.md @@ -172,16 +172,24 @@ And then just run the command as usual: restic backup ``` -### Autofill +### Autotype based Autofill [goldwarden_autofill.webm](https://github.com/quexten/goldwarden/assets/11866552/6ac7cdc2-0cd7-42fd-9fd0-cfff26e2ceee) -The autofill feature is a bit experimental. It autotypes the password via uinput. This needs a keyboardlayout to map the letters to +You can bind this to a hotkey in your desktop environment (i.e i3/sway config file, Gnome custom shortcuts, etc). + +#### XDG-RemoteDesktop-Portal + +By default, the remote desktop portal is used. As long as your desktop environment handle this (KDE and Gnome do, wlroots does not yet) +this enables autotyping without having to modify permissions. +`goldwarden autofill` + +#### (Legacy) Uinput +If your desktop environment does not implement the remotedesktop portal, your only other option is uinput based autotype. This requires your user +to have access to the input group to use uinput to autotype. This needs a keyboardlayout to map the letters to keycodes. Currently supported are qwerty and dvorak. `goldwarden autofill --layout qwerty` `goldwarden autofill --layout dvorak` -You can bind this to a hotkey in your desktop environment (i.e i3/sway config file, Gnome custom shortcuts, etc). - ### Login with device Approving other devices works out of the box and is enabled by default. If the agent is unlocked, you will be prompted to approve the device. If you want to log into goldwarden using another device, add the "--passwordless" parameter to the login command. diff --git a/autofill/autofill.go b/autofill/autofill.go index 6c9fe5f..527ed14 100644 --- a/autofill/autofill.go +++ b/autofill/autofill.go @@ -49,7 +49,7 @@ func ListLogins(client client.Client) ([]messages.DecryptedLoginCipher, error) { } } -func Run(layout string, useCopyPaste bool, client client.Client) { +func Run(layout string, client client.Client) { logins, err := ListLogins(client) if err != nil { panic(err) @@ -70,17 +70,7 @@ func Run(layout string, useCopyPaste bool, client client.Client) { panic(err) } - if useCopyPaste { - clipboard.WriteAll(string(login.Username)) - autotype.Paste(layout) - autotype.TypeString("\t", layout) - clipboard.WriteAll(login.Password) - autotype.Paste(layout) - } else { - autotype.TypeString(string(login.Username), layout) - autotype.TypeString("\t", layout) - autotype.TypeString(string(login.Password), layout) - } + autotype.TypeString(string(login.Username)+"\t"+string(login.Password), layout) clipboard.WriteAll(login.TwoFactorCode) c <- true diff --git a/autofill/autotype/libportalautotype.go b/autofill/autotype/libportalautotype.go new file mode 100644 index 0000000..64ad3fb --- /dev/null +++ b/autofill/autotype/libportalautotype.go @@ -0,0 +1,70 @@ +//go:build linux && !uinput + +package autotype + +import ( + "fmt" + "time" + + "github.com/godbus/dbus/v5" +) + +var globalID = 0 + +const autoTypeDelay = 1 * time.Millisecond + +func TypeString(textToType string, layout string) { + bus, err := dbus.SessionBus() + if err != nil { + panic(err) + } + + obj := bus.Object("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop") + obj.AddMatchSignal("org.freedesktop.portal.Request", "Response") + + globalID++ + obj.Call("org.freedesktop.portal.RemoteDesktop.CreateSession", 0, map[string]dbus.Variant{ + "session_handle_token": dbus.MakeVariant("u" + fmt.Sprint(globalID)), + }) + + signals := make(chan *dbus.Signal, 10) + bus.Signal(signals) + + var state = 0 + var sessionHandle dbus.ObjectPath + + for { + select { + case message := <-signals: + fmt.Println("Message:", message) + if state == 0 { + result := message.Body[1].(map[string]dbus.Variant) + resultSessionHandle := result["session_handle"] + sessionHandle = dbus.ObjectPath(resultSessionHandle.String()[1 : len(resultSessionHandle.String())-1]) + obj.Call("org.freedesktop.portal.RemoteDesktop.SelectDevices", 0, sessionHandle, map[string]dbus.Variant{}) + state = 1 + } else if state == 1 { + obj.Call("org.freedesktop.portal.RemoteDesktop.Start", 0, sessionHandle, "", map[string]dbus.Variant{}) + state = 2 + } else if state == 2 { + state = 3 + time.Sleep(200 * time.Millisecond) + for _, char := range textToType { + if char == '\t' { + obj.Call("org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeycode", 0, sessionHandle, map[string]dbus.Variant{}, 15, uint32(1)) + time.Sleep(autoTypeDelay) + obj.Call("org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeycode", 0, sessionHandle, map[string]dbus.Variant{}, 15, uint32(0)) + time.Sleep(autoTypeDelay) + } else { + obj.Call("org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeysym", 0, sessionHandle, map[string]dbus.Variant{}, int32(char), uint32(1)) + time.Sleep(autoTypeDelay) + obj.Call("org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeysym", 0, sessionHandle, map[string]dbus.Variant{}, int32(char), uint32(0)) + time.Sleep(autoTypeDelay) + } + } + bus.Close() + return + } + } + } +} diff --git a/autofill/autotype/uinputautotype.go b/autofill/autotype/uinputautotype.go index 5a32c46..50bb452 100644 --- a/autofill/autotype/uinputautotype.go +++ b/autofill/autotype/uinputautotype.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build linux && uinput package autotype @@ -7,7 +7,3 @@ import "github.com/quexten/goldwarden/autofill/autotype/uinput" func TypeString(text string, layout string) error { return uinput.TypeString(text, layout) } - -func Paste(layout string) error { - return uinput.Paste(layout) -} diff --git a/cmd/autofill.go b/cmd/autofill.go index 6ce4baf..1710720 100644 --- a/cmd/autofill.go +++ b/cmd/autofill.go @@ -13,8 +13,7 @@ var autofillCmd = &cobra.Command{ Long: `Autofill credentials`, Run: func(cmd *cobra.Command, args []string) { layout := cmd.Flag("layout").Value.String() - useCopyPaste, _ := cmd.Flags().GetBool("use-copy-paste") - autofill.Run(layout, useCopyPaste, commandClient) + autofill.Run(layout, commandClient) }, }