mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
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:
parent
d74610c345
commit
666f84edea
4 changed files with 19 additions and 11 deletions
4
testdata/httpaccount/domains.conf
vendored
4
testdata/httpaccount/domains.conf
vendored
|
@ -1,11 +1,11 @@
|
||||||
Domains:
|
Domains:
|
||||||
mox.example: nil
|
mox.example: nil
|
||||||
Accounts:
|
Accounts:
|
||||||
mjl:
|
mjl☺:
|
||||||
Domain: mox.example
|
Domain: mox.example
|
||||||
FullName: mjl
|
FullName: mjl
|
||||||
Destinations:
|
Destinations:
|
||||||
mjl@mox.example:
|
mjl☺@mox.example:
|
||||||
Mailbox: Inbox
|
Mailbox: Inbox
|
||||||
Rulesets:
|
Rulesets:
|
||||||
-
|
-
|
||||||
|
|
2
testdata/httpaccount/mox.conf
vendored
2
testdata/httpaccount/mox.conf
vendored
|
@ -3,7 +3,7 @@ User: 1000
|
||||||
LogLevel: trace
|
LogLevel: trace
|
||||||
Hostname: mox.example
|
Hostname: mox.example
|
||||||
Postmaster:
|
Postmaster:
|
||||||
Account: mjl
|
Account: mjl☺
|
||||||
Mailbox: postmaster
|
Mailbox: postmaster
|
||||||
Listeners:
|
Listeners:
|
||||||
local: nil
|
local: nil
|
||||||
|
|
|
@ -79,7 +79,7 @@ func TestAccount(t *testing.T) {
|
||||||
mox.ConfigDynamicPath = filepath.Join(filepath.Dir(mox.ConfigStaticPath), "domains.conf")
|
mox.ConfigDynamicPath = filepath.Join(filepath.Dir(mox.ConfigStaticPath), "domains.conf")
|
||||||
mox.MustLoadConfig(true, false)
|
mox.MustLoadConfig(true, false)
|
||||||
log := mlog.New("webaccount", nil)
|
log := mlog.New("webaccount", nil)
|
||||||
acc, err := store.OpenAccount(log, "mjl")
|
acc, err := store.OpenAccount(log, "mjl☺")
|
||||||
tcheck(t, err, "open account")
|
tcheck(t, err, "open account")
|
||||||
err = acc.SetPassword(log, "test1234")
|
err = acc.SetPassword(log, "test1234")
|
||||||
tcheck(t, err, "set password")
|
tcheck(t, err, "set password")
|
||||||
|
@ -99,14 +99,14 @@ func TestAccount(t *testing.T) {
|
||||||
ctx := context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
ctx := context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
||||||
|
|
||||||
// Missing login token.
|
// 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.
|
// Login with loginToken.
|
||||||
loginCookie := &http.Cookie{Name: "webaccountlogin"}
|
loginCookie := &http.Cookie{Name: "webaccountlogin"}
|
||||||
loginCookie.Value = api.LoginPrep(ctx)
|
loginCookie.Value = api.LoginPrep(ctx)
|
||||||
reqInfo.Request.Header = http.Header{"Cookie": []string{loginCookie.String()}}
|
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
|
var sessionCookie *http.Cookie
|
||||||
for _, c := range respRec.Result().Cookies() {
|
for _, c := range respRec.Result().Cookies() {
|
||||||
if c.Name == "webaccountsession" {
|
if c.Name == "webaccountsession" {
|
||||||
|
@ -121,7 +121,7 @@ func TestAccount(t *testing.T) {
|
||||||
// Valid loginToken, but bad credentials.
|
// Valid loginToken, but bad credentials.
|
||||||
loginCookie.Value = api.LoginPrep(ctx)
|
loginCookie.Value = api.LoginPrep(ctx)
|
||||||
reqInfo.Request.Header = http.Header{"Cookie": []string{loginCookie.String()}}
|
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@mox.example", "badauth") })
|
||||||
tneedErrorCode(t, "user:loginFailed", func() { api.Login(ctx, loginCookie.Value, "baduser@baddomain.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.
|
// SetPassword needs the token.
|
||||||
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, 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)
|
ctx = context.WithValue(ctxbg, requestInfoCtxKey, reqInfo)
|
||||||
|
|
||||||
api.SetPassword(ctx, "test1234")
|
api.SetPassword(ctx, "test1234")
|
||||||
|
|
||||||
fullName, _, dests, _, _ := api.Account(ctx)
|
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+" changed") // todo: check if value was changed
|
||||||
api.AccountSaveFullName(ctx, fullName)
|
api.AccountSaveFullName(ctx, fullName)
|
||||||
|
|
|
@ -41,6 +41,7 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -154,9 +155,15 @@ func Check(ctx context.Context, log mlog.Log, sessionAuth SessionAuth, kind stri
|
||||||
return "", "", "", false
|
return "", "", "", false
|
||||||
}
|
}
|
||||||
sessionToken = store.SessionToken(t[0])
|
sessionToken = store.SessionToken(t[0])
|
||||||
accountName = t[1]
|
|
||||||
|
|
||||||
var err error
|
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)
|
loginAddress, err = sessionAuth.use(ctx, log, accountName, sessionToken, csrfToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
time.Sleep(BadAuthDelay)
|
time.Sleep(BadAuthDelay)
|
||||||
|
@ -258,7 +265,8 @@ func Login(ctx context.Context, log mlog.Log, sessionAuth SessionAuth, kind, coo
|
||||||
// Add session cookie.
|
// Add session cookie.
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: kind + "session",
|
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,
|
Path: cookiePath,
|
||||||
Secure: isHTTPS(isForwarded, r),
|
Secure: isHTTPS(isForwarded, r),
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
|
|
Loading…
Reference in a new issue