Add timeout to notifications

This commit is contained in:
Bernd Schoolmann 2024-01-19 05:14:25 +01:00
parent 715e1eb30a
commit 794593a71f
No known key found for this signature in database
8 changed files with 73 additions and 30 deletions

View file

@ -3,6 +3,7 @@ package actions
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/quexten/goldwarden/agent/bitwarden" "github.com/quexten/goldwarden/agent/bitwarden"
"github.com/quexten/goldwarden/agent/bitwarden/crypto" "github.com/quexten/goldwarden/agent/bitwarden/crypto"
@ -85,7 +86,7 @@ func handleLogin(msg messages.IPCMessage, cfg *config.Config, vault *vault.Vault
err = crypto.InitKeyringFromMasterKey(vault.Keyring, profile.Profile.Key, profile.Profile.PrivateKey, orgKeys, masterKey) err = crypto.InitKeyringFromMasterKey(vault.Keyring, profile.Profile.Key, profile.Profile.PrivateKey, orgKeys, masterKey)
if err != nil { if err != nil {
defer func() { defer func() {
notify.Notify("Goldwarden", "Could not decrypt. Wrong password?", "", func() {}) notify.Notify("Goldwarden", "Could not decrypt. Wrong password?", "", 10*time.Second, func() {})
cfg.SetToken(config.LoginToken{}) cfg.SetToken(config.LoginToken{})
vault.Clear() vault.Clear()
}() }()
@ -112,7 +113,7 @@ func handleLogin(msg messages.IPCMessage, cfg *config.Config, vault *vault.Vault
} }
if err != nil { if err != nil {
defer func() { defer func() {
notify.Notify("Goldwarden", "Could not decrypt. Wrong password?", "", func() {}) notify.Notify("Goldwarden", "Could not decrypt. Wrong password?", "", 10*time.Second, func() {})
cfg.SetToken(config.LoginToken{}) cfg.SetToken(config.LoginToken{})
vault.Clear() vault.Clear()
}() }()

View file

@ -2,7 +2,6 @@ package actions
import ( import (
"context" "context"
"fmt"
"github.com/quexten/goldwarden/agent/bitwarden" "github.com/quexten/goldwarden/agent/bitwarden"
"github.com/quexten/goldwarden/agent/bitwarden/crypto" "github.com/quexten/goldwarden/agent/bitwarden/crypto"
@ -53,16 +52,25 @@ func handleUnlockVault(request messages.IPCMessage, cfg *config.Config, vault *v
return return
} }
actionsLog.Info("Unlocking vault...")
if cfg.IsLoggedIn() { if cfg.IsLoggedIn() {
token, err := cfg.GetToken() token, err := cfg.GetToken()
if err == nil { if err == nil {
if token.AccessToken != "" { if token.AccessToken != "" {
ctx := context.Background() ctx := context.Background()
bitwarden.RefreshToken(ctx, cfg) gotToken := bitwarden.RefreshToken(ctx, cfg)
if gotToken {
actionsLog.Info("Token refreshed")
} else {
actionsLog.Warn("Token refresh failed")
}
token, err := cfg.GetToken() token, err := cfg.GetToken()
if err != nil {
actionsLog.Error("Could not get token: %s", err.Error())
}
userSymmkey, err := cfg.GetUserSymmetricKey() userSymmkey, err := cfg.GetUserSymmetricKey()
if err != nil { if err != nil {
fmt.Println(err) actionsLog.Error("Could not get user symmetric key: %s", err.Error())
} }
var safeUserSymmkey crypto.SymmetricEncryptionKey var safeUserSymmkey crypto.SymmetricEncryptionKey
@ -72,14 +80,18 @@ func handleUnlockVault(request messages.IPCMessage, cfg *config.Config, vault *v
safeUserSymmkey, err = crypto.MemorySymmetricEncryptionKeyFromBytes(userSymmkey) safeUserSymmkey, err = crypto.MemorySymmetricEncryptionKeyFromBytes(userSymmkey)
} }
if err != nil { if err != nil {
fmt.Println(err) actionsLog.Error("Could not create safe user symmetric key: %s", err.Error())
} }
err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, cfg, &safeUserSymmkey, true) err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, cfg, &safeUserSymmkey, true)
if err != nil { if err != nil {
fmt.Println(err) actionsLog.Error("Could not sync: %s", err.Error())
} }
} else {
actionsLog.Warn("Access token is empty")
} }
} else {
actionsLog.Warn("Could not get token: %s", err.Error())
} }
} }

View file

@ -71,12 +71,12 @@ func deviceType() string {
func LoginWithApiKey(ctx context.Context, email string, cfg *config.Config, vault *vault.Vault) (LoginResponseToken, crypto.MasterKey, string, error) { func LoginWithApiKey(ctx context.Context, email string, cfg *config.Config, vault *vault.Vault) (LoginResponseToken, crypto.MasterKey, string, error) {
clientID, err := cfg.GetClientID() clientID, err := cfg.GetClientID()
if err != nil { if err != nil {
notify.Notify("Goldwarden", fmt.Sprintf("Could not get client ID: %v", err), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Could not get client ID: %v", err), "", 0, func() {})
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not get client ID: %v", err) return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not get client ID: %v", err)
} }
clientSecret, err := cfg.GetClientSecret() clientSecret, err := cfg.GetClientSecret()
if err != nil { if err != nil {
notify.Notify("Goldwarden", fmt.Sprintf("Could not get client secret: %v", err), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Could not get client secret: %v", err), "", 0, func() {})
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not get client secret: %v", err) return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not get client secret: %v", err)
} }
@ -93,19 +93,19 @@ func LoginWithApiKey(ctx context.Context, email string, cfg *config.Config, vaul
var loginResponseToken LoginResponseToken var loginResponseToken LoginResponseToken
err = authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values) err = authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values)
if err != nil { if err != nil {
notify.Notify("Goldwarden", fmt.Sprintf("Could not login via API key: %v", err), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Could not login via API key: %v", err), "", 0, func() {})
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not login via API key: %v", err) return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not login via API key: %v", err)
} }
password, err := pinentry.GetPassword("Bitwarden Password", "Enter your Bitwarden password") password, err := pinentry.GetPassword("Bitwarden Password", "Enter your Bitwarden password")
if err != nil { if err != nil {
notify.Notify("Goldwarden", fmt.Sprintf("Could not get password: %v", err), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Could not get password: %v", err), "", 0, func() {})
return LoginResponseToken{}, crypto.MasterKey{}, "", err return LoginResponseToken{}, crypto.MasterKey{}, "", err
} }
masterKey, err := crypto.DeriveMasterKey([]byte(strings.Clone(password)), email, crypto.KDFConfig{Type: crypto.KDFType(loginResponseToken.Kdf), Iterations: uint32(loginResponseToken.KdfIterations), Memory: uint32(loginResponseToken.KdfMemory), Parallelism: uint32(loginResponseToken.KdfParallelism)}) masterKey, err := crypto.DeriveMasterKey([]byte(strings.Clone(password)), email, crypto.KDFConfig{Type: crypto.KDFType(loginResponseToken.Kdf), Iterations: uint32(loginResponseToken.KdfIterations), Memory: uint32(loginResponseToken.KdfMemory), Parallelism: uint32(loginResponseToken.KdfParallelism)})
if err != nil { if err != nil {
notify.Notify("Goldwarden", fmt.Sprintf("Could not derive master key: %v", err), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Could not derive master key: %v", err), "", 0, func() {})
return LoginResponseToken{}, crypto.MasterKey{}, "", err return LoginResponseToken{}, crypto.MasterKey{}, "", err
} }
hashedPassword := b64enc.EncodeToString(pbkdf2.Key(masterKey.GetBytes(), []byte(password), 1, 32, sha256.New)) hashedPassword := b64enc.EncodeToString(pbkdf2.Key(masterKey.GetBytes(), []byte(password), 1, 32, sha256.New))
@ -119,7 +119,7 @@ func LoginWithMasterpassword(ctx context.Context, email string, cfg *config.Conf
if err := authenticatedHTTPPost(ctx, cfg.ConfigFile.ApiUrl+"/accounts/prelogin", &preLogin, preLoginRequest{ if err := authenticatedHTTPPost(ctx, cfg.ConfigFile.ApiUrl+"/accounts/prelogin", &preLogin, preLoginRequest{
Email: email, Email: email,
}); err != nil { }); err != nil {
notify.Notify("Goldwarden", fmt.Sprintf("Could not pre-login: %v", err), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Could not pre-login: %v", err), "", 0, func() {})
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not pre-login: %v", err) return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not pre-login: %v", err)
} }
@ -129,13 +129,13 @@ func LoginWithMasterpassword(ctx context.Context, email string, cfg *config.Conf
password, err := pinentry.GetPassword("Bitwarden Password", "Enter your Bitwarden password") password, err := pinentry.GetPassword("Bitwarden Password", "Enter your Bitwarden password")
if err != nil { if err != nil {
notify.Notify("Goldwarden", fmt.Sprintf("Could not get password: %v", err), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Could not get password: %v", err), "", 0, func() {})
return LoginResponseToken{}, crypto.MasterKey{}, "", err return LoginResponseToken{}, crypto.MasterKey{}, "", err
} }
masterKey, err = crypto.DeriveMasterKey([]byte(strings.Clone(password)), email, crypto.KDFConfig{Type: crypto.KDFType(preLogin.KDF), Iterations: uint32(preLogin.KDFIterations), Memory: uint32(preLogin.KDFMemory), Parallelism: uint32(preLogin.KDFParallelism)}) masterKey, err = crypto.DeriveMasterKey([]byte(strings.Clone(password)), email, crypto.KDFConfig{Type: crypto.KDFType(preLogin.KDF), Iterations: uint32(preLogin.KDFIterations), Memory: uint32(preLogin.KDFMemory), Parallelism: uint32(preLogin.KDFParallelism)})
if err != nil { if err != nil {
notify.Notify("Goldwarden", fmt.Sprintf("Could not derive master key: %v", err), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Could not derive master key: %v", err), "", 0, func() {})
return LoginResponseToken{}, crypto.MasterKey{}, "", err return LoginResponseToken{}, crypto.MasterKey{}, "", err
} }
@ -158,14 +158,14 @@ func LoginWithMasterpassword(ctx context.Context, email string, cfg *config.Conf
if ok && bytes.Contains(errsc.body, []byte("TwoFactor")) { if ok && bytes.Contains(errsc.body, []byte("TwoFactor")) {
loginResponseToken, err = Perform2FA(values, errsc, cfg, ctx) loginResponseToken, err = Perform2FA(values, errsc, cfg, ctx)
if err != nil { if err != nil {
notify.Notify("Goldwarden", fmt.Sprintf("Could not login via two-factor: %v", err), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Could not login via two-factor: %v", err), "", 0, func() {})
return LoginResponseToken{}, crypto.MasterKey{}, "", err return LoginResponseToken{}, crypto.MasterKey{}, "", err
} }
} else if err != nil && strings.Contains(err.Error(), "Captcha required.") { } else if err != nil && strings.Contains(err.Error(), "Captcha required.") {
notify.Notify("Goldwarden", fmt.Sprintf("Captcha required"), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Captcha required"), "", 0, func() {})
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("captcha required, please login via the web interface") return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("captcha required, please login via the web interface")
} else if err != nil { } else if err != nil {
notify.Notify("Goldwarden", fmt.Sprintf("Could not login via password: %v", err), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Could not login via password: %v", err), "", 0, func() {})
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not login via password: %v", err) return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not login via password: %v", err)
} }
@ -278,7 +278,7 @@ func RefreshToken(ctx context.Context, cfg *config.Config) bool {
err = authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values) err = authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values)
if err != nil { if err != nil {
authLog.Error("Could not refresh token: %s", err.Error()) authLog.Error("Could not refresh token: %s", err.Error())
notify.Notify("Goldwarden", fmt.Sprintf("Could not refresh token: %v", err), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Could not refresh token: %v", err), "", 0, func() {})
return false return false
} }
@ -303,7 +303,7 @@ func RefreshToken(ctx context.Context, cfg *config.Config) bool {
)) ))
if err != nil { if err != nil {
authLog.Error("Could not refresh token: %s", err.Error()) authLog.Error("Could not refresh token: %s", err.Error())
notify.Notify("Goldwarden", fmt.Sprintf("Could not refresh token: %v", err), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Could not refresh token: %v", err), "", 0, func() {})
return false return false
} }
cfg.SetToken(config.LoginToken{ cfg.SetToken(config.LoginToken{

View file

@ -35,7 +35,7 @@ func DoFullSync(ctx context.Context, vault *vault.Vault, config *config.Config,
sync, err := Sync(ctx, config) sync, err := Sync(ctx, config)
if err != nil { if err != nil {
log.Error("Could not sync: %v", err) log.Error("Could not sync: %v", err)
notify.Notify("Goldwarden", "Could not sync", "", func() {}) notify.Notify("Goldwarden", "Could not sync", "", 0, func() {})
if allowCache { if allowCache {
home, _ := os.UserHomeDir() home, _ := os.UserHomeDir()
sync, err = ReadVault(home + path) sync, err = ReadVault(home + path)

View file

@ -11,6 +11,7 @@ import (
"runtime/debug" "runtime/debug"
"strings" "strings"
"sync" "sync"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/quexten/goldwarden/agent/bitwarden/crypto" "github.com/quexten/goldwarden/agent/bitwarden/crypto"
@ -131,7 +132,7 @@ func (c *Config) Unlock(password string) bool {
keyBuffer := NewBufferFromBytes(key, c.useMemguard) keyBuffer := NewBufferFromBytes(key, c.useMemguard)
c.key = &keyBuffer c.key = &keyBuffer
notify.Notify("Goldwarden", "Vault Unlocked", "", func() {}) notify.Notify("Goldwarden", "Vault Unlocked", "", 60*time.Second, func() {})
return true return true
} }
@ -155,7 +156,7 @@ func (c *Config) Lock() {
return return
} }
(*c.key).Wipe() (*c.key).Wipe()
notify.Notify("Goldwarden", "Vault Locked", "", func() {}) notify.Notify("Goldwarden", "Vault Locked", "", 60*time.Second, func() {})
} }
func (c *Config) Purge() { func (c *Config) Purge() {

View file

@ -3,12 +3,15 @@
package notify package notify
import ( import (
"time"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
) )
var closeListenerMap = make(map[uint32]func()) var closeListenerMap = make(map[uint32]func())
var notificationID uint32 = 1000000
func Notify(title string, body string, actionName string, onclose func()) error { func Notify(title string, body string, actionName string, timeout time.Duration, onclose func()) error {
bus, err := dbus.SessionBus() bus, err := dbus.SessionBus()
if err != nil { if err != nil {
return err return err
@ -18,7 +21,10 @@ func Notify(title string, body string, actionName string, onclose func()) error
if actionName != "" { if actionName != "" {
actions = append(actions, actionName) actions = append(actions, actionName)
} }
call := obj.Call("org.freedesktop.Notifications.Notify", 0, "goldwarden", uint32(0), "", title, body, actions, map[string]dbus.Variant{}, int32(60000))
notificationID++
call := obj.Call("org.freedesktop.Notifications.Notify", 0, "goldwarden", uint32(notificationID), "", title, body, actions, map[string]dbus.Variant{}, int32(60000))
if call.Err != nil { if call.Err != nil {
return call.Err return call.Err
} }
@ -28,6 +34,18 @@ func Notify(title string, body string, actionName string, onclose func()) error
id := call.Body[0].(uint32) id := call.Body[0].(uint32)
closeListenerMap[id] = onclose closeListenerMap[id] = onclose
if timeout == 0 {
return nil
} else {
go func(id uint32) {
time.Sleep(timeout)
call := obj.Call("org.freedesktop.Notifications.CloseNotification", 0, uint32(id))
if call.Err != nil {
return
}
}(id)
}
return nil return nil
} }

View file

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"time"
"github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/notify" "github.com/quexten/goldwarden/agent/notify"
@ -125,9 +126,9 @@ func (vaultAgent vaultAgent) Sign(key ssh.PublicKey, data []byte) (*ssh.Signatur
var rand = rand.Reader var rand = rand.Reader
log.Info("Sign Request for key: %s %s accepted", ssh.FingerprintSHA256(key), sshKey.Name) log.Info("Sign Request for key: %s %s accepted", ssh.FingerprintSHA256(key), sshKey.Name)
if isGit { if isGit {
notify.Notify("Goldwarden", fmt.Sprintf("Git Signing Request Approved for %s", sshKey.Name), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("Git Signing Request Approved for %s", sshKey.Name), "", 10*time.Second, func() {})
} else { } else {
notify.Notify("Goldwarden", fmt.Sprintf("SSH Signing Request Approved for %s", sshKey.Name), "", func() {}) notify.Notify("Goldwarden", fmt.Sprintf("SSH Signing Request Approved for %s", sshKey.Name), "", 10*time.Second, func() {})
} }
return signer.Sign(rand, data) return signer.Sign(rand, data)
} }

View file

@ -210,10 +210,14 @@ func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
token, err := cfg.GetToken() token, err := cfg.GetToken()
if err == nil { if err == nil {
if token.AccessToken != "" { if token.AccessToken != "" {
bitwarden.RefreshToken(ctx, &cfg) gotToken := bitwarden.RefreshToken(ctx, &cfg)
if !gotToken {
log.Warn("Could not get token")
return false
}
userSymmetricKey, err := cfg.GetUserSymmetricKey() userSymmetricKey, err := cfg.GetUserSymmetricKey()
if err != nil { if err != nil {
fmt.Println(err) log.Error("Could not get user symmetric key: %s", err.Error())
} }
var protectedUserSymetricKey crypto.SymmetricEncryptionKey var protectedUserSymetricKey crypto.SymmetricEncryptionKey
if vault.Keyring.IsMemguard { if vault.Keyring.IsMemguard {
@ -224,11 +228,17 @@ func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, &cfg, &protectedUserSymetricKey, true) err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, &cfg, &protectedUserSymetricKey, true)
if err != nil { if err != nil {
fmt.Println(err) log.Error("Could not sync: %s", err.Error())
} }
} else {
log.Warn("Access token is empty")
} }
} else {
log.Error("Could not get token: %s", err.Error())
} }
return true return true
} else {
log.Warn("Could not unlock: %s", err.Error())
} }
return false return false
}) })