frontend/app/OAuth2Helper.ts (63 lines of code) (raw):
/**
* Call out to the IdP to request a refresh of the login using the refresh token stored in the local storage.
* on success, the updated token is stored in the local storage and the promise resolves
* on failure, the local storage is not touched and the promise rejects with an error string
* if the server returns a 500 or 503/504 error then it is assumed to be transient and the request will be retried
* after a two second delay.
*
* This is NOT written as a conventional async function in order to utilise more fine-grained control of when the promise
* is resolved; i.e., it calls itself on a timer in order to retry so we must only resolve the promise once there has been
* a definitive success or failure of the operation which could be after multiple calls
* @param tokenUri server uri to make the refresh request to
* @returns a Promise
*/
export const refreshLogin: (tokenUri: string) => Promise<void> = (tokenUri) =>
new Promise((resolve, reject) => {
const refreshToken = localStorage.getItem("vaultdoor:refresh-token");
if (!refreshToken) {
reject("No refresh token");
}
const postdata: { [index: string]: string } = {
grant_type: "refresh_token",
client_id: "vaultdoor",
refresh_token: refreshToken as string,
};
const content_elements = Object.keys(postdata).map(
(k) => k + "=" + encodeURIComponent(postdata[k])
);
const body_content = content_elements.join("&");
const performRefresh = async () => {
const response = await fetch(tokenUri, {
method: "POST",
body: body_content,
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
},
});
switch (response.status) {
case 200:
const content = await response.json();
console.log("Server response: ", content);
localStorage.setItem("vaultdoor:access-token", content.access_token);
if (content.refresh_token)
localStorage.setItem(
"vaultdoor:refresh-token",
content.refresh_token
);
resolve();
break;
case 403:
case 401:
console.log("Refresh was rejected with a forbidden error");
reject("Request forbidden");
break;
case 500:
console.log("Refresh was rejected due to a server error");
window.setTimeout(() => performRefresh(), 2000); //try again in two seconds
break;
case 503:
case 504:
console.log("Authentication server not available");
window.setTimeout(() => performRefresh(), 2000); //try again in two seconds
break;
default:
const errorbody = await response.text();
console.log(
"Unexpected response from authentication server: ",
response.status,
errorbody
);
reject("Unexpected response");
break;
}
};
performRefresh().catch((err) => reject(err.toString()));
});