mirror of
https://gitlab.com/comunic/comunicapiv2
synced 2024-11-22 13:29:22 +00:00
Sign in user
This commit is contained in:
parent
89e612900c
commit
293752b0f4
@ -1,4 +1,5 @@
|
||||
import { RequestHandler } from "../entities/RequestHandler";
|
||||
import { AccountHelper } from "../helpers/AccountHelper";
|
||||
|
||||
/**
|
||||
* Account controller
|
||||
@ -13,9 +14,31 @@ export class AccountController {
|
||||
*
|
||||
* @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 { APIHelper } from "../helpers/APIHelper";
|
||||
import { APIClient } from "./APIClient";
|
||||
import { checkMail } from "../utils/StringUtils";
|
||||
|
||||
/**
|
||||
* Response to a request
|
||||
@ -31,7 +32,7 @@ export class RequestHandler {
|
||||
* @param required If set to true (true by default), an error will
|
||||
* 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);
|
||||
|
||||
// Check if parameter was not found
|
||||
@ -47,6 +48,20 @@ export class RequestHandler {
|
||||
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
|
||||
@ -56,8 +71,8 @@ export class RequestHandler {
|
||||
public async checkAPITokens() {
|
||||
|
||||
// Extract API name & token from request
|
||||
const apiName = this.getString("serviceName");
|
||||
const apiToken = this.getString("serviceToken");
|
||||
const apiName = this.postString("serviceName");
|
||||
const apiToken = this.postString("serviceToken");
|
||||
|
||||
// Validate the client
|
||||
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 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 {
|
||||
table: string,
|
||||
fields ?: Array<String>,
|
||||
where ?: Object,
|
||||
limit ?: number,
|
||||
}
|
||||
@ -51,7 +52,12 @@ export class DatabaseHelper {
|
||||
*/
|
||||
static async Query(info: QueryInformation) : Promise<Array<any>> {
|
||||
// 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 = [];
|
||||
|
||||
// Add where arguments
|
||||
@ -61,7 +67,7 @@ export class DatabaseHelper {
|
||||
for(const k in info.where) {
|
||||
if(!info.where.hasOwnProperty(k))
|
||||
continue;
|
||||
const v = info.where[k];
|
||||
const v = info.where[k].toString();
|
||||
|
||||
request += k;
|
||||
request += v.startsWith("%") || v.endsWith("%") ? " LIKE " : " = "
|
||||
@ -107,4 +113,21 @@ export class DatabaseHelper {
|
||||
|
||||
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 {
|
||||
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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user