GODT-1779: Remove go-imap

This commit is contained in:
James Houlahan
2022-08-26 17:00:21 +02:00
parent 3b0bc1ca15
commit 39433fe707
593 changed files with 12725 additions and 91626 deletions

19
internal/vault/certs.go Normal file
View File

@ -0,0 +1,19 @@
package vault
func (vault *Vault) GetBridgeTLSCert() []byte {
return vault.get().Certs.Bridge.Cert
}
func (vault *Vault) GetBridgeTLSKey() []byte {
return vault.get().Certs.Bridge.Key
}
func (vault *Vault) GetCertsInstalled() bool {
return vault.get().Certs.Installed
}
func (vault *Vault) SetCertsInstalled(installed bool) error {
return vault.mod(func(data *Data) {
data.Certs.Installed = installed
})
}

View File

@ -0,0 +1,25 @@
package vault_test
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestVault_TLSCerts(t *testing.T) {
// create a new test vault.
s := newVault(t)
// Check the default bridge TLS certs.
require.NotEmpty(t, s.GetBridgeTLSCert())
require.NotEmpty(t, s.GetBridgeTLSKey())
// Check the certificates are not installed.
require.False(t, s.GetCertsInstalled())
// Install the certificates.
require.NoError(t, s.SetCertsInstalled(true))
// Check the certificates are installed.
require.True(t, s.GetCertsInstalled())
}

11
internal/vault/cookies.go Normal file
View File

@ -0,0 +1,11 @@
package vault
func (vault *Vault) GetCookies() ([]byte, error) {
return vault.get().Cookies, nil
}
func (vault *Vault) SetCookies(cookies []byte) error {
return vault.mod(func(data *Data) {
data.Cookies = cookies
})
}

View File

@ -0,0 +1,25 @@
package vault_test
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestVault_Cookies(t *testing.T) {
// create a new test vault.
s := newVault(t)
// Check the default cookies are empty.
cookies, err := s.GetCookies()
require.NoError(t, err)
require.Empty(t, cookies)
// Set some cookies.
require.NoError(t, s.SetCookies([]byte("something")))
// Check the cookies are as set.
newCookies, err := s.GetCookies()
require.NoError(t, err)
require.Equal(t, []byte("something"), newCookies)
}

41
internal/vault/helper.go Normal file
View File

@ -0,0 +1,41 @@
package vault
import (
"encoding/json"
"errors"
"io/fs"
"os"
"path/filepath"
)
type Keychain struct {
Helper string
}
func GetHelper(vaultDir string) (string, error) {
var keychain Keychain
if _, err := os.Stat(filepath.Join(vaultDir, "keychain.json")); errors.Is(err, fs.ErrNotExist) {
return "", nil
}
b, err := os.ReadFile(filepath.Join(vaultDir, "keychain.json"))
if err != nil {
return "", err
}
if err := json.Unmarshal(b, &keychain); err != nil {
return "", err
}
return keychain.Helper, nil
}
func SetHelper(vaultDir, helper string) error {
b, err := json.MarshalIndent(Keychain{Helper: helper}, "", " ")
if err != nil {
return err
}
return os.WriteFile(filepath.Join(vaultDir, "keychain.json"), b, 0o600)
}

186
internal/vault/settings.go Normal file
View File

