added IMAP, LetsEncrypt, update layout
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package webui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"mailgoserver/internal/db"
|
||||
)
|
||||
|
||||
// MailboxPrefix is the self-service webmail portal's URL prefix — a mailbox owner's
|
||||
// login/account area, entirely separate from the admin dashboard at Prefix.
|
||||
const MailboxPrefix = "/webmail"
|
||||
|
||||
const mailboxSessionCookieName = "mailgoserver_mailbox_session"
|
||||
|
||||
// mailboxCtxKey is its own type (not webui's ctxKey) so a mailbox session can never
|
||||
// collide with or be confused for an admin session in request context — the two
|
||||
// actor types are deliberately kept fully separate, per the parallel-schema design.
|
||||
type mailboxCtxKey int
|
||||
|
||||
const ctxMailboxKey mailboxCtxKey = iota
|
||||
|
||||
func setMailboxSessionCookie(w http.ResponseWriter, token string, secure bool) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: mailboxSessionCookieName,
|
||||
Value: token,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: secure,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
MaxAge: int(sessionTTL.Seconds()),
|
||||
})
|
||||
}
|
||||
|
||||
func clearMailboxSessionCookie(w http.ResponseWriter) {
|
||||
http.SetCookie(w, &http.Cookie{Name: mailboxSessionCookieName, Value: "", Path: "/", MaxAge: -1})
|
||||
}
|
||||
|
||||
// currentMailboxSession loads the session + mailbox for the request's cookie, if any
|
||||
// and valid. A nil session/mailbox (no error) means "not logged in".
|
||||
func (a *App) currentMailboxSession(r *http.Request) (*db.MailboxSession, *db.Mailbox, error) {
|
||||
c, err := r.Cookie(mailboxSessionCookieName)
|
||||
if err != nil || c.Value == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
sess, err := a.DB.GetMailboxSession(c.Value)
|
||||
if err != nil || sess == nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if time.Now().After(sess.ExpiresAt) {
|
||||
_ = a.DB.DeleteMailboxSession(sess.Token)
|
||||
return nil, nil, nil
|
||||
}
|
||||
mbox, err := a.DB.GetMailboxByID(sess.MailboxID)
|
||||
if err != nil || mbox == nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return sess, mbox, nil
|
||||
}
|
||||
|
||||
func mailboxFromContext(r *http.Request) *db.Mailbox {
|
||||
m, _ := r.Context().Value(ctxMailboxKey).(*db.Mailbox)
|
||||
return m
|
||||
}
|
||||
|
||||
// requireMailboxAuth gates every webmail route behind a valid, fully-authenticated
|
||||
// mailbox session: logged in, and second factor satisfied if one is enabled.
|
||||
func (a *App) requireMailboxAuth(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
sess, mbox, err := a.currentMailboxSession(r)
|
||||
if err != nil {
|
||||
a.Logger.Error("mailbox session lookup: %v", err)
|
||||
}
|
||||
if sess == nil || mbox == nil {
|
||||
http.Redirect(w, r, MailboxPrefix+"/login", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
needsMFA := mbox.TOTPEnabled
|
||||
if !needsMFA {
|
||||
if n, _ := a.DB.CountMailboxWebAuthnCredentials(mbox.ID); n > 0 {
|
||||
needsMFA = true
|
||||
}
|
||||
}
|
||||
if needsMFA && !sess.MFAVerified {
|
||||
http.Redirect(w, r, MailboxPrefix+"/login/mfa", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), ctxMailboxKey, mbox)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user