abstract tokenstore to support different storage methods of tokenstore
This commit is contained in:
261
src/utils/Api.ts
261
src/utils/Api.ts
@ -1,4 +1,5 @@
|
||||
import GlobalInfos from './GlobalInfos';
|
||||
import {TokenStore} from './TokenStore/TokenStore';
|
||||
|
||||
const APIPREFIX: string = '/api/';
|
||||
|
||||
@ -11,165 +12,127 @@ interface ApiBaseRequest {
|
||||
[_: string]: string | number | boolean | object;
|
||||
}
|
||||
|
||||
// store api token - empty if not set
|
||||
let apiToken = '';
|
||||
export namespace token {
|
||||
// store api token - empty if not set
|
||||
let apiToken = '';
|
||||
|
||||
// a callback que to be called after api token refresh
|
||||
let callQue: ((error: string) => void)[] = [];
|
||||
// flag to check wheter a api refresh is currently pending
|
||||
let refreshInProcess = false;
|
||||
// store the expire seconds of token
|
||||
let expireSeconds = -1;
|
||||
// a callback que to be called after api token refresh
|
||||
let callQue: ((error: string) => void)[] = [];
|
||||
// flag to check wheter a api refresh is currently pending
|
||||
let refreshInProcess = false;
|
||||
// store the expire seconds of token
|
||||
let expireSeconds = -1;
|
||||
|
||||
/**
|
||||
* refresh the api token or use that one in cookie if still valid
|
||||
* @param callback to be called after successful refresh
|
||||
* @param password
|
||||
* @param force
|
||||
*/
|
||||
export function refreshAPIToken(callback: (error: string) => void, force?: boolean, password?: string): void {
|
||||
callQue.push(callback);
|
||||
let tokenStore: TokenStore;
|
||||
|
||||
// check if already is a token refresh is in process
|
||||
if (refreshInProcess) {
|
||||
// if yes return
|
||||
return;
|
||||
} else {
|
||||
// if not set flat
|
||||
refreshInProcess = true;
|
||||
export function setTokenStore(ts: TokenStore): void {
|
||||
tokenStore = ts;
|
||||
}
|
||||
|
||||
if (apiTokenValid() && !force) {
|
||||
console.log('token still valid...');
|
||||
callFuncQue('');
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* refresh the api token or use that one in cookie if still valid
|
||||
* @param callback to be called after successful refresh
|
||||
* @param password
|
||||
* @param force
|
||||
*/
|
||||
export function refreshAPIToken(callback: (error: string) => void, force?: boolean, password?: string): void {
|
||||
callQue.push(callback);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('grant_type', 'client_credentials');
|
||||
formData.append('client_id', 'openmediacenter');
|
||||
formData.append('client_secret', password ? password : 'openmediacenter');
|
||||
formData.append('scope', 'all');
|
||||
// check if already is a token refresh is in process
|
||||
if (refreshInProcess) {
|
||||
// if yes return
|
||||
return;
|
||||
} else {
|
||||
// if not set flat
|
||||
refreshInProcess = true;
|
||||
}
|
||||
|
||||
interface APIToken {
|
||||
error?: string;
|
||||
// eslint-disable-next-line camelcase
|
||||
access_token: string; // no camel case allowed because of backendlib
|
||||
// eslint-disable-next-line camelcase
|
||||
expires_in: number; // no camel case allowed because of backendlib
|
||||
scope: string;
|
||||
// eslint-disable-next-line camelcase
|
||||
token_type: string; // no camel case allowed because of backendlib
|
||||
}
|
||||
|
||||
fetch('/token', {method: 'POST', body: formData}).then((response) =>
|
||||
response.json().then((result: APIToken) => {
|
||||
if (result.error) {
|
||||
callFuncQue(result.error);
|
||||
return;
|
||||
}
|
||||
// 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
|
||||
if (apiTokenValid() && !force) {
|
||||
console.log('token still valid...');
|
||||
callFuncQue('');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function apiTokenValid(): boolean {
|
||||
// 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;
|
||||
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* call all qued callbacks
|
||||
*/
|
||||
function callFuncQue(error: string): void {
|
||||
// call all pending handlers
|
||||
callQue.map((func) => {
|
||||
return func(error);
|
||||
});
|
||||
// reset pending que
|
||||
callQue = [];
|
||||
// release flag to be able to start new refresh
|
||||
refreshInProcess = false;
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('grant_type', 'client_credentials');
|
||||
formData.append('client_id', 'openmediacenter');
|
||||
formData.append('client_secret', password ? password : 'openmediacenter');
|
||||
formData.append('scope', 'all');
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
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);
|
||||
interface APIToken {
|
||||
error?: string;
|
||||
// eslint-disable-next-line camelcase
|
||||
access_token: string; // no camel case allowed because of backendlib
|
||||
// eslint-disable-next-line camelcase
|
||||
expires_in: number; // no camel case allowed because of backendlib
|
||||
scope: string;
|
||||
// eslint-disable-next-line camelcase
|
||||
token_type: string; // no camel case allowed because of backendlib
|
||||
}
|
||||
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();
|
||||
fetch('/token', {method: 'POST', body: formData}).then((response) =>
|
||||
response.json().then((result: APIToken) => {
|
||||
if (result.error) {
|
||||
callFuncQue(result.error);
|
||||
return;
|
||||
}
|
||||
// set api token
|
||||
apiToken = result.access_token;
|
||||
// set expire time
|
||||
expireSeconds = new Date().getTime() / 1000 + result.expires_in;
|
||||
// setTokenCookie(apiToken, expireSeconds);
|
||||
tokenStore.setToken({accessToken: apiToken, expireTime: expireSeconds, tokenType: '', scope: ''});
|
||||
// call all handlers and release flag
|
||||
callFuncQue('');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function apiTokenValid(): boolean {
|
||||
// check if a cookie with token is available
|
||||
// const token = getTokenCookie();
|
||||
const tmptoken = tokenStore.loadToken();
|
||||
if (tmptoken !== null) {
|
||||
// check if token is at least valid for the next minute
|
||||
if (tmptoken.expireTime > new Date().getTime() / 1000 + 60) {
|
||||
apiToken = tmptoken.accessToken;
|
||||
expireSeconds = tmptoken.expireTime;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* call all qued callbacks
|
||||
*/
|
||||
function callFuncQue(error: string): void {
|
||||
// call all pending handlers
|
||||
callQue.map((func) => {
|
||||
return func(error);
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
// reset pending que
|
||||
callQue = [];
|
||||
// release flag to be able to start new refresh
|
||||
refreshInProcess = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if api token is valid -- if not request new one
|
||||
* when finished call callback
|
||||
* @param callback function to be called afterwards
|
||||
*/
|
||||
export function checkAPITokenValid(callback: (mytoken: string) => void): void {
|
||||
// check if token is valid and set
|
||||
if (apiToken === '' || expireSeconds <= new Date().getTime() / 1000) {
|
||||
console.log('token not valid...');
|
||||
refreshAPIToken(() => {
|
||||
callback(apiToken);
|
||||
});
|
||||
} else {
|
||||
callback(apiToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,13 +149,13 @@ export function callAPI<T>(
|
||||
callback: (_: T) => void,
|
||||
errorcallback: (_: string) => void = (_: string): void => {}
|
||||
): void {
|
||||
checkAPITokenValid(() => {
|
||||
token.checkAPITokenValid((mytoken) => {
|
||||
fetch(APIPREFIX + apinode, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(fd),
|
||||
headers: new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer ' + apiToken
|
||||
Authorization: 'Bearer ' + mytoken
|
||||
})
|
||||
})
|
||||
.then((response) => {
|
||||
@ -252,13 +215,13 @@ export function callApiUnsafe<T>(
|
||||
* @param callback the callback with PLAIN text reply from backend
|
||||
*/
|
||||
export function callAPIPlain(apinode: APINode, fd: ApiBaseRequest, callback: (_: string) => void): void {
|
||||
checkAPITokenValid(() => {
|
||||
token.checkAPITokenValid((mytoken) => {
|
||||
fetch(APIPREFIX + apinode, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(fd),
|
||||
headers: new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer ' + apiToken
|
||||
Authorization: 'Bearer ' + mytoken
|
||||
})
|
||||
}).then((response) =>
|
||||
response.text().then((result) => {
|
||||
|
48
src/utils/TokenStore/CookieTokenStore.ts
Normal file
48
src/utils/TokenStore/CookieTokenStore.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import {Token, TokenStore} from './TokenStore';
|
||||
|
||||
export class CookieTokenStore extends TokenStore {
|
||||
loadToken(): Token | null {
|
||||
const token = this.decodeCookie('token');
|
||||
const expireInString = this.decodeCookie('token_expire');
|
||||
const expireIn = parseInt(expireInString, 10);
|
||||
|
||||
if (expireIn !== 0 && token !== '') {
|
||||
return {accessToken: token, expireTime: expireIn, scope: '', tokenType: ''};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set the cookie for the currently gotten token
|
||||
* @param token the token to set
|
||||
*/
|
||||
setToken(token: Token): void {
|
||||
let d = new Date();
|
||||
d.setTime(token.expireTime * 1000);
|
||||
console.log('token set' + d.toUTCString());
|
||||
let expires = 'expires=' + d.toUTCString();
|
||||
document.cookie = 'token=' + token.accessToken + ';' + expires + ';path=/';
|
||||
document.cookie = 'token_expire=' + token.expireTime + ';' + expires + ';path=/';
|
||||
}
|
||||
|
||||
/**
|
||||
* decode a simple cookie with key specified
|
||||
* @param key cookie key
|
||||
*/
|
||||
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 '';
|
||||
}
|
||||
}
|
11
src/utils/TokenStore/TokenStore.ts
Normal file
11
src/utils/TokenStore/TokenStore.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export interface Token {
|
||||
accessToken: string;
|
||||
expireTime: number; // second time when token will be invalidated
|
||||
scope: string;
|
||||
tokenType: string;
|
||||
}
|
||||
|
||||
export abstract class TokenStore {
|
||||
abstract loadToken(): Token | null;
|
||||
abstract setToken(token: Token): void;
|
||||
}
|
Reference in New Issue
Block a user