@ -0,0 +1,186 @@
package vault
import (
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
)
// GetIMAPPort sets the port that the IMAP server should listen on.
func (vault *Vault) GetIMAPPort() int {
return vault.get().Settings.IMAPPort
}
// SetIMAPPort sets the port that the IMAP server should listen on.
func (vault *Vault) SetIMAPPort(port int) error {
return vault.mod(func(data *Data) {
data.Settings.IMAPPort = port
})
}
// GetSMTPPort sets the port that the SMTP server should listen on.
func (vault *Vault) GetSMTPPort() int {
return vault.get().Settings.SMTPPort
}
// SetSMTPPort sets the port that the SMTP server should listen on.
func (vault *Vault) SetSMTPPort(port int) error {
return vault.mod(func(data *Data) {
data.Settings.SMTPPort = port
})
}
// GetIMAPSSL sets whether the IMAP server should use SSL.
func (vault *Vault) GetIMAPSSL() bool {
return vault.get().Settings.IMAPSSL
}
// SetIMAPSSL sets whether the IMAP server should use SSL.
func (vault *Vault) SetIMAPSSL(ssl bool) error {
return vault.mod(func(data *Data) {
data.Settings.IMAPSSL = ssl
})
}
// GetSMTPSSL sets whether the SMTP server should use SSL.
func (vault *Vault) GetSMTPSSL() bool {
return vault.get().Settings.SMTPSSL
}
// SetSMTPSSL sets whether the SMTP server should use SSL.
func (vault *Vault) SetSMTPSSL(ssl bool) error {
return vault.mod(func(data *Data) {
data.Settings.SMTPSSL = ssl
})
}
// GetGluonDir sets the directory where the gluon should store its data.
func (vault *Vault) GetGluonDir() string {
return vault.get().Settings.GluonDir
}
// SetGluonDir sets the directory where the gluon should store its data.
func (vault *Vault) SetGluonDir(dir string) error {
return vault.mod(func(data *Data) {
data.Settings.GluonDir = dir
})
}
// GetUpdateChannel sets the update channel.
func (vault *Vault) GetUpdateChannel() updater.Channel {
return vault.get().Settings.UpdateChannel
}
// SetUpdateChannel sets the update channel.
func (vault *Vault) SetUpdateChannel(channel updater.Channel) error {
return vault.mod(func(data *Data) {
data.Settings.UpdateChannel = channel
})
}
// GetUpdateRollout sets the update rollout.
func (vault *Vault) GetUpdateRollout() float64 {
return vault.get().Settings.UpdateRollout
}
// SetUpdateRollout sets the update rollout.
func (vault *Vault) SetUpdateRollout(rollout float64) error {
return vault.mod(func(data *Data) {
data.Settings.UpdateRollout = rollout
})
}
// GetColorScheme sets the color scheme to be used by the bridge GUI.
func (vault *Vault) GetColorScheme() string {
return vault.get().Settings.ColorScheme
}
// SetColorScheme sets the color scheme to be used by the bridge GUI.
func (vault *Vault) SetColorScheme(colorScheme string) error {
return vault.mod(func(data *Data) {
data.Settings.ColorScheme = colorScheme
})
}
// GetProxyAllowed sets whether the bridge is allowed to use alternative routing.
func (vault *Vault) GetProxyAllowed() bool {
return vault.get().Settings.ProxyAllowed
}
// SetProxyAllowed sets whether the bridge is allowed to use alternative routing.
func (vault *Vault) SetProxyAllowed(allowed bool) error {
return vault.mod(func(data *Data) {
data.Settings.ProxyAllowed = allowed
})
}
// GetShowAllMail sets whether the bridge should show the All Mail folder.
func (vault *Vault) GetShowAllMail() bool {
return vault.get().Settings.ShowAllMail
}
// SetShowAllMail sets whether the bridge should show the All Mail folder.
func (vault *Vault) SetShowAllMail(showAllMail bool) error {
return vault.mod(func(data *Data) {
data.Settings.ShowAllMail = showAllMail
})
}
// GetAutostart sets whether the bridge should autostart.
func (vault *Vault) GetAutostart() bool {
return vault.get().Settings.Autostart
}
// SetAutostart sets whether the bridge should autostart.
func (vault *Vault) SetAutostart(autostart bool) error {
return vault.mod(func(data *Data) {
data.Settings.Autostart = autostart
})
}
// GetAutoUpdate sets whether the bridge should automatically update.
func (vault *Vault) GetAutoUpdate() bool {
return vault.get().Settings.AutoUpdate
}
// SetAutoUpdate sets whether the bridge should automatically update.
func (vault *Vault) SetAutoUpdate(autoUpdate bool) error {
return vault.mod(func(data *Data) {
data.Settings.AutoUpdate = autoUpdate
})
}
// GetLastVersion returns the last version of the bridge that was run.
func (vault *Vault) GetLastVersion() *semver.Version {
return vault.get().Settings.LastVersion
}
// SetLastVersion sets the last version of the bridge that was run.
func (vault *Vault) SetLastVersion(version *semver.Version) error {
return vault.mod(func(data *Data) {
data.Settings.LastVersion = version
})
}
// GetFirstStart sets whether this is the first time the bridge has been started.
func (vault *Vault) GetFirstStart() bool {
return vault.get().Settings.FirstStart
}
// SetFirstStart sets whether this is the first time the bridge has been started.
func (vault *Vault) SetFirstStart(firstStart bool) error {
return vault.mod(func(data *Data) {
data.Settings.FirstStart = firstStart
})
}
// GetFirstStartGUI sets whether this is the first time the bridge GUI has been started.
func (vault *Vault) GetFirstStartGUI() bool {
return vault.get().Settings.FirstStartGUI
}
// SetFirstStartGUI sets whether this is the first time the bridge GUI has been started.
func (vault *Vault) SetFirstStartGUI(firstStartGUI bool) error {
return vault.mod(func(data *Data) {
data.Settings.FirstStartGUI = firstStartGUI
})
}

