1
0
mirror of https://gitlab.com/comunic/comunicapiv2 synced 2024-11-26 07:19:23 +00:00

Sign in user

This commit is contained in:
Pierre HUBERT 2019-11-23 13:16:28 +01:00
parent 89e612900c
commit 293752b0f4
7 changed files with 234 additions and 8 deletions

View File

@ -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
}
});
} }
} }

View File

@ -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

View File

@ -0,0 +1,12 @@
/**
* User tokens
*
* @author Pierre HUBERT
*/
export interface UserLoginTokens {
userID : number,
clientID : number,
token1: string,
token2: string,
}

View 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
};
}
}

View File

@ -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);
});
});
}
} }

View File

@ -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
View 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);
}