mirror of
https://github.com/mjl-/mox.git
synced 2024-12-27 08:53:48 +03:00
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:
parent
a977082b89
commit
594182aae5
10 changed files with 35 additions and 29 deletions
|
@ -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)
|
||||||
|
|
|
@ -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": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -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[][] = []
|
||||||
|
|
|
@ -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 = [];
|
||||||
|
|
|
@ -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 = [];
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
|
@ -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})
|
||||||
|
|
Loading…
Reference in a new issue