View File

@ -0,0 +1,201 @@
package vault_test
import (
"testing"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
"github.com/stretchr/testify/require"
)
func TestVault_Settings_IMAP(t *testing.T) {
// Create a new test vault.
s := newVault(t)
// Check the default IMAP port and SSL setting.
require.Equal(t, 1143, s.GetIMAPPort())
require.Equal(t, false, s.GetIMAPSSL())
// Modify the IMAP port and SSL setting.
require.NoError(t, s.SetIMAPPort(1234))
require.NoError(t, s.SetIMAPSSL(true))
// Check the new IMAP port and SSL setting.
require.Equal(t, 1234, s.GetIMAPPort())
require.Equal(t, true, s.GetIMAPSSL())
}
func TestVault_Settings_SMTP(t *testing.T) {
// Create a new test vault.
s := newVault(t)
// Check the default SMTP port and SSL setting.
require.Equal(t, 1025, s.GetSMTPPort())
require.Equal(t, false, s.GetSMTPSSL())
// Modify the SMTP port and SSL setting.
require.NoError(t, s.SetSMTPPort(1234))
require.NoError(t, s.SetSMTPSSL(true))
// Check the new SMTP port and SSL setting.
require.Equal(t, 1234, s.GetSMTPPort())
require.Equal(t, true, s.GetSMTPSSL())
}
func TestVault_Settings_GluonDir(t *testing.T) {
// create a new test vault.
s, corrupt, err := vault.New(t.TempDir(), "/path/to/gluon", []byte("my secret key"))
require.NoError(t, err)
require.False(t, corrupt)
// Check the default gluon dir.
require.Equal(t, "/path/to/gluon", s.GetGluonDir())
// Modify the gluon dir.
require.NoError(t, s.SetGluonDir("/tmp/gluon"))
// Check the new gluon dir.
require.Equal(t, "/tmp/gluon", s.GetGluonDir())
}
func TestVault_Settings_UpdateChannel(t *testing.T) {
// create a new test vault.
s := newVault(t)
// Check the default update channel.
require.Equal(t, updater.StableChannel, s.GetUpdateChannel())
// Modify the update channel.
require.NoError(t, s.SetUpdateChannel(updater.EarlyChannel))
// Check the new update channel.
require.Equal(t, updater.EarlyChannel, s.GetUpdateChannel())
}
func TestVault_Settings_UpdateRollout(t *testing.T) {
// create a new test vault.
s := newVault(t)
// Check the default update rollout.
require.GreaterOrEqual(t, s.GetUpdateRollout(), float64(0))
require.LessOrEqual(t, s.GetUpdateRollout(), float64(1))
// Modify the update rollout.
require.NoError(t, s.SetUpdateRollout(0.5))
// Check the new update rollout.
require.Equal(t, float64(0.5), s.GetUpdateRollout())
}
func TestVault_Settings_ColorScheme(t *testing.T) {
// create a new test vault.
s := newVault(t)
// Check the default color scheme.
require.Equal(t, "", s.GetColorScheme())
// Modify the color scheme.
require.NoError(t, s.SetColorScheme("dark"))
// Check the new color scheme.
require.Equal(t, "dark", s.GetColorScheme())
}
func TestVault_Settings_ProxyAllowed(t *testing.T) {
// create a new test vault.
s := newVault(t)
// Check the default proxy allowed setting.
require.Equal(t, true, s.GetProxyAllowed())
// Modify the proxy allowed setting.
require.NoError(t, s.SetProxyAllowed(false))
// Check the new proxy allowed setting.
require.Equal(t, false, s.GetProxyAllowed())
}
func TestVault_Settings_ShowAllMail(t *testing.T) {
// create a new test vault.
s := newVault(t)
// Check the default show all mail setting.
require.Equal(t, true, s.GetShowAllMail())
// Modify the show all mail setting.
require.NoError(t, s.SetShowAllMail(false))
// Check the new show all mail setting.
require.Equal(t, false, s.GetShowAllMail())
}
func TestVault_Settings_Autostart(t *testing.T) {
// create a new test vault.
s := newVault(t)
// Check the default autostart setting.
require.Equal(t, false, s.GetAutostart())
// Modify the autostart setting.
require.NoError(t, s.SetAutostart(true))
// Check the new autostart setting.
require.Equal(t, true, s.GetAutostart())
}
func TestVault_Settings_AutoUpdate(t *testing.T) {
// create a new test vault.
s := newVault(t)
// Check the default auto update setting.
require.Equal(t, true, s.GetAutoUpdate())
// Modify the auto update setting.
require.NoError(t, s.SetAutoUpdate(false))
// Check the new auto update setting.
require.Equal(t, false, s.GetAutoUpdate())
}
func TestVault_Settings_LastVersion(t *testing.T) {
// create a new test vault.
s := newVault(t)
// Check the default first start value.
require.True(t, semver.MustParse("0.0.0").Equal(s.GetLastVersion()))
// Modify the first start value.
require.NoError(t, s.SetLastVersion(semver.MustParse("1.2.3")))
// Check the new first start value.
require.True(t, semver.MustParse("1.2.3").Equal(s.GetLastVersion()))
}
func TestVault_Settings_FirstStart(t *testing.T) {
// create a new test vault.
s := newVault(t)
// Check the default first start value.
require.Equal(t, true, s.GetFirstStart())
// Modify the first start value.
require.NoError(t, s.SetFirstStart(false))
// Check the new first start value.
require.Equal(t, false, s.GetFirstStart())
}
func TestVault_Settings_FirstStartGUI(t *testing.T) {
// create a new test vault.
s := newVault(t)
// Check the default first start value.
require.Equal(t, true, s.GetFirstStartGUI())
// Modify the first start value.
require.NoError(t, s.SetFirstStartGUI(false))
// Check the new first start value.
require.Equal(t, false, s.GetFirstStartGUI())
}

