import { Response, Request } from "express"; import { APIHelper } from "../helpers/APIHelper"; import { APIClient } from "./APIClient"; import { checkMail } from "../utils/StringUtils"; import { AccountHelper } from "../helpers/AccountHelper"; import { UploadedFile } from "express-fileupload"; /** * Response to a request * * @author Pierre HUBERT */ export class RequestHandler { private client : APIClient = null; private userID : number = -1; private responseSent = false; public constructor(private req : Request, private response : Response) {} /** * Get a parameter included in the post request * * @param name Name of the parameter to get */ private getPostParam(name : string) : any { return this.req.body[name]; } /** * Get a String from the request * * @param name The name of the string to get * @param minLength Minimal required size of the string * @param required If set to true (true by default), an error will * be thrown if the string is not included in the request */ public postString(name : string, minLength : number = 1, required : boolean = true) : string { const param = this.getPostParam(name); // Check if parameter was not found if(param == undefined) { if(required) this.error(400, "Could not find required string: '"+name+"'"); return ""; } if(param.length < minLength) this.error(400, "Parameter "+name+" is too short!"); return param; } /** * Check out whether a post parameter is present into the request or not * * @param name The name of the post field to check */ public hasPostParameter(name: string) : boolean { return this.getPostParam(name) != undefined; } /** * Get an email address included in a post request * * @param name The name of the POST filed */ public postEmail(name: string) : string { const email = this.postString(name, 3); if(!checkMail(email)) this.error(400, email + " is not a valid email address!"); return email; } /** * Get an integer included in the request * * @param name Name of POST field * @param fallback Fallback value (if none, throw an error) * @returns The number (throws in case of error) */ public postInt(name: string, fallback ?: number) : number { const param = this.getPostParam(name); if(param == undefined) { if(!fallback) this.error(400, "Missing integer '"+name+"' in the request!"); return fallback; } // Check number if(Number.parseInt(param).toString() !== param.toString()) this.error(400, "'"+name+"' is an invalid integer!"); return Number.parseInt(param); } /** * Get a list of integeres included in the request * * @param name The name of the post field * @param minEntries Specify the minimum number of entries required */ public postNumbersList(name: string, minEntries : number = 1) : Array { const param = this.postString(name, minEntries < 1 ? 0 : minEntries, minEntries > 0); let list = []; for (const el of param.split(",")) { if(el == "") continue; if(Number.parseInt(el).toString() != el) this.error(400, "Invalid number detected in '"+name+"'!"); list.push(Number.parseInt(el)); } if(list.length < minEntries) this.error(400, "Not enough entries in '" + name + "'!") return list; } /** * Turn a list of string into a Set object * * @param name Name of POST field * @param minEntries Minimum number of entries to specify */ public postNumbersSet(name : string, minEntries : number = 1) : Set { return new Set(this.postNumbersList(name, minEntries)); } /** * Attempt to decode JSON included in a POST request * * @param name Name of POST field */ public postJSON(name: string) : any { const src = this.getPostParam(name); if(src == undefined) this.error(400, "Missing JSON '" + name + "' in the request!"); try { const response = JSON.parse(src); return response; } catch(e) { this.error(500, "'" + name + "' is not a valid JSON !"); } } /** * Get a boolean included in the request * * @param name The name of the POST field */ public postBool(name: string) : boolean { const param = this.getPostParam(name); if(param == undefined) this.error(400, "Missing boolean '" + name + "' in the request!"); return param === "true" || param === true; } /** * Get information about an uploaded file * * @param name The name of the posted file */ private GetUploadedFile(name: string) : undefined | UploadedFile { if(!this.req.files) return undefined; if(this.req.files[name] instanceof Array) this.error(500, "Multiple upload are not supported!"); return this.req.files[name]; } /** * Check out whether a file has been included in the request or not * * @param name The name of the file to check */ public hasFile(name: string) : boolean { return this.GetUploadedFile(name) != undefined; } /** * Validate API tokens * * @throws If the tokens could not be validated */ public async checkAPITokens() { // Extract API name & token from request const apiName = this.postString("serviceName"); const apiToken = this.postString("serviceToken"); // Validate the client const client = await APIHelper.GetClient(apiName, apiToken); if(client == null) this.error(400, "Client not recognized!"); if(client.domain) { const allowedOrigin = "http://" + client.domain; const referer = this.req.get("Referer"); if(!referer || !referer.startsWith(allowedOrigin)) this.error(401, "Use of this client is prohibited from this domain!"); this.response.set("Access-Control-Allow-Origin", allowedOrigin); } // Save client information for latter access this.client = client; } /** * Validate user tokens * * @param required Specify whether the user MUST be authenticated or not */ public async checkUserTokens(required ?: boolean) { const token1 = this.postString("userToken1", 0, false); const token2 = this.postString("userToken2", 0, false); if(token1.length < 1 || token2.length < 1) { if(required !== false) this.error(401, "This method requires the user to be signed in!"); return; } // Validate user tokens this.userID = await AccountHelper.GetUserIdFromTokens(this.getClientInfo(), token1, token2); if(this.userID < 1) this.error(412, "Please check your login tokens!"); } /** * Get information about API client */ public getClientInfo() : APIClient { if(!this.client) throw Error("Try to get information about client but client has not been authenticated!"); return this.client; } /** * Get information about current user */ public getUserId() : number { if(this.userID < 1) throw Error("Trying to get user ID but none are available!"); return this.userID; } /** * Check out whether user is signed in or not */ public get signedIn() : boolean { return this.userID > 0; } /** * Output an error code and throws an error * * @param code HTTP Status code * @param message The message to send */ public error(code : number, message : string, should_throw: boolean = true) { if(this.responseSent) return; this.response.status(code).send({ error: { code: code, message: message } }); this.responseSent = true; if(should_throw) throw Error("Could not complete request! ("+ message +")"); } /** * Return a successful operation * * @param message Message associated to success */ public success(message: string) { this.responseSent = true; this.response.send({ success: message }); } /** * Send some data to the server * * @param data The data to send back to the server */ public send(data: any) { this.responseSent = true; this.response.send(data); } }