mirror of
				https://gitlab.com/comunic/comunicapiv2
				synced 2025-11-04 03:24:04 +00:00 
			
		
		
		
	Sign in user
This commit is contained in:
		@@ -1,4 +1,5 @@
 | 
				
			|||||||
import { RequestHandler } from "../entities/RequestHandler";
 | 
					import { RequestHandler } from "../entities/RequestHandler";
 | 
				
			||||||
 | 
					import { AccountHelper } from "../helpers/AccountHelper";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Account controller
 | 
					 * Account controller
 | 
				
			||||||
@@ -13,9 +14,31 @@ export class AccountController {
 | 
				
			|||||||
	 * 
 | 
						 * 
 | 
				
			||||||
	 * @param handler 
 | 
						 * @param handler 
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static LoginUser(handler: RequestHandler) {
 | 
						public static async LoginUser(handler: RequestHandler) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		handler.success("Successful operation.");
 | 
							// Get post data
 | 
				
			||||||
 | 
							const email = handler.postEmail("userMail");
 | 
				
			||||||
 | 
							const password = handler.postString("userPassword");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO : add limits
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Authenticate user
 | 
				
			||||||
 | 
							const tokens = await AccountHelper.LoginUser(email, password, handler.getClientInfo());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(tokens == null) {
 | 
				
			||||||
 | 
								// TODO : add limits
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								handler.error(401, "Invalid e-mail address / password !");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Success
 | 
				
			||||||
 | 
							handler.send({
 | 
				
			||||||
 | 
								success: "User signed in!",
 | 
				
			||||||
 | 
								tokens: {
 | 
				
			||||||
 | 
									token1: tokens.token1,
 | 
				
			||||||
 | 
									token2: tokens.token2
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import { Response, Request } from "express";
 | 
					import { Response, Request } from "express";
 | 
				
			||||||
import { APIHelper } from "../helpers/APIHelper";
 | 
					import { APIHelper } from "../helpers/APIHelper";
 | 
				
			||||||
import { APIClient } from "./APIClient";
 | 
					import { APIClient } from "./APIClient";
 | 
				
			||||||
 | 
					import { checkMail } from "../utils/StringUtils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Response to a request
 | 
					 * Response to a request
 | 
				
			||||||
@@ -31,7 +32,7 @@ export class RequestHandler {
 | 
				
			|||||||
	 * @param required If set to true (true by default), an error will
 | 
						 * @param required If set to true (true by default), an error will
 | 
				
			||||||
	 * be thrown if the string is not included in the request
 | 
						 * be thrown if the string is not included in the request
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public getString(name : string, minLength : number = 1, required : boolean = true) : string {
 | 
						public postString(name : string, minLength : number = 1, required : boolean = true) : string {
 | 
				
			||||||
		const param = this.getPostParam(name);
 | 
							const param = this.getPostParam(name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Check if parameter was not found
 | 
							// Check if parameter was not found
 | 
				
			||||||
@@ -47,6 +48,20 @@ export class RequestHandler {
 | 
				
			|||||||
		return param;
 | 
							return param;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * 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;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Validate API tokens
 | 
						 * Validate API tokens
 | 
				
			||||||
@@ -56,8 +71,8 @@ export class RequestHandler {
 | 
				
			|||||||
	public async checkAPITokens() {
 | 
						public async checkAPITokens() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Extract API name & token from request
 | 
							// Extract API name & token from request
 | 
				
			||||||
		const apiName = this.getString("serviceName");
 | 
							const apiName = this.postString("serviceName");
 | 
				
			||||||
		const apiToken = this.getString("serviceToken");
 | 
							const apiToken = this.postString("serviceToken");
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		// Validate the client
 | 
							// Validate the client
 | 
				
			||||||
		const client = await APIHelper.GetClient(apiName, apiToken);
 | 
							const client = await APIHelper.GetClient(apiName, apiToken);
 | 
				
			||||||
@@ -81,7 +96,18 @@ export class RequestHandler {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Output an error code
 | 
						 * 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 code HTTP Status code
 | 
				
			||||||
	 * @param message The message to send
 | 
						 * @param message The message to send
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										12
									
								
								src/entities/UserLoginTokens.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/entities/UserLoginTokens.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * User tokens
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @author Pierre HUBERT
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface UserLoginTokens {
 | 
				
			||||||
 | 
						userID : number,
 | 
				
			||||||
 | 
						clientID : number,
 | 
				
			||||||
 | 
						token1: string,
 | 
				
			||||||
 | 
						token2: string,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										102
									
								
								src/helpers/AccountHelper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/helpers/AccountHelper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
				
			|||||||
 | 
					import { crypt, sha1, randomStr } from "../utils/CryptUtils";
 | 
				
			||||||
 | 
					import { APIClient } from "../entities/APIClient";
 | 
				
			||||||
 | 
					import { UserLoginTokens } from "../entities/UserLoginTokens";
 | 
				
			||||||
 | 
					import { DatabaseHelper } from "./DatabaseHelper";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Account helper
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @author Pierre HUBERT
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const USER_TABLE = "utilisateurs";
 | 
				
			||||||
 | 
					const USERS_TOKENS_TABLE = "comunic_api_users_tokens";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class AccountHelper {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Given email address and password, try to sign in user
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param email The email of the user
 | 
				
			||||||
 | 
						 * @param password User password
 | 
				
			||||||
 | 
						 * @param client Information about associated client / null if none found
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						static async LoginUser(email: string, password: string, client: APIClient) : Promise<UserLoginTokens | null> {
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Perform a request on the database
 | 
				
			||||||
 | 
							const row = await DatabaseHelper.QueryRow({
 | 
				
			||||||
 | 
								table: USER_TABLE,
 | 
				
			||||||
 | 
								fields: ["ID"],
 | 
				
			||||||
 | 
								where: {
 | 
				
			||||||
 | 
									mail: email,
 | 
				
			||||||
 | 
									password: this.CryptPassword(password)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check if user was found
 | 
				
			||||||
 | 
							if(row == null)
 | 
				
			||||||
 | 
								return null;
 | 
				
			||||||
 | 
							const userID = row.ID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check for existing tokens
 | 
				
			||||||
 | 
							let tokens = await this.GetClientTokens(userID, client);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(tokens != null)
 | 
				
			||||||
 | 
								return tokens;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							const newTokens : UserLoginTokens = {
 | 
				
			||||||
 | 
								userID: userID,
 | 
				
			||||||
 | 
								clientID: client.id,
 | 
				
			||||||
 | 
								token1: randomStr(150),
 | 
				
			||||||
 | 
								token2: "dummy_data"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Save new tokens
 | 
				
			||||||
 | 
							await DatabaseHelper.InsertRow(USERS_TOKENS_TABLE, {
 | 
				
			||||||
 | 
								user_id: newTokens.userID,
 | 
				
			||||||
 | 
								service_id: newTokens.clientID,
 | 
				
			||||||
 | 
								token1: newTokens.token1,
 | 
				
			||||||
 | 
								token2: newTokens.token2
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return newTokens;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Get user client tokens (if it exists)
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param userID Target user ID
 | 
				
			||||||
 | 
						 * @param client Information about associated client / null if none found
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private static async GetClientTokens(userID: number, client: APIClient): Promise<UserLoginTokens | null> {
 | 
				
			||||||
 | 
							const row = await DatabaseHelper.QueryRow({
 | 
				
			||||||
 | 
								table: USERS_TOKENS_TABLE,
 | 
				
			||||||
 | 
								where: {
 | 
				
			||||||
 | 
									user_id: userID,
 | 
				
			||||||
 | 
									service_id: client.id
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return row == null ? null : this.DBToUserTokens(row);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Crypt a password
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param pass The password to crypt
 | 
				
			||||||
 | 
						 * @return Encrypted string
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private static CryptPassword(pass: string) : string {
 | 
				
			||||||
 | 
							return crypt(sha1(pass), sha1(pass));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static DBToUserTokens(row : any) : UserLoginTokens {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								userID: row.user_id,
 | 
				
			||||||
 | 
								clientID: row.service_id,
 | 
				
			||||||
 | 
								token1: row.token1,
 | 
				
			||||||
 | 
								token2: row.token2
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -9,6 +9,7 @@ import { conf } from "./ConfigHelper";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export interface QueryInformation {
 | 
					export interface QueryInformation {
 | 
				
			||||||
	table: string,
 | 
						table: string,
 | 
				
			||||||
 | 
						fields ?: Array<String>,
 | 
				
			||||||
	where ?: Object,
 | 
						where ?: Object,
 | 
				
			||||||
	limit ?: number,
 | 
						limit ?: number,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -51,7 +52,12 @@ export class DatabaseHelper {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	static async Query(info: QueryInformation) : Promise<Array<any>> {
 | 
						static async Query(info: QueryInformation) : Promise<Array<any>> {
 | 
				
			||||||
		// Prepare SQL request
 | 
							// Prepare SQL request
 | 
				
			||||||
		let request = "SELECT * FROM " + info.table;
 | 
							let request = "SELECT ";
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Requested fields
 | 
				
			||||||
 | 
							request += info.fields ? info.fields.join(",") : "*";
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							request += " FROM " + info.table;
 | 
				
			||||||
		let args = [];
 | 
							let args = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Add where arguments
 | 
							// Add where arguments
 | 
				
			||||||
@@ -61,7 +67,7 @@ export class DatabaseHelper {
 | 
				
			|||||||
			for(const k in info.where) {
 | 
								for(const k in info.where) {
 | 
				
			||||||
				if(!info.where.hasOwnProperty(k))
 | 
									if(!info.where.hasOwnProperty(k))
 | 
				
			||||||
					continue;
 | 
										continue;
 | 
				
			||||||
				const v = info.where[k];
 | 
									const v = info.where[k].toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				request += k;
 | 
									request += k;
 | 
				
			||||||
				request += v.startsWith("%") || v.endsWith("%") ? " LIKE " : " = "
 | 
									request += v.startsWith("%") || v.endsWith("%") ? " LIKE " : " = "
 | 
				
			||||||
@@ -107,4 +113,21 @@ export class DatabaseHelper {
 | 
				
			|||||||
		
 | 
							
 | 
				
			||||||
		return result[0];
 | 
							return result[0];
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Insert an new entry into the database
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param info Information about the entry
 | 
				
			||||||
 | 
						 * @returns The ID of the inserted column (if any)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						static async InsertRow(table : string, values : any) : Promise<number> {
 | 
				
			||||||
 | 
							return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
								this.connection.query("INSERT INTO " + table + " SET ?", values, (err, results, fields) => {
 | 
				
			||||||
 | 
									if(err)
 | 
				
			||||||
 | 
										reject(err);
 | 
				
			||||||
 | 
									
 | 
				
			||||||
 | 
									resolve(results.insertId);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -28,3 +28,28 @@ export function sha1(str : string) : string {
 | 
				
			|||||||
export function crypt(str : string, salt: string) : string {
 | 
					export function crypt(str : string, salt: string) : string {
 | 
				
			||||||
	return execSync("/usr/bin/php -r \"echo crypt(\'"+str+"\', \'"+salt+"\');\"").toString();
 | 
						return execSync("/usr/bin/php -r \"echo crypt(\'"+str+"\', \'"+salt+"\');\"").toString();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Generate a random integer
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param max Maximum value to except
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function randInt(max: number) : number {
 | 
				
			||||||
 | 
						return Math.floor(Math.random() * Math.floor(max));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Generate a random string
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param length The length of the string to generate
 | 
				
			||||||
 | 
					 * @param keyspace Keyspace to use
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function randomStr(length: number, keyspace : string = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") : string {
 | 
				
			||||||
 | 
						let str = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (let i = 0; i < length; i++) {
 | 
				
			||||||
 | 
							str += keyspace[randInt(keyspace.length)];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return str;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/utils/StringUtils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/utils/StringUtils.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * String utilities
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @author Pierre HUBERT
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Check a given email address
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param {String} emailAddress The email address to check
 | 
				
			||||||
 | 
					 * @return {Boolean} True for a valid email address / false else
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function checkMail(emailAddress: string): boolean {
 | 
				
			||||||
 | 
						return (emailAddress.match(/^[a-zA-Z0-9_.]+@[a-zA-Z0-9-.]{1,}[.][a-zA-Z]{2,8}$/) === null ? false : true);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user