mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-17 23:56:56 +00:00
GODT-1779: Remove go-imap
This commit is contained in:
@ -22,7 +22,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/abiosoft/ishell"
|
||||
)
|
||||
|
||||
@ -35,6 +35,7 @@ func (f *frontendCLI) completeUsernames(args []string) (usernames []string) {
|
||||
if len(args) == 1 {
|
||||
arg = args[0]
|
||||
}
|
||||
|
||||
for _, userID := range f.bridge.GetUserIDs() {
|
||||
user, err := f.bridge.GetUserInfo(userID)
|
||||
if err != nil {
|
||||
@ -50,8 +51,7 @@ func (f *frontendCLI) completeUsernames(args []string) (usernames []string) {
|
||||
// noAccountWrapper is a decorator for functions which need any account to be properly functional.
|
||||
func (f *frontendCLI) noAccountWrapper(callback func(*ishell.Context)) func(*ishell.Context) {
|
||||
return func(c *ishell.Context) {
|
||||
users := f.bridge.GetUserIDs()
|
||||
if len(users) == 0 {
|
||||
if len(f.bridge.GetUserIDs()) == 0 {
|
||||
f.Println("No active accounts. Please add account to continue.")
|
||||
} else {
|
||||
callback(c)
|
||||
@ -59,9 +59,9 @@ func (f *frontendCLI) noAccountWrapper(callback func(*ishell.Context)) func(*ish
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) users.UserInfo {
|
||||
func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) bridge.UserInfo {
|
||||
user := f.getUserByIndexOrName("")
|
||||
if user.ID != "" {
|
||||
if user.UserID != "" {
|
||||
return user
|
||||
}
|
||||
|
||||
@ -69,24 +69,24 @@ func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) users.UserInfo {
|
||||
indexRange := fmt.Sprintf("number between 0 and %d", numberOfAccounts-1)
|
||||
if len(c.Args) == 0 {
|
||||
f.Printf("Please choose %s or username.\n", indexRange)
|
||||
return users.UserInfo{}
|
||||
return bridge.UserInfo{}
|
||||
}
|
||||
arg := c.Args[0]
|
||||
user = f.getUserByIndexOrName(arg)
|
||||
if user.ID == "" {
|
||||
if user.UserID == "" {
|
||||
f.Printf("Wrong input '%s'. Choose %s or username.\n", bold(arg), indexRange)
|
||||
return users.UserInfo{}
|
||||
return bridge.UserInfo{}
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func (f *frontendCLI) getUserByIndexOrName(arg string) users.UserInfo {
|
||||
func (f *frontendCLI) getUserByIndexOrName(arg string) bridge.UserInfo {
|
||||
userIDs := f.bridge.GetUserIDs()
|
||||
numberOfAccounts := len(userIDs)
|
||||
if numberOfAccounts == 0 {
|
||||
return users.UserInfo{}
|
||||
return bridge.UserInfo{}
|
||||
}
|
||||
res := make([]users.UserInfo, len(userIDs))
|
||||
res := make([]bridge.UserInfo, len(userIDs))
|
||||
for idx, userID := range userIDs {
|
||||
user, err := f.bridge.GetUserInfo(userID)
|
||||
if err != nil {
|
||||
@ -99,7 +99,7 @@ func (f *frontendCLI) getUserByIndexOrName(arg string) users.UserInfo {
|
||||
}
|
||||
if index, err := strconv.Atoi(arg); err == nil {
|
||||
if index < 0 || index >= numberOfAccounts {
|
||||
return users.UserInfo{}
|
||||
return bridge.UserInfo{}
|
||||
}
|
||||
return res[index]
|
||||
}
|
||||
@ -108,5 +108,5 @@ func (f *frontendCLI) getUserByIndexOrName(arg string) users.UserInfo {
|
||||
return user
|
||||
}
|
||||
}
|
||||
return users.UserInfo{}
|
||||
return bridge.UserInfo{}
|
||||
}
|
||||
|
||||
@ -22,8 +22,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
|
||||
"github.com/abiosoft/ishell"
|
||||
)
|
||||
|
||||
@ -40,7 +39,7 @@ func (f *frontendCLI) listAccounts(c *ishell.Context) {
|
||||
connected = "connected"
|
||||
}
|
||||
mode := "split"
|
||||
if user.Mode == users.CombinedMode {
|
||||
if user.AddressMode == bridge.CombinedMode {
|
||||
mode = "combined"
|
||||
}
|
||||
f.Printf(spacing, idx, user.Username, connected, mode)
|
||||
@ -50,7 +49,7 @@ func (f *frontendCLI) listAccounts(c *ishell.Context) {
|
||||
|
||||
func (f *frontendCLI) showAccountInfo(c *ishell.Context) {
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user.ID == "" {
|
||||
if user.UserID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
@ -59,8 +58,8 @@ func (f *frontendCLI) showAccountInfo(c *ishell.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if user.Mode == users.CombinedMode {
|
||||
f.showAccountAddressInfo(user, user.Addresses[user.Primary])
|
||||
if user.AddressMode == bridge.CombinedMode {
|
||||
f.showAccountAddressInfo(user, user.Addresses[0])
|
||||
} else {
|
||||
for _, address := range user.Addresses {
|
||||
f.showAccountAddressInfo(user, address)
|
||||
@ -68,25 +67,31 @@ func (f *frontendCLI) showAccountInfo(c *ishell.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) showAccountAddressInfo(user users.UserInfo, address string) {
|
||||
func (f *frontendCLI) showAccountAddressInfo(user bridge.UserInfo, address string) {
|
||||
imapSecurity := "STARTTLS"
|
||||
if f.bridge.GetIMAPSSL() {
|
||||
imapSecurity = "SSL"
|
||||
}
|
||||
|
||||
smtpSecurity := "STARTTLS"
|
||||
if f.bridge.GetBool(settings.SMTPSSLKey) {
|
||||
if f.bridge.GetSMTPSSL() {
|
||||
smtpSecurity = "SSL"
|
||||
}
|
||||
|
||||
f.Println(bold("Configuration for " + address))
|
||||
f.Printf("IMAP Settings\nAddress: %s\nIMAP port: %d\nUsername: %s\nPassword: %s\nSecurity: %s\n",
|
||||
bridge.Host,
|
||||
f.bridge.GetInt(settings.IMAPPortKey),
|
||||
constants.Host,
|
||||
f.bridge.GetIMAPPort(),
|
||||
address,
|
||||
user.Password,
|
||||
"STARTTLS",
|
||||
user.BridgePass,
|
||||
imapSecurity,
|
||||
)
|
||||
f.Println("")
|
||||
f.Printf("SMTP Settings\nAddress: %s\nSMTP port: %d\nUsername: %s\nPassword: %s\nSecurity: %s\n",
|
||||
bridge.Host,
|
||||
f.bridge.GetInt(settings.SMTPPortKey),
|
||||
constants.Host,
|
||||
f.bridge.GetSMTPPort(),
|
||||
address,
|
||||
user.Password,
|
||||
user.BridgePass,
|
||||
smtpSecurity,
|
||||
)
|
||||
f.Println("")
|
||||
@ -99,8 +104,8 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { //nolint:funlen
|
||||
loginName := ""
|
||||
if len(c.Args) > 0 {
|
||||
user := f.getUserByIndexOrName(c.Args[0])
|
||||
if user.ID != "" {
|
||||
loginName = user.Addresses[user.Primary]
|
||||
if user.UserID != "" {
|
||||
loginName = user.Addresses[0]
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,41 +124,23 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { //nolint:funlen
|
||||
}
|
||||
|
||||
f.Println("Authenticating ... ")
|
||||
client, auth, err := f.bridge.Login(loginName, []byte(password))
|
||||
|
||||
userID, err := f.bridge.LoginUser(
|
||||
context.Background(),
|
||||
loginName,
|
||||
password,
|
||||
func() (string, error) {
|
||||
return f.readStringInAttempts("Two factor code", c.ReadLine, isNotEmpty), nil
|
||||
},
|
||||
func() ([]byte, error) {
|
||||
return []byte(f.readStringInAttempts("Mailbox password", c.ReadPassword, isNotEmpty)), nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if auth.HasTwoFactor() {
|
||||
twoFactor := f.readStringInAttempts("Two factor code", c.ReadLine, isNotEmpty)
|
||||
if twoFactor == "" {
|
||||
return
|
||||
}
|
||||
|
||||
err = client.Auth2FA(context.Background(), twoFactor)
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
mailboxPassword := password
|
||||
if auth.HasMailboxPassword() {
|
||||
mailboxPassword = f.readStringInAttempts("Mailbox password", c.ReadPassword, isNotEmpty)
|
||||
}
|
||||
if mailboxPassword == "" {
|
||||
return
|
||||
}
|
||||
|
||||
f.Println("Adding account ...")
|
||||
userID, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword))
|
||||
if err != nil {
|
||||
log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful")
|
||||
f.Println("Adding account was unsuccessful:", err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := f.bridge.GetUserInfo(userID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -167,11 +154,12 @@ func (f *frontendCLI) logoutAccount(c *ishell.Context) {
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user.ID == "" {
|
||||
if user.UserID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to logout account " + bold(user.Username)) {
|
||||
if err := f.bridge.LogoutUser(user.ID); err != nil {
|
||||
if err := f.bridge.LogoutUser(context.Background(), user.UserID); err != nil {
|
||||
f.printAndLogError("Logging out failed: ", err)
|
||||
}
|
||||
}
|
||||
@ -182,12 +170,12 @@ func (f *frontendCLI) deleteAccount(c *ishell.Context) {
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user.ID == "" {
|
||||
if user.UserID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to " + bold("remove account "+user.Username)) {
|
||||
clearCache := f.yesNoQuestion("Do you want to remove cache for this account")
|
||||
if err := f.bridge.DeleteUser(user.ID, clearCache); err != nil {
|
||||
if err := f.bridge.DeleteUser(context.Background(), user.UserID); err != nil {
|
||||
f.printAndLogError("Cannot delete account: ", err)
|
||||
return
|
||||
}
|
||||
@ -205,10 +193,13 @@ func (f *frontendCLI) deleteAccounts(c *ishell.Context) {
|
||||
for _, userID := range f.bridge.GetUserIDs() {
|
||||
user, err := f.bridge.GetUserInfo(userID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
f.printAndLogError("Cannot get user info: ", err)
|
||||
return
|
||||
}
|
||||
if err := f.bridge.DeleteUser(user.ID, false); err != nil {
|
||||
|
||||
if err := f.bridge.DeleteUser(context.Background(), user.UserID); err != nil {
|
||||
f.printAndLogError("Cannot delete account ", user.Username, ": ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,37 +214,50 @@ func (f *frontendCLI) deleteEverything(c *ishell.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
f.bridge.FactoryReset()
|
||||
f.bridge.FactoryReset(context.Background())
|
||||
|
||||
c.Println("Everything cleared")
|
||||
|
||||
// Clearing data removes everything (db, preferences, ...) so everything has to be stopped and started again.
|
||||
f.restarter.SetToRestart()
|
||||
|
||||
f.Stop()
|
||||
}
|
||||
|
||||
func (f *frontendCLI) changeMode(c *ishell.Context) {
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user.ID == "" {
|
||||
if user.UserID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var targetMode users.AddressMode
|
||||
var targetMode bridge.AddressMode
|
||||
|
||||
if user.Mode == users.CombinedMode {
|
||||
targetMode = users.SplitMode
|
||||
if user.AddressMode == bridge.CombinedMode {
|
||||
targetMode = bridge.SplitMode
|
||||
} else {
|
||||
targetMode = users.CombinedMode
|
||||
targetMode = bridge.CombinedMode
|
||||
}
|
||||
|
||||
if !f.yesNoQuestion("Are you sure you want to change the mode for account " + bold(user.Username) + " to " + bold(targetMode)) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := f.bridge.SetAddressMode(user.ID, targetMode); err != nil {
|
||||
if err := f.bridge.SetAddressMode(user.UserID, targetMode); err != nil {
|
||||
f.printAndLogError("Cannot switch address mode:", err)
|
||||
}
|
||||
|
||||
f.Printf("Address mode for account %s changed to %s\n", user.Username, targetMode)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) configureAppleMail(c *ishell.Context) {
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user.UserID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if !f.yesNoQuestion("Are you sure you want to configure Apple Mail for " + bold(user.Username) + " with address " + bold(user.Addresses[0])) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := f.bridge.ConfigureAppleMail(user.UserID, user.Addresses[0]); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
f.Printf("Apple Mail configured for %v with address %v\n", user.Username, user.Addresses[0])
|
||||
}
|
||||
|
||||
@ -19,11 +19,13 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/listener"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
|
||||
"gitlab.protontech.ch/go/liteapi"
|
||||
|
||||
"github.com/abiosoft/ishell"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -34,30 +36,14 @@ var log = logrus.WithField("pkg", "frontend/cli") //nolint:gochecknoglobals
|
||||
type frontendCLI struct {
|
||||
*ishell.Shell
|
||||
|
||||
eventListener listener.Listener
|
||||
updater types.Updater
|
||||
bridge types.Bridger
|
||||
|
||||
restarter types.Restarter
|
||||
bridge *bridge.Bridge
|
||||
}
|
||||
|
||||
// New returns a new CLI frontend configured with the given options.
|
||||
func New( //nolint:funlen
|
||||
panicHandler types.PanicHandler,
|
||||
|
||||
eventListener listener.Listener,
|
||||
updater types.Updater,
|
||||
bridge types.Bridger,
|
||||
restarter types.Restarter,
|
||||
) *frontendCLI { //nolint:revive
|
||||
func New(bridge *bridge.Bridge) *frontendCLI {
|
||||
fe := &frontendCLI{
|
||||
Shell: ishell.New(),
|
||||
|
||||
eventListener: eventListener,
|
||||
updater: updater,
|
||||
bridge: bridge,
|
||||
|
||||
restarter: restarter,
|
||||
Shell: ishell.New(),
|
||||
bridge: bridge,
|
||||
}
|
||||
|
||||
// Clear commands.
|
||||
@ -66,12 +52,6 @@ func New( //nolint:funlen
|
||||
Help: "remove stored accounts and preferences. (alias: cl)",
|
||||
Aliases: []string{"cl"},
|
||||
}
|
||||
clearCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "cache",
|
||||
Help: "remove stored preferences for accounts (aliases: c, prefs, preferences)",
|
||||
Aliases: []string{"c", "prefs", "preferences"},
|
||||
Func: fe.deleteCache,
|
||||
})
|
||||
clearCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "accounts",
|
||||
Help: "remove all accounts from keychain. (aliases: a, k, keychain)",
|
||||
@ -100,15 +80,30 @@ func New( //nolint:funlen
|
||||
Completer: fe.completeUsernames,
|
||||
})
|
||||
changeCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "port",
|
||||
Help: "change port numbers of IMAP and SMTP servers. (alias: p)",
|
||||
Aliases: []string{"p"},
|
||||
Func: fe.changePort,
|
||||
Name: "change-location",
|
||||
Help: "change the location of the encrypted message cache",
|
||||
Func: fe.setGluonLocation,
|
||||
})
|
||||
changeCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "imap-port",
|
||||
Help: "change port number of IMAP server.",
|
||||
Func: fe.changeIMAPPort,
|
||||
})
|
||||
changeCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "smtp-port",
|
||||
Help: "change port number of SMTP server.",
|
||||
Func: fe.changeSMTPPort,
|
||||
})
|
||||
changeCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "imap-security",
|
||||
Help: "change IMAP SSL settings servers.(alias: ssl-imap, starttls-imap)",
|
||||
Aliases: []string{"ssl-imap", "starttls-imap"},
|
||||
Func: fe.changeIMAPSecurity,
|
||||
})
|
||||
changeCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "smtp-security",
|
||||
Help: "change port numbers of IMAP and SMTP servers.(alias: ssl, starttls)",
|
||||
Aliases: []string{"ssl", "starttls"},
|
||||
Help: "change SMTP SSL settings servers.(alias: ssl-smtp, starttls-smtp)",
|
||||
Aliases: []string{"ssl-smtp", "starttls-smtp"},
|
||||
Func: fe.changeSMTPSecurity,
|
||||
})
|
||||
fe.AddCmd(changeCmd)
|
||||
@ -130,6 +125,22 @@ func New( //nolint:funlen
|
||||
})
|
||||
fe.AddCmd(dohCmd)
|
||||
|
||||
// Apple Mail commands.
|
||||
configureCmd := &ishell.Cmd{
|
||||
Name: "configure-apple-mail",
|
||||
Help: "Configures Apple Mail to use ProtonMail Bridge",
|
||||
Func: fe.configureAppleMail,
|
||||
}
|
||||
fe.AddCmd(configureCmd)
|
||||
|
||||
// TLS commands.
|
||||
exportTLSCmd := &ishell.Cmd{
|
||||
Name: "export-tls",
|
||||
Help: "Export the TLS certificate used by the Bridge",
|
||||
Func: fe.exportTLSCerts,
|
||||
}
|
||||
fe.AddCmd(exportTLSCmd)
|
||||
|
||||
// All mail visibility commands.
|
||||
allMailCmd := &ishell.Cmd{
|
||||
Name: "all-mail-visibility",
|
||||
@ -147,28 +158,6 @@ func New( //nolint:funlen
|
||||
})
|
||||
fe.AddCmd(allMailCmd)
|
||||
|
||||
// Cache-On-Disk commands.
|
||||
codCmd := &ishell.Cmd{
|
||||
Name: "local-cache",
|
||||
Help: "manage the local encrypted message cache",
|
||||
}
|
||||
codCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "enable",
|
||||
Help: "enable the local cache",
|
||||
Func: fe.enableCacheOnDisk,
|
||||
})
|
||||
codCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "disable",
|
||||
Help: "disable the local cache",
|
||||
Func: fe.disableCacheOnDisk,
|
||||
})
|
||||
codCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "change-location",
|
||||
Help: "change the location of the local cache",
|
||||
Func: fe.setCacheOnDiskLocation,
|
||||
})
|
||||
fe.AddCmd(codCmd)
|
||||
|
||||
// Updates commands.
|
||||
updatesCmd := &ishell.Cmd{
|
||||
Name: "updates",
|
||||
@ -224,7 +213,6 @@ func New( //nolint:funlen
|
||||
Aliases: []string{"man"},
|
||||
Func: fe.printManual,
|
||||
})
|
||||
|
||||
fe.AddCmd(&ishell.Cmd{
|
||||
Name: "credits",
|
||||
Help: "print used resources.",
|
||||
@ -267,55 +255,122 @@ func New( //nolint:funlen
|
||||
Completer: fe.completeUsernames,
|
||||
})
|
||||
|
||||
// System commands.
|
||||
fe.AddCmd(&ishell.Cmd{
|
||||
Name: "restart",
|
||||
Help: "restart the bridge.",
|
||||
Func: fe.restart,
|
||||
})
|
||||
go fe.watchEvents()
|
||||
|
||||
go func() {
|
||||
defer panicHandler.HandlePanic()
|
||||
fe.watchEvents()
|
||||
}()
|
||||
return fe
|
||||
}
|
||||
|
||||
func (f *frontendCLI) watchEvents() {
|
||||
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
|
||||
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
|
||||
internetConnChangedCh := f.eventListener.ProvideChannel(events.InternetConnChangedEvent)
|
||||
addressChangedCh := f.eventListener.ProvideChannel(events.AddressChangedEvent)
|
||||
addressChangedLogoutCh := f.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
|
||||
logoutCh := f.eventListener.ProvideChannel(events.LogoutEvent)
|
||||
certIssue := f.eventListener.ProvideChannel(events.TLSCertIssue)
|
||||
for {
|
||||
select {
|
||||
case errorDetails := <-errorCh:
|
||||
f.Println("Bridge failed:", errorDetails)
|
||||
case <-credentialsErrorCh:
|
||||
eventCh, done := f.bridge.GetEvents()
|
||||
defer done()
|
||||
|
||||
// TODO: Better error events.
|
||||
for _, err := range f.bridge.GetErrors() {
|
||||
switch {
|
||||
case errors.Is(err, vault.ErrCorrupt):
|
||||
f.notifyCredentialsError()
|
||||
case stat := <-internetConnChangedCh:
|
||||
if stat == events.InternetOff {
|
||||
|
||||
case errors.Is(err, vault.ErrInsecure):
|
||||
f.notifyCredentialsError()
|
||||
|
||||
case errors.Is(err, bridge.ErrServeIMAP):
|
||||
f.Println("IMAP server error:", err)
|
||||
|
||||
case errors.Is(err, bridge.ErrServeSMTP):
|
||||
f.Println("SMTP server error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
for event := range eventCh {
|
||||
switch event := event.(type) {
|
||||
case events.ConnStatus:
|
||||
switch event.Status {
|
||||
case liteapi.StatusUp:
|
||||
f.notifyInternetOn()
|
||||
|
||||
case liteapi.StatusDown:
|
||||
f.notifyInternetOff()
|
||||
}
|
||||
if stat == events.InternetOn {
|
||||
f.notifyInternetOn()
|
||||
}
|
||||
case address := <-addressChangedCh:
|
||||
f.Printf("Address changed for %s. You may need to reconfigure your email client.", address)
|
||||
case address := <-addressChangedLogoutCh:
|
||||
f.notifyLogout(address)
|
||||
case userID := <-logoutCh:
|
||||
user, err := f.bridge.GetUserInfo(userID)
|
||||
|
||||
case events.UserDeauth:
|
||||
user, err := f.bridge.GetUserInfo(event.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f.notifyLogout(user.Username)
|
||||
case <-certIssue:
|
||||
|
||||
case events.UserAddressChanged:
|
||||
user, err := f.bridge.GetUserInfo(event.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f.Printf("Address changed for %s. You may need to reconfigure your email client.\n", user.Username)
|
||||
|
||||
case events.UserAddressDeleted:
|
||||
f.notifyLogout(event.Address)
|
||||
|
||||
case events.SyncStarted:
|
||||
user, err := f.bridge.GetUserInfo(event.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f.Printf("A sync has begun for %s.\n", user.Username)
|
||||
|
||||
case events.SyncFinished:
|
||||
user, err := f.bridge.GetUserInfo(event.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f.Printf("A sync has finished for %s.\n", user.Username)
|
||||
|
||||
case events.SyncProgress:
|
||||
user, err := f.bridge.GetUserInfo(event.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f.Printf(
|
||||
"Sync (%v): %.1f%% (Elapsed: %0.1fs, ETA: %0.1fs)\n",
|
||||
user.Username,
|
||||
100*event.Progress,
|
||||
event.Elapsed.Seconds(),
|
||||
event.Remaining.Seconds(),
|
||||
)
|
||||
|
||||
case events.UpdateAvailable:
|
||||
f.Printf("An update is available (version %v)\n", event.Version.Version)
|
||||
|
||||
case events.UpdateForced:
|
||||
f.notifyNeedUpgrade()
|
||||
|
||||
case events.TLSIssue:
|
||||
f.notifyCertIssue()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
|
||||
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
|
||||
for {
|
||||
select {
|
||||
case errorDetails := <-errorCh:
|
||||
f.Println("Bridge failed:", errorDetails)
|
||||
case <-credentialsErrorCh:
|
||||
f.notifyCredentialsError()
|
||||
case stat := <-internetConnChangedCh:
|
||||
if stat == events.InternetOff {
|
||||
f.notifyInternetOff()
|
||||
}
|
||||
if stat == events.InternetOn {
|
||||
f.notifyInternetOn()
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// Loop starts the frontend loop with an interactive shell.
|
||||
@ -340,12 +395,3 @@ func (f *frontendCLI) Loop() error {
|
||||
f.Run()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *frontendCLI) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {
|
||||
// NOTE: Save the update somewhere so that it can be installed when user chooses "install now".
|
||||
}
|
||||
|
||||
func (f *frontendCLI) WaitUntilFrontendIsReady() {}
|
||||
func (f *frontendCLI) SetVersion(version updater.VersionInfo) {}
|
||||
func (f *frontendCLI) NotifySilentUpdateInstalled() {}
|
||||
func (f *frontendCLI) NotifySilentUpdateError(err error) {}
|
||||
|
||||
@ -18,28 +18,21 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/ports"
|
||||
"github.com/abiosoft/ishell"
|
||||
)
|
||||
|
||||
var currentPort = "" //nolint:gochecknoglobals
|
||||
|
||||
func (f *frontendCLI) restart(c *ishell.Context) {
|
||||
if f.yesNoQuestion("Are you sure you want to restart the Bridge") {
|
||||
f.Println("Restarting Bridge...")
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printLogDir(c *ishell.Context) {
|
||||
if path, err := f.bridge.ProvideLogsPath(); err != nil {
|
||||
if path, err := f.bridge.GetLogsPath(); err != nil {
|
||||
f.Println("Failed to determine location of log files")
|
||||
} else {
|
||||
f.Println("Log files are stored in\n\n ", path)
|
||||
@ -50,79 +43,91 @@ func (f *frontendCLI) printManual(c *ishell.Context) {
|
||||
f.Println("More instructions about the Bridge can be found at\n\n https://protonmail.com/bridge")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) deleteCache(c *ishell.Context) {
|
||||
func (f *frontendCLI) printCredits(c *ishell.Context) {
|
||||
for _, pkg := range strings.Split(bridge.Credits, ";") {
|
||||
f.Println(pkg)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) changeIMAPSecurity(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
if !f.yesNoQuestion("Do you really want to remove all stored preferences") {
|
||||
return
|
||||
newSecurity := "SSL"
|
||||
if f.bridge.GetIMAPSSL() {
|
||||
newSecurity = "STARTTLS"
|
||||
}
|
||||
|
||||
if err := f.bridge.ClearData(); err != nil {
|
||||
f.printAndLogError("Cache clear failed: ", err.Error())
|
||||
return
|
||||
msg := fmt.Sprintf("Are you sure you want to change IMAP setting to %q", newSecurity)
|
||||
|
||||
if f.yesNoQuestion(msg) {
|
||||
if err := f.bridge.SetIMAPSSL(!f.bridge.GetIMAPSSL()); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
f.Println("Cached cleared, restarting bridge")
|
||||
|
||||
// Clearing data removes everything (db, preferences, ...) so everything has to be stopped and started again.
|
||||
f.restarter.SetToRestart()
|
||||
|
||||
f.Stop()
|
||||
}
|
||||
|
||||
func (f *frontendCLI) changeSMTPSecurity(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
isSSL := f.bridge.GetBool(settings.SMTPSSLKey)
|
||||
newSecurity := "SSL"
|
||||
if isSSL {
|
||||
if f.bridge.GetSMTPSSL() {
|
||||
newSecurity = "STARTTLS"
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("Are you sure you want to change SMTP setting to %q and restart the Bridge", newSecurity)
|
||||
msg := fmt.Sprintf("Are you sure you want to change SMTP setting to %q", newSecurity)
|
||||
|
||||
if f.yesNoQuestion(msg) {
|
||||
f.bridge.SetBool(settings.SMTPSSLKey, !isSSL)
|
||||
f.Println("Restarting Bridge...")
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
if err := f.bridge.SetSMTPSSL(!f.bridge.GetSMTPSSL()); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) changePort(c *ishell.Context) {
|
||||
func (f *frontendCLI) changeIMAPPort(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
currentPort = f.bridge.Get(settings.IMAPPortKey)
|
||||
newIMAPPort := f.readStringInAttempts("Set IMAP port (current "+currentPort+")", c.ReadLine, f.isPortFree)
|
||||
newIMAPPort := f.readStringInAttempts(fmt.Sprintf("Set IMAP port (current %v)", f.bridge.GetIMAPPort()), c.ReadLine, f.isPortFree)
|
||||
if newIMAPPort == "" {
|
||||
newIMAPPort = currentPort
|
||||
}
|
||||
imapPortChanged := newIMAPPort != currentPort
|
||||
|
||||
currentPort = f.bridge.Get(settings.SMTPPortKey)
|
||||
newSMTPPort := f.readStringInAttempts("Set SMTP port (current "+currentPort+")", c.ReadLine, f.isPortFree)
|
||||
if newSMTPPort == "" {
|
||||
newSMTPPort = currentPort
|
||||
}
|
||||
smtpPortChanged := newSMTPPort != currentPort
|
||||
|
||||
if newIMAPPort == newSMTPPort {
|
||||
f.Println("SMTP and IMAP ports must be different!")
|
||||
f.printAndLogError(errors.New("failed to get new port"))
|
||||
return
|
||||
}
|
||||
|
||||
if imapPortChanged || smtpPortChanged {
|
||||
f.Println("Saving values IMAP:", newIMAPPort, "SMTP:", newSMTPPort)
|
||||
f.bridge.Set(settings.IMAPPortKey, newIMAPPort)
|
||||
f.bridge.Set(settings.SMTPPortKey, newSMTPPort)
|
||||
f.Println("Restarting Bridge...")
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
} else {
|
||||
f.Println("Nothing changed")
|
||||
newIMAPPortInt, err := strconv.Atoi(newIMAPPort)
|
||||
if err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := f.bridge.SetIMAPPort(newIMAPPortInt); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) changeSMTPPort(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
newSMTPPort := f.readStringInAttempts(fmt.Sprintf("Set SMTP port (current %v)", f.bridge.GetSMTPPort()), c.ReadLine, f.isPortFree)
|
||||
if newSMTPPort == "" {
|
||||
f.printAndLogError(errors.New("failed to get new port"))
|
||||
return
|
||||
}
|
||||
|
||||
newSMTPPortInt, err := strconv.Atoi(newSMTPPort)
|
||||
if err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := f.bridge.SetSMTPPort(newSMTPPortInt); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,7 +140,10 @@ func (f *frontendCLI) allowProxy(c *ishell.Context) {
|
||||
f.Println("Bridge is currently set to NOT use alternative routing to connect to Proton if it is being blocked.")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to allow bridge to do this") {
|
||||
f.bridge.SetProxyAllowed(true)
|
||||
if err := f.bridge.SetProxyAllowed(true); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,12 +156,15 @@ func (f *frontendCLI) disallowProxy(c *ishell.Context) {
|
||||
f.Println("Bridge is currently set to use alternative routing to connect to Proton if it is being blocked.")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to stop bridge from doing this") {
|
||||
f.bridge.SetProxyAllowed(false)
|
||||
if err := f.bridge.SetProxyAllowed(false); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) hideAllMail(c *ishell.Context) {
|
||||
if !f.bridge.IsAllMailVisible() {
|
||||
if !f.bridge.GetShowAllMail() {
|
||||
f.Println("All Mail folder is not listed in your local client.")
|
||||
return
|
||||
}
|
||||
@ -161,12 +172,15 @@ func (f *frontendCLI) hideAllMail(c *ishell.Context) {
|
||||
f.Println("All Mail folder is listed in your client right now.")
|
||||
|
||||
if f.yesNoQuestion("Do you want to hide All Mail folder") {
|
||||
f.bridge.SetIsAllMailVisible(false)
|
||||
if err := f.bridge.SetShowAllMail(false); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) showAllMail(c *ishell.Context) {
|
||||
if f.bridge.IsAllMailVisible() {
|
||||
if f.bridge.GetShowAllMail() {
|
||||
f.Println("All Mail folder is listed in your local client.")
|
||||
return
|
||||
}
|
||||
@ -174,68 +188,47 @@ func (f *frontendCLI) showAllMail(c *ishell.Context) {
|
||||
f.Println("All Mail folder is not listed in your client right now.")
|
||||
|
||||
if f.yesNoQuestion("Do you want to show All Mail folder") {
|
||||
f.bridge.SetIsAllMailVisible(true)
|
||||
if err := f.bridge.SetShowAllMail(true); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) enableCacheOnDisk(c *ishell.Context) {
|
||||
if f.bridge.GetBool(settings.CacheEnabledKey) {
|
||||
f.Println("The local cache is already enabled.")
|
||||
return
|
||||
func (f *frontendCLI) setGluonLocation(c *ishell.Context) {
|
||||
if gluonDir := f.bridge.GetGluonDir(); gluonDir != "" {
|
||||
f.Println("The current message cache location is:", gluonDir)
|
||||
}
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to enable the local cache") {
|
||||
if err := f.bridge.EnableCache(); err != nil {
|
||||
f.Println("The local cache could not be enabled.")
|
||||
if location := f.readStringInAttempts("Enter a new location for the message cache", c.ReadLine, f.isCacheLocationUsable); location != "" {
|
||||
if err := f.bridge.SetGluonDir(context.Background(), location); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) disableCacheOnDisk(c *ishell.Context) {
|
||||
if !f.bridge.GetBool(settings.CacheEnabledKey) {
|
||||
f.Println("The local cache is already disabled.")
|
||||
return
|
||||
}
|
||||
func (f *frontendCLI) exportTLSCerts(c *ishell.Context) {
|
||||
if location := f.readStringInAttempts("Enter a path to which to export the TLS certificate used for IMAP and SMTP", c.ReadLine, f.isCacheLocationUsable); location != "" {
|
||||
cert, key := f.bridge.GetBridgeTLSCert()
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to disable the local cache") {
|
||||
if err := f.bridge.DisableCache(); err != nil {
|
||||
f.Println("The local cache could not be disabled.")
|
||||
if err := os.WriteFile(filepath.Join(location, "cert.pem"), cert, 0600); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) setCacheOnDiskLocation(c *ishell.Context) {
|
||||
if !f.bridge.GetBool(settings.CacheEnabledKey) {
|
||||
f.Println("The local cache must be enabled.")
|
||||
return
|
||||
}
|
||||
|
||||
if location := f.bridge.Get(settings.CacheLocationKey); location != "" {
|
||||
f.Println("The current local cache location is:", location)
|
||||
}
|
||||
|
||||
if location := f.readStringInAttempts("Enter a new location for the cache", c.ReadLine, f.isCacheLocationUsable); location != "" {
|
||||
if err := f.bridge.MigrateCache(f.bridge.Get(settings.CacheLocationKey), location); err != nil {
|
||||
f.Println("The local cache location could not be changed.")
|
||||
if err := os.WriteFile(filepath.Join(location, "key.pem"), key, 0600); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
f.Println("TLS certificate exported to", location)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) isPortFree(port string) bool {
|
||||
port = strings.ReplaceAll(port, ":", "")
|
||||
if port == "" || port == currentPort {
|
||||
if port == "" {
|
||||
return true
|
||||
}
|
||||
number, err := strconv.Atoi(port)
|
||||
|
||||
@ -18,36 +18,16 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||
"github.com/abiosoft/ishell"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) checkUpdates(c *ishell.Context) {
|
||||
version, err := f.updater.Check()
|
||||
if err != nil {
|
||||
f.Println("An error occurred while checking for updates.")
|
||||
return
|
||||
}
|
||||
|
||||
if f.updater.IsUpdateApplicable(version) {
|
||||
f.Println("An update is available.")
|
||||
} else {
|
||||
f.Println("Your version is up to date.")
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printCredits(c *ishell.Context) {
|
||||
for _, pkg := range strings.Split(bridge.Credits, ";") {
|
||||
f.Println(pkg)
|
||||
}
|
||||
f.bridge.CheckForUpdates()
|
||||
}
|
||||
|
||||
func (f *frontendCLI) enableAutoUpdates(c *ishell.Context) {
|
||||
if f.bridge.GetBool(settings.AutoUpdateKey) {
|
||||
if f.bridge.GetAutoUpdate() {
|
||||
f.Println("Bridge is already set to automatically install updates.")
|
||||
return
|
||||
}
|
||||
@ -55,12 +35,15 @@ func (f *frontendCLI) enableAutoUpdates(c *ishell.Context) {
|
||||
f.Println("Bridge is currently set to NOT automatically install updates.")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to allow bridge to do this") {
|
||||
f.bridge.SetBool(settings.AutoUpdateKey, true)
|
||||
if err := f.bridge.SetAutoUpdate(true); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) disableAutoUpdates(c *ishell.Context) {
|
||||
if !f.bridge.GetBool(settings.AutoUpdateKey) {
|
||||
if !f.bridge.GetAutoUpdate() {
|
||||
f.Println("Bridge is already set to NOT automatically install updates.")
|
||||
return
|
||||
}
|
||||
@ -68,7 +51,10 @@ func (f *frontendCLI) disableAutoUpdates(c *ishell.Context) {
|
||||
f.Println("Bridge is currently set to automatically install updates.")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to stop bridge from doing this") {
|
||||
f.bridge.SetBool(settings.AutoUpdateKey, false)
|
||||
if err := f.bridge.SetAutoUpdate(false); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,7 +67,10 @@ func (f *frontendCLI) selectEarlyChannel(c *ishell.Context) {
|
||||
f.Println("Bridge is currently on the stable update channel.")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to switch to the early-access update channel") {
|
||||
f.bridge.SetUpdateChannel(updater.EarlyChannel)
|
||||
if err := f.bridge.SetUpdateChannel(updater.EarlyChannel); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,6 +84,9 @@ func (f *frontendCLI) selectStableChannel(c *ishell.Context) {
|
||||
f.Println("Switching to the stable channel may reset all data!")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to switch to the stable update channel") {
|
||||
f.bridge.SetUpdateChannel(updater.StableChannel)
|
||||
if err := f.bridge.SetUpdateChannel(updater.StableChannel); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,6 @@ package cli
|
||||
import (
|
||||
"strings"
|
||||
|
||||
pmapi "github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
@ -67,15 +66,7 @@ func (f *frontendCLI) printAndLogError(args ...interface{}) {
|
||||
}
|
||||
|
||||
func (f *frontendCLI) processAPIError(err error) {
|
||||
log.Warn("API error: ", err)
|
||||
switch err {
|
||||
case pmapi.ErrNoConnection:
|
||||
f.notifyInternetOff()
|
||||
case pmapi.ErrUpgradeApplication:
|
||||
f.notifyNeedUpgrade()
|
||||
default:
|
||||
f.Println("Server error:", err.Error())
|
||||
}
|
||||
f.printAndLogError(err)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyInternetOff() {
|
||||
@ -91,12 +82,7 @@ func (f *frontendCLI) notifyLogout(address string) {
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyNeedUpgrade() {
|
||||
version, err := f.updater.Check()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to notify need upgrade")
|
||||
return
|
||||
}
|
||||
f.Println("Please download and install the newest version of application from", version.LandingPage)
|
||||
f.Println("Please download and install the newest version of the application.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyCredentialsError() {
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package frontend provides all interfaces of the Bridge.
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/cli"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/grpc"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/listener"
|
||||
)
|
||||
|
||||
type Frontend interface {
|
||||
Loop() error
|
||||
NotifyManualUpdate(update updater.VersionInfo, canInstall bool)
|
||||
SetVersion(update updater.VersionInfo)
|
||||
NotifySilentUpdateInstalled()
|
||||
NotifySilentUpdateError(error)
|
||||
WaitUntilFrontendIsReady()
|
||||
}
|
||||
|
||||
// New returns initialized frontend based on `frontendType`, which can be `cli` or `grpc`.
|
||||
func New(
|
||||
frontendType string,
|
||||
showWindowOnStart bool,
|
||||
panicHandler types.PanicHandler,
|
||||
eventListener listener.Listener,
|
||||
updater types.Updater,
|
||||
bridge *bridge.Bridge,
|
||||
restarter types.Restarter,
|
||||
locations *locations.Locations,
|
||||
) Frontend {
|
||||
switch frontendType {
|
||||
case "grpc":
|
||||
return grpc.NewService(
|
||||
showWindowOnStart,
|
||||
panicHandler,
|
||||
eventListener,
|
||||
updater,
|
||||
bridge,
|
||||
restarter,
|
||||
locations,
|
||||
)
|
||||
|
||||
case "cli":
|
||||
return cli.New(
|
||||
panicHandler,
|
||||
eventListener,
|
||||
updater,
|
||||
bridge,
|
||||
restarter,
|
||||
)
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -72,7 +72,6 @@ service Bridge {
|
||||
rpc IsAutomaticUpdateOn(google.protobuf.Empty) returns (google.protobuf.BoolValue);
|
||||
|
||||
// cache
|
||||
rpc IsCacheOnDiskEnabled (google.protobuf.Empty) returns (google.protobuf.BoolValue);
|
||||
rpc DiskCachePath(google.protobuf.Empty) returns (google.protobuf.StringValue);
|
||||
rpc ChangeLocalCache(ChangeLocalCacheRequest) returns (google.protobuf.Empty);
|
||||
|
||||
@ -160,7 +159,6 @@ message LoginAbortRequest {
|
||||
// Cache on disk related message
|
||||
//**********************************************************
|
||||
message ChangeLocalCacheRequest {
|
||||
bool enableDiskCache = 1;
|
||||
string diskCachePath = 2;
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v3.21.3
|
||||
// - protoc v3.21.7
|
||||
// source: bridge.proto
|
||||
|
||||
package grpc
|
||||
@ -64,7 +64,6 @@ type BridgeClient interface {
|
||||
SetIsAutomaticUpdateOn(ctx context.Context, in *wrapperspb.BoolValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
IsAutomaticUpdateOn(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error)
|
||||
// cache
|
||||
IsCacheOnDiskEnabled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error)
|
||||
DiskCachePath(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error)
|
||||
ChangeLocalCache(ctx context.Context, in *ChangeLocalCacheRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
// mail
|
||||
@ -425,15 +424,6 @@ func (c *bridgeClient) IsAutomaticUpdateOn(ctx context.Context, in *emptypb.Empt
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *bridgeClient) IsCacheOnDiskEnabled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
|
||||
out := new(wrapperspb.BoolValue)
|
||||
err := c.cc.Invoke(ctx, "/grpc.Bridge/IsCacheOnDiskEnabled", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *bridgeClient) DiskCachePath(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
|
||||
out := new(wrapperspb.StringValue)
|
||||
err := c.cc.Invoke(ctx, "/grpc.Bridge/DiskCachePath", in, out, opts...)
|
||||
@ -699,7 +689,6 @@ type BridgeServer interface {
|
||||
SetIsAutomaticUpdateOn(context.Context, *wrapperspb.BoolValue) (*emptypb.Empty, error)
|
||||
IsAutomaticUpdateOn(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error)
|
||||
// cache
|
||||
IsCacheOnDiskEnabled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error)
|
||||
DiskCachePath(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error)
|
||||
ChangeLocalCache(context.Context, *ChangeLocalCacheRequest) (*emptypb.Empty, error)
|
||||
// mail
|
||||
@ -841,9 +830,6 @@ func (UnimplementedBridgeServer) SetIsAutomaticUpdateOn(context.Context, *wrappe
|
||||
func (UnimplementedBridgeServer) IsAutomaticUpdateOn(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method IsAutomaticUpdateOn not implemented")
|
||||
}
|
||||
func (UnimplementedBridgeServer) IsCacheOnDiskEnabled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method IsCacheOnDiskEnabled not implemented")
|
||||
}
|
||||
func (UnimplementedBridgeServer) DiskCachePath(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DiskCachePath not implemented")
|
||||
}
|
||||
@ -1571,24 +1557,6 @@ func _Bridge_IsAutomaticUpdateOn_Handler(srv interface{}, ctx context.Context, d
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Bridge_IsCacheOnDiskEnabled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BridgeServer).IsCacheOnDiskEnabled(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/grpc.Bridge/IsCacheOnDiskEnabled",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BridgeServer).IsCacheOnDiskEnabled(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Bridge_DiskCachePath_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
@ -2139,10 +2107,6 @@ var Bridge_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "IsAutomaticUpdateOn",
|
||||
Handler: _Bridge_IsAutomaticUpdateOn_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "IsCacheOnDiskEnabled",
|
||||
Handler: _Bridge_IsCacheOnDiskEnabled_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DiskCachePath",
|
||||
Handler: _Bridge_DiskCachePath_Handler,
|
||||
|
||||
32
internal/frontend/grpc/event_utils.go
Normal file
32
internal/frontend/grpc/event_utils.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package grpc
|
||||
|
||||
import "github.com/bradenaw/juniper/xslices"
|
||||
|
||||
// isInternetStatus returns true iff the event is InternetStatus.
|
||||
func (x *StreamEvent) isInternetStatus() bool {
|
||||
appEvent := x.GetApp()
|
||||
|
||||
return (appEvent != nil) && (appEvent.GetInternetStatus() != nil)
|
||||
}
|
||||
|
||||
// filterOutInternetStatusEvents return a copy of the events list where all internet connection events have been removed.
|
||||
func filterOutInternetStatusEvents(events []*StreamEvent) []*StreamEvent {
|
||||
return xslices.Filter(events, func(event *StreamEvent) bool { return !event.isInternetStatus() })
|
||||
}
|
||||
@ -21,34 +21,29 @@ package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
cryptotls "crypto/tls"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/tls"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/certs"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/crash"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/listener"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/restarter"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gitlab.protontech.ch/go/liteapi"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -59,6 +54,7 @@ const (
|
||||
// Service is the RPC service struct.
|
||||
type Service struct { // nolint:structcheck
|
||||
UnimplementedBridgeServer
|
||||
|
||||
grpcServer *grpc.Server // the gGRPC server
|
||||
listener net.Listener
|
||||
eventStreamCh chan *StreamEvent
|
||||
@ -66,99 +62,87 @@ type Service struct { // nolint:structcheck
|
||||
eventQueue []*StreamEvent
|
||||
eventQueueMutex sync.Mutex
|
||||
|
||||
panicHandler types.PanicHandler
|
||||
eventListener listener.Listener
|
||||
updater types.Updater
|
||||
updateCheckMutex sync.Mutex
|
||||
bridge types.Bridger
|
||||
restarter types.Restarter
|
||||
showOnStartup bool
|
||||
authClient pmapi.Client
|
||||
auth *pmapi.Auth
|
||||
password []byte
|
||||
newVersionInfo updater.VersionInfo
|
||||
panicHandler *crash.Handler
|
||||
restarter *restarter.Restarter
|
||||
bridge *bridge.Bridge
|
||||
newVersionInfo updater.VersionInfo
|
||||
|
||||
log *logrus.Entry
|
||||
initializing sync.WaitGroup
|
||||
initializationDone sync.Once
|
||||
firstTimeAutostart sync.Once
|
||||
locations *locations.Locations
|
||||
token string
|
||||
pemCert string
|
||||
|
||||
showOnStartup bool
|
||||
}
|
||||
|
||||
// NewService returns a new instance of the service.
|
||||
func NewService(
|
||||
showOnStartup bool,
|
||||
panicHandler types.PanicHandler,
|
||||
eventListener listener.Listener,
|
||||
updater types.Updater,
|
||||
bridge types.Bridger,
|
||||
restarter types.Restarter,
|
||||
panicHandler *crash.Handler,
|
||||
restarter *restarter.Restarter,
|
||||
locations *locations.Locations,
|
||||
) *Service {
|
||||
s := Service{
|
||||
UnimplementedBridgeServer: UnimplementedBridgeServer{},
|
||||
panicHandler: panicHandler,
|
||||
eventListener: eventListener,
|
||||
updater: updater,
|
||||
bridge: bridge,
|
||||
restarter: restarter,
|
||||
showOnStartup: showOnStartup,
|
||||
bridge *bridge.Bridge,
|
||||
showOnStartup bool,
|
||||
) (*Service, error) {
|
||||
tlsConfig, certPEM, err := newTLSConfig()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Panic("Could not generate gRPC TLS config")
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0") // Port should be provided by the OS.
|
||||
if err != nil {
|
||||
logrus.WithError(err).Panic("Could not create gRPC listener")
|
||||
}
|
||||
|
||||
token := uuid.NewString()
|
||||
|
||||
if path, err := saveGRPCServerConfigFile(locations, listener, token, certPEM); err != nil {
|
||||
logrus.WithError(err).WithField("path", path).Panic("Could not write gRPC service config file")
|
||||
} else {
|
||||
logrus.WithField("path", path).Info("Successfully saved gRPC service config file")
|
||||
}
|
||||
|
||||
s := &Service{
|
||||
grpcServer: grpc.NewServer(
|
||||
grpc.Creds(credentials.NewTLS(tlsConfig)),
|
||||
grpc.UnaryInterceptor(newUnaryTokenValidator(token)),
|
||||
grpc.StreamInterceptor(newStreamTokenValidator(token)),
|
||||
),
|
||||
listener: listener,
|
||||
|
||||
panicHandler: panicHandler,
|
||||
restarter: restarter,
|
||||
bridge: bridge,
|
||||
|
||||
log: logrus.WithField("pkg", "grpc"),
|
||||
initializing: sync.WaitGroup{},
|
||||
initializationDone: sync.Once{},
|
||||
firstTimeAutostart: sync.Once{},
|
||||
locations: locations,
|
||||
token: uuid.NewString(),
|
||||
|
||||
showOnStartup: showOnStartup,
|
||||
}
|
||||
|
||||
// Initializing.Done is only called sync.Once. Please keep the increment
|
||||
// set to 1
|
||||
// Initializing.Done is only called sync.Once. Please keep the increment set to 1
|
||||
s.initializing.Add(1)
|
||||
|
||||
tlsConfig, pemCert, err := s.generateTLSConfig()
|
||||
if err != nil {
|
||||
s.log.WithError(err).Panic("Could not generate gRPC TLS config")
|
||||
}
|
||||
|
||||
s.pemCert = string(pemCert)
|
||||
|
||||
// Initialize the autostart.
|
||||
s.initAutostart()
|
||||
s.grpcServer = grpc.NewServer(
|
||||
grpc.Creds(credentials.NewTLS(tlsConfig)),
|
||||
grpc.UnaryInterceptor(s.validateUnaryServerToken),
|
||||
grpc.StreamInterceptor(s.validateStreamServerToken),
|
||||
)
|
||||
|
||||
RegisterBridgeServer(s.grpcServer, &s)
|
||||
|
||||
s.listener, err = net.Listen("tcp", "127.0.0.1:0") // Port 0 means that the port is randomly picked by the system.
|
||||
if err != nil {
|
||||
s.log.WithError(err).Panic("Could not create gRPC listener")
|
||||
}
|
||||
|
||||
if path, err := s.saveGRPCServerConfigFile(); err != nil {
|
||||
s.log.WithError(err).WithField("path", path).Panic("Could not write gRPC service config file")
|
||||
} else {
|
||||
s.log.WithField("path", path).Info("Successfully saved gRPC service config file")
|
||||
}
|
||||
// Register the gRPC service implementation.
|
||||
RegisterBridgeServer(s.grpcServer, s)
|
||||
|
||||
s.log.Info("gRPC server listening on ", s.listener.Addr())
|
||||
|
||||
return &s
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GODT-1507 Windows: autostart needs to be created after Qt is initialized.
|
||||
// GODT-1206: if preferences file says it should be on enable it here.
|
||||
// TO-DO GODT-1681 Autostart needs to be properly implement for gRPC approach.
|
||||
func (s *Service) initAutostart() {
|
||||
// GODT-1507 Windows: autostart needs to be created after Qt is initialized.
|
||||
// GODT-1206: if preferences file says it should be on enable it here.
|
||||
|
||||
// TO-DO GODT-1681 Autostart needs to be properly implement for gRPC approach.
|
||||
|
||||
s.firstTimeAutostart.Do(func() {
|
||||
shouldAutostartBeOn := s.bridge.GetBool(settings.AutostartKey)
|
||||
if s.bridge.IsFirstStart() || shouldAutostartBeOn {
|
||||
if err := s.bridge.EnableAutostart(); err != nil {
|
||||
shouldAutostartBeOn := s.bridge.GetAutostart()
|
||||
if s.bridge.GetFirstStart() || shouldAutostartBeOn {
|
||||
if err := s.bridge.SetAutostart(true); err != nil {
|
||||
s.log.WithField("prefs", shouldAutostartBeOn).WithError(err).Error("Failed to enable first autostart")
|
||||
}
|
||||
return
|
||||
@ -168,7 +152,7 @@ func (s *Service) initAutostart() {
|
||||
|
||||
func (s *Service) Loop() error {
|
||||
defer func() {
|
||||
s.bridge.SetBool(settings.FirstStartGUIKey, false)
|
||||
_ = s.bridge.SetFirstStartGUI(false)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
@ -179,7 +163,7 @@ func (s *Service) Loop() error {
|
||||
s.log.Info("Starting gRPC server")
|
||||
|
||||
if err := s.grpcServer.Serve(s.listener); err != nil {
|
||||
s.log.WithError(err).Error("Error serving gRPC")
|
||||
s.log.WithError(err).Error("Failed to serve gRPC")
|
||||
return err
|
||||
}
|
||||
|
||||
@ -212,140 +196,59 @@ func (s *Service) WaitUntilFrontendIsReady() {
|
||||
s.initializing.Wait()
|
||||
}
|
||||
|
||||
func (s *Service) watchEvents() { // nolint:funlen
|
||||
if s.bridge.HasError(bridge.ErrLocalCacheUnavailable) {
|
||||
_ = s.SendEvent(NewCacheErrorEvent(CacheErrorType_CACHE_UNAVAILABLE_ERROR))
|
||||
}
|
||||
func (s *Service) watchEvents() {
|
||||
eventCh, done := s.bridge.GetEvents()
|
||||
defer done()
|
||||
|
||||
errorCh := s.eventListener.ProvideChannel(events.ErrorEvent)
|
||||
credentialsErrorCh := s.eventListener.ProvideChannel(events.CredentialsErrorEvent)
|
||||
noActiveKeyForRecipientCh := s.eventListener.ProvideChannel(events.NoActiveKeyForRecipientEvent)
|
||||
internetConnChangedCh := s.eventListener.ProvideChannel(events.InternetConnChangedEvent)
|
||||
secondInstanceCh := s.eventListener.ProvideChannel(events.SecondInstanceEvent)
|
||||
restartBridgeCh := s.eventListener.ProvideChannel(events.RestartBridgeEvent)
|
||||
addressChangedCh := s.eventListener.ProvideChannel(events.AddressChangedEvent)
|
||||
addressChangedLogoutCh := s.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
|
||||
logoutCh := s.eventListener.ProvideChannel(events.LogoutEvent)
|
||||
updateApplicationCh := s.eventListener.ProvideChannel(events.UpgradeApplicationEvent)
|
||||
userChangedCh := s.eventListener.ProvideChannel(events.UserRefreshEvent)
|
||||
certIssue := s.eventListener.ProvideChannel(events.TLSCertIssue)
|
||||
|
||||
// we forward events to the GUI/frontend via the gRPC event stream.
|
||||
for {
|
||||
select {
|
||||
case errorDetails := <-errorCh:
|
||||
if strings.Contains(errorDetails, "IMAP failed") {
|
||||
_ = s.SendEvent(NewMailSettingsErrorEvent(MailSettingsErrorType_IMAP_PORT_ISSUE))
|
||||
}
|
||||
if strings.Contains(errorDetails, "SMTP failed") {
|
||||
_ = s.SendEvent(NewMailSettingsErrorEvent(MailSettingsErrorType_SMTP_PORT_ISSUE))
|
||||
}
|
||||
case reason := <-credentialsErrorCh:
|
||||
if reason == keychain.ErrMacKeychainRebuild.Error() {
|
||||
_ = s.SendEvent(NewKeychainRebuildKeychainEvent())
|
||||
continue
|
||||
}
|
||||
// TODO: Better error events.
|
||||
for _, err := range s.bridge.GetErrors() {
|
||||
switch {
|
||||
case errors.Is(err, vault.ErrCorrupt):
|
||||
_ = s.SendEvent(NewKeychainHasNoKeychainEvent())
|
||||
case email := <-noActiveKeyForRecipientCh:
|
||||
_ = s.SendEvent(NewMailNoActiveKeyForRecipientEvent(email))
|
||||
case stat := <-internetConnChangedCh:
|
||||
if stat == events.InternetOff {
|
||||
_ = s.SendEvent(NewInternetStatusEvent(false))
|
||||
}
|
||||
if stat == events.InternetOn {
|
||||
_ = s.SendEvent(NewInternetStatusEvent(true))
|
||||
}
|
||||
|
||||
case <-secondInstanceCh:
|
||||
_ = s.SendEvent(NewShowMainWindowEvent())
|
||||
case <-restartBridgeCh:
|
||||
_, _ = s.Restart(
|
||||
metadata.AppendToOutgoingContext(context.Background(), serverTokenMetadataKey, s.token),
|
||||
&emptypb.Empty{},
|
||||
)
|
||||
case address := <-addressChangedCh:
|
||||
_ = s.SendEvent(NewMailAddressChangeEvent(address))
|
||||
case address := <-addressChangedLogoutCh:
|
||||
_ = s.SendEvent(NewMailAddressChangeLogoutEvent(address))
|
||||
case userID := <-logoutCh:
|
||||
user, err := s.bridge.GetUserInfo(userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = s.SendEvent(NewUserDisconnectedEvent(user.Username))
|
||||
case <-updateApplicationCh:
|
||||
s.updateForce()
|
||||
case userID := <-userChangedCh:
|
||||
_ = s.SendEvent(NewUserChangedEvent(userID))
|
||||
case <-certIssue:
|
||||
_ = s.SendEvent(NewMailApiCertIssue())
|
||||
case errors.Is(err, vault.ErrInsecure):
|
||||
_ = s.SendEvent(NewKeychainHasNoKeychainEvent())
|
||||
|
||||
case errors.Is(err, bridge.ErrServeIMAP):
|
||||
_ = s.SendEvent(NewMailSettingsErrorEvent(MailSettingsErrorType_IMAP_PORT_ISSUE))
|
||||
|
||||
case errors.Is(err, bridge.ErrServeSMTP):
|
||||
_ = s.SendEvent(NewMailSettingsErrorEvent(MailSettingsErrorType_SMTP_PORT_ISSUE))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) loginAbort() {
|
||||
s.loginClean()
|
||||
}
|
||||
for event := range eventCh {
|
||||
switch event := event.(type) {
|
||||
case events.ConnStatus:
|
||||
_ = s.SendEvent(NewInternetStatusEvent(event.Status == liteapi.StatusUp))
|
||||
|
||||
func (s *Service) loginClean() {
|
||||
s.auth = nil
|
||||
s.authClient = nil
|
||||
for i := range s.password {
|
||||
s.password[i] = '\x00'
|
||||
}
|
||||
s.password = s.password[0:0]
|
||||
}
|
||||
case events.Raise:
|
||||
_ = s.SendEvent(NewShowMainWindowEvent())
|
||||
|
||||
func (s *Service) finishLogin() {
|
||||
defer s.loginClean()
|
||||
case events.UserAddressCreated:
|
||||
_ = s.SendEvent(NewMailAddressChangeEvent(event.Address))
|
||||
|
||||
if len(s.password) == 0 || s.auth == nil || s.authClient == nil {
|
||||
s.log.
|
||||
WithField("hasPass", len(s.password) != 0).
|
||||
WithField("hasAuth", s.auth != nil).
|
||||
WithField("hasClient", s.authClient != nil).
|
||||
Error("Finish login: authentication incomplete")
|
||||
case events.UserAddressChanged:
|
||||
_ = s.SendEvent(NewMailAddressChangeEvent(event.Address))
|
||||
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_TWO_PASSWORDS_ABORT, "Missing authentication, try again."))
|
||||
return
|
||||
}
|
||||
case events.UserAddressDeleted:
|
||||
_ = s.SendEvent(NewMailAddressChangeLogoutEvent(event.Address))
|
||||
|
||||
done := make(chan string)
|
||||
s.eventListener.Add(events.UserChangeDone, done)
|
||||
defer s.eventListener.Remove(events.UserChangeDone, done)
|
||||
case events.UserChanged:
|
||||
_ = s.SendEvent(NewUserChangedEvent(event.UserID))
|
||||
|
||||
userID, err := s.bridge.FinishLogin(s.authClient, s.auth, s.password)
|
||||
|
||||
if err != nil && err != users.ErrUserAlreadyConnected {
|
||||
s.log.WithError(err).Errorf("Finish login failed")
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_TWO_PASSWORDS_ABORT, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// The user changed should be triggered by FinishLogin, but it is not
|
||||
// guaranteed when this is going to happen. Therefor we should wait
|
||||
// until we receive the signal from userChanged function.
|
||||
s.waitForUserChangeDone(done, userID)
|
||||
|
||||
s.log.WithField("userID", userID).Debug("Login finished")
|
||||
_ = s.SendEvent(NewLoginFinishedEvent(userID))
|
||||
|
||||
if err == users.ErrUserAlreadyConnected {
|
||||
s.log.WithError(err).Error("User already logged in")
|
||||
_ = s.SendEvent(NewLoginAlreadyLoggedInEvent(userID))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) waitForUserChangeDone(done <-chan string, userID string) {
|
||||
for {
|
||||
select {
|
||||
case changedID := <-done:
|
||||
if changedID == userID {
|
||||
return
|
||||
case events.UserDeauth:
|
||||
if user, err := s.bridge.GetUserInfo(event.UserID); err != nil {
|
||||
s.log.WithError(err).Error("Failed to get user info")
|
||||
} else {
|
||||
_ = s.SendEvent(NewUserDisconnectedEvent(user.Username))
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
s.log.WithField("ID", userID).Warning("Login finished but user not added within 2 seconds")
|
||||
return
|
||||
|
||||
case events.TLSIssue:
|
||||
_ = s.SendEvent(NewMailApiCertIssue())
|
||||
|
||||
case events.UpdateForced:
|
||||
panic("TODO")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -354,103 +257,46 @@ func (s *Service) triggerReset() {
|
||||
defer func() {
|
||||
_ = s.SendEvent(NewResetFinishedEvent())
|
||||
}()
|
||||
s.bridge.FactoryReset()
|
||||
if err := s.bridge.FactoryReset(context.Background()); err != nil {
|
||||
s.log.WithError(err).Error("Failed to reset")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) checkUpdate() {
|
||||
version, err := s.updater.Check()
|
||||
func newTLSConfig() (*tls.Config, []byte, error) {
|
||||
template, err := certs.NewTLSTemplate()
|
||||
if err != nil {
|
||||
s.log.WithError(err).Error("An error occurred while checking for updates")
|
||||
s.SetVersion(updater.VersionInfo{})
|
||||
return
|
||||
}
|
||||
s.SetVersion(version)
|
||||
}
|
||||
|
||||
func (s *Service) updateForce() {
|
||||
s.updateCheckMutex.Lock()
|
||||
defer s.updateCheckMutex.Unlock()
|
||||
s.checkUpdate()
|
||||
_ = s.SendEvent(NewUpdateForceEvent(s.newVersionInfo.Version.String()))
|
||||
}
|
||||
|
||||
func (s *Service) checkUpdateAndNotify(isReqFromUser bool) {
|
||||
s.updateCheckMutex.Lock()
|
||||
defer func() {
|
||||
s.updateCheckMutex.Unlock()
|
||||
_ = s.SendEvent(NewUpdateCheckFinishedEvent())
|
||||
}()
|
||||
|
||||
s.checkUpdate()
|
||||
version := s.newVersionInfo
|
||||
if (version.Version == nil) || (version.Version.String() == "") {
|
||||
if isReqFromUser {
|
||||
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
|
||||
}
|
||||
return
|
||||
}
|
||||
if !s.updater.IsUpdateApplicable(s.newVersionInfo) {
|
||||
s.log.Info("No need to update")
|
||||
if isReqFromUser {
|
||||
_ = s.SendEvent(NewUpdateIsLatestVersionEvent())
|
||||
}
|
||||
} else if isReqFromUser {
|
||||
s.NotifyManualUpdate(s.newVersionInfo, s.updater.CanInstall(s.newVersionInfo))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) installUpdate() {
|
||||
s.updateCheckMutex.Lock()
|
||||
defer s.updateCheckMutex.Unlock()
|
||||
|
||||
if !s.updater.CanInstall(s.newVersionInfo) {
|
||||
s.log.Warning("Skipping update installation, current version too old")
|
||||
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
|
||||
return
|
||||
return nil, nil, fmt.Errorf("failed to create TLS template: %w", err)
|
||||
}
|
||||
|
||||
if err := s.updater.InstallUpdate(s.newVersionInfo); err != nil {
|
||||
if errors.Cause(err) == updater.ErrDownloadVerify {
|
||||
s.log.WithError(err).Warning("Skipping update installation due to temporary error")
|
||||
} else {
|
||||
s.log.WithError(err).Error("The update couldn't be installed")
|
||||
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_ = s.SendEvent(NewUpdateSilentRestartNeededEvent())
|
||||
}
|
||||
|
||||
func (s *Service) generateTLSConfig() (tlsConfig *cryptotls.Config, pemCert []byte, err error) {
|
||||
pemCert, pemKey, err := tls.NewPEMKeyPair()
|
||||
certPEM, keyPEM, err := certs.GenerateCert(template)
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("Could not get TLS config")
|
||||
return nil, nil, fmt.Errorf("failed to generate cert: %w", err)
|
||||
}
|
||||
|
||||
tlsConfig, err = tls.GetConfigFromPEMKeyPair(pemCert, pemKey)
|
||||
cert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("Could not get TLS config")
|
||||
return nil, nil, fmt.Errorf("failed to load cert: %w", err)
|
||||
}
|
||||
|
||||
tlsConfig.ClientAuth = cryptotls.NoClientCert // skip client auth if the certificate allow it.
|
||||
|
||||
return tlsConfig, pemCert, nil
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ClientAuth: tls.NoClientCert,
|
||||
}, certPEM, nil
|
||||
}
|
||||
|
||||
func (s *Service) saveGRPCServerConfigFile() (string, error) {
|
||||
address, ok := s.listener.Addr().(*net.TCPAddr)
|
||||
func saveGRPCServerConfigFile(locations *locations.Locations, listener net.Listener, token string, certPEM []byte) (string, error) {
|
||||
address, ok := listener.Addr().(*net.TCPAddr)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("could not retrieve gRPC service listener address")
|
||||
}
|
||||
|
||||
sc := config{
|
||||
Port: address.Port,
|
||||
Cert: s.pemCert,
|
||||
Token: s.token,
|
||||
Cert: string(certPEM),
|
||||
Token: token,
|
||||
}
|
||||
|
||||
settingsPath, err := s.locations.ProvideSettingsPath()
|
||||
settingsPath, err := locations.ProvideSettingsPath()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -461,7 +307,7 @@ func (s *Service) saveGRPCServerConfigFile() (string, error) {
|
||||
}
|
||||
|
||||
// validateServerToken verify that the server token provided by the client is valid.
|
||||
func (s *Service) validateServerToken(ctx context.Context) error {
|
||||
func validateServerToken(ctx context.Context, wantToken string) error {
|
||||
values, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return status.Error(codes.Unauthenticated, "missing server token")
|
||||
@ -476,40 +322,31 @@ func (s *Service) validateServerToken(ctx context.Context) error {
|
||||
return status.Error(codes.Unauthenticated, "more than one server token was provided")
|
||||
}
|
||||
|
||||
if token[0] != s.token {
|
||||
if token[0] != wantToken {
|
||||
return status.Error(codes.Unauthenticated, "invalid server token")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateUnaryServerToken check the server token for every unary gRPC call.
|
||||
func (s *Service) validateUnaryServerToken(
|
||||
ctx context.Context,
|
||||
req interface{},
|
||||
info *grpc.UnaryServerInfo,
|
||||
handler grpc.UnaryHandler,
|
||||
) (resp interface{}, err error) {
|
||||
if err := s.validateServerToken(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// newUnaryTokenValidator checks the server token for every unary gRPC call.
|
||||
func newUnaryTokenValidator(wantToken string) grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
if err := validateServerToken(ctx, wantToken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler(ctx, req)
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// validateStreamServerToken check the server token for every gRPC stream request.
|
||||
func (s *Service) validateStreamServerToken(
|
||||
srv interface{},
|
||||
ss grpc.ServerStream,
|
||||
info *grpc.StreamServerInfo,
|
||||
handler grpc.StreamHandler,
|
||||
) error {
|
||||
logEntry := s.log.WithField("FullMethod", info.FullMethod)
|
||||
// newStreamTokenValidator checks the server token for every gRPC stream request.
|
||||
func newStreamTokenValidator(wantToken string) grpc.StreamServerInterceptor {
|
||||
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
if err := validateServerToken(stream.Context(), wantToken); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.validateServerToken(ss.Context()); err != nil {
|
||||
logEntry.WithError(err).Error("Stream validator failed")
|
||||
return err
|
||||
return handler(srv, stream)
|
||||
}
|
||||
|
||||
return handler(srv, ss)
|
||||
}
|
||||
|
||||
@ -23,15 +23,13 @@ import (
|
||||
"runtime"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/theme"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/ports"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/exp/maps"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
@ -116,7 +114,7 @@ func (s *Service) Quit(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empt
|
||||
func (s *Service) Restart(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
s.log.Debug("Restart")
|
||||
|
||||
s.restarter.SetToRestart()
|
||||
s.restarter.Set(true, false)
|
||||
return s.Quit(ctx, empty)
|
||||
}
|
||||
|
||||
@ -129,25 +127,19 @@ func (s *Service) ShowOnStartup(ctx context.Context, _ *emptypb.Empty) (*wrapper
|
||||
func (s *Service) ShowSplashScreen(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) {
|
||||
s.log.Debug("ShowSplashScreen")
|
||||
|
||||
if s.bridge.IsFirstStart() {
|
||||
return wrapperspb.Bool(false), nil
|
||||
}
|
||||
|
||||
ver, err := semver.NewVersion(s.bridge.GetLastVersion())
|
||||
if err != nil {
|
||||
s.log.WithError(err).WithField("last", s.bridge.GetLastVersion()).Debug("Cannot parse last version")
|
||||
if s.bridge.GetFirstStart() {
|
||||
return wrapperspb.Bool(false), nil
|
||||
}
|
||||
|
||||
// Current splash screen contains update on rebranding. Therefore, it
|
||||
// should be shown only if the last used version was less than 2.2.0.
|
||||
return wrapperspb.Bool(ver.LessThan(semver.MustParse("2.2.0"))), nil
|
||||
return wrapperspb.Bool(s.bridge.GetLastVersion().LessThan(semver.MustParse("2.2.0"))), nil
|
||||
}
|
||||
|
||||
func (s *Service) IsFirstGuiStart(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) {
|
||||
s.log.Debug("IsFirstGuiStart")
|
||||
|
||||
return wrapperspb.Bool(s.bridge.GetBool(settings.FirstStartGUIKey)), nil
|
||||
return wrapperspb.Bool(s.bridge.GetFirstStartGUI()), nil
|
||||
}
|
||||
|
||||
func (s *Service) SetIsAutostartOn(ctx context.Context, isOn *wrapperspb.BoolValue) (*emptypb.Empty, error) {
|
||||
@ -155,22 +147,16 @@ func (s *Service) SetIsAutostartOn(ctx context.Context, isOn *wrapperspb.BoolVal
|
||||
|
||||
defer func() { _ = s.SendEvent(NewToggleAutostartFinishedEvent()) }()
|
||||
|
||||
if isOn.Value == s.bridge.IsAutostartEnabled() {
|
||||
if isOn.Value == s.bridge.GetAutostart() {
|
||||
s.initAutostart()
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if isOn.Value {
|
||||
err = s.bridge.EnableAutostart()
|
||||
} else {
|
||||
err = s.bridge.DisableAutostart()
|
||||
}
|
||||
|
||||
s.initAutostart()
|
||||
|
||||
if err != nil {
|
||||
if err := s.bridge.SetAutostart(isOn.Value); err != nil {
|
||||
s.log.WithField("makeItEnabled", isOn.Value).WithError(err).Error("Autostart change failed")
|
||||
return nil, status.Errorf(codes.Internal, "failed to set autostart: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
@ -179,7 +165,7 @@ func (s *Service) SetIsAutostartOn(ctx context.Context, isOn *wrapperspb.BoolVal
|
||||
func (s *Service) IsAutostartOn(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) {
|
||||
s.log.Debug("IsAutostartOn")
|
||||
|
||||
return wrapperspb.Bool(s.bridge.IsAutostartEnabled()), nil
|
||||
return wrapperspb.Bool(s.bridge.GetAutostart()), nil
|
||||
}
|
||||
|
||||
func (s *Service) SetIsBetaEnabled(ctx context.Context, isEnabled *wrapperspb.BoolValue) (*emptypb.Empty, error) {
|
||||
@ -190,8 +176,10 @@ func (s *Service) SetIsBetaEnabled(ctx context.Context, isEnabled *wrapperspb.Bo
|
||||
channel = updater.EarlyChannel
|
||||
}
|
||||
|
||||
s.bridge.SetUpdateChannel(channel)
|
||||
s.checkUpdate()
|
||||
if err := s.bridge.SetUpdateChannel(channel); err != nil {
|
||||
s.log.WithError(err).Error("Failed to set update channel")
|
||||
return nil, status.Errorf(codes.Internal, "failed to set update channel: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
@ -205,7 +193,10 @@ func (s *Service) IsBetaEnabled(ctx context.Context, _ *emptypb.Empty) (*wrapper
|
||||
func (s *Service) SetIsAllMailVisible(ctx context.Context, isVisible *wrapperspb.BoolValue) (*emptypb.Empty, error) {
|
||||
s.log.WithField("isVisible", isVisible.Value).Debug("SetIsAllMailVisible")
|
||||
|
||||
s.bridge.SetIsAllMailVisible(isVisible.Value)
|
||||
if err := s.bridge.SetShowAllMail(isVisible.Value); err != nil {
|
||||
s.log.WithError(err).Error("Failed to set show all mail")
|
||||
return nil, status.Errorf(codes.Internal, "failed to set show all mail: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
@ -213,7 +204,7 @@ func (s *Service) SetIsAllMailVisible(ctx context.Context, isVisible *wrapperspb
|
||||
func (s *Service) IsAllMailVisible(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) {
|
||||
s.log.Debug("IsAllMailVisible")
|
||||
|
||||
return wrapperspb.Bool(s.bridge.IsAllMailVisible()), nil
|
||||
return wrapperspb.Bool(s.bridge.GetShowAllMail()), nil
|
||||
}
|
||||
|
||||
func (s *Service) GoOs(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
||||
@ -241,7 +232,7 @@ func (s *Service) Version(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.St
|
||||
func (s *Service) LogsPath(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
||||
s.log.Debug("LogsPath")
|
||||
|
||||
path, err := s.bridge.ProvideLogsPath()
|
||||
path, err := s.bridge.GetLogsPath()
|
||||
if err != nil {
|
||||
s.log.WithError(err).Error("Cannot determine logs path")
|
||||
return nil, err
|
||||
@ -275,7 +266,10 @@ func (s *Service) SetColorSchemeName(ctx context.Context, name *wrapperspb.Strin
|
||||
return nil, status.Error(codes.NotFound, "Color scheme not available")
|
||||
}
|
||||
|
||||
s.bridge.Set(settings.ColorScheme, name.Value)
|
||||
if err := s.bridge.SetColorScheme(name.Value); err != nil {
|
||||
s.log.WithError(err).Error("Failed to set color scheme")
|
||||
return nil, status.Errorf(codes.Internal, "failed to set color scheme: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
@ -283,10 +277,13 @@ func (s *Service) SetColorSchemeName(ctx context.Context, name *wrapperspb.Strin
|
||||
func (s *Service) ColorSchemeName(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
||||
s.log.Debug("ColorSchemeName")
|
||||
|
||||
current := s.bridge.Get(settings.ColorScheme)
|
||||
current := s.bridge.GetColorScheme()
|
||||
if !theme.IsAvailable(theme.Theme(current)) {
|
||||
current = string(theme.DefaultTheme())
|
||||
s.bridge.Set(settings.ColorScheme, current)
|
||||
if err := s.bridge.SetColorScheme(current); err != nil {
|
||||
s.log.WithError(err).Error("Failed to set color scheme")
|
||||
return nil, status.Errorf(codes.Internal, "failed to set color scheme: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return wrapperspb.String(current), nil
|
||||
@ -312,6 +309,7 @@ func (s *Service) ReportBug(ctx context.Context, report *ReportBugRequest) (*emp
|
||||
defer func() { _ = s.SendEvent(NewReportBugFinishedEvent()) }()
|
||||
|
||||
if err := s.bridge.ReportBug(
|
||||
context.Background(),
|
||||
report.OsType,
|
||||
report.OsVersion,
|
||||
report.Description,
|
||||
@ -331,6 +329,7 @@ func (s *Service) ReportBug(ctx context.Context, report *ReportBugRequest) (*emp
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
func (s *Service) ForceLauncher(ctx context.Context, launcher *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
||||
s.log.WithField("launcher", launcher.Value).Debug("ForceLauncher")
|
||||
|
||||
@ -350,6 +349,7 @@ func (s *Service) SetMainExecutable(ctx context.Context, exe *wrapperspb.StringV
|
||||
}()
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
*/
|
||||
|
||||
func (s *Service) Login(ctx context.Context, login *LoginRequest) (*emptypb.Empty, error) {
|
||||
s.log.WithField("username", login.Username).Debug("Login")
|
||||
@ -357,135 +357,44 @@ func (s *Service) Login(ctx context.Context, login *LoginRequest) (*emptypb.Empt
|
||||
go func() {
|
||||
defer s.panicHandler.HandlePanic()
|
||||
|
||||
var err error
|
||||
s.password, err = base64.StdEncoding.DecodeString(login.Password)
|
||||
password, err := base64.StdEncoding.DecodeString(login.Password)
|
||||
if err != nil {
|
||||
s.log.WithError(err).Error("Cannot decode password")
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, "Cannot decode password"))
|
||||
s.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
s.authClient, s.auth, err = s.bridge.Login(login.Username, s.password)
|
||||
// TODO: Handle different error types!
|
||||
// - bad credentials
|
||||
// - bad proton plan
|
||||
// - user already exists
|
||||
userID, err := s.bridge.LoginUser(context.Background(), login.Username, string(password), nil, nil)
|
||||
if err != nil {
|
||||
if err == pmapi.ErrPasswordWrong {
|
||||
// Remove error message since it is hardcoded in QML.
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, ""))
|
||||
s.loginClean()
|
||||
return
|
||||
}
|
||||
if err == pmapi.ErrPaidPlanRequired {
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_FREE_USER, ""))
|
||||
s.loginClean()
|
||||
return
|
||||
}
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, err.Error()))
|
||||
s.loginClean()
|
||||
s.log.WithError(err).Error("Cannot login user")
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, "Cannot login user"))
|
||||
return
|
||||
}
|
||||
|
||||
if s.auth.HasTwoFactor() {
|
||||
_ = s.SendEvent(NewLoginTfaRequestedEvent(login.Username))
|
||||
return
|
||||
}
|
||||
if s.auth.HasMailboxPassword() {
|
||||
_ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent())
|
||||
return
|
||||
}
|
||||
|
||||
s.finishLogin()
|
||||
}()
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *Service) Login2FA(ctx context.Context, login *LoginRequest) (*emptypb.Empty, error) {
|
||||
s.log.WithField("username", login.Username).Debug("Login2FA")
|
||||
|
||||
go func() {
|
||||
defer s.panicHandler.HandlePanic()
|
||||
|
||||
if s.auth == nil || s.authClient == nil {
|
||||
s.log.Errorf("Login 2FA: authethication incomplete %p %p", s.auth, s.authClient)
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ABORT, "Missing authentication, try again."))
|
||||
s.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
twoFA, err := base64.StdEncoding.DecodeString(login.Password)
|
||||
if err != nil {
|
||||
s.log.WithError(err).Error("Cannot decode 2fa code")
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, "Cannot decode 2fa code"))
|
||||
s.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
err = s.authClient.Auth2FA(context.Background(), string(twoFA))
|
||||
if err == pmapi.ErrBad2FACodeTryAgain {
|
||||
s.log.Warn("Login 2FA: retry 2fa")
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ERROR, ""))
|
||||
return
|
||||
}
|
||||
|
||||
if err == pmapi.ErrBad2FACode {
|
||||
s.log.Warn("Login 2FA: abort 2fa")
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ABORT, ""))
|
||||
s.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.log.WithError(err).Warn("Login 2FA: failed.")
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ABORT, err.Error()))
|
||||
s.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
if s.auth.HasMailboxPassword() {
|
||||
_ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent())
|
||||
return
|
||||
}
|
||||
|
||||
s.finishLogin()
|
||||
_ = s.SendEvent(NewLoginFinishedEvent(userID))
|
||||
}()
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *Service) Login2Passwords(ctx context.Context, login *LoginRequest) (*emptypb.Empty, error) {
|
||||
s.log.WithField("username", login.Username).Debug("Login2Passwords")
|
||||
|
||||
go func() {
|
||||
defer s.panicHandler.HandlePanic()
|
||||
|
||||
var err error
|
||||
s.password, err = base64.StdEncoding.DecodeString(login.Password)
|
||||
|
||||
if err != nil {
|
||||
s.log.WithError(err).Error("Cannot decode mbox password")
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, "Cannot decode mbox password"))
|
||||
s.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
s.finishLogin()
|
||||
}()
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
func (s *Service) Login2FA(_ context.Context, login *LoginRequest) (*emptypb.Empty, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (s *Service) LoginAbort(ctx context.Context, loginAbort *LoginAbortRequest) (*emptypb.Empty, error) {
|
||||
s.log.WithField("username", loginAbort.Username).Debug("LoginAbort")
|
||||
|
||||
go func() {
|
||||
defer s.panicHandler.HandlePanic()
|
||||
|
||||
s.loginAbort()
|
||||
}()
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
func (s *Service) Login2Passwords(_ context.Context, login *LoginRequest) (*emptypb.Empty, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (s *Service) CheckUpdate(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
func (s *Service) LoginAbort(_ context.Context, loginAbort *LoginAbortRequest) (*emptypb.Empty, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
/*
|
||||
func (s *Service) CheckUpdate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
s.log.Debug("CheckUpdate")
|
||||
|
||||
go func() {
|
||||
@ -507,21 +416,20 @@ func (s *Service) InstallUpdate(ctx context.Context, _ *emptypb.Empty) (*emptypb
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
*/
|
||||
|
||||
func (s *Service) SetIsAutomaticUpdateOn(ctx context.Context, isOn *wrapperspb.BoolValue) (*emptypb.Empty, error) {
|
||||
s.log.WithField("isOn", isOn.Value).Debug("SetIsAutomaticUpdateOn")
|
||||
|
||||
currentlyOn := s.bridge.GetBool(settings.AutoUpdateKey)
|
||||
currentlyOn := s.bridge.GetAutoUpdate()
|
||||
if currentlyOn == isOn.Value {
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
s.bridge.SetBool(settings.AutoUpdateKey, isOn.Value)
|
||||
go func() {
|
||||
defer s.panicHandler.HandlePanic()
|
||||
|
||||
s.checkUpdateAndNotify(false)
|
||||
}()
|
||||
if err := s.bridge.SetAutoUpdate(isOn.Value); err != nil {
|
||||
s.log.WithError(err).Error("Failed to set auto update")
|
||||
return nil, status.Errorf(codes.Internal, "failed to set auto update: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
@ -529,51 +437,21 @@ func (s *Service) SetIsAutomaticUpdateOn(ctx context.Context, isOn *wrapperspb.B
|
||||
func (s *Service) IsAutomaticUpdateOn(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) {
|
||||
s.log.Debug("IsAutomaticUpdateOn")
|
||||
|
||||
return wrapperspb.Bool(s.bridge.GetBool(settings.AutoUpdateKey)), nil
|
||||
}
|
||||
|
||||
func (s *Service) IsCacheOnDiskEnabled(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) {
|
||||
s.log.Debug("IsCacheOnDiskEnabled")
|
||||
|
||||
return wrapperspb.Bool(s.bridge.GetBool(settings.CacheEnabledKey)), nil
|
||||
return wrapperspb.Bool(s.bridge.GetAutoUpdate()), nil
|
||||
}
|
||||
|
||||
func (s *Service) DiskCachePath(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
||||
s.log.Debug("DiskCachePath")
|
||||
|
||||
return wrapperspb.String(s.bridge.Get(settings.CacheLocationKey)), nil
|
||||
return wrapperspb.String(s.bridge.GetGluonDir()), nil
|
||||
}
|
||||
|
||||
func (s *Service) ChangeLocalCache(ctx context.Context, change *ChangeLocalCacheRequest) (*emptypb.Empty, error) {
|
||||
s.log.WithField("enableDiskCache", change.EnableDiskCache).
|
||||
WithField("diskCachePath", change.DiskCachePath).
|
||||
Debug("DiskCachePath")
|
||||
s.log.WithField("diskCachePath", change.DiskCachePath).Debug("DiskCachePath")
|
||||
|
||||
restart := false
|
||||
defer func(willRestart *bool) {
|
||||
_ = s.SendEvent(NewCacheChangeLocalCacheFinishedEvent(*willRestart))
|
||||
if *willRestart {
|
||||
_, _ = s.Restart(ctx, &emptypb.Empty{})
|
||||
}
|
||||
}(&restart)
|
||||
|
||||
if change.EnableDiskCache != s.bridge.GetBool(settings.CacheEnabledKey) {
|
||||
if change.EnableDiskCache {
|
||||
if err := s.bridge.EnableCache(); err != nil {
|
||||
s.log.WithError(err).Error("Cannot enable disk cache")
|
||||
} else {
|
||||
restart = true
|
||||
_ = s.SendEvent(NewIsCacheOnDiskEnabledChanged(s.bridge.GetBool(settings.CacheEnabledKey)))
|
||||
}
|
||||
} else {
|
||||
if err := s.bridge.DisableCache(); err != nil {
|
||||
s.log.WithError(err).Error("Cannot disable disk cache")
|
||||
} else {
|
||||
restart = true
|
||||
_ = s.SendEvent(NewIsCacheOnDiskEnabledChanged(s.bridge.GetBool(settings.CacheEnabledKey)))
|
||||
}
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
_ = s.SendEvent(NewCacheChangeLocalCacheFinishedEvent(false))
|
||||
}()
|
||||
|
||||
path := change.DiskCachePath
|
||||
//goland:noinspection GoBoolExpressions
|
||||
@ -581,16 +459,14 @@ func (s *Service) ChangeLocalCache(ctx context.Context, change *ChangeLocalCache
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
if change.EnableDiskCache && path != s.bridge.Get(settings.CacheLocationKey) {
|
||||
if err := s.bridge.MigrateCache(s.bridge.Get(settings.CacheLocationKey), path); err != nil {
|
||||
if path != s.bridge.GetGluonDir() {
|
||||
if err := s.bridge.SetGluonDir(ctx, path); err != nil {
|
||||
s.log.WithError(err).Error("The local cache location could not be changed.")
|
||||
_ = s.SendEvent(NewCacheErrorEvent(CacheErrorType_CACHE_CANT_MOVE_ERROR))
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
s.bridge.Set(settings.CacheLocationKey, path)
|
||||
restart = true
|
||||
_ = s.SendEvent(NewDiskCachePathChanged(s.bridge.Get(settings.CacheLocationKey)))
|
||||
_ = s.SendEvent(NewDiskCachePathChanged(s.bridge.GetGluonDir()))
|
||||
}
|
||||
|
||||
_ = s.SendEvent(NewCacheLocationChangeSuccessEvent())
|
||||
@ -601,7 +477,10 @@ func (s *Service) ChangeLocalCache(ctx context.Context, change *ChangeLocalCache
|
||||
func (s *Service) SetIsDoHEnabled(ctx context.Context, isEnabled *wrapperspb.BoolValue) (*emptypb.Empty, error) {
|
||||
s.log.WithField("isEnabled", isEnabled.Value).Debug("SetIsDohEnabled")
|
||||
|
||||
s.bridge.SetProxyAllowed(isEnabled.Value)
|
||||
if err := s.bridge.SetProxyAllowed(isEnabled.Value); err != nil {
|
||||
s.log.WithError(err).Error("Failed to set DoH")
|
||||
return nil, status.Errorf(codes.Internal, "failed to set DoH: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
@ -615,13 +494,14 @@ func (s *Service) IsDoHEnabled(ctx context.Context, _ *emptypb.Empty) (*wrappers
|
||||
func (s *Service) SetUseSslForSmtp(ctx context.Context, useSsl *wrapperspb.BoolValue) (*emptypb.Empty, error) { //nolint:revive,stylecheck
|
||||
s.log.WithField("useSsl", useSsl.Value).Debug("SetUseSslForSmtp")
|
||||
|
||||
if s.bridge.GetBool(settings.SMTPSSLKey) == useSsl.Value {
|
||||
if s.bridge.GetSMTPSSL() == useSsl.Value {
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
s.bridge.SetBool(settings.SMTPSSLKey, useSsl.Value)
|
||||
|
||||
defer func() { _, _ = s.Restart(ctx, &emptypb.Empty{}) }()
|
||||
if err := s.bridge.SetSMTPSSL(useSsl.Value); err != nil {
|
||||
s.log.WithError(err).Error("Failed to set SMTP SSL")
|
||||
return nil, status.Errorf(codes.Internal, "failed to set SMTP SSL: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, s.SendEvent(NewMailSettingsUseSslForSmtpFinishedEvent())
|
||||
}
|
||||
@ -629,34 +509,39 @@ func (s *Service) SetUseSslForSmtp(ctx context.Context, useSsl *wrapperspb.BoolV
|
||||
func (s *Service) UseSslForSmtp(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) { //nolint:revive,stylecheck
|
||||
s.log.Debug("UseSslForSmtp")
|
||||
|
||||
return wrapperspb.Bool(s.bridge.GetBool(settings.SMTPSSLKey)), nil
|
||||
return wrapperspb.Bool(s.bridge.GetSMTPSSL()), nil
|
||||
}
|
||||
|
||||
func (s *Service) Hostname(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
||||
s.log.Debug("Hostname")
|
||||
|
||||
return wrapperspb.String(bridge.Host), nil
|
||||
return wrapperspb.String(constants.Host), nil
|
||||
}
|
||||
|
||||
func (s *Service) ImapPort(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.Int32Value, error) {
|
||||
s.log.Debug("ImapPort")
|
||||
|
||||
return wrapperspb.Int32(int32(s.bridge.GetInt(settings.IMAPPortKey))), nil
|
||||
return wrapperspb.Int32(int32(s.bridge.GetIMAPPort())), nil
|
||||
}
|
||||
|
||||
func (s *Service) SmtpPort(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.Int32Value, error) { //nolint:revive,stylecheck
|
||||
s.log.Debug("SmtpPort")
|
||||
|
||||
return wrapperspb.Int32(int32(s.bridge.GetInt(settings.SMTPPortKey))), nil
|
||||
return wrapperspb.Int32(int32(s.bridge.GetSMTPPort())), nil
|
||||
}
|
||||
|
||||
func (s *Service) ChangePorts(ctx context.Context, ports *ChangePortsRequest) (*emptypb.Empty, error) {
|
||||
s.log.WithField("imapPort", ports.ImapPort).WithField("smtpPort", ports.SmtpPort).Debug("ChangePorts")
|
||||
|
||||
s.bridge.SetInt(settings.IMAPPortKey, int(ports.ImapPort))
|
||||
s.bridge.SetInt(settings.SMTPPortKey, int(ports.SmtpPort))
|
||||
if err := s.bridge.SetIMAPPort(int(ports.ImapPort)); err != nil {
|
||||
s.log.WithError(err).Error("Failed to set IMAP port")
|
||||
return nil, status.Errorf(codes.Internal, "failed to set IMAP port: %v", err)
|
||||
}
|
||||
|
||||
defer func() { _, _ = s.Restart(ctx, &emptypb.Empty{}) }()
|
||||
if err := s.bridge.SetSMTPPort(int(ports.SmtpPort)); err != nil {
|
||||
s.log.WithError(err).Error("Failed to set SMTP port")
|
||||
return nil, status.Errorf(codes.Internal, "failed to set SMTP port: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, s.SendEvent(NewMailSettingsChangePortFinishedEvent())
|
||||
}
|
||||
@ -670,12 +555,7 @@ func (s *Service) IsPortFree(ctx context.Context, port *wrapperspb.Int32Value) (
|
||||
func (s *Service) AvailableKeychains(ctx context.Context, _ *emptypb.Empty) (*AvailableKeychainsResponse, error) {
|
||||
s.log.Debug("AvailableKeychains")
|
||||
|
||||
keychains := make([]string, 0, len(keychain.Helpers))
|
||||
for chain := range keychain.Helpers {
|
||||
keychains = append(keychains, chain)
|
||||
}
|
||||
|
||||
return &AvailableKeychainsResponse{Keychains: keychains}, nil
|
||||
return &AvailableKeychainsResponse{Keychains: maps.Keys(keychain.Helpers)}, nil
|
||||
}
|
||||
|
||||
func (s *Service) SetCurrentKeychain(ctx context.Context, keychain *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
||||
@ -684,11 +564,20 @@ func (s *Service) SetCurrentKeychain(ctx context.Context, keychain *wrapperspb.S
|
||||
defer func() { _, _ = s.Restart(ctx, &emptypb.Empty{}) }()
|
||||
defer func() { _ = s.SendEvent(NewKeychainChangeKeychainFinishedEvent()) }()
|
||||
|
||||
if s.bridge.GetKeychainApp() == keychain.Value {
|
||||
helper, err := s.bridge.GetKeychainApp()
|
||||
if err != nil {
|
||||
s.log.WithError(err).Error("Failed to get current keychain")
|
||||
return nil, status.Errorf(codes.Internal, "failed to get current keychain: %v", err)
|
||||
}
|
||||
|
||||
if helper == keychain.Value {
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
s.bridge.SetKeychainApp(keychain.Value)
|
||||
if err := s.bridge.SetKeychainApp(keychain.Value); err != nil {
|
||||
s.log.WithError(err).Error("Failed to set keychain")
|
||||
return nil, status.Errorf(codes.Internal, "failed to set keychain: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
@ -696,5 +585,11 @@ func (s *Service) SetCurrentKeychain(ctx context.Context, keychain *wrapperspb.S
|
||||
func (s *Service) CurrentKeychain(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
||||
s.log.Debug("CurrentKeychain")
|
||||
|
||||
return wrapperspb.String(s.bridge.GetKeychainApp()), nil
|
||||
helper, err := s.bridge.GetKeychainApp()
|
||||
if err != nil {
|
||||
s.log.WithError(err).Error("Failed to get current keychain")
|
||||
return nil, status.Errorf(codes.Internal, "failed to get current keychain: %v", err)
|
||||
}
|
||||
|
||||
return wrapperspb.String(helper), nil
|
||||
}
|
||||
|
||||
@ -87,12 +87,8 @@ func (s *Service) StopEventStream(ctx context.Context, _ *emptypb.Empty) (*empty
|
||||
|
||||
// SendEvent sends an event to the via the gRPC event stream.
|
||||
func (s *Service) SendEvent(event *StreamEvent) error {
|
||||
s.eventQueueMutex.Lock()
|
||||
defer s.eventQueueMutex.Unlock()
|
||||
|
||||
if s.eventStreamCh == nil {
|
||||
// nobody is connected to the event stream, we queue events
|
||||
s.eventQueue = append(s.eventQueue, event)
|
||||
if s.eventStreamCh == nil { // nobody is connected to the event stream, we queue events
|
||||
s.queueEvent(event)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -167,3 +163,14 @@ func (s *Service) StartEventTest() error { //nolint:funlen
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) queueEvent(event *StreamEvent) {
|
||||
s.eventQueueMutex.Lock()
|
||||
defer s.eventQueueMutex.Unlock()
|
||||
|
||||
if event.isInternetStatus() {
|
||||
s.eventQueue = append(filterOutInternetStatusEvents(s.eventQueue), event)
|
||||
} else {
|
||||
s.eventQueue = append(s.eventQueue, event)
|
||||
}
|
||||
}
|
||||
|
||||
68
internal/frontend/grpc/service_updates.go
Normal file
68
internal/frontend/grpc/service_updates.go
Normal file
@ -0,0 +1,68 @@
|
||||
package grpc
|
||||
|
||||
/*
|
||||
func (s *Service) checkUpdate() {
|
||||
version, err := s.updater.Check()
|
||||
if err != nil {
|
||||
s.log.WithError(err).Error("An error occurred while checking for updates")
|
||||
s.SetVersion(updater.VersionInfo{})
|
||||
return
|
||||
}
|
||||
s.SetVersion(version)
|
||||
}
|
||||
|
||||
func (s *Service) updateForce() {
|
||||
s.updateCheckMutex.Lock()
|
||||
defer s.updateCheckMutex.Unlock()
|
||||
s.checkUpdate()
|
||||
_ = s.SendEvent(NewUpdateForceEvent(s.newVersionInfo.Version.String()))
|
||||
}
|
||||
|
||||
func (s *Service) checkUpdateAndNotify(isReqFromUser bool) {
|
||||
s.updateCheckMutex.Lock()
|
||||
defer func() {
|
||||
s.updateCheckMutex.Unlock()
|
||||
_ = s.SendEvent(NewUpdateCheckFinishedEvent())
|
||||
}()
|
||||
|
||||
s.checkUpdate()
|
||||
version := s.newVersionInfo
|
||||
if version.Version.String() == "" {
|
||||
if isReqFromUser {
|
||||
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
|
||||
}
|
||||
return
|
||||
}
|
||||
if !s.updater.IsUpdateApplicable(s.newVersionInfo) {
|
||||
s.log.Info("No need to update")
|
||||
if isReqFromUser {
|
||||
_ = s.SendEvent(NewUpdateIsLatestVersionEvent())
|
||||
}
|
||||
} else if isReqFromUser {
|
||||
s.NotifyManualUpdate(s.newVersionInfo, s.updater.CanInstall(s.newVersionInfo))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) installUpdate() {
|
||||
s.updateCheckMutex.Lock()
|
||||
defer s.updateCheckMutex.Unlock()
|
||||
|
||||
if !s.updater.CanInstall(s.newVersionInfo) {
|
||||
s.log.Warning("Skipping update installation, current version too old")
|
||||
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.updater.InstallUpdate(s.newVersionInfo); err != nil {
|
||||
if errors.Cause(err) == updater.ErrDownloadVerify {
|
||||
s.log.WithError(err).Warning("Skipping update installation due to temporary error")
|
||||
} else {
|
||||
s.log.WithError(err).Error("The update couldn't be installed")
|
||||
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_ = s.SendEvent(NewUpdateSilentRestartNeededEvent())
|
||||
}
|
||||
*/
|
||||
@ -19,9 +19,8 @@ package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
@ -75,15 +74,15 @@ func (s *Service) SetUserSplitMode(ctx context.Context, splitMode *UserSplitMode
|
||||
defer s.panicHandler.HandlePanic()
|
||||
defer func() { _ = s.SendEvent(NewUserToggleSplitModeFinishedEvent(splitMode.UserID)) }()
|
||||
|
||||
var targetMode users.AddressMode
|
||||
var targetMode bridge.AddressMode
|
||||
|
||||
if splitMode.Active && user.Mode == users.CombinedMode {
|
||||
targetMode = users.SplitMode
|
||||
} else if !splitMode.Active && user.Mode == users.SplitMode {
|
||||
targetMode = users.CombinedMode
|
||||
if splitMode.Active && user.AddressMode == bridge.CombinedMode {
|
||||
targetMode = bridge.SplitMode
|
||||
} else if !splitMode.Active && user.AddressMode == bridge.SplitMode {
|
||||
targetMode = bridge.CombinedMode
|
||||
}
|
||||
|
||||
if err := s.bridge.SetAddressMode(user.ID, targetMode); err != nil {
|
||||
if err := s.bridge.SetAddressMode(user.UserID, targetMode); err != nil {
|
||||
logrus.WithError(err).Error("Failed to set address mode")
|
||||
}
|
||||
}()
|
||||
@ -101,7 +100,7 @@ func (s *Service) LogoutUser(ctx context.Context, userID *wrapperspb.StringValue
|
||||
go func() {
|
||||
defer s.panicHandler.HandlePanic()
|
||||
|
||||
if err := s.bridge.LogoutUser(userID.Value); err != nil {
|
||||
if err := s.bridge.LogoutUser(context.Background(), userID.Value); err != nil {
|
||||
logrus.WithError(err).Error("Failed to log user out")
|
||||
}
|
||||
}()
|
||||
@ -116,7 +115,7 @@ func (s *Service) RemoveUser(ctx context.Context, userID *wrapperspb.StringValue
|
||||
defer s.panicHandler.HandlePanic()
|
||||
|
||||
// remove preferences
|
||||
if err := s.bridge.DeleteUser(userID.Value, false); err != nil {
|
||||
if err := s.bridge.DeleteUser(context.Background(), userID.Value); err != nil {
|
||||
s.log.WithError(err).Error("Failed to remove user")
|
||||
// notification
|
||||
}
|
||||
@ -127,18 +126,10 @@ func (s *Service) RemoveUser(ctx context.Context, userID *wrapperspb.StringValue
|
||||
func (s *Service) ConfigureUserAppleMail(ctx context.Context, request *ConfigureAppleMailRequest) (*emptypb.Empty, error) {
|
||||
s.log.WithField("UserID", request.UserID).WithField("Address", request.Address).Debug("ConfigureUserAppleMail")
|
||||
|
||||
restart, err := s.bridge.ConfigureAppleMail(request.UserID, request.Address)
|
||||
if err != nil {
|
||||
if err := s.bridge.ConfigureAppleMail(request.UserID, request.Address); err != nil {
|
||||
s.log.WithField("userID", request.UserID).Error("Cannot configure AppleMail for user")
|
||||
return nil, status.Error(codes.Internal, "Apple Mail config failed")
|
||||
}
|
||||
|
||||
// There is delay needed for external window to open.
|
||||
if restart {
|
||||
s.log.Warn("Detected Catalina or newer with bad SMTP SSL settings, now using SSL, bridge needs to restart")
|
||||
time.Sleep(2 * time.Second)
|
||||
return s.Restart(ctx, &emptypb.Empty{})
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -58,17 +58,17 @@ func getInitials(fullName string) string {
|
||||
}
|
||||
|
||||
// grpcUserFromInfo converts a bridge user to a gRPC user.
|
||||
func grpcUserFromInfo(user users.UserInfo) *User {
|
||||
func grpcUserFromInfo(user bridge.UserInfo) *User {
|
||||
return &User{
|
||||
Id: user.ID,
|
||||
Id: user.UserID,
|
||||
Username: user.Username,
|
||||
AvatarText: getInitials(user.Username),
|
||||
LoggedIn: user.Connected,
|
||||
SplitMode: user.Mode == users.SplitMode,
|
||||
SplitMode: user.AddressMode == bridge.SplitMode,
|
||||
SetupGuideSeen: true, // users listed have already seen the setup guide.
|
||||
UsedBytes: user.UsedBytes,
|
||||
TotalBytes: user.TotalBytes,
|
||||
Password: user.Password,
|
||||
UsedBytes: int64(user.UsedSpace),
|
||||
TotalBytes: int64(user.MaxSpace),
|
||||
Password: user.BridgePass,
|
||||
Addresses: user.Addresses,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,101 +0,0 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package types provides interfaces used in frontend packages.
|
||||
package types
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
|
||||
)
|
||||
|
||||
// PanicHandler is an interface of a type that can be used to gracefully handle panics which occur.
|
||||
type PanicHandler interface {
|
||||
HandlePanic()
|
||||
}
|
||||
|
||||
// Restarter allows the app to set itself to restart next time it is closed.
|
||||
type Restarter interface {
|
||||
SetToRestart()
|
||||
ForceLauncher(string)
|
||||
SetMainExecutable(string)
|
||||
}
|
||||
|
||||
type Updater interface {
|
||||
Check() (updater.VersionInfo, error)
|
||||
InstallUpdate(updater.VersionInfo) error
|
||||
IsUpdateApplicable(updater.VersionInfo) bool
|
||||
CanInstall(updater.VersionInfo) bool
|
||||
}
|
||||
|
||||
// Bridger is an interface of bridge needed by frontend.
|
||||
type Bridger interface {
|
||||
Login(username string, password []byte) (pmapi.Client, *pmapi.Auth, error)
|
||||
FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword []byte) (string, error)
|
||||
|
||||
GetUserIDs() []string
|
||||
GetUserInfo(string) (users.UserInfo, error)
|
||||
LogoutUser(userID string) error
|
||||
DeleteUser(userID string, clearCache bool) error
|
||||
SetAddressMode(userID string, split users.AddressMode) error
|
||||
|
||||
ClearData() error
|
||||
ClearUsers() error
|
||||
FactoryReset()
|
||||
|
||||
GetTLSConfig() (*tls.Config, error)
|
||||
ProvideLogsPath() (string, error)
|
||||
GetLicenseFilePath() string
|
||||
GetDependencyLicensesLink() string
|
||||
|
||||
GetCurrentUserAgent() string
|
||||
SetCurrentPlatform(string)
|
||||
|
||||
Get(settings.Key) string
|
||||
Set(settings.Key, string)
|
||||
GetBool(settings.Key) bool
|
||||
SetBool(settings.Key, bool)
|
||||
GetInt(settings.Key) int
|
||||
SetInt(settings.Key, int)
|
||||
|
||||
ConfigureAppleMail(userID, address string) (bool, error)
|
||||
|
||||
// -- old --
|
||||
|
||||
ReportBug(osType, osVersion, description, accountName, address, emailClient string, attachLogs bool) error
|
||||
SetProxyAllowed(bool)
|
||||
GetProxyAllowed() bool
|
||||
EnableCache() error
|
||||
DisableCache() error
|
||||
MigrateCache(from, to string) error
|
||||
GetUpdateChannel() updater.UpdateChannel
|
||||
SetUpdateChannel(updater.UpdateChannel)
|
||||
GetKeychainApp() string
|
||||
SetKeychainApp(keychain string)
|
||||
HasError(err error) bool
|
||||
IsAutostartEnabled() bool
|
||||
EnableAutostart() error
|
||||
DisableAutostart() error
|
||||
GetLastVersion() string
|
||||
IsFirstStart() bool
|
||||
IsAllMailVisible() bool
|
||||
SetIsAllMailVisible(bool)
|
||||
}
|
||||
Reference in New Issue
Block a user