mirror of
https://gitlab.com/comunic/comunicapiv2
synced 2024-11-22 21:39:22 +00:00
391 lines
9.5 KiB
TypeScript
391 lines
9.5 KiB
TypeScript
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";
|
|
import { prepareFileCreation, generateNewUserDataFileName, pathUserData } from "../utils/UserDataUtils";
|
|
import * as sharp from 'sharp';
|
|
import { UserHelper } from "../helpers/UserHelper";
|
|
|
|
/**
|
|
* 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<number> {
|
|
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<number> {
|
|
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
|
|
* @param fallback Fallback value to use if the value is not
|
|
* found in the request
|
|
*/
|
|
public postBool(name: string, fallback ?: boolean) : boolean {
|
|
const param = this.getPostParam(name);
|
|
|
|
if(param == undefined) {
|
|
if(fallback != undefined)
|
|
return fallback;
|
|
|
|
this.error(400, "Missing boolean '" + name + "' in the request!");
|
|
}
|
|
|
|
return param === "true" || param === true;
|
|
}
|
|
|
|
/**
|
|
* Get the ID of a user specified in a POST request
|
|
*
|
|
* @param name Name of the POST field
|
|
*/
|
|
public async postUserId(name: string) : Promise<number> {
|
|
const userID = this.postInt(name);
|
|
|
|
if(userID < 1)
|
|
this.error(400, "Invalid user ID specified in '" + name +"'!");
|
|
|
|
if(!await UserHelper.Exists(userID))
|
|
this.error(404, "User with ID " + userID + " not found!");
|
|
|
|
return userID;
|
|
}
|
|
|
|
/**
|
|
* 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 <UploadedFile|undefined>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<string> {
|
|
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);
|
|
}
|
|
|
|
// Save image
|
|
await img.png().toFile(targetSysPath);
|
|
|
|
|
|
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 = "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);
|
|
}
|
|
} |