264
internal/vault/store.go Normal file
View File

@ -0,0 +1,264 @@
package vault
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"io/fs"
"math/rand"
"os"
"path/filepath"
"github.com/ProtonMail/proton-bridge/v2/internal/certs"
"github.com/bradenaw/juniper/xslices"
)
var (
ErrInsecure = errors.New("the vault is insecure")
ErrCorrupt = errors.New("the vault is corrupt")
)
type Vault struct {
path string
enc []byte
gcm cipher.AEAD
}
// New constructs a new encrypted data vault at the given filepath using the given encryption key.
func New(vaultDir, gluonDir string, key []byte) (*Vault, bool, error) {
if err := os.MkdirAll(vaultDir, 0o700); err != nil {
return nil, false, err
}
hash256 := sha256.Sum256(key)
aes, err := aes.NewCipher(hash256[:])
if err != nil {
return nil, false, err
}
gcm, err := cipher.NewGCM(aes)
if err != nil {
return nil, false, err
}
vault, corrupt, err := newVault(filepath.Join(vaultDir, "vault.enc"), gluonDir, gcm)
if err != nil {
return nil, false, err
}
return vault, corrupt, nil
}
// GetUserIDs returns the user IDs and usernames of all users in the vault.
func (vault *Vault) GetUserIDs() []string {
return xslices.Map(vault.get().Users, func(user UserData) string {
return user.UserID
})
}
// GetUserIDs returns the user IDs and usernames of all users in the vault.
func (vault *Vault) GetUser(userID string) (*User, error) {
if idx := xslices.IndexFunc(vault.get().Users, func(user UserData) bool {
return user.UserID == userID
}); idx < 0 {
return nil, errors.New("no such user")
}
return &User{
vault: vault,
userID: userID,
}, nil
}
// AddUser creates a new user in the vault with the given ID and username.
// A bridge password is generated using the package's token generator.
func (vault *Vault) AddUser(userID, username, authUID, authRef string, keyPass []byte) (*User, error) {
if idx := xslices.IndexFunc(vault.get().Users, func(user UserData) bool {
return user.UserID == userID
}); idx >= 0 {
return nil, errors.New("user already exists")
}
tok, err := RandomToken(16)
if err != nil {
return nil, err
}
if err := vault.mod(func(data *Data) {
data.Users = append(data.Users, UserData{
UserID: userID,
Username: username,
BridgePass: hex.EncodeToString(tok),
AuthUID: authUID,
AuthRef: authRef,
KeyPass: keyPass,
})
}); err != nil {
return nil, err
}
return vault.GetUser(userID)
}
// DeleteUser removes the given user from the vault.
func (vault *Vault) DeleteUser(userID string) error {
return vault.mod(func(data *Data) {
idx := xslices.IndexFunc(data.Users, func(user UserData) bool {
return user.UserID == userID
})
if idx < 0 {
return
}
data.Users = append(data.Users[:idx], data.Users[idx+1:]...)
})
}
func newVault(path, gluonDir string, gcm cipher.AEAD) (*Vault, bool, error) {
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
if _, err := initVault(path, gluonDir, gcm); err != nil {
return nil, false, err
}
}
enc, err := os.ReadFile(path)
if err != nil {
return nil, false, err
}
var corrupt bool
if _, err := decrypt(gcm, enc); err != nil {
corrupt = true
newEnc, err := initVault(path, gluonDir, gcm)
if err != nil {
return nil, false, err
}
enc = newEnc
}
return &Vault{path: path, enc: enc, gcm: gcm}, corrupt, nil
}
func (vault *Vault) get() Data {
dec, err := decrypt(vault.gcm, vault.enc)
if err != nil {
panic(err)
}
var data Data
if err := json.Unmarshal(dec, &data); err != nil {
panic(err)
}
return data
}
func (vault *Vault) mod(fn func(data *Data)) error {
data := vault.get()
fn(&data)
return vault.set(data)
}
func (vault *Vault) set(data Data) error {
dec, err := json.Marshal(data)
if err != nil {
return err
}
enc, err := encrypt(vault.gcm, dec)
if err != nil {
return err
}
vault.enc = enc
return os.WriteFile(vault.path, vault.enc, 0o600)
}
func (vault *Vault) getUser(userID string) UserData {
return vault.get().Users[xslices.IndexFunc(vault.get().Users, func(user UserData) bool {
return user.UserID == userID
})]
}
func (vault *Vault) modUser(userID string, fn func(userData *UserData)) error {
return vault.mod(func(data *Data) {
idx := xslices.IndexFunc(data.Users, func(user UserData) bool {
return user.UserID == userID
})
fn(&data.Users[idx])
})
}
func initVault(path, gluonDir string, gcm cipher.AEAD) ([]byte, error) {
bridgeCert, err := newTLSCert()
if err != nil {
return nil, err
}
dec, err := json.Marshal(Data{
Settings: newDefaultSettings(gluonDir),
Certs: Certs{
Bridge: bridgeCert,
},
})
if err != nil {
return nil, err
}
enc, err := encrypt(gcm, dec)
if err != nil {
return nil, err
}
if err := os.WriteFile(path, enc, 0o600); err != nil {
return nil, err
}
return enc, nil
}
func decrypt(gcm cipher.AEAD, enc []byte) ([]byte, error) {
return gcm.Open(nil, enc[:gcm.NonceSize()], enc[gcm.NonceSize():], nil)
}
func encrypt(gcm cipher.AEAD, data []byte) ([]byte, error) {
nonce := make([]byte, gcm.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, data, nil), nil
}
func newTLSCert() (Cert, error) {
template, err := certs.NewTLSTemplate()
if err != nil {
return Cert{}, err
}
certPEM, keyPEM, err := certs.GenerateCert(template)
if err != nil {
return Cert{}, err
}
return Cert{
Cert: certPEM,
Key: keyPEM,
}, nil
}

