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})