mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
webmail: less boilerplate code for api functions
open the account at the beginning of the api handler, and close accounts there too
This commit is contained in:
parent
ed0c520562
commit
a3f5fd26a6
5 changed files with 236 additions and 336 deletions
256
webmail/api.go
256
webmail/api.go
|
@ -94,8 +94,8 @@ func init() {
|
||||||
// LoginPrep returns a login token, and also sets it as cookie. Both must be
|
// LoginPrep returns a login token, and also sets it as cookie. Both must be
|
||||||
// present in the call to Login.
|
// present in the call to Login.
|
||||||
func (w Webmail) LoginPrep(ctx context.Context) string {
|
func (w Webmail) LoginPrep(ctx context.Context) string {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
|
log := reqInfo.Log
|
||||||
|
|
||||||
var data [8]byte
|
var data [8]byte
|
||||||
_, err := cryptorand.Read(data[:])
|
_, err := cryptorand.Read(data[:])
|
||||||
|
@ -110,8 +110,8 @@ func (w Webmail) LoginPrep(ctx context.Context) string {
|
||||||
// Login returns a session token for the credentials, or fails with error code
|
// Login returns a session token for the credentials, or fails with error code
|
||||||
// "user:badLogin". Call LoginPrep to get a loginToken.
|
// "user:badLogin". Call LoginPrep to get a loginToken.
|
||||||
func (w Webmail) Login(ctx context.Context, loginToken, username, password string) store.CSRFToken {
|
func (w Webmail) Login(ctx context.Context, loginToken, username, password string) store.CSRFToken {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
|
log := reqInfo.Log
|
||||||
|
|
||||||
csrfToken, err := webauth.Login(ctx, log, webauth.Accounts, "webmail", w.cookiePath, w.isForwarded, reqInfo.Response, reqInfo.Request, loginToken, username, password)
|
csrfToken, err := webauth.Login(ctx, log, webauth.Accounts, "webmail", w.cookiePath, w.isForwarded, reqInfo.Response, reqInfo.Request, loginToken, username, password)
|
||||||
if _, ok := err.(*sherpa.Error); ok {
|
if _, ok := err.(*sherpa.Error); ok {
|
||||||
|
@ -123,10 +123,10 @@ func (w Webmail) Login(ctx context.Context, loginToken, username, password strin
|
||||||
|
|
||||||
// Logout invalidates the session token.
|
// Logout invalidates the session token.
|
||||||
func (w Webmail) Logout(ctx context.Context) {
|
func (w Webmail) Logout(ctx context.Context) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
|
log := reqInfo.Log
|
||||||
|
|
||||||
err := webauth.Logout(ctx, log, webauth.Accounts, "webmail", w.cookiePath, w.isForwarded, reqInfo.Response, reqInfo.Request, reqInfo.AccountName, reqInfo.SessionToken)
|
err := webauth.Logout(ctx, log, webauth.Accounts, "webmail", w.cookiePath, w.isForwarded, reqInfo.Response, reqInfo.Request, reqInfo.Account.Name, reqInfo.SessionToken)
|
||||||
xcheckf(ctx, err, "logout")
|
xcheckf(ctx, err, "logout")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ func (w Webmail) Logout(ctx context.Context) {
|
||||||
// with at most 10 unused tokens (the most recently created) per account.
|
// with at most 10 unused tokens (the most recently created) per account.
|
||||||
func (Webmail) Token(ctx context.Context) string {
|
func (Webmail) Token(ctx context.Context) string {
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
return sseTokens.xgenerate(ctx, reqInfo.AccountName, reqInfo.LoginAddress, reqInfo.SessionToken)
|
return sseTokens.xgenerate(ctx, reqInfo.Account.Name, reqInfo.LoginAddress, reqInfo.SessionToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Requests sends a new request for an open SSE connection. Any currently active
|
// Requests sends a new request for an open SSE connection. Any currently active
|
||||||
|
@ -150,7 +150,7 @@ func (Webmail) Request(ctx context.Context, req Request) {
|
||||||
xcheckuserf(ctx, errors.New("Page.Count must be >= 1"), "checking request")
|
xcheckuserf(ctx, errors.New("Page.Count must be >= 1"), "checking request")
|
||||||
}
|
}
|
||||||
|
|
||||||
sse, ok := sseGet(req.SSEID, reqInfo.AccountName)
|
sse, ok := sseGet(req.SSEID, reqInfo.Account.Name)
|
||||||
if !ok {
|
if !ok {
|
||||||
xcheckuserf(ctx, errors.New("unknown sseid"), "looking up connection")
|
xcheckuserf(ctx, errors.New("unknown sseid"), "looking up connection")
|
||||||
}
|
}
|
||||||
|
@ -160,20 +160,16 @@ func (Webmail) Request(ctx context.Context, req Request) {
|
||||||
// ParsedMessage returns enough to render the textual body of a message. It is
|
// ParsedMessage returns enough to render the textual body of a message. It is
|
||||||
// assumed the client already has other fields through MessageItem.
|
// assumed the client already has other fields through MessageItem.
|
||||||
func (Webmail) ParsedMessage(ctx context.Context, msgID int64) (pm ParsedMessage) {
|
func (Webmail) ParsedMessage(ctx context.Context, msgID int64) (pm ParsedMessage) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
log := reqInfo.Log
|
||||||
xcheckf(ctx, err, "open account")
|
acc := reqInfo.Account
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
xdbread(ctx, acc, func(tx *bstore.Tx) {
|
xdbread(ctx, acc, func(tx *bstore.Tx) {
|
||||||
m := xmessageID(ctx, tx, msgID)
|
m := xmessageID(ctx, tx, msgID)
|
||||||
|
|
||||||
state := msgState{acc: acc}
|
state := msgState{acc: acc}
|
||||||
defer state.clear()
|
defer state.clear()
|
||||||
|
var err error
|
||||||
pm, err = parsedMessage(log, m, &state, true, false)
|
pm, err = parsedMessage(log, m, &state, true, false)
|
||||||
xcheckf(ctx, err, "parsing message")
|
xcheckf(ctx, err, "parsing message")
|
||||||
|
|
||||||
|
@ -202,14 +198,8 @@ func fromAddrViewMode(tx *bstore.Tx, from MessageAddress) (store.ViewMode, error
|
||||||
|
|
||||||
// FromAddressSettingsSave saves per-"From"-address settings.
|
// FromAddressSettingsSave saves per-"From"-address settings.
|
||||||
func (Webmail) FromAddressSettingsSave(ctx context.Context, fas store.FromAddressSettings) {
|
func (Webmail) FromAddressSettingsSave(ctx context.Context, fas store.FromAddressSettings) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
if fas.FromAddress == "" {
|
if fas.FromAddress == "" {
|
||||||
xcheckuserf(ctx, errors.New("empty from address"), "checking address")
|
xcheckuserf(ctx, errors.New("empty from address"), "checking address")
|
||||||
|
@ -231,14 +221,8 @@ func (Webmail) FromAddressSettingsSave(ctx context.Context, fas store.FromAddres
|
||||||
// for editing again.
|
// for editing again.
|
||||||
// If no message is find, zero is returned, not an error.
|
// If no message is find, zero is returned, not an error.
|
||||||
func (Webmail) MessageFindMessageID(ctx context.Context, messageID string) (id int64) {
|
func (Webmail) MessageFindMessageID(ctx context.Context, messageID string) (id int64) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
messageID, _, _ = message.MessageIDCanonical(messageID)
|
messageID, _, _ = message.MessageIDCanonical(messageID)
|
||||||
if messageID == "" {
|
if messageID == "" {
|
||||||
|
@ -272,6 +256,12 @@ type ComposeMessage struct {
|
||||||
// MessageCompose composes a message and saves it to the mailbox. Used for
|
// MessageCompose composes a message and saves it to the mailbox. Used for
|
||||||
// saving draft messages.
|
// saving draft messages.
|
||||||
func (w Webmail) MessageCompose(ctx context.Context, m ComposeMessage, mailboxID int64) (id int64) {
|
func (w Webmail) MessageCompose(ctx context.Context, m ComposeMessage, mailboxID int64) (id int64) {
|
||||||
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
|
acc := reqInfo.Account
|
||||||
|
log := reqInfo.Log
|
||||||
|
|
||||||
|
log.Debug("message compose")
|
||||||
|
|
||||||
// Prevent any accidental control characters, or attempts at getting bare \r or \n
|
// Prevent any accidental control characters, or attempts at getting bare \r or \n
|
||||||
// into messages.
|
// into messages.
|
||||||
for _, l := range [][]string{m.To, m.Cc, m.Bcc, {m.From, m.Subject, m.ReplyTo}} {
|
for _, l := range [][]string{m.To, m.Cc, m.Bcc, {m.From, m.Subject, m.ReplyTo}} {
|
||||||
|
@ -284,17 +274,6 @@ func (w Webmail) MessageCompose(ctx context.Context, m ComposeMessage, mailboxID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
|
||||||
log := pkglog.WithContext(ctx).With(slog.String("account", reqInfo.AccountName))
|
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
|
||||||
xcheckf(ctx, err, "open account")
|
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Debug("message compose")
|
|
||||||
|
|
||||||
fromAddr, err := parseAddress(m.From)
|
fromAddr, err := parseAddress(m.From)
|
||||||
xcheckuserf(ctx, err, "parsing From address")
|
xcheckuserf(ctx, err, "parsing From address")
|
||||||
|
|
||||||
|
@ -602,6 +581,12 @@ func xrandom(ctx context.Context, n int) []byte {
|
||||||
// to the delivery queue. If Bcc addresses were present, a header is prepended
|
// to the delivery queue. If Bcc addresses were present, a header is prepended
|
||||||
// to the message stored in the Sent mailbox.
|
// to the message stored in the Sent mailbox.
|
||||||
func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
|
func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
|
||||||
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
|
acc := reqInfo.Account
|
||||||
|
log := reqInfo.Log
|
||||||
|
|
||||||
|
log.Debug("message submit")
|
||||||
|
|
||||||
// Similar between ../smtpserver/server.go:/submit\( and ../webmail/api.go:/MessageSubmit\( and ../webapisrv/server.go:/Send\(
|
// Similar between ../smtpserver/server.go:/submit\( and ../webmail/api.go:/MessageSubmit\( and ../webapisrv/server.go:/Send\(
|
||||||
|
|
||||||
// todo: consider making this an HTTP POST, so we can upload as regular form, which is probably more efficient for encoding for the client and we can stream the data in. also not unlike the webapi Submit method.
|
// todo: consider making this an HTTP POST, so we can upload as regular form, which is probably more efficient for encoding for the client and we can stream the data in. also not unlike the webapi Submit method.
|
||||||
|
@ -618,17 +603,6 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
|
||||||
log := pkglog.WithContext(ctx).With(slog.String("account", reqInfo.AccountName))
|
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
|
||||||
xcheckf(ctx, err, "open account")
|
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Debug("message submit")
|
|
||||||
|
|
||||||
fromAddr, err := parseAddress(m.From)
|
fromAddr, err := parseAddress(m.From)
|
||||||
xcheckuserf(ctx, err, "parsing From address")
|
xcheckuserf(ctx, err, "parsing From address")
|
||||||
|
|
||||||
|
@ -667,7 +641,7 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
|
||||||
|
|
||||||
// Check if from address is allowed for account.
|
// Check if from address is allowed for account.
|
||||||
fromAccName, _, _, err := mox.FindAccount(fromAddr.Address.Localpart, fromAddr.Address.Domain, false)
|
fromAccName, _, _, err := mox.FindAccount(fromAddr.Address.Localpart, fromAddr.Address.Domain, false)
|
||||||
if err == nil && fromAccName != reqInfo.AccountName {
|
if err == nil && fromAccName != reqInfo.Account.Name {
|
||||||
err = mox.ErrAccountNotFound
|
err = mox.ErrAccountNotFound
|
||||||
}
|
}
|
||||||
if err != nil && (errors.Is(err, mox.ErrAccountNotFound) || errors.Is(err, mox.ErrDomainNotFound)) {
|
if err != nil && (errors.Is(err, mox.ErrAccountNotFound) || errors.Is(err, mox.ErrDomainNotFound)) {
|
||||||
|
@ -1008,7 +982,7 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
|
||||||
// no qm.Extra from webmail
|
// no qm.Extra from webmail
|
||||||
qml[i] = qm
|
qml[i] = qm
|
||||||
}
|
}
|
||||||
err = queue.Add(ctx, log, reqInfo.AccountName, dataFile, qml...)
|
err = queue.Add(ctx, log, reqInfo.Account.Name, dataFile, qml...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
metricSubmission.WithLabelValues("queueerror").Inc()
|
metricSubmission.WithLabelValues("queueerror").Inc()
|
||||||
}
|
}
|
||||||
|
@ -1153,14 +1127,9 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
|
||||||
// MessageMove moves messages to another mailbox. If the message is already in
|
// MessageMove moves messages to another mailbox. If the message is already in
|
||||||
// the mailbox an error is returned.
|
// the mailbox an error is returned.
|
||||||
func (Webmail) MessageMove(ctx context.Context, messageIDs []int64, mailboxID int64) {
|
func (Webmail) MessageMove(ctx context.Context, messageIDs []int64, mailboxID int64) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
log := reqInfo.Log
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
xops.MessageMove(ctx, log, acc, messageIDs, "", mailboxID)
|
xops.MessageMove(ctx, log, acc, messageIDs, "", mailboxID)
|
||||||
}
|
}
|
||||||
|
@ -1173,14 +1142,9 @@ var xops = webops.XOps{
|
||||||
|
|
||||||
// MessageDelete permanently deletes messages, without moving them to the Trash mailbox.
|
// MessageDelete permanently deletes messages, without moving them to the Trash mailbox.
|
||||||
func (Webmail) MessageDelete(ctx context.Context, messageIDs []int64) {
|
func (Webmail) MessageDelete(ctx context.Context, messageIDs []int64) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
log := reqInfo.Log
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
if len(messageIDs) == 0 {
|
if len(messageIDs) == 0 {
|
||||||
return
|
return
|
||||||
|
@ -1192,43 +1156,28 @@ func (Webmail) MessageDelete(ctx context.Context, messageIDs []int64) {
|
||||||
// FlagsAdd adds flags, either system flags like \Seen or custom keywords. The
|
// FlagsAdd adds flags, either system flags like \Seen or custom keywords. The
|
||||||
// flags should be lower-case, but will be converted and verified.
|
// flags should be lower-case, but will be converted and verified.
|
||||||
func (Webmail) FlagsAdd(ctx context.Context, messageIDs []int64, flaglist []string) {
|
func (Webmail) FlagsAdd(ctx context.Context, messageIDs []int64, flaglist []string) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
log := reqInfo.Log
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
xops.MessageFlagsAdd(ctx, log, acc, messageIDs, flaglist)
|
xops.MessageFlagsAdd(ctx, log, acc, messageIDs, flaglist)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlagsClear clears flags, either system flags like \Seen or custom keywords.
|
// FlagsClear clears flags, either system flags like \Seen or custom keywords.
|
||||||
func (Webmail) FlagsClear(ctx context.Context, messageIDs []int64, flaglist []string) {
|
func (Webmail) FlagsClear(ctx context.Context, messageIDs []int64, flaglist []string) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
log := reqInfo.Log
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
xops.MessageFlagsClear(ctx, log, acc, messageIDs, flaglist)
|
xops.MessageFlagsClear(ctx, log, acc, messageIDs, flaglist)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailboxCreate creates a new mailbox.
|
// MailboxCreate creates a new mailbox.
|
||||||
func (Webmail) MailboxCreate(ctx context.Context, name string) {
|
func (Webmail) MailboxCreate(ctx context.Context, name string) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
|
var err error
|
||||||
name, _, err = store.CheckMailboxName(name, false)
|
name, _, err = store.CheckMailboxName(name, false)
|
||||||
xcheckuserf(ctx, err, "checking mailbox name")
|
xcheckuserf(ctx, err, "checking mailbox name")
|
||||||
|
|
||||||
|
@ -1250,14 +1199,9 @@ func (Webmail) MailboxCreate(ctx context.Context, name string) {
|
||||||
|
|
||||||
// MailboxDelete deletes a mailbox and all its messages.
|
// MailboxDelete deletes a mailbox and all its messages.
|
||||||
func (Webmail) MailboxDelete(ctx context.Context, mailboxID int64) {
|
func (Webmail) MailboxDelete(ctx context.Context, mailboxID int64) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
log := reqInfo.Log
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Messages to remove after having broadcasted the removal of messages.
|
// Messages to remove after having broadcasted the removal of messages.
|
||||||
var removeMessageIDs []int64
|
var removeMessageIDs []int64
|
||||||
|
@ -1294,14 +1238,9 @@ func (Webmail) MailboxDelete(ctx context.Context, mailboxID int64) {
|
||||||
// MailboxEmpty empties a mailbox, removing all messages from the mailbox, but not
|
// MailboxEmpty empties a mailbox, removing all messages from the mailbox, but not
|
||||||
// its child mailboxes.
|
// its child mailboxes.
|
||||||
func (Webmail) MailboxEmpty(ctx context.Context, mailboxID int64) {
|
func (Webmail) MailboxEmpty(ctx context.Context, mailboxID int64) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
log := reqInfo.Log
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
var expunged []store.Message
|
var expunged []store.Message
|
||||||
|
|
||||||
|
@ -1372,17 +1311,12 @@ func (Webmail) MailboxEmpty(ctx context.Context, mailboxID int64) {
|
||||||
// MailboxRename renames a mailbox, possibly moving it to a new parent. The mailbox
|
// MailboxRename renames a mailbox, possibly moving it to a new parent. The mailbox
|
||||||
// ID and its messages are unchanged.
|
// ID and its messages are unchanged.
|
||||||
func (Webmail) MailboxRename(ctx context.Context, mailboxID int64, newName string) {
|
func (Webmail) MailboxRename(ctx context.Context, mailboxID int64, newName string) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Renaming Inbox is special for IMAP. For IMAP we have to implement it per the
|
// Renaming Inbox is special for IMAP. For IMAP we have to implement it per the
|
||||||
// standard. We can just say no.
|
// standard. We can just say no.
|
||||||
|
var err error
|
||||||
newName, _, err = store.CheckMailboxName(newName, false)
|
newName, _, err = store.CheckMailboxName(newName, false)
|
||||||
xcheckuserf(ctx, err, "checking new mailbox name")
|
xcheckuserf(ctx, err, "checking new mailbox name")
|
||||||
|
|
||||||
|
@ -1408,14 +1342,8 @@ func (Webmail) MailboxRename(ctx context.Context, mailboxID int64, newName strin
|
||||||
// matches, most recently used first, and whether this is the full list and further
|
// matches, most recently used first, and whether this is the full list and further
|
||||||
// requests for longer prefixes aren't necessary.
|
// requests for longer prefixes aren't necessary.
|
||||||
func (Webmail) CompleteRecipient(ctx context.Context, search string) ([]string, bool) {
|
func (Webmail) CompleteRecipient(ctx context.Context, search string) ([]string, bool) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
search = strings.ToLower(search)
|
search = strings.ToLower(search)
|
||||||
|
|
||||||
|
@ -1511,14 +1439,8 @@ func addressString(a message.Address, smtputf8 bool) string {
|
||||||
|
|
||||||
// MailboxSetSpecialUse sets the special use flags of a mailbox.
|
// MailboxSetSpecialUse sets the special use flags of a mailbox.
|
||||||
func (Webmail) MailboxSetSpecialUse(ctx context.Context, mb store.Mailbox) {
|
func (Webmail) MailboxSetSpecialUse(ctx context.Context, mb store.Mailbox) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
acc.WithWLock(func() {
|
acc.WithWLock(func() {
|
||||||
var changes []store.Change
|
var changes []store.Change
|
||||||
|
@ -1551,7 +1473,7 @@ func (Webmail) MailboxSetSpecialUse(ctx context.Context, mb store.Mailbox) {
|
||||||
clearPrevious(mb.Trash, "Trash")
|
clearPrevious(mb.Trash, "Trash")
|
||||||
|
|
||||||
xmb.SpecialUse = mb.SpecialUse
|
xmb.SpecialUse = mb.SpecialUse
|
||||||
err = tx.Update(&xmb)
|
err := tx.Update(&xmb)
|
||||||
xcheckf(ctx, err, "updating special-use flags for mailbox")
|
xcheckf(ctx, err, "updating special-use flags for mailbox")
|
||||||
changes = append(changes, xmb.ChangeSpecialUse())
|
changes = append(changes, xmb.ChangeSpecialUse())
|
||||||
})
|
})
|
||||||
|
@ -1564,14 +1486,8 @@ func (Webmail) MailboxSetSpecialUse(ctx context.Context, mb store.Mailbox) {
|
||||||
// children. The messageIDs are typically thread roots. But not all roots
|
// children. The messageIDs are typically thread roots. But not all roots
|
||||||
// (without parent) of a thread need to have the same collapsed state.
|
// (without parent) of a thread need to have the same collapsed state.
|
||||||
func (Webmail) ThreadCollapse(ctx context.Context, messageIDs []int64, collapse bool) {
|
func (Webmail) ThreadCollapse(ctx context.Context, messageIDs []int64, collapse bool) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
if len(messageIDs) == 0 {
|
if len(messageIDs) == 0 {
|
||||||
xcheckuserf(ctx, errors.New("no messages"), "setting collapse")
|
xcheckuserf(ctx, errors.New("no messages"), "setting collapse")
|
||||||
|
@ -1610,7 +1526,7 @@ func (Webmail) ThreadCollapse(ctx context.Context, messageIDs []int64, collapse
|
||||||
})
|
})
|
||||||
q.Gather(&updated)
|
q.Gather(&updated)
|
||||||
q.SortAsc("ID") // Consistent order for testing.
|
q.SortAsc("ID") // Consistent order for testing.
|
||||||
_, err = q.UpdateFields(map[string]any{"ThreadCollapsed": collapse})
|
_, err := q.UpdateFields(map[string]any{"ThreadCollapsed": collapse})
|
||||||
xcheckf(ctx, err, "updating collapse in database")
|
xcheckf(ctx, err, "updating collapse in database")
|
||||||
|
|
||||||
for _, m := range updated {
|
for _, m := range updated {
|
||||||
|
@ -1624,14 +1540,8 @@ func (Webmail) ThreadCollapse(ctx context.Context, messageIDs []int64, collapse
|
||||||
// ThreadMute saves the ThreadMute field for the messages and their children.
|
// ThreadMute saves the ThreadMute field for the messages and their children.
|
||||||
// If messages are muted, they are also marked collapsed.
|
// If messages are muted, they are also marked collapsed.
|
||||||
func (Webmail) ThreadMute(ctx context.Context, messageIDs []int64, mute bool) {
|
func (Webmail) ThreadMute(ctx context.Context, messageIDs []int64, mute bool) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
|
|
||||||
if len(messageIDs) == 0 {
|
if len(messageIDs) == 0 {
|
||||||
xcheckuserf(ctx, errors.New("no messages"), "setting mute")
|
xcheckuserf(ctx, errors.New("no messages"), "setting mute")
|
||||||
|
@ -1674,7 +1584,7 @@ func (Webmail) ThreadMute(ctx context.Context, messageIDs []int64, mute bool) {
|
||||||
if mute {
|
if mute {
|
||||||
fields["ThreadCollapsed"] = true
|
fields["ThreadCollapsed"] = true
|
||||||
}
|
}
|
||||||
_, err = q.UpdateFields(fields)
|
_, err := q.UpdateFields(fields)
|
||||||
xcheckf(ctx, err, "updating mute in database")
|
xcheckf(ctx, err, "updating mute in database")
|
||||||
|
|
||||||
for _, m := range updated {
|
for _, m := range updated {
|
||||||
|
@ -1724,8 +1634,11 @@ type RecipientSecurity struct {
|
||||||
// RecipientSecurity looks up security properties of the address in the
|
// RecipientSecurity looks up security properties of the address in the
|
||||||
// single-address message addressee (as it appears in a To/Cc/Bcc/etc header).
|
// single-address message addressee (as it appears in a To/Cc/Bcc/etc header).
|
||||||
func (Webmail) RecipientSecurity(ctx context.Context, messageAddressee string) (RecipientSecurity, error) {
|
func (Webmail) RecipientSecurity(ctx context.Context, messageAddressee string) (RecipientSecurity, error) {
|
||||||
resolver := dns.StrictResolver{Pkg: "webmail", Log: pkglog.WithContext(ctx).Logger}
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
return recipientSecurity(ctx, resolver, messageAddressee)
|
log := reqInfo.Log
|
||||||
|
|
||||||
|
resolver := dns.StrictResolver{Pkg: "webmail", Log: log.Logger}
|
||||||
|
return recipientSecurity(ctx, log, resolver, messageAddressee)
|
||||||
}
|
}
|
||||||
|
|
||||||
// logPanic can be called with a defer from a goroutine to prevent the entire program from being shutdown in case of a panic.
|
// logPanic can be called with a defer from a goroutine to prevent the entire program from being shutdown in case of a panic.
|
||||||
|
@ -1741,9 +1654,7 @@ func logPanic(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// separate function for testing with mocked resolver.
|
// separate function for testing with mocked resolver.
|
||||||
func recipientSecurity(ctx context.Context, resolver dns.Resolver, messageAddressee string) (RecipientSecurity, error) {
|
func recipientSecurity(ctx context.Context, log mlog.Log, resolver dns.Resolver, messageAddressee string) (RecipientSecurity, error) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
|
|
||||||
rs := RecipientSecurity{
|
rs := RecipientSecurity{
|
||||||
SecurityResultUnknown,
|
SecurityResultUnknown,
|
||||||
SecurityResultUnknown,
|
SecurityResultUnknown,
|
||||||
|
@ -1833,14 +1744,7 @@ func recipientSecurity(ctx context.Context, resolver dns.Resolver, messageAddres
|
||||||
|
|
||||||
// STARTTLS and RequireTLS
|
// STARTTLS and RequireTLS
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
|
||||||
defer func() {
|
|
||||||
if acc != nil {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = acc.DB.Read(ctx, func(tx *bstore.Tx) error {
|
err = acc.DB.Read(ctx, func(tx *bstore.Tx) error {
|
||||||
q := bstore.QueryTx[store.RecipientDomainTLS](tx)
|
q := bstore.QueryTx[store.RecipientDomainTLS](tx)
|
||||||
|
@ -1868,12 +1772,6 @@ func recipientSecurity(ctx context.Context, resolver dns.Resolver, messageAddres
|
||||||
})
|
})
|
||||||
xcheckf(ctx, err, "lookup recipient domain")
|
xcheckf(ctx, err, "lookup recipient domain")
|
||||||
|
|
||||||
// Close account as soon as possible, not after waiting for MTA-STS/DNSSEC/DANE
|
|
||||||
// checks to complete, which can take a while.
|
|
||||||
err = acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
acc = nil
|
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
return rs, nil
|
return rs, nil
|
||||||
|
@ -1888,24 +1786,19 @@ func (Webmail) DecodeMIMEWords(ctx context.Context, text string) string {
|
||||||
|
|
||||||
// SettingsSave saves settings, e.g. for composing.
|
// SettingsSave saves settings, e.g. for composing.
|
||||||
func (Webmail) SettingsSave(ctx context.Context, settings store.Settings) {
|
func (Webmail) SettingsSave(ctx context.Context, settings store.Settings) {
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
acc := reqInfo.Account
|
||||||
xcheckf(ctx, err, "open account")
|
|
||||||
defer func() {
|
|
||||||
if acc != nil {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
settings.ID = 1
|
settings.ID = 1
|
||||||
err = acc.DB.Update(ctx, &settings)
|
err := acc.DB.Update(ctx, &settings)
|
||||||
xcheckf(ctx, err, "save settings")
|
xcheckf(ctx, err, "save settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Webmail) RulesetSuggestMove(ctx context.Context, msgID, mbSrcID, mbDstID int64) (listID string, msgFrom string, isRemove bool, rcptTo string, ruleset *config.Ruleset) {
|
func (Webmail) RulesetSuggestMove(ctx context.Context, msgID, mbSrcID, mbDstID int64) (listID string, msgFrom string, isRemove bool, rcptTo string, ruleset *config.Ruleset) {
|
||||||
withAccount(ctx, func(log mlog.Log, acc *store.Account) {
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
|
acc := reqInfo.Account
|
||||||
|
log := reqInfo.Log
|
||||||
|
|
||||||
xdbread(ctx, acc, func(tx *bstore.Tx) {
|
xdbread(ctx, acc, func(tx *bstore.Tx) {
|
||||||
m := xmessageID(ctx, tx, msgID)
|
m := xmessageID(ctx, tx, msgID)
|
||||||
mbSrc := xmailboxID(ctx, tx, mbSrcID)
|
mbSrc := xmailboxID(ctx, tx, mbSrcID)
|
||||||
|
@ -2055,7 +1948,6 @@ func (Webmail) RulesetSuggestMove(ctx context.Context, msgID, mbSrcID, mbDstID i
|
||||||
nrs.Comment = "by webmail on " + time.Now().Format("2006-01-02")
|
nrs.Comment = "by webmail on " + time.Now().Format("2006-01-02")
|
||||||
ruleset = nrs
|
ruleset = nrs
|
||||||
})
|
})
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2083,7 +1975,7 @@ func parseListID(s string) (listID string, dom dns.Domain) {
|
||||||
func (Webmail) RulesetAdd(ctx context.Context, rcptTo string, ruleset config.Ruleset) {
|
func (Webmail) RulesetAdd(ctx context.Context, rcptTo string, ruleset config.Ruleset) {
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
|
|
||||||
err := mox.AccountSave(ctx, reqInfo.AccountName, func(acc *config.Account) {
|
err := mox.AccountSave(ctx, reqInfo.Account.Name, func(acc *config.Account) {
|
||||||
dest, ok := acc.Destinations[rcptTo]
|
dest, ok := acc.Destinations[rcptTo]
|
||||||
if !ok {
|
if !ok {
|
||||||
// todo: we could find the catchall address and add the rule, or add the address explicitly.
|
// todo: we could find the catchall address and add the rule, or add the address explicitly.
|
||||||
|
@ -2104,7 +1996,7 @@ func (Webmail) RulesetAdd(ctx context.Context, rcptTo string, ruleset config.Rul
|
||||||
func (Webmail) RulesetRemove(ctx context.Context, rcptTo string, ruleset config.Ruleset) {
|
func (Webmail) RulesetRemove(ctx context.Context, rcptTo string, ruleset config.Ruleset) {
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
|
|
||||||
err := mox.AccountSave(ctx, reqInfo.AccountName, func(acc *config.Account) {
|
err := mox.AccountSave(ctx, reqInfo.Account.Name, func(acc *config.Account) {
|
||||||
dest, ok := acc.Destinations[rcptTo]
|
dest, ok := acc.Destinations[rcptTo]
|
||||||
if !ok {
|
if !ok {
|
||||||
xcheckuserf(ctx, errors.New("destination address not found in account"), "looking up address")
|
xcheckuserf(ctx, errors.New("destination address not found in account"), "looking up address")
|
||||||
|
@ -2134,7 +2026,9 @@ func (Webmail) RulesetRemove(ctx context.Context, rcptTo string, ruleset config.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Webmail) RulesetMessageNever(ctx context.Context, rcptTo, listID, msgFrom string, toInbox bool) {
|
func (Webmail) RulesetMessageNever(ctx context.Context, rcptTo, listID, msgFrom string, toInbox bool) {
|
||||||
withAccount(ctx, func(log mlog.Log, acc *store.Account) {
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
|
acc := reqInfo.Account
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if listID != "" {
|
if listID != "" {
|
||||||
err = acc.DB.Insert(ctx, &store.RulesetNoListID{RcptToAddress: rcptTo, ListID: listID, ToInbox: toInbox})
|
err = acc.DB.Insert(ctx, &store.RulesetNoListID{RcptToAddress: rcptTo, ListID: listID, ToInbox: toInbox})
|
||||||
|
@ -2142,14 +2036,14 @@ func (Webmail) RulesetMessageNever(ctx context.Context, rcptTo, listID, msgFrom
|
||||||
err = acc.DB.Insert(ctx, &store.RulesetNoMsgFrom{RcptToAddress: rcptTo, MsgFromAddress: msgFrom, ToInbox: toInbox})
|
err = acc.DB.Insert(ctx, &store.RulesetNoMsgFrom{RcptToAddress: rcptTo, MsgFromAddress: msgFrom, ToInbox: toInbox})
|
||||||
}
|
}
|
||||||
xcheckf(ctx, err, "storing user response")
|
xcheckf(ctx, err, "storing user response")
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Webmail) RulesetMailboxNever(ctx context.Context, mailboxID int64, toMailbox bool) {
|
func (Webmail) RulesetMailboxNever(ctx context.Context, mailboxID int64, toMailbox bool) {
|
||||||
withAccount(ctx, func(log mlog.Log, acc *store.Account) {
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
|
acc := reqInfo.Account
|
||||||
|
|
||||||
err := acc.DB.Insert(ctx, &store.RulesetNoMailbox{MailboxID: mailboxID, ToMailbox: toMailbox})
|
err := acc.DB.Insert(ctx, &store.RulesetNoMailbox{MailboxID: mailboxID, ToMailbox: toMailbox})
|
||||||
xcheckf(ctx, err, "storing user response")
|
xcheckf(ctx, err, "storing user response")
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func slicesAny[T any](l []T) []any {
|
func slicesAny[T any](l []T) []any {
|
||||||
|
@ -2160,18 +2054,6 @@ func slicesAny[T any](l []T) []any {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func withAccount(ctx context.Context, fn func(log mlog.Log, acc *store.Account)) {
|
|
||||||
log := pkglog.WithContext(ctx)
|
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
|
||||||
acc, err := store.OpenAccount(log, reqInfo.AccountName)
|
|
||||||
xcheckf(ctx, err, "open account")
|
|
||||||
defer func() {
|
|
||||||
err := acc.Close()
|
|
||||||
log.Check(err, "closing account")
|
|
||||||
}()
|
|
||||||
fn(log, acc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSETypes exists to ensure the generated API contains the types, for use in SSE events.
|
// SSETypes exists to ensure the generated API contains the types, for use in SSE events.
|
||||||
func (Webmail) SSETypes() (start EventStart, viewErr EventViewErr, viewReset EventViewReset, viewMsgs EventViewMsgs, viewChanges EventViewChanges, msgAdd ChangeMsgAdd, msgRemove ChangeMsgRemove, msgFlags ChangeMsgFlags, msgThread ChangeMsgThread, mailboxRemove ChangeMailboxRemove, mailboxAdd ChangeMailboxAdd, mailboxRename ChangeMailboxRename, mailboxCounts ChangeMailboxCounts, mailboxSpecialUse ChangeMailboxSpecialUse, mailboxKeywords ChangeMailboxKeywords, flags store.Flags) {
|
func (Webmail) SSETypes() (start EventStart, viewErr EventViewErr, viewReset EventViewReset, viewMsgs EventViewMsgs, viewChanges EventViewChanges, msgAdd ChangeMsgAdd, msgRemove ChangeMsgRemove, msgFlags ChangeMsgFlags, msgThread ChangeMsgThread, mailboxRemove ChangeMailboxRemove, mailboxAdd ChangeMailboxAdd, mailboxRename ChangeMailboxRename, mailboxCounts ChangeMailboxCounts, mailboxSpecialUse ChangeMailboxSpecialUse, mailboxKeywords ChangeMailboxKeywords, flags store.Flags) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -90,7 +90,7 @@ func TestAPI(t *testing.T) {
|
||||||
api := Webmail{maxMessageSize: 1024 * 1024, cookiePath: "/webmail/"}
|
api := Webmail{maxMessageSize: 1024 * 1024, cookiePath: "/webmail/"}
|
||||||
|
|
||||||
// Test login, and rate limiter.
|
// Test login, and rate limiter.
|
||||||
loginReqInfo := requestInfo{"mjl@mox.example", "mjl", "", httptest.NewRecorder(), &http.Request{RemoteAddr: "1.1.1.1:1234"}}
|
loginReqInfo := requestInfo{log, "mjl@mox.example", nil, "", httptest.NewRecorder(), &http.Request{RemoteAddr: "1.1.1.1:1234"}}
|
||||||
loginctx := context.WithValue(ctxbg, requestInfoCtxKey, loginReqInfo)
|
loginctx := context.WithValue(ctxbg, requestInfoCtxKey, loginReqInfo)
|
||||||
|
|
||||||
// Missing login token.
|
// Missing login token.
|
||||||
|
@ -138,7 +138,7 @@ func TestAPI(t *testing.T) {
|
||||||
testLogin("bad@bad.example", pw0, "user:error")
|
testLogin("bad@bad.example", pw0, "user:error")
|
||||||
|
|
||||||
// Context with different IP, for clear rate limit history.
|
// Context with different IP, for clear rate limit history.
|
||||||
reqInfo := requestInfo{"mjl@mox.example", "mjl", "", nil, &http.Request{RemoteAddr: "127.0.0.1:1234"}}
|
reqInfo := requestInfo{log, "mjl@mox.example", acc, "", nil, &http.Request{RemoteAddr: "127.0.0.1:1234"}}
|
||||||
ctx := context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
ctx := context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
||||||
|
|
||||||
// FlagsAdd
|
// FlagsAdd
|
||||||
|
@ -462,12 +462,12 @@ func TestAPI(t *testing.T) {
|
||||||
|
|
||||||
// RecipientSecurity
|
// RecipientSecurity
|
||||||
resolver := dns.MockResolver{}
|
resolver := dns.MockResolver{}
|
||||||
rs, err := recipientSecurity(ctx, resolver, "mjl@a.mox.example")
|
rs, err := recipientSecurity(ctx, log, resolver, "mjl@a.mox.example")
|
||||||
tcompare(t, err, nil)
|
tcompare(t, err, nil)
|
||||||
tcompare(t, rs, RecipientSecurity{SecurityResultUnknown, SecurityResultNo, SecurityResultNo, SecurityResultNo, SecurityResultUnknown})
|
tcompare(t, rs, RecipientSecurity{SecurityResultUnknown, SecurityResultNo, SecurityResultNo, SecurityResultNo, SecurityResultUnknown})
|
||||||
err = acc.DB.Insert(ctx, &store.RecipientDomainTLS{Domain: "a.mox.example", STARTTLS: true, RequireTLS: false})
|
err = acc.DB.Insert(ctx, &store.RecipientDomainTLS{Domain: "a.mox.example", STARTTLS: true, RequireTLS: false})
|
||||||
tcheck(t, err, "insert recipient domain tls info")
|
tcheck(t, err, "insert recipient domain tls info")
|
||||||
rs, err = recipientSecurity(ctx, resolver, "mjl@a.mox.example")
|
rs, err = recipientSecurity(ctx, log, resolver, "mjl@a.mox.example")
|
||||||
tcompare(t, err, nil)
|
tcompare(t, err, nil)
|
||||||
tcompare(t, rs, RecipientSecurity{SecurityResultYes, SecurityResultNo, SecurityResultNo, SecurityResultNo, SecurityResultNo})
|
tcompare(t, rs, RecipientSecurity{SecurityResultYes, SecurityResultNo, SecurityResultNo, SecurityResultNo, SecurityResultNo})
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ func TestView(t *testing.T) {
|
||||||
api := Webmail{maxMessageSize: 1024 * 1024, cookiePath: "/"}
|
api := Webmail{maxMessageSize: 1024 * 1024, cookiePath: "/"}
|
||||||
|
|
||||||
respRec := httptest.NewRecorder()
|
respRec := httptest.NewRecorder()
|
||||||
reqInfo := requestInfo{"mjl@mox.example", "mjl", "", respRec, &http.Request{RemoteAddr: "127.0.0.1:1234"}}
|
reqInfo := requestInfo{log, "mjl@mox.example", acc, "", respRec, &http.Request{RemoteAddr: "127.0.0.1:1234"}}
|
||||||
ctx := context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
ctx := context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
||||||
|
|
||||||
// Prepare loginToken.
|
// Prepare loginToken.
|
||||||
|
@ -70,7 +70,7 @@ func TestView(t *testing.T) {
|
||||||
}
|
}
|
||||||
sessionToken := store.SessionToken(sct[0])
|
sessionToken := store.SessionToken(sct[0])
|
||||||
|
|
||||||
reqInfo = requestInfo{"mjl@mox.example", "mjl", sessionToken, respRec, &http.Request{}}
|
reqInfo = requestInfo{log, "mjl@mox.example", acc, sessionToken, respRec, &http.Request{}}
|
||||||
ctx = context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
ctx = context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
||||||
|
|
||||||
api.MailboxCreate(ctx, "Lists/Go/Nuts")
|
api.MailboxCreate(ctx, "Lists/Go/Nuts")
|
||||||
|
|
|
@ -51,8 +51,9 @@ type ctxKey string
|
||||||
var requestInfoCtxKey ctxKey = "requestInfo"
|
var requestInfoCtxKey ctxKey = "requestInfo"
|
||||||
|
|
||||||
type requestInfo struct {
|
type requestInfo struct {
|
||||||
|
Log mlog.Log
|
||||||
LoginAddress string
|
LoginAddress string
|
||||||
AccountName string
|
Account *store.Account // Nil only for methods Login and LoginPrep.
|
||||||
SessionToken store.SessionToken
|
SessionToken store.SessionToken
|
||||||
Response http.ResponseWriter
|
Response http.ResponseWriter
|
||||||
Request *http.Request // For Proto and TLS connection state during message submit.
|
Request *http.Request // For Proto and TLS connection state during message submit.
|
||||||
|
@ -266,7 +267,22 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt
|
||||||
}
|
}
|
||||||
|
|
||||||
if isAPI {
|
if isAPI {
|
||||||
reqInfo := requestInfo{loginAddress, accName, sessionToken, w, r}
|
var acc *store.Account
|
||||||
|
if accName != "" {
|
||||||
|
log = log.With(slog.String("account", accName))
|
||||||
|
var err error
|
||||||
|
acc, err = store.OpenAccount(log, accName)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorx("open account", err)
|
||||||
|
http.Error(w, "500 - internal server error - error opening account", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := acc.Close()
|
||||||
|
log.Check(err, "closing account")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
reqInfo := requestInfo{log, loginAddress, acc, sessionToken, w, r}
|
||||||
ctx = context.WithValue(ctx, requestInfoCtxKey, reqInfo)
|
ctx = context.WithValue(ctx, requestInfoCtxKey, reqInfo)
|
||||||
apiHandler.ServeHTTP(w, r.WithContext(ctx))
|
apiHandler.ServeHTTP(w, r.WithContext(ctx))
|
||||||
return
|
return
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/mjl-/sherpa"
|
"github.com/mjl-/sherpa"
|
||||||
|
|
||||||
"github.com/mjl-/mox/message"
|
"github.com/mjl-/mox/message"
|
||||||
|
"github.com/mjl-/mox/mlog"
|
||||||
"github.com/mjl-/mox/mox-"
|
"github.com/mjl-/mox/mox-"
|
||||||
"github.com/mjl-/mox/moxio"
|
"github.com/mjl-/mox/moxio"
|
||||||
"github.com/mjl-/mox/store"
|
"github.com/mjl-/mox/store"
|
||||||
|
@ -304,6 +305,7 @@ func TestWebmail(t *testing.T) {
|
||||||
mox.MustLoadConfig(true, false)
|
mox.MustLoadConfig(true, false)
|
||||||
defer store.Switchboard()()
|
defer store.Switchboard()()
|
||||||
|
|
||||||
|
log := mlog.New("webmail", nil)
|
||||||
acc, err := store.OpenAccount(pkglog, "mjl")
|
acc, err := store.OpenAccount(pkglog, "mjl")
|
||||||
tcheck(t, err, "open account")
|
tcheck(t, err, "open account")
|
||||||
err = acc.SetPassword(pkglog, "test1234")
|
err = acc.SetPassword(pkglog, "test1234")
|
||||||
|
@ -319,7 +321,7 @@ func TestWebmail(t *testing.T) {
|
||||||
tcheck(t, err, "sherpa handler")
|
tcheck(t, err, "sherpa handler")
|
||||||
|
|
||||||
respRec := httptest.NewRecorder()
|
respRec := httptest.NewRecorder()
|
||||||
reqInfo := requestInfo{"", "", "", respRec, &http.Request{RemoteAddr: "127.0.0.1:1234"}}
|
reqInfo := requestInfo{log, "", nil, "", respRec, &http.Request{RemoteAddr: "127.0.0.1:1234"}}
|
||||||
ctx := context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
ctx := context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
||||||
|
|
||||||
// Prepare loginToken.
|
// Prepare loginToken.
|
||||||
|
@ -339,7 +341,7 @@ func TestWebmail(t *testing.T) {
|
||||||
t.Fatalf("missing session cookie")
|
t.Fatalf("missing session cookie")
|
||||||
}
|
}
|
||||||
|
|
||||||
reqInfo = requestInfo{"mjl@mox.example", "mjl", "", respRec, &http.Request{RemoteAddr: "127.0.0.1:1234"}}
|
reqInfo = requestInfo{log, "mjl@mox.example", acc, "", respRec, &http.Request{RemoteAddr: "127.0.0.1:1234"}}
|
||||||
ctx = context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
ctx = context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
||||||
|
|
||||||
tneedError(t, func() { api.MailboxCreate(ctx, "Inbox") }) // Cannot create inbox.
|
tneedError(t, func() { api.MailboxCreate(ctx, "Inbox") }) // Cannot create inbox.
|
||||||
|
@ -611,7 +613,7 @@ func TestWebmail(t *testing.T) {
|
||||||
// Normally the generic /api/ auth check returns a user error. We bypass it and
|
// Normally the generic /api/ auth check returns a user error. We bypass it and
|
||||||
// check for the server error.
|
// check for the server error.
|
||||||
sessionToken := store.SessionToken(strings.SplitN(sessionCookie.Value, " ", 2)[0])
|
sessionToken := store.SessionToken(strings.SplitN(sessionCookie.Value, " ", 2)[0])
|
||||||
reqInfo = requestInfo{"mjl@mox.example", "mjl", sessionToken, httptest.NewRecorder(), &http.Request{RemoteAddr: "127.0.0.1:1234"}}
|
reqInfo = requestInfo{log, "mjl@mox.example", acc, sessionToken, httptest.NewRecorder(), &http.Request{RemoteAddr: "127.0.0.1:1234"}}
|
||||||
ctx = context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
ctx = context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
||||||
api.Logout(ctx)
|
api.Logout(ctx)
|
||||||
tneedErrorCode(t, "server:error", func() { api.Logout(ctx) })
|
tneedErrorCode(t, "server:error", func() { api.Logout(ctx) })
|
||||||
|
|
Loading…
Reference in a new issue