From 594182aae5881c4c4f5e8caacaab8c25cea7e3c8 Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Fri, 23 Aug 2024 15:08:27 +0200 Subject: [PATCH] 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... --- webmail/api.go | 7 ++++--- webmail/api.json | 2 +- webmail/api.ts | 7 ++++--- webmail/msg.js | 7 ++++--- webmail/text.js | 7 ++++--- webmail/view.go | 5 +++-- webmail/view_test.go | 14 +++++++------- webmail/webmail.go | 4 ++-- webmail/webmail.js | 9 +++++---- webmail/webmail.ts | 2 +- 10 files changed, 35 insertions(+), 29 deletions(-) diff --git a/webmail/api.go b/webmail/api.go index 8822095..9d07f62 100644 --- a/webmail/api.go +++ b/webmail/api.go @@ -130,9 +130,10 @@ func (w Webmail) Logout(ctx context.Context) { xcheckf(ctx, err, "logout") } -// Token returns a token to use for an SSE connection. A token can only be used for -// a single SSE connection. Tokens are stored in memory for a maximum of 1 minute, -// with at most 10 unused tokens (the most recently created) per account. +// Token returns a single-use token to use for an SSE connection. A token can only +// be used for a single SSE connection. Tokens are stored in memory for a maximum +// of 1 minute, with at most 10 unused tokens (the most recently created) per +// account. func (Webmail) Token(ctx context.Context) string { reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo) return sseTokens.xgenerate(ctx, reqInfo.Account.Name, reqInfo.LoginAddress, reqInfo.SessionToken) diff --git a/webmail/api.json b/webmail/api.json index 5d6e23f..148c54c 100644 --- a/webmail/api.json +++ b/webmail/api.json @@ -55,7 +55,7 @@ }, { "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": [], "Returns": [ { diff --git a/webmail/api.ts b/webmail/api.ts index da243d5..0b637da 100644 --- a/webmail/api.ts +++ b/webmail/api.ts @@ -750,9 +750,10 @@ export class Client { 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 - // a single SSE connection. Tokens are stored in memory for a maximum of 1 minute, - // with at most 10 unused tokens (the most recently created) per account. + // Token returns a single-use token to use for an SSE connection. A token can only + // be used for a single SSE connection. Tokens are stored in memory for a maximum + // of 1 minute, with at most 10 unused tokens (the most recently created) per + // account. async Token(): Promise { const fn: string = "Token" const paramTypes: string[][] = [] diff --git a/webmail/msg.js b/webmail/msg.js index 7de3349..def7253 100644 --- a/webmail/msg.js +++ b/webmail/msg.js @@ -448,9 +448,10 @@ var api; const 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 - // a single SSE connection. Tokens are stored in memory for a maximum of 1 minute, - // with at most 10 unused tokens (the most recently created) per account. + // Token returns a single-use token to use for an SSE connection. A token can only + // be used for a single SSE connection. Tokens are stored in memory for a maximum + // of 1 minute, with at most 10 unused tokens (the most recently created) per + // account. async Token() { const fn = "Token"; const paramTypes = []; diff --git a/webmail/text.js b/webmail/text.js index 4eab7c0..2ba1bf1 100644 --- a/webmail/text.js +++ b/webmail/text.js @@ -448,9 +448,10 @@ var api; const 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 - // a single SSE connection. Tokens are stored in memory for a maximum of 1 minute, - // with at most 10 unused tokens (the most recently created) per account. + // Token returns a single-use token to use for an SSE connection. A token can only + // be used for a single SSE connection. Tokens are stored in memory for a maximum + // of 1 minute, with at most 10 unused tokens (the most recently created) per + // account. async Token() { const fn = "Token"; const paramTypes = []; diff --git a/webmail/view.go b/webmail/view.go index 96fd421..9a6598c 100644 --- a/webmail/view.go +++ b/webmail/view.go @@ -489,7 +489,8 @@ type ioErr struct { } // 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) { if r.Method != "GET" { 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() - token := q.Get("token") + token := q.Get("singleUseToken") if token == "" { http.Error(w, "400 - bad request - missing credentials", http.StatusBadRequest) return diff --git a/webmail/view_test.go b/webmail/view_test.go index f77b956..c861f52 100644 --- a/webmail/view_test.go +++ b/webmail/view_test.go @@ -131,13 +131,13 @@ func TestView(t *testing.T) { } } - testFail("POST", eventsURL+"?token="+tokens[0]+"&request="+string(requestJSON), http.StatusMethodNotAllowed) // Must be GET. - testFail("GET", eventsURL, http.StatusBadRequest) // Missing token. - testFail("GET", eventsURL+"?token="+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("POST", eventsURL+"?singleUseToken="+tokens[0]+"&request="+string(requestJSON), http.StatusMethodNotAllowed) // Must be GET. + testFail("GET", eventsURL, http.StatusBadRequest) // Missing token. + testFail("GET", eventsURL+"?singleUseToken="+tokens[0]+"&request="+string(requestJSON), http.StatusBadRequest) // Bad (old) token. + testFail("GET", eventsURL+"?singleUseToken="+tokens[len(tokens)-5]+"&request=bad", http.StatusBadRequest) // Bad request. // 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") resp, err := http.DefaultClient.Do(req) tcheck(t, err, "http transaction") @@ -168,7 +168,7 @@ func TestView(t *testing.T) { } // 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. 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) 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") resp, err := http.DefaultClient.Do(req) tcheck(t, err, "http transaction") diff --git a/webmail/webmail.go b/webmail/webmail.go index b036c4b..55dea82 100644 --- a/webmail/webmail.go +++ b/webmail/webmail.go @@ -194,8 +194,8 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt log := pkglog.WithContext(ctx).With(slog.String("userauth", "")) // 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 - // string, which it got from a Token API call. + // messages, and all events afterwards. Authenticated through a single use token in + // the query string, which it got from a Token API call. if r.URL.Path == "/events" { serveEvents(ctx, log, accountPath, w, r) return diff --git a/webmail/webmail.js b/webmail/webmail.js index 34d66df..9e6288c 100644 --- a/webmail/webmail.js +++ b/webmail/webmail.js @@ -448,9 +448,10 @@ var api; const 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 - // a single SSE connection. Tokens are stored in memory for a maximum of 1 minute, - // with at most 10 unused tokens (the most recently created) per account. + // Token returns a single-use token to use for an SSE connection. A token can only + // be used for a single SSE connection. Tokens are stored in memory for a maximum + // of 1 minute, with at most 10 unused tokens (the most recently created) per + // account. async Token() { const fn = "Token"; const paramTypes = []; @@ -6911,7 +6912,7 @@ const init = async () => { } } 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); eventSource.addEventListener('open', (e) => { log('eventsource open', { e }); diff --git a/webmail/webmail.ts b/webmail/webmail.ts index 51d6319..6082ee2 100644 --- a/webmail/webmail.ts +++ b/webmail/webmail.ts @@ -7208,7 +7208,7 @@ const init = async () => { } } 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) eventSource.addEventListener('open', (e: Event) => { log('eventsource open', {e})