add secure requests with tokens.

generate new token on every new page load
This commit is contained in:
2021-03-09 12:56:53 +00:00
parent 162b4efd0e
commit c24c2ac2d8
7 changed files with 372 additions and 35 deletions

View File

@ -43,6 +43,153 @@ interface ApiBaseRequest {
[_: string]: string | number | boolean | object
}
// store api token - empty if not set
let apiToken = ''
// a callback que to be called after api token refresh
let callQue: (() => void)[] = []
// flag to check wheter a api refresh is currently pending
let refreshInProcess = false;
// store the expire seconds of token
let expireSeconds = -1;
interface APIToken {
access_token: string;
expires_in: number;
scope: string;
token_type: string;
}
/**
* refresh the api token or use that one in cookie if still valid
* @param callback to be called after successful refresh
*/
export function refreshAPIToken(callback: () => void): void {
callQue.push(callback);
// check if already is a token refresh is in process
if (refreshInProcess) {
// if yes return
return;
} else {
// if not set flat
refreshInProcess = true;
}
// check if a cookie with token is available
const token = getTokenCookie();
if (token !== null) {
// check if token is at least valid for the next minute
if (token.expire > (new Date().getTime() / 1000) + 60) {
apiToken = token.token;
expireSeconds = token.expire;
callback();
console.log("token still valid...")
callFuncQue();
return;
}
}
const formData = new FormData();
formData.append("grant_type", "client_credentials");
formData.append("client_id", "openmediacenter");
formData.append("client_secret", 'openmediacenter');
formData.append("scope", 'all');
fetch(getBackendDomain() + '/token', {method: 'POST', body: formData})
.then((response) => response.json()
.then((result: APIToken) => {
console.log(result)
// set api token
apiToken = result.access_token;
// set expire time
expireSeconds = (new Date().getTime() / 1000) + result.expires_in;
setTokenCookie(apiToken, expireSeconds);
// call all handlers and release flag
callFuncQue();
}));
}
/**
* call all qued callbacks
*/
function callFuncQue(): void {
// call all pending handlers
callQue.map(func => {
return func();
})
// reset pending que
callQue = []
// release flag to be able to start new refresh
refreshInProcess = false;
}
/**
* set the cookie for the currently gotten token
* @param token token string
* @param validSec second time when the token will be invalid
*/
function setTokenCookie(token: string, validSec: number): void {
let d = new Date();
d.setTime(validSec * 1000);
console.log("token set" + d.toUTCString())
let expires = "expires=" + d.toUTCString();
document.cookie = "token=" + token + ";" + expires + ";path=/";
document.cookie = "token_expire=" + validSec + ";" + expires + ";path=/";
}
/**
* get all required cookies for the token
*/
function getTokenCookie(): { token: string, expire: number } | null {
const token = decodeCookie('token');
const expireInString = decodeCookie('token_expire');
const expireIn = parseInt(expireInString, 10) | 0;
if (expireIn !== 0 && token !== '') {
return {token: token, expire: expireIn};
} else {
return null
}
}
/**
* decode a simple cookie with key specified
* @param key cookie key
*/
function decodeCookie(key: string): string {
let name = key + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1);
}
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
/**
* check if api token is valid -- if not request new one
* when finished call callback
* @param callback function to be called afterwards
*/
function checkAPITokenValid(callback: () => void): void {
// check if token is valid and set
if (apiToken === '' || expireSeconds <= new Date().getTime() / 1000) {
refreshAPIToken(() => {
callback()
})
} else {
callback()
}
}
/**
* A backend api call
* @param apinode which api backend handler to call
@ -50,12 +197,28 @@ interface ApiBaseRequest {
* @param callback the callback with json reply from backend
* @param errorcallback a optional callback if an error occured
*/
export function callAPI<T>(apinode: APINode, fd: ApiBaseRequest, callback: (_: T) => void, errorcallback: (_: string) => void = (_: string): void => {}): void {
fetch(getAPIDomain() + apinode, {method: 'POST', body: JSON.stringify(fd)})
.then((response) => response.json()
.then((result) => {
callback(result);
})).catch(reason => errorcallback(reason));
export function callAPI<T>(apinode: APINode,
fd: ApiBaseRequest,
callback: (_: T) => void,
errorcallback: (_: string) => void = (_: string): void => {
}): void {
checkAPITokenValid(() => {
fetch(getAPIDomain() + apinode, {
method: 'POST', body: JSON.stringify(fd), headers: new Headers({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + apiToken,
}),
}).then((response) => {
if (response.status !== 200) {
console.log('Error: ' + response.statusText);
// todo place error popup here
} else {
response.json().then((result: T) => {
callback(result);
})
}
}).catch(reason => errorcallback(reason));
})
}
/**
@ -65,12 +228,18 @@ export function callAPI<T>(apinode: APINode, fd: ApiBaseRequest, callback: (_: T
* @param callback the callback with PLAIN text reply from backend
*/
export function callAPIPlain(apinode: APINode, fd: ApiBaseRequest, callback: (_: string) => void): void {
fetch(getAPIDomain() + apinode, {method: 'POST', body: JSON.stringify(fd)})
.then((response) => response.text()
.then((result) => {
callback(result);
}));
checkAPITokenValid(() => {
fetch(getAPIDomain() + apinode, {
method: 'POST', body: JSON.stringify(fd), headers: new Headers({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + apiToken,
})
})
.then((response) => response.text()
.then((result) => {
callback(result);
}));
});
}
/**