View File

@ -0,0 +1,40 @@
package vault_test
import (
"testing"
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
"github.com/stretchr/testify/require"
)
func TestVaultCorrupt(t *testing.T) {
vaultDir, gluonDir := t.TempDir(), t.TempDir()
{
_, corrupt, err := vault.New(vaultDir, gluonDir, []byte("my secret key"))
require.NoError(t, err)
require.False(t, corrupt)
}
{
_, corrupt, err := vault.New(vaultDir, gluonDir, []byte("my secret key"))
require.NoError(t, err)
require.False(t, corrupt)
}
{
_, corrupt, err := vault.New(vaultDir, gluonDir, []byte("bad key"))
require.NoError(t, err)
require.True(t, corrupt)
}
}
func newVault(t *testing.T) *vault.Vault {
t.Helper()
s, corrupt, err := vault.New(t.TempDir(), t.TempDir(), []byte("my secret key"))
require.NoError(t, err)
require.False(t, corrupt)
return s
}

13
internal/vault/token.go Normal file
View File

@ -0,0 +1,13 @@
package vault
import (
"github.com/ProtonMail/gopenpgp/v2/crypto"
)
// RandomToken is a function that returns a random token.
var RandomToken func(size int) ([]byte, error)
// By default, we use crypto.RandomToken to generate tokens.
func init() {
RandomToken = crypto.RandomToken
}

