add secure requests with tokens.
generate new token on every new page load
This commit is contained in:
		@@ -12,12 +12,8 @@ describe('<ActorTile/>', function () {
 | 
			
		||||
        const func = jest.fn((_) => {});
 | 
			
		||||
        const wrapper = shallow(<ActorTile actor={{Thumbnail: '-1', Name: 'testname', id: 3}} onClick={() => func()}/>);
 | 
			
		||||
 | 
			
		||||
        const func1 = jest.fn();
 | 
			
		||||
        prepareViewBinding(func1);
 | 
			
		||||
 | 
			
		||||
        wrapper.simulate('click');
 | 
			
		||||
 | 
			
		||||
        expect(func1).toBeCalledTimes(0);
 | 
			
		||||
        expect(func).toBeCalledTimes(1);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,8 @@ global.prepareFetchApi = (response) => {
 | 
			
		||||
    const mockJsonPromise = Promise.resolve(response);
 | 
			
		||||
    const mockFetchPromise = Promise.resolve({
 | 
			
		||||
        json: () => mockJsonPromise,
 | 
			
		||||
        text: () => mockJsonPromise
 | 
			
		||||
        text: () => mockJsonPromise,
 | 
			
		||||
        status: 200
 | 
			
		||||
    });
 | 
			
		||||
    return (jest.fn().mockImplementation(() => mockFetchPromise));
 | 
			
		||||
};
 | 
			
		||||
@@ -33,19 +34,6 @@ global.prepareFailingFetchApi = () => {
 | 
			
		||||
    return (jest.fn().mockImplementation(() => mockFetchPromise));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * prepares a viewbinding instance
 | 
			
		||||
 * @param func a mock function to be called
 | 
			
		||||
 */
 | 
			
		||||
global.prepareViewBinding = (func) => {
 | 
			
		||||
    GlobalInfos.getViewBinding = () => {
 | 
			
		||||
        return {
 | 
			
		||||
            changeRootElement: func,
 | 
			
		||||
            returnToLastElement: func
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
global.callAPIMock = (resonse) => {
 | 
			
		||||
    const helpers = require('./utils/Api');
 | 
			
		||||
    helpers.callAPI = jest.fn().mockImplementation((_, __, func1) => {func1(resonse);});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										193
									
								
								src/utils/Api.ts
									
									
									
									
									
								
							
							
						
						
									
										193
									
								
								src/utils/Api.ts
									
									
									
									
									
								
							@@ -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);
 | 
			
		||||
                }));
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user