import { Request, Response } from "express"; import { UploadedFile } from "express-fileupload"; import { writeFileSync } from "fs"; import * as sharp from 'sharp'; import { AccountHelper } from "../helpers/AccountHelper"; import { APIHelper } from "../helpers/APIHelper"; import { conf } from "../helpers/ConfigHelper"; import { generateNewUserDataFileName, pathUserData, prepareFileCreation } from "../utils/UserDataUtils"; import { APIClient } from "./APIClient"; import { BaseRequestsHandler } from "./BaseRequestsHandler"; /** * Response to a request * * @author Pierre HUBERT */ export class RequestHandler extends BaseRequestsHandler { private client : APIClient = null; protected userID : number = -1; private responseSent = false; public constructor(private req : Request, private response : Response) { super(); } public get sentResponse() { return this.responseSent; } /** * Get remote IP address */ public get remoteIP() : string { return this.req.ip; } /** * Get a parameter included in the post request * * @param name Name of the parameter to get */ protected getPostParam(name : string) : any { return this.req.body[name]; } /** * 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 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; } /** * Save an image in user data directory * * @param postField The name of the POST field * @param folder Target folder in user data directory * @param maxW Maximum width of the image * @param maxH Maximum height of the image */ public async savePostImage(postField: string, folder: string, maxW: number, maxH: number) : Promise { const file = this.getUploadedFile(postField); if(file == undefined) this.error(400, "File '"+postField+"' is missing in the request !"); const targetUserDataFolder = prepareFileCreation(this.getUserId(), folder); const targetFilePath = generateNewUserDataFileName(targetUserDataFolder, "png"); const targetSysPath = pathUserData(targetFilePath, true); // Process image size let img = sharp(file.data); const stats = await img.metadata(); if(stats.width > maxW || stats.height > maxH) { if(stats.width > maxW) img = img.resize(maxW, Math.floor((stats.height*maxW)/stats.width)); else img = img.resize(Math.floor((stats.width*maxH)/stats.height), maxH); } // Rotate the image, if required if(stats.orientation != undefined) { img = img.rotate(/* use EXIF orientation header */) } // Save image await img.png().toFile(targetSysPath); return targetFilePath; } /** * Save the file uploaded by a user * * @param postField The name of the POST field containing the file * @param mime_type Required mime type * @param ext Required file extension * @param folder The target user folder */ public async savePostFile(postField: string, folder: string, ext:string, mime_type: string) : Promise { const file = this.getUploadedFile(postField); if(file == undefined) this.error(400, "File '"+postField+"' is missing in the request !"); if(file.mimetype != mime_type) this.error(400, "Invalid file mime type (required: " + mime_type+")") const targetUserDataFolder = prepareFileCreation(this.getUserId(), folder); const targetFilePath = generateNewUserDataFileName(targetUserDataFolder, ext); const targetSysPath = pathUserData(targetFilePath, true); writeFileSync(targetSysPath, file.data, "binary"); return targetFilePath; } /** * 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 = (conf().force_clients_https ? "https://" : "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; } /** * 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) { if(this.responseSent) return; this.response.status(code).send({ error: { code: code, message: message } }); this.responseSent = true; 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); } }