mirror of
https://github.com/shenxn/protonmail-bridge-docker.git
synced 2026-01-18 14:44:41 +01:00
Initial commit for http rest bridge
This commit is contained in:
147
http_rest_frontend/cli/accounts.go
Normal file
147
http_rest_frontend/cli/accounts.go
Normal file
@@ -0,0 +1,147 @@
|
||||
// Package cli provides HTTP interface of the bridge
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) loginAccount(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(w, "ParseForm() err: %v", err)
|
||||
return
|
||||
}
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
twoFactor := r.FormValue("two-factor")
|
||||
mailboxPassword := r.FormValue("mailbox-password")
|
||||
client, auth, err := f.bridge.Login(username, []byte(password))
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintln(w, "Server error:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if auth.HasTwoFactor() {
|
||||
if twoFactor == "" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintln(w, "2FA enabled for the account but a 2FA code was not provided.")
|
||||
return
|
||||
}
|
||||
err = client.Auth2FA(context.Background(), twoFactor)
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintln(w, "Server error:", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if auth.HasMailboxPassword() {
|
||||
if mailboxPassword == "" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintln(w, "Two password mode enabled but a mailbox password was not provided.")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
mailboxPassword = password
|
||||
}
|
||||
user, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword))
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintln(w, "Server error:", err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "Account %s was added successfully.\n", user.Username())
|
||||
f.printAccountInfo(w, user)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) deleteAccount(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
account := params.ByName("account")
|
||||
user := f.getUserByIndexOrName(account)
|
||||
if user == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(w, "Account %s does not exist.\n", account)
|
||||
return
|
||||
}
|
||||
account = user.Username()
|
||||
if err := f.bridge.DeleteUser(user.ID(), true); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintln(w, "Cannot delete account: ", err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "Account %s was deleted successfully.\n", account)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) listAccounts(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
users := f.bridge.GetUsers()
|
||||
if len(users) == 0 {
|
||||
fmt.Fprintln(w, "No account found.")
|
||||
return
|
||||
}
|
||||
spacing := "%-2d: %-20s (%-15s, %-15s)\n"
|
||||
fmt.Fprintf(w, strings.ReplaceAll(spacing, "d", "s"), "#", "account", "status", "address mode")
|
||||
for idx, user := range users {
|
||||
connected := "disconnected"
|
||||
if user.IsConnected() {
|
||||
connected = "connected"
|
||||
}
|
||||
mode := "split"
|
||||
if user.IsCombinedAddressMode() {
|
||||
mode = "combined"
|
||||
}
|
||||
fmt.Fprintf(w, spacing, idx, user.Username(), connected, mode)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) showAccountInfo(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
account := params.ByName("account")
|
||||
user := f.getUserByIndexOrName(account)
|
||||
if user == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(w, "Account %s does not exist.\n", account)
|
||||
return
|
||||
}
|
||||
if !user.IsConnected() {
|
||||
fmt.Fprintf(w, "Please login to %s to get email client configuration.\n", user.Username())
|
||||
return
|
||||
}
|
||||
f.printAccountInfo(w, user)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printAccountInfo(w io.Writer, user types.User) {
|
||||
if user.IsCombinedAddressMode() {
|
||||
f.printAccountAddressInfo(w, user, user.GetPrimaryAddress())
|
||||
} else {
|
||||
for _, address := range user.GetAddresses() {
|
||||
f.printAccountAddressInfo(w, user, address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printAccountAddressInfo(w io.Writer, user types.User, address string) {
|
||||
fmt.Fprintln(w, "Configuration for", address)
|
||||
smtpSecurity := "STARTTLS"
|
||||
if f.settings.GetBool(settings.SMTPSSLKey) {
|
||||
smtpSecurity = "SSL"
|
||||
}
|
||||
fmt.Fprintf(w, "IMAP port: %d\nIMAP security: %s\nSMTP port: %d\nSMTP security: %s\nUsername: %s\nPassword: %s\n",
|
||||
143,
|
||||
"STARTTLS",
|
||||
25,
|
||||
smtpSecurity,
|
||||
address,
|
||||
user.GetBridgePassword(),
|
||||
)
|
||||
fmt.Fprintln(w, "")
|
||||
}
|
||||
24
http_rest_frontend/cli/accounts_util.go
Normal file
24
http_rest_frontend/cli/accounts_util.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) getUserByIndexOrName(account string) types.User {
|
||||
users := f.bridge.GetUsers()
|
||||
numberOfAccounts := len(users)
|
||||
if index, err := strconv.Atoi(account); err == nil {
|
||||
if index < 0 || index >= numberOfAccounts {
|
||||
return nil
|
||||
}
|
||||
return users[index]
|
||||
}
|
||||
for _, user := range users {
|
||||
if user.Username() == account {
|
||||
return user
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
157
http_rest_frontend/cli/frontend.go
Normal file
157
http_rest_frontend/cli/frontend.go
Normal file
@@ -0,0 +1,157 @@
|
||||
// Package cli provides HTTP interface of the bridge
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type frontendCLI struct {
|
||||
*httprouter.Router
|
||||
|
||||
locations *locations.Locations
|
||||
settings *settings.Settings
|
||||
eventListener listener.Listener
|
||||
updater types.Updater
|
||||
bridge types.Bridger
|
||||
restarter types.Restarter
|
||||
}
|
||||
|
||||
func New(
|
||||
panicHandler types.PanicHandler,
|
||||
|
||||
locations *locations.Locations,
|
||||
settings *settings.Settings,
|
||||
eventListener listener.Listener,
|
||||
updater types.Updater,
|
||||
bridge types.Bridger,
|
||||
restarter types.Restarter,
|
||||
) *frontendCLI {
|
||||
fe := &frontendCLI{
|
||||
Router: httprouter.New(),
|
||||
locations: locations,
|
||||
settings: settings,
|
||||
eventListener: eventListener,
|
||||
updater: updater,
|
||||
bridge: bridge,
|
||||
restarter: restarter,
|
||||
}
|
||||
|
||||
fe.PUT("/accounts", fe.loginAccount)
|
||||
fe.GET("/accounts", fe.listAccounts)
|
||||
fe.GET("/accounts/:account", fe.showAccountInfo)
|
||||
fe.DELETE("/accounts/:account", fe.deleteAccount)
|
||||
|
||||
return fe
|
||||
}
|
||||
|
||||
func (f *frontendCLI) loginWithEnv() {
|
||||
if len(f.bridge.GetUsers()) > 0 {
|
||||
fmt.Println("More than 0 accounts found. Skip auto login.")
|
||||
return
|
||||
}
|
||||
username := os.Getenv("PROTON_USERNAME")
|
||||
password := os.Getenv("PROTON_PASSWORD")
|
||||
if username == "" {
|
||||
logrus.Info("PROTON_USERNAME and PROTON_PASSWORD are not set. Skip auto login.")
|
||||
return
|
||||
}
|
||||
client, auth, err := f.bridge.Login(username, []byte(password))
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
logrus.WithError(err).Warn("Login failed.")
|
||||
return
|
||||
}
|
||||
|
||||
if auth.HasTwoFactor() {
|
||||
twoFactor := os.Getenv("PROTON_2FA")
|
||||
if twoFactor == "" {
|
||||
logrus.Warn("Login failed: 2FA enabled for the account but PROTON_2FA was not set.")
|
||||
return
|
||||
}
|
||||
err = client.Auth2FA(context.Background(), twoFactor)
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
logrus.WithError(err).Warn("Login failed.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
mailboxPassword := password
|
||||
if auth.HasMailboxPassword() {
|
||||
mailboxPassword = os.Getenv("PROTON_MAILBOX_PASSWORD")
|
||||
if mailboxPassword == "" {
|
||||
logrus.Warn("Login failed: Two password mode enabled but PROTON_MAILBOX_PASSWORD was not set.")
|
||||
return
|
||||
}
|
||||
}
|
||||
user, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword))
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
logrus.WithError(err).Warn("Login failed.")
|
||||
return
|
||||
}
|
||||
logrus.Infof("Account %s was added successfully.\n", user.Username())
|
||||
if strings.ToLower(os.Getenv("PROTON_PRINT_ACCOUNT_INFO")) != "off" {
|
||||
f.printAccountInfo(os.Stdout, user)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) watchEvents() {
|
||||
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
|
||||
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
|
||||
internetOffCh := f.eventListener.ProvideChannel(events.InternetOffEvent)
|
||||
internetOnCh := f.eventListener.ProvideChannel(events.InternetOnEvent)
|
||||
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:
|
||||
fmt.Println("Bridge failed:", errorDetails)
|
||||
case <-credentialsErrorCh:
|
||||
f.notifyCredentialsError()
|
||||
case <-internetOffCh:
|
||||
f.notifyInternetOff()
|
||||
case <-internetOnCh:
|
||||
f.notifyInternetOn()
|
||||
case address := <-addressChangedCh:
|
||||
fmt.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.GetUser(userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
f.notifyLogout(user.Username())
|
||||
case <-certIssue:
|
||||
f.notifyCertIssue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) Loop() error {
|
||||
f.loginWithEnv()
|
||||
http.ListenAndServe(":1080", f)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *frontendCLI) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {}
|
||||
func (f *frontendCLI) WaitUntilFrontendIsReady() {}
|
||||
func (f *frontendCLI) SetVersion(version updater.VersionInfo) {}
|
||||
func (f *frontendCLI) NotifySilentUpdateInstalled() {}
|
||||
func (f *frontendCLI) NotifySilentUpdateError(err error) {}
|
||||
57
http_rest_frontend/cli/utils.go
Normal file
57
http_rest_frontend/cli/utils.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) processAPIError(err error) {
|
||||
switch err {
|
||||
case pmapi.ErrNoConnection:
|
||||
f.notifyInternetOff()
|
||||
case pmapi.ErrUpgradeApplication:
|
||||
f.notifyNeedUpgrade()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyInternetOff() {
|
||||
logrus.Warn("Internet connection is not available.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyInternetOn() {
|
||||
logrus.Info("Internet connection is available again.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyLogout(address string) {
|
||||
logrus.Infof("Account %s is disconnected. Login to continue using this account with email client.", address)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyNeedUpgrade() {
|
||||
logrus.Info("Upgrade needed. Please download and install the newest version of application.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyCredentialsError() {
|
||||
logrus.Error(`ProtonMail Bridge is not able to detect a supported password manager
|
||||
(secret-service or pass). Please install and set up a supported password manager
|
||||
and restart the application.
|
||||
`)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyCertIssue() {
|
||||
// Print in 80-column width.
|
||||
logrus.Error(`Connection security error: Your network connection to Proton services may
|
||||
be insecure.
|
||||
|
||||
Description:
|
||||
ProtonMail Bridge was not able to establish a secure connection to Proton
|
||||
servers due to a TLS certificate error. This means your connection may
|
||||
potentially be insecure and susceptible to monitoring by third parties.
|
||||
|
||||
Recommendation:
|
||||
* If you trust your network operator, you can continue to use ProtonMail
|
||||
as usual.
|
||||
* If you don't trust your network operator, reconnect to ProtonMail over a VPN
|
||||
(such as ProtonVPN) which encrypts your Internet connection, or use
|
||||
a different network to access ProtonMail.
|
||||
`)
|
||||
}
|
||||
Reference in New Issue
Block a user