Skip to content
This repository was archived by the owner on Sep 4, 2021. It is now read-only.

Commit 5239bdd

Browse files
committed
dashboard: OAuth: retry requests for 5xx responses and ensure refresh token timeout is set when using exisitng token on init
Signed-off-by: Jesse Stuart <[email protected]>
1 parent 60fff2c commit 5239bdd

File tree

2 files changed

+68
-10
lines changed

2 files changed

+68
-10
lines changed

dashboard/src/worker/oauthClient.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { encode as base64URLEncode } from '../util/base64url';
1010
import { postMessageAll, postMessage } from './external';
1111
import { isTokenValid, canRefreshToken } from './tokenHelpers';
1212
import _debug from './debug';
13+
import retryFetch from './retryFetch';
1314

1415
function debug(msg: string, ...args: any[]) {
1516
_debug(`[oauthClient]: ${msg}`, ...args);
@@ -55,13 +56,10 @@ function dbClearAuth(): Promise<any> {
5556

5657
export async function initClient(clientID: string) {
5758
const token = await getToken();
58-
if (isTokenValid(token)) {
59+
if (token && isTokenValid(token)) {
5960
debug('[initClient]: using existing valid token', token);
60-
61-
await postMessageAll({
62-
type: types.MessageType.AUTH_TOKEN,
63-
payload: token as types.OAuthToken
64-
});
61+
// setToken will handle setting up the refresh cycle
62+
await setToken(token);
6563
return;
6664
} else if (canRefreshToken(token)) {
6765
debug('[initClient]: attempting to refresh existing token', token);
@@ -191,7 +189,8 @@ async function setToken(token: types.OAuthToken) {
191189
await dbSet(DBKeys.TOKEN, token);
192190

193191
if (canRefreshToken(token)) {
194-
const expiresInMs = token.expires_in * 1000;
192+
const delta = Date.now() - token.issued_time;
193+
const expiresInMs = token.expires_in * 1000 - delta;
195194
const minRefreshDelayMs = 5000;
196195
const maxRefreshDelayMs = 20000;
197196
// refresh 5 to 20 seconds before it expires
@@ -244,7 +243,7 @@ async function getServerMeta(): Promise<ServerMetadata> {
244243
}
245244

246245
const url = `${config.OAUTH_ISSUER}/.well-known/oauth-authorization-server`;
247-
const res = await fetch(url);
246+
const res = await retryFetch(url);
248247
const meta = await res.json();
249248
if (!codePointCompare(config.OAUTH_ISSUER, meta.issuer)) {
250249
throw new Error(
@@ -386,7 +385,7 @@ async function doTokenExchange(params: types.OAuthCallbackResponse) {
386385
body.set('redirect_uri', cachedValues.redirectURI);
387386
body.set('client_id', config.OAUTH_CLIENT_ID);
388387
body.set('audience', config.CONTROLLER_HOST);
389-
const res = await fetch(meta.token_endpoint, {
388+
const res = await retryFetch(meta.token_endpoint, {
390389
method: 'POST',
391390
headers: {
392391
'Content-Type': 'application/x-www-form-urlencoded'
@@ -416,7 +415,7 @@ async function doTokenRefresh(refreshToken: string) {
416415
body.set('refresh_token', refreshToken);
417416
body.set('client_id', config.OAUTH_CLIENT_ID);
418417
body.set('audience', config.CONTROLLER_HOST);
419-
const res = await fetch(meta.token_endpoint, {
418+
const res = await retryFetch(meta.token_endpoint, {
420419
method: 'POST',
421420
headers: {
422421
'Content-Type': 'application/x-www-form-urlencoded'

dashboard/src/worker/retryFetch.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import _debug from './debug';
2+
3+
function debug(msg: string, ...args: any[]) {
4+
_debug(`[retryFetch]: ${msg}`, ...args);
5+
}
6+
7+
const maxRetries = 3;
8+
export default async function retryFetch(
9+
request: string | Request,
10+
init?: RequestInit,
11+
nRetries: number = 0
12+
): Promise<Response> {
13+
const response = await fetch(request, init);
14+
if (response.ok) return response;
15+
if (isRetriableStatus(response.status) && nRetries < maxRetries) {
16+
const signal = (init && init.signal) || null;
17+
return new Promise<Response>((resolve, reject) => {
18+
const timeout = calcTimeout(nRetries);
19+
debug(`retrying fetch in ${timeout}ms`);
20+
let complete = false;
21+
const timeoutID = setTimeout(() => {
22+
retryFetch(request, init, nRetries + 1)
23+
.then((response: Response) => {
24+
if (complete) return;
25+
complete = true;
26+
resolve(response);
27+
})
28+
.catch((error) => {
29+
if (complete) return;
30+
complete = true;
31+
reject(error);
32+
});
33+
}, timeout);
34+
if (signal) {
35+
signal.addEventListener('abort', () => {
36+
clearTimeout(timeoutID);
37+
if (complete) return;
38+
complete = true;
39+
reject(new Error('AbortError'));
40+
});
41+
}
42+
});
43+
}
44+
return response;
45+
}
46+
47+
function isRetriableStatus(status: number): boolean {
48+
if (status === 0) return true;
49+
if (status <= 500 && status < 600) return true;
50+
return false;
51+
}
52+
53+
function calcTimeout(n: number): number {
54+
let timeout = 1000;
55+
for (let i = 0; i < n; i++) {
56+
timeout += timeout;
57+
}
58+
return timeout;
59+
}

0 commit comments

Comments
 (0)