import * as utils from '@cobalt-engine/utils'; import * as fs from 'fs'; import * as Promise from 'bluebird'; import CustomError from 'modules/custom-error'; import { CoError } from '../co-error'; import { Config } from '../config'; import { Readable as ReadableStream } from 'stream'; import { FileTokenHandler } from '../token-handler/FileTokenHandler'; import { ITokenHandler } from '../token-handler/ITokenHandler'; import IRequest from '../interfaces/IRequest'; import AuthService from '../interfaces/AuthService'; import Request from '../Request'; const config = Config.getSync(); export default class SecureRequest extends Request implements IRequest { public constructor(private tokenHandler: ITokenHandler, private authService: AuthService) { super(); } public get(url: string, options: any = {}): Promise { return this.sendRequest(data => super.get(url, utils.deepMixin(options, data))); } public post(url: string, options: any = {}): Promise { return this.sendRequest(data => super.post(url, createFormData(data, options))); } public put(url: string, options: any = {}): Promise { return this.sendRequest(data => super.put(url, createFormData(data, options))); } public patch(url: string, options: any = {}): Promise { return this.sendRequest(data => super.patch(url, createFormData(data, options))); } public delete(url: string, options: any = {}): Promise { return this.sendRequest(data => super.delete(url, utils.deepMixin(options, data))); } public download(url: string, filename: string, options: any = {}): Promise { return this.sendRequest(data => super.download(url, filename, utils.deepMixin(options, data))) .then(() => {}); } private sendRequest(request: (data: any) => T): Promise { const sendRequest = this.createRequestWithAuth(request); return this.tokenHandler.get() .then(sendRequest) .catch(err => this.refreshTokenAndTryAgain(err, sendRequest)); } private createRequestWithAuth(request: (data: any) => T): (data: any) => T { return data => request({ auth: { bearer: data.access_token, }, headers: { realm: config.authServer.realm, }, }); } private refreshTokenAndTryAgain( err: CustomError, action: (data: any) => T, ): Promise { if (isNotAuthorized(err.code)) { return this.authService.refreshTokenAndTryAgain(action) .catch(authErrorHandler); } throw err; } } function authErrorHandler(err: CustomError): never { if (isNotAuthorized(err.code)) { throw new CoError('NOT_LOGGED_IN'); } throw err; } function isNotAuthorized(code: string | number): boolean { return code === 'FORBIDDEN' || code === 'UNAUTHORIZED' || code === 'TOKEN_EXPIRED'; } interface FormData { formData: ClosedData; form: ClosedData; [key: string]: any; } interface ClosedData { closed: boolean; path: string | Buffer; [key: string]: any; } function createFormData(data: any, options: FormData): FormData { return utils.deepMixin(data, { formData: refreshClosedStreams(options.formData), form: refreshClosedStreams(options.form), }); } function refreshClosedStreams(data: ClosedData) : any { if (!data) { return; } return Object.keys(data).reduce((acc: any, key: string) => { const value: ClosedData = data[key]; if (value instanceof ReadableStream && value.closed) { acc[key] = fs.createReadStream(value.path); }else { acc[key] = value; } return acc; }, {}); }