fix login for account names with non-ascii chars

we include the username in session cookie values. but cookie values must be ascii-only, go's net/http's drops bad values. the typical solution is to querystring-encode/decode the cookie values, which we'll now do.

problem found by arnt, thanks for reporting!
This commit is contained in:
Mechiel Lukkien 2024-04-11 23:11:31 +02:00
parent d74610c345
commit 666f84edea
No known key found for this signature in database
4 changed files with 19 additions and 11 deletions

View file

@ -1,11 +1,11 @@
Domains:
mox.example: nil
Accounts:
mjl:
mjl:
Domain: mox.example
FullName: mjl
Destinations:
mjl@mox.example:
mjl@mox.example:
Mailbox: Inbox
Rulesets:
-

View file

@ -3,7 +3,7 @@ User: 1000
LogLevel: trace
Hostname: mox.example
Postmaster:
Account: mjl
Account: mjl
Mailbox: postmaster
Listeners:
local: nil

View file

@ -79,7 +79,7 @@ func TestAccount(t *testing.T) {
mox.ConfigDynamicPath = filepath.Join(filepath.Dir(mox.ConfigStaticPath), "domains.conf")
mox.MustLoadConfig(true, false)
log := mlog.New("webaccount", nil)
acc, err := store.OpenAccount(log, "mjl")
acc, err := store.OpenAccount(log, "mjl")
tcheck(t, err, "open account")
err = acc.SetPassword(log, "test1234")
tcheck(t, err, "set password")
@ -99,14 +99,14 @@ func TestAccount(t *testing.T) {
ctx := context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
// Missing login token.
tneedErrorCode(t, "user:error", func() { api.Login(ctx, "", "mjl@mox.example", "test1234") })
tneedErrorCode(t, "user:error", func() { api.Login(ctx, "", "mjl@mox.example", "test1234") })
// Login with loginToken.
loginCookie := &http.Cookie{Name: "webaccountlogin"}
loginCookie.Value = api.LoginPrep(ctx)
reqInfo.Request.Header = http.Header{"Cookie": []string{loginCookie.String()}}
csrfToken := api.Login(ctx, loginCookie.Value, "mjl@mox.example", "test1234")
csrfToken := api.Login(ctx, loginCookie.Value, "mjl@mox.example", "test1234")
var sessionCookie *http.Cookie
for _, c := range respRec.Result().Cookies() {
if c.Name == "webaccountsession" {
@ -121,7 +121,7 @@ func TestAccount(t *testing.T) {
// Valid loginToken, but bad credentials.
loginCookie.Value = api.LoginPrep(ctx)
reqInfo.Request.Header = http.Header{"Cookie": []string{loginCookie.String()}}
tneedErrorCode(t, "user:loginFailed", func() { api.Login(ctx, loginCookie.Value, "mjl@mox.example", "badauth") })
tneedErrorCode(t, "user:loginFailed", func() { api.Login(ctx, loginCookie.Value, "mjl@mox.example", "badauth") })
tneedErrorCode(t, "user:loginFailed", func() { api.Login(ctx, loginCookie.Value, "baduser@mox.example", "badauth") })
tneedErrorCode(t, "user:loginFailed", func() { api.Login(ctx, loginCookie.Value, "baduser@baddomain.example", "badauth") })
@ -211,13 +211,13 @@ func TestAccount(t *testing.T) {
// SetPassword needs the token.
sessionToken := store.SessionToken(strings.SplitN(sessionCookie.Value, " ", 2)[0])
reqInfo = requestInfo{"mjl@mox.example", "mjl", sessionToken, respRec, &http.Request{RemoteAddr: "127.0.0.1:1234"}}
reqInfo = requestInfo{"mjl@mox.example", "mjl", sessionToken, respRec, &http.Request{RemoteAddr: "127.0.0.1:1234"}}
ctx = context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
api.SetPassword(ctx, "test1234")
fullName, _, dests, _, _ := api.Account(ctx)
api.DestinationSave(ctx, "mjl@mox.example", dests["mjl@mox.example"], dests["mjl@mox.example"]) // todo: save modified value and compare it afterwards
api.DestinationSave(ctx, "mjl@mox.example", dests["mjl@mox.example"], dests["mjl@mox.example"]) // todo: save modified value and compare it afterwards
api.AccountSaveFullName(ctx, fullName+" changed") // todo: check if value was changed
api.AccountSaveFullName(ctx, fullName)

View file

@ -41,6 +41,7 @@ import (
"log/slog"
"net"
"net/http"
"net/url"
"strings"
"time"
@ -154,9 +155,15 @@ func Check(ctx context.Context, log mlog.Log, sessionAuth SessionAuth, kind stri
return "", "", "", false
}
sessionToken = store.SessionToken(t[0])
accountName = t[1]
var err error
accountName, err = url.QueryUnescape(t[1])
if err != nil {
time.Sleep(BadAuthDelay)
respondAuthError("user:badAuth", "malformed session account name")
return "", "", "", false
}
loginAddress, err = sessionAuth.use(ctx, log, accountName, sessionToken, csrfToken)
if err != nil {
time.Sleep(BadAuthDelay)
@ -258,7 +265,8 @@ func Login(ctx context.Context, log mlog.Log, sessionAuth SessionAuth, kind, coo
// Add session cookie.
http.SetCookie(w, &http.Cookie{
Name: kind + "session",
Value: string(sessionToken) + " " + accountName,
// Cookies values are ascii only, so we keep the account name query escaped.
Value: string(sessionToken) + " " + url.QueryEscape(accountName),
Path: cookiePath,
Secure: isHTTPS(isForwarded, r),
HttpOnly: true,