webmail: rename query string param "token" to "singleUseToken" to be less scary in access logs

these singleusetokens can be redeemed once. so when you see it in the logs, it
can't be used again. they are short-lived anyway.

this change should help prevent me periodically investigating token handling...
This commit is contained in:
Mechiel Lukkien 2024-08-23 15:08:27 +02:00
parent a977082b89
commit 594182aae5
No known key found for this signature in database
10 changed files with 35 additions and 29 deletions

View file

@ -130,9 +130,10 @@ func (w Webmail) Logout(ctx context.Context) {
xcheckf(ctx, err, "logout") xcheckf(ctx, err, "logout")
} }
// Token returns a token to use for an SSE connection. A token can only be used for // Token returns a single-use token to use for an SSE connection. A token can only
// a single SSE connection. Tokens are stored in memory for a maximum of 1 minute, // be used for a single SSE connection. Tokens are stored in memory for a maximum
// with at most 10 unused tokens (the most recently created) per account. // of 1 minute, 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.Account.Name, reqInfo.LoginAddress, reqInfo.SessionToken) return sseTokens.xgenerate(ctx, reqInfo.Account.Name, reqInfo.LoginAddress, reqInfo.SessionToken)

View file

@ -55,7 +55,7 @@
}, },
{ {
"Name": "Token", "Name": "Token",
"Docs": "Token returns a token to use for an SSE connection. A token can only be used for\na single SSE connection. Tokens are stored in memory for a maximum of 1 minute,\nwith at most 10 unused tokens (the most recently created) per account.", "Docs": "Token returns a single-use token to use for an SSE connection. A token can only\nbe used for a single SSE connection. Tokens are stored in memory for a maximum\nof 1 minute, with at most 10 unused tokens (the most recently created) per\naccount.",
"Params": [], "Params": [],
"Returns": [ "Returns": [
{ {

View file

@ -750,9 +750,10 @@ export class Client {
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
} }
// Token returns a token to use for an SSE connection. A token can only be used for // Token returns a single-use token to use for an SSE connection. A token can only
// a single SSE connection. Tokens are stored in memory for a maximum of 1 minute, // be used for a single SSE connection. Tokens are stored in memory for a maximum
// with at most 10 unused tokens (the most recently created) per account. // of 1 minute, with at most 10 unused tokens (the most recently created) per
// account.
async Token(): Promise<string> { async Token(): Promise<string> {
const fn: string = "Token" const fn: string = "Token"
const paramTypes: string[][] = [] const paramTypes: string[][] = []

View file

@ -448,9 +448,10 @@ var api;
const params = []; const params = [];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
} }
// Token returns a token to use for an SSE connection. A token can only be used for // Token returns a single-use token to use for an SSE connection. A token can only
// a single SSE connection. Tokens are stored in memory for a maximum of 1 minute, // be used for a single SSE connection. Tokens are stored in memory for a maximum
// with at most 10 unused tokens (the most recently created) per account. // of 1 minute, with at most 10 unused tokens (the most recently created) per
// account.
async Token() { async Token() {
const fn = "Token"; const fn = "Token";
const paramTypes = []; const paramTypes = [];

View file

@ -448,9 +448,10 @@ var api;
const params = []; const params = [];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
} }
// Token returns a token to use for an SSE connection. A token can only be used for // Token returns a single-use token to use for an SSE connection. A token can only
// a single SSE connection. Tokens are stored in memory for a maximum of 1 minute, // be used for a single SSE connection. Tokens are stored in memory for a maximum
// with at most 10 unused tokens (the most recently created) per account. // of 1 minute, with at most 10 unused tokens (the most recently created) per
// account.
async Token() { async Token() {
const fn = "Token"; const fn = "Token";
const paramTypes = []; const paramTypes = [];

View file

@ -489,7 +489,8 @@ type ioErr struct {
} }
// serveEvents serves an SSE connection. Authentication is done through a query // serveEvents serves an SSE connection. Authentication is done through a query
// string parameter "token", a one-time-use token returned by the Token API call. // string parameter "singleUseToken", a one-time-use token returned by the Token
// API call.
func serveEvents(ctx context.Context, log mlog.Log, accountPath string, w http.ResponseWriter, r *http.Request) { func serveEvents(ctx context.Context, log mlog.Log, accountPath string, w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" { if r.Method != "GET" {
http.Error(w, "405 - method not allowed - use get", http.StatusMethodNotAllowed) http.Error(w, "405 - method not allowed - use get", http.StatusMethodNotAllowed)
@ -504,7 +505,7 @@ func serveEvents(ctx context.Context, log mlog.Log, accountPath string, w http.R
} }
q := r.URL.Query() q := r.URL.Query()
token := q.Get("token") token := q.Get("singleUseToken")
if token == "" { if token == "" {
http.Error(w, "400 - bad request - missing credentials", http.StatusBadRequest) http.Error(w, "400 - bad request - missing credentials", http.StatusBadRequest)
return return

View file

@ -131,13 +131,13 @@ func TestView(t *testing.T) {
} }
} }
testFail("POST", eventsURL+"?token="+tokens[0]+"&request="+string(requestJSON), http.StatusMethodNotAllowed) // Must be GET. testFail("POST", eventsURL+"?singleUseToken="+tokens[0]+"&request="+string(requestJSON), http.StatusMethodNotAllowed) // Must be GET.
testFail("GET", eventsURL, http.StatusBadRequest) // Missing token. testFail("GET", eventsURL, http.StatusBadRequest) // Missing token.
testFail("GET", eventsURL+"?token="+tokens[0]+"&request="+string(requestJSON), http.StatusBadRequest) // Bad (old) token. testFail("GET", eventsURL+"?singleUseToken="+tokens[0]+"&request="+string(requestJSON), http.StatusBadRequest) // Bad (old) token.
testFail("GET", eventsURL+"?token="+tokens[len(tokens)-5]+"&request=bad", http.StatusBadRequest) // Bad request. testFail("GET", eventsURL+"?singleUseToken="+tokens[len(tokens)-5]+"&request=bad", http.StatusBadRequest) // Bad request.
// Start connection for testing and filters below. // Start connection for testing and filters below.
req, err := http.NewRequest("GET", eventsURL+"?token="+tokens[len(tokens)-1]+"&request="+string(requestJSON), nil) req, err := http.NewRequest("GET", eventsURL+"?singleUseToken="+tokens[len(tokens)-1]+"&request="+string(requestJSON), nil)
tcheck(t, err, "making request") tcheck(t, err, "making request")
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
tcheck(t, err, "http transaction") tcheck(t, err, "http transaction")
@ -168,7 +168,7 @@ func TestView(t *testing.T) {
} }
// Can only use a token once. // Can only use a token once.
testFail("GET", eventsURL+"?token="+tokens[len(tokens)-1]+"&request=bad", http.StatusBadRequest) testFail("GET", eventsURL+"?singleUseToken="+tokens[len(tokens)-1]+"&request=bad", http.StatusBadRequest)
// Check a few initial query/page combinations. // Check a few initial query/page combinations.
testConn := func(token, more string, request Request, check func(EventStart, eventReader)) { testConn := func(token, more string, request Request, check func(EventStart, eventReader)) {
@ -176,7 +176,7 @@ func TestView(t *testing.T) {
reqJSON, err := json.Marshal(request) reqJSON, err := json.Marshal(request)
tcheck(t, err, "marshal request json") tcheck(t, err, "marshal request json")
req, err := http.NewRequest("GET", eventsURL+"?token="+token+more+"&request="+string(reqJSON), nil) req, err := http.NewRequest("GET", eventsURL+"?singleUseToken="+token+more+"&request="+string(reqJSON), nil)
tcheck(t, err, "making request") tcheck(t, err, "making request")
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
tcheck(t, err, "http transaction") tcheck(t, err, "http transaction")

View file

@ -194,8 +194,8 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt
log := pkglog.WithContext(ctx).With(slog.String("userauth", "")) log := pkglog.WithContext(ctx).With(slog.String("userauth", ""))
// Server-sent event connection, for all initial data (list of mailboxes), list of // Server-sent event connection, for all initial data (list of mailboxes), list of
// messages, and all events afterwards. Authenticated through a token in the query // messages, and all events afterwards. Authenticated through a single use token in
// string, which it got from a Token API call. // the query string, which it got from a Token API call.
if r.URL.Path == "/events" { if r.URL.Path == "/events" {
serveEvents(ctx, log, accountPath, w, r) serveEvents(ctx, log, accountPath, w, r)
return return

View file

@ -448,9 +448,10 @@ var api;
const params = []; const params = [];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
} }
// Token returns a token to use for an SSE connection. A token can only be used for // Token returns a single-use token to use for an SSE connection. A token can only
// a single SSE connection. Tokens are stored in memory for a maximum of 1 minute, // be used for a single SSE connection. Tokens are stored in memory for a maximum
// with at most 10 unused tokens (the most recently created) per account. // of 1 minute, with at most 10 unused tokens (the most recently created) per
// account.
async Token() { async Token() {
const fn = "Token"; const fn = "Token";
const paramTypes = []; const paramTypes = [];
@ -6911,7 +6912,7 @@ const init = async () => {
} }
} }
catch (err) { } catch (err) { }
eventSource = new window.EventSource('events?token=' + encodeURIComponent(token) + '&request=' + encodeURIComponent(JSON.stringify(request)) + slow); eventSource = new window.EventSource('events?singleUseToken=' + encodeURIComponent(token) + '&request=' + encodeURIComponent(JSON.stringify(request)) + slow);
let eventID = window.setTimeout(() => dom._kids(statusElem, 'Connecting... '), 1000); let eventID = window.setTimeout(() => dom._kids(statusElem, 'Connecting... '), 1000);
eventSource.addEventListener('open', (e) => { eventSource.addEventListener('open', (e) => {
log('eventsource open', { e }); log('eventsource open', { e });

View file

@ -7208,7 +7208,7 @@ const init = async () => {
} }
} catch (err) {} } catch (err) {}
eventSource = new window.EventSource('events?token=' + encodeURIComponent(token)+'&request='+encodeURIComponent(JSON.stringify(request))+slow) eventSource = new window.EventSource('events?singleUseToken=' + encodeURIComponent(token)+'&request='+encodeURIComponent(JSON.stringify(request))+slow)
let eventID = window.setTimeout(() => dom._kids(statusElem, 'Connecting... '), 1000) let eventID = window.setTimeout(() => dom._kids(statusElem, 'Connecting... '), 1000)
eventSource.addEventListener('open', (e: Event) => { eventSource.addEventListener('open', (e: Event) => {
log('eventsource open', {e}) log('eventsource open', {e})