88
internal/vault/types.go Normal file
View File

@ -0,0 +1,88 @@
package vault
import (
"math/rand"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
)
type Data struct {
Settings Settings
Users []UserData
Cookies []byte
Certs Certs
}
type Certs struct {
Bridge Cert
Installed bool
}
type Cert struct {
Cert, Key []byte
}
type Settings struct {
GluonDir string
IMAPPort int
SMTPPort int
IMAPSSL bool
SMTPSSL bool
UpdateChannel updater.Channel
UpdateRollout float64
ColorScheme string
ProxyAllowed bool
ShowAllMail bool
Autostart bool
AutoUpdate bool
LastVersion *semver.Version
FirstStart bool
FirstStartGUI bool
}
// UserData holds information about a single bridge user.
// The user may or may not be logged in.
type UserData struct {
UserID string
Username string
GluonID string
GluonKey []byte
BridgePass string
AuthUID string
AuthRef string
KeyPass []byte
EventID string
HasSync bool
}
func newDefaultSettings(gluonDir string) Settings {
return Settings{
GluonDir: gluonDir,
IMAPPort: 1143,
SMTPPort: 1025,
IMAPSSL: false,
SMTPSSL: false,
UpdateChannel: updater.DefaultUpdateChannel,
UpdateRollout: rand.Float64(),
ColorScheme: "",
ProxyAllowed: true,
ShowAllMail: true,
Autostart: false,
AutoUpdate: true,
LastVersion: semver.MustParse("0.0.0"),
FirstStart: true,
FirstStartGUI: true,
}
}

91
internal/vault/user.go Normal file
View File

@ -0,0 +1,91 @@
package vault
type User struct {
vault *Vault
userID string
}
func (user *User) UserID() string {
return user.vault.getUser(user.userID).UserID
}
func (user *User) Username() string {
return user.vault.getUser(user.userID).Username
}
func (user *User) GluonID() string {
return user.vault.getUser(user.userID).GluonID
}
func (user *User) GluonKey() []byte {
return user.vault.getUser(user.userID).GluonKey
}
func (user *User) BridgePass() string {
return user.vault.getUser(user.userID).BridgePass
}
func (user *User) AuthUID() string {
return user.vault.getUser(user.userID).AuthUID
}
func (user *User) AuthRef() string {
return user.vault.getUser(user.userID).AuthRef
}
func (user *User) KeyPass() []byte {
return user.vault.getUser(user.userID).KeyPass
}
func (user *User) EventID() string {
return user.vault.getUser(user.userID).EventID
}
func (user *User) HasSync() bool {
return user.vault.getUser(user.userID).HasSync
}
func (user *User) UpdateKeyPass(keyPass []byte) error {
return user.vault.modUser(user.userID, func(data *UserData) {
data.KeyPass = keyPass
})
}
// UpdateAuth updates the auth secrets for the given user.
func (user *User) UpdateAuth(authUID, authRef string) error {
return user.vault.modUser(user.userID, func(data *UserData) {
data.AuthUID = authUID
data.AuthRef = authRef
})
}
// UpdateGluonData updates the gluon ID and key for the given user.
func (user *User) UpdateGluonData(gluonID string, gluonKey []byte) error {
return user.vault.modUser(user.userID, func(data *UserData) {
data.GluonID = gluonID
data.GluonKey = gluonKey
})
}
// UpdateEventID updates the event ID for the given user.
func (user *User) UpdateEventID(eventID string) error {
return user.vault.modUser(user.userID, func(data *UserData) {
data.EventID = eventID
})
}
// UpdateSync updates the sync state for the given user.
func (user *User) UpdateSync(hasSync bool) error {
return user.vault.modUser(user.userID, func(data *UserData) {
data.HasSync = hasSync
})
}
// Clear clears the secrets for the given user.
func (user *User) Clear() error {
return user.vault.modUser(user.userID, func(data *UserData) {
data.AuthUID = ""
data.AuthRef = ""
data.KeyPass = nil
})
}

View File

@ -0,0 +1,84 @@
package vault_test
import (
"encoding/hex"
"testing"
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
"github.com/stretchr/testify/require"
)
func TestUser(t *testing.T) {
// Replace the token generator with a dummy one.
vault.RandomToken = func(size int) ([]byte, error) {
return []byte("token"), nil
}
// create a new test vault.
s := newVault(t)
// Set auth information for user 1 and 2.
user1, err := s.AddUser("userID1", "user1", "authUID1", "authRef1", []byte("keyPass1"))
require.NoError(t, err)
user2, err := s.AddUser("userID2", "user2", "authUID2", "authRef2", []byte("keyPass2"))
require.NoError(t, err)
// Set event IDs for user 1 and 2.
require.NoError(t, user1.UpdateEventID("eventID1"))
require.NoError(t, user2.UpdateEventID("eventID2"))
// Set sync state for user 1 and 2.
require.NoError(t, user1.UpdateSync(true))
require.NoError(t, user2.UpdateSync(false))
// Set gluon data for user 1 and 2.
require.NoError(t, user1.UpdateGluonData("gluonID1", []byte("gluonKey1")))
require.NoError(t, user2.UpdateGluonData("gluonID2", []byte("gluonKey2")))
// List available users.
require.ElementsMatch(t, []string{"userID1", "userID2"}, s.GetUserIDs())
// Get auth information for user 1.
require.Equal(t, "userID1", user1.UserID())
require.Equal(t, "user1", user1.Username())
require.Equal(t, "gluonID1", user1.GluonID())
require.Equal(t, []byte("gluonKey1"), user1.GluonKey())
require.Equal(t, hex.EncodeToString([]byte("token")), user1.BridgePass())
require.Equal(t, "authUID1", user1.AuthUID())
require.Equal(t, "authRef1", user1.AuthRef())
require.Equal(t, []byte("keyPass1"), user1.KeyPass())
require.Equal(t, "eventID1", user1.EventID())
require.Equal(t, true, user1.HasSync())
// Get auth information for user 2.
require.Equal(t, "userID2", user2.UserID())
require.Equal(t, "user2", user2.Username())
require.Equal(t, "gluonID2", user2.GluonID())
require.Equal(t, []byte("gluonKey2"), user2.GluonKey())
require.Equal(t, hex.EncodeToString([]byte("token")), user2.BridgePass())
require.Equal(t, "authUID2", user2.AuthUID())
require.Equal(t, "authRef2", user2.AuthRef())
require.Equal(t, []byte("keyPass2"), user2.KeyPass())
require.Equal(t, "eventID2", user2.EventID())
require.Equal(t, false, user2.HasSync())
// Clear the users.
require.NoError(t, user1.Clear())
require.NoError(t, user2.Clear())
// Their secrets should now be cleared.
require.Equal(t, "", user1.AuthUID())
require.Equal(t, "", user1.AuthRef())
require.Empty(t, user1.KeyPass())
// Get auth information for user 2.
require.Equal(t, "", user2.AuthUID())
require.Equal(t, "", user2.AuthRef())
require.Empty(t, user2.KeyPass())
// Delete auth information for user 1.
require.NoError(t, s.DeleteUser("userID1"))
// List available userIDs. User 1 should be gone.
require.ElementsMatch(t, []string{"userID2"}, s.GetUserIDs())
}