GODT-1779: Remove go-imap

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

219
internal/user/user.go Normal file
View File

@ -0,0 +1,219 @@
package user
import (
"context"
"runtime"
"time"
"github.com/ProtonMail/gluon/connector"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/internal/pool"
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
"github.com/bradenaw/juniper/xslices"
"github.com/emersion/go-smtp"
"github.com/sirupsen/logrus"
"gitlab.protontech.ch/go/liteapi"
"golang.org/x/exp/slices"
)
var (
DefaultEventPeriod = 20 * time.Second
DefaultEventJitter = 20 * time.Second
)
// TODO: Is it bad to store the key pass in the user? Any worse than storing private keys?
type User struct {
vault *vault.User
client *liteapi.Client
builder *pool.Pool[request, *imap.MessageCreated]
apiUser liteapi.User
addresses []liteapi.Address
settings liteapi.MailSettings
notifyCh chan events.Event
updateCh chan imap.Update
userKR *crypto.KeyRing
addrKRs map[string]*crypto.KeyRing
imapConn *imapConnector
}
func New(
ctx context.Context,
vault *vault.User,
client *liteapi.Client,
apiUser liteapi.User,
apiAddrs []liteapi.Address,
userKR *crypto.KeyRing,
addrKRs map[string]*crypto.KeyRing,
) (*User, error) {
if vault.EventID() == "" {
eventID, err := client.GetLatestEventID(ctx)
if err != nil {
return nil, err
}
if err := vault.UpdateEventID(eventID); err != nil {
return nil, err
}
}
settings, err := client.GetMailSettings(ctx)
if err != nil {
return nil, err
}
user := &User{
apiUser: apiUser,
addresses: apiAddrs,
settings: settings,
vault: vault,
client: client,
builder: newBuilder(client, runtime.NumCPU()*runtime.NumCPU(), runtime.NumCPU()*runtime.NumCPU()),
notifyCh: make(chan events.Event),
updateCh: make(chan imap.Update),
userKR: userKR,
addrKRs: addrKRs,
}
// When we receive an auth object, we update it in the store.
// This will be used to authorize the user on the next run.
client.AddAuthHandler(func(auth liteapi.Auth) {
if err := user.vault.UpdateAuth(auth.UID, auth.RefreshToken); err != nil {
logrus.WithError(err).Error("Failed to update auth")
}
})
// When we are deauthorized, we send a deauth event to the notify channel.
// Bridge will catch this and log the user out.
client.AddDeauthHandler(func() {
user.notifyCh <- events.UserDeauth{
UserID: user.ID(),
}
})
// When we receive an API event, we attempt to handle it. If successful, we send the event to the event channel.
go func() {
for event := range user.client.NewEventStreamer(DefaultEventPeriod, DefaultEventJitter, vault.EventID()).Subscribe() {
if err := user.handleAPIEvent(event); err != nil {
logrus.WithError(err).Error("Failed to handle event")
} else {
if err := user.vault.UpdateEventID(event.EventID); err != nil {
logrus.WithError(err).Error("Failed to update event ID")
}
}
}
}()
// TODO: Use a proper sync manager! (if partial sync, pickup from where we last stopped)
if !vault.HasSync() {
go user.sync(context.Background())
}
return user, nil
}
func (user *User) ID() string {
return user.apiUser.ID
}
func (user *User) Name() string {
return user.apiUser.Name
}
func (user *User) Match(query string) bool {
if query == user.Name() {
return true
}
return slices.Contains(user.Addresses(), query)
}
func (user *User) Addresses() []string {
return xslices.Map(
sort(user.addresses, func(a, b liteapi.Address) bool {
return a.Order < b.Order
}),
func(address liteapi.Address) string {
return address.Email
},
)
}
func (user *User) GluonID() string {
return user.vault.GluonID()
}
func (user *User) GluonKey() []byte {
return user.vault.GluonKey()
}
func (user *User) BridgePass() string {
return user.vault.BridgePass()
}
func (user *User) UsedSpace() int {
return user.apiUser.UsedSpace
}
func (user *User) MaxSpace() int {
return user.apiUser.MaxSpace
}
// GetNotifyCh returns a channel which notifies of events happening to the user (such as deauth, address change)
func (user *User) GetNotifyCh() <-chan events.Event {
return user.notifyCh
}
func (user *User) NewGluonConnector(ctx context.Context) (connector.Connector, error) {
if user.imapConn != nil {
if err := user.imapConn.Close(ctx); err != nil {
return nil, err
}
}
user.imapConn = newIMAPConnector(user.client, user.updateCh, user.Addresses(), user.vault.BridgePass())
return user.imapConn, nil
}
func (user *User) NewSMTPSession(username string) (smtp.Session, error) {
return newSMTPSession(user.client, username, user.addresses, user.userKR, user.addrKRs, user.settings), nil
}
func (user *User) Logout(ctx context.Context) error {
return user.client.AuthDelete(ctx)
}
func (user *User) Close(ctx context.Context) error {
// Close the user's IMAP connectors.
if user.imapConn != nil {
if err := user.imapConn.Close(ctx); err != nil {
return err
}
}
// Close the user's message builder.
user.builder.Done()
// Close the user's API client.
user.client.Close()
// Close the user's notify channel.
close(user.notifyCh)
return nil
}
// sort returns the slice, sorted by the given callback.
func sort[T any](slice []T, less func(a, b T) bool) []T {
slices.SortFunc(slice, less)
return slice
}