forked from Silverfish/proton-bridge
GODT-1779: Remove go-imap
This commit is contained in:
219
internal/user/user.go
Normal file
219
internal/user/user.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user