From f6a7415884fe2d4dda51999a41b27fedd4536bf9 Mon Sep 17 00:00:00 2001 From: Yanis Rigaudeau Date: Sat, 15 Oct 2022 22:04:27 +0200 Subject: [PATCH] error handler + login --- .gitignore | 1 + api/api.service | 1 + api/package-lock.json | 45 +++++++++++++++++++++++++ api/package.json | 5 +-- api/src/app.ts | 26 +++++++++----- api/src/config.ts | 21 ++++++++++++ api/src/entities/user.ts | 14 ++++---- api/src/framework/express/middleware.ts | 10 +++++- api/src/framework/express/server.ts | 13 ++++--- api/src/framework/express/user.ts | 23 ++++++++----- api/src/framework/mongo/user.ts | 33 +++++++++++++++++- api/src/functions/logger.ts | 11 ++++++ api/src/functions/password.ts | 5 +++ api/src/functions/user.ts | 23 +++++++++---- core/src/user.ts | 9 +++-- 15 files changed, 197 insertions(+), 43 deletions(-) create mode 100644 api/src/config.ts create mode 100644 api/src/functions/logger.ts create mode 100644 api/src/functions/password.ts diff --git a/.gitignore b/.gitignore index 479883d..7b1c6ee 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ **/node_modules **/public/build **/dist +**/config !.prettierrc.json \ No newline at end of file diff --git a/api/api.service b/api/api.service index c35f75b..a8f853c 100644 --- a/api/api.service +++ b/api/api.service @@ -9,6 +9,7 @@ Restart=on-failure User=node Group=node +Environment="CONFIGFILE=/home/node/config/config.json" WorkingDirectory=/home/node/app ExecStart=/usr/bin/node /home/node/app/dist/app.js diff --git a/api/package-lock.json b/api/package-lock.json index 77167d8..edbb758 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -11,6 +11,7 @@ "cors": "^2.8.5", "express": "^4.18.1", "express-session": "^1.17.3", + "express-validator": "^6.14.2", "ip": "^1.1.8", "mongodb": "^4.10.0", "uuid": "^9.0.0" @@ -636,6 +637,18 @@ "node": ">= 0.6" } }, + "node_modules/express-validator": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.14.2.tgz", + "integrity": "sha512-8XfAUrQ6Y7dIIuy9KcUPCfG/uCbvREctrxf5EeeME+ulanJ4iiW71lWmm9r4YcKKYOCBMan0WpVg7FtHu4Z4Wg==", + "dependencies": { + "lodash": "^4.17.21", + "validator": "^13.7.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -900,6 +913,11 @@ "node": ">=0.12.0" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -1629,6 +1647,14 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/validator": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2175,6 +2201,15 @@ } } }, + "express-validator": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.14.2.tgz", + "integrity": "sha512-8XfAUrQ6Y7dIIuy9KcUPCfG/uCbvREctrxf5EeeME+ulanJ4iiW71lWmm9r4YcKKYOCBMan0WpVg7FtHu4Z4Wg==", + "requires": { + "lodash": "^4.17.21", + "validator": "^13.7.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2361,6 +2396,11 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2866,6 +2906,11 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "validator": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/api/package.json b/api/package.json index 08a8dcc..3773981 100644 --- a/api/package.json +++ b/api/package.json @@ -6,8 +6,8 @@ "private": true, "scripts": { "build": "tsc", - "dev": "ts-node-dev --respawn --transpile-only ./src/app.ts", - "start": "npm run build && node ./dist/app.js", + "dev": "CONFIGFILE=config/config.json ts-node-dev --respawn --transpile-only ./src/app.ts", + "start": "npm run build && CONFIGFILE=config/config.json node ./dist/app.js", "prettier": "prettier -w ./src" }, "devDependencies": { @@ -25,6 +25,7 @@ "cors": "^2.8.5", "express": "^4.18.1", "express-session": "^1.17.3", + "express-validator": "^6.14.2", "ip": "^1.1.8", "mongodb": "^4.10.0", "uuid": "^9.0.0" diff --git a/api/src/app.ts b/api/src/app.ts index 9e05094..b260673 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -2,26 +2,34 @@ import Server from './framework/express/server'; import ip from 'ip'; import UserModel from './framework/mongo/user'; import { MongoClient } from 'mongodb'; - -const PORT = 8000; -const MONGOURI = 'mongodb://localhost:27017'; -const DBNAME = 'dev'; +import { Config } from './config'; +import { exit, env } from 'process'; export type Services = { userModel: UserModel; }; -const mongo = new MongoClient(MONGOURI); -const db = mongo.db(DBNAME); +const configFile = env.CONFIGFILE; +if (configFile === undefined) { + console.log('env var CONFIGFILE not set'); + exit(1); +} + +const config = new Config(configFile); + +const mongo = new MongoClient(config.mongo.uri); +const db = mongo.db(config.mongo.dbName); const services: Services = { userModel: new UserModel(db), }; -const server = new Server(services); +const server = new Server(config.server, services); -server.start(PORT, () => +server.start(() => console.log( - `Running on http://127.0.0.1:${PORT} http://${ip.address()}:${PORT}`, + `Running on http://127.0.0.1:${config.server.port} http://${ip.address()}:${ + config.server.port + }`, ), ); diff --git a/api/src/config.ts b/api/src/config.ts new file mode 100644 index 0000000..af48e6c --- /dev/null +++ b/api/src/config.ts @@ -0,0 +1,21 @@ +import { readFileSync } from 'fs'; + +export type ServerConfig = { + port: number; +}; + +export type MongoConfig = { + uri: string; + dbName: string; +}; + +export class Config { + server: ServerConfig; + mongo: MongoConfig; + + constructor(configFile: string) { + const config = JSON.parse(readFileSync(configFile).toString()) as Config; + this.server = config.server; + this.mongo = config.mongo; + } +} diff --git a/api/src/entities/user.ts b/api/src/entities/user.ts index 473d8e2..d07fed1 100644 --- a/api/src/entities/user.ts +++ b/api/src/entities/user.ts @@ -4,22 +4,22 @@ import { UserInfoWithPassword, UserWithPasswordCtor, } from '@core'; -import { createHash } from 'crypto'; +import { generateHash } from '../functions/password'; import { Entity } from './entity'; export class User extends Entity implements UserInfo { - name: string; + username: string; constructor(raw: UserCtor) { super(raw); - this.name = raw.name ? raw.name : ''; + this.username = raw.username ? raw.username : ''; } Info(): UserInfo { return { uuid: this.uuid, - name: this.name, + username: this.username, }; } } @@ -30,15 +30,13 @@ export class UserWithPassword extends User implements UserInfoWithPassword { constructor(raw: UserWithPasswordCtor) { super(raw); - this.password = createHash('sha256') - .update(`${this.uuid}+${raw.password}`) - .digest('hex'); + this.password = generateHash(this.uuid, raw.password || ''); } Info(): UserInfoWithPassword { return { uuid: this.uuid, - name: this.name, + username: this.username, password: this.password, }; } diff --git a/api/src/framework/express/middleware.ts b/api/src/framework/express/middleware.ts index 3bc9f66..8a21827 100644 --- a/api/src/framework/express/middleware.ts +++ b/api/src/framework/express/middleware.ts @@ -1,4 +1,4 @@ -import { Request, RequestHandler } from 'express'; +import { ErrorRequestHandler, Request, RequestHandler } from 'express'; import { randomUUID } from 'crypto'; export function getId(req: Request): string { @@ -11,3 +11,11 @@ export function BeforeEach(): RequestHandler { next(); }; } + +export function ErrorHandler(): ErrorRequestHandler { + return (error, req, res, next) => { + error.status + ? res.status(error.status).send(error) + : res.status(500).send(error); + }; +} diff --git a/api/src/framework/express/server.ts b/api/src/framework/express/server.ts index b193a00..24eb2c4 100644 --- a/api/src/framework/express/server.ts +++ b/api/src/framework/express/server.ts @@ -1,22 +1,27 @@ import express, { Express } from 'express'; import cors from 'cors'; import * as router from './router'; -import { BeforeEach } from './middleware'; +import { BeforeEach, ErrorHandler } from './middleware'; import { Services } from '../../app'; +import { ServerConfig } from '../../config'; class Server { private app: Express; + private config: ServerConfig; + + constructor(config: ServerConfig, services: Services) { + this.config = config; - constructor(services: Services) { this.app = express(); this.app.use(express.json()); this.app.use(cors()); this.app.use(BeforeEach()); this.app.use(router.getRoutes(services)); + this.app.use(ErrorHandler()); } - start(port: number, func: () => void): void { - this.app.listen(port, func); + start(func: () => void): void { + this.app.listen(this.config.port, func); } } diff --git a/api/src/framework/express/user.ts b/api/src/framework/express/user.ts index 0073a97..9898c95 100644 --- a/api/src/framework/express/user.ts +++ b/api/src/framework/express/user.ts @@ -1,31 +1,36 @@ import { RequestHandler, Router } from 'express'; import { Services } from '../../app'; -import { Create, Read } from '../../functions/user'; +import { Create, Read, Login } from '../../functions/user'; import { getId } from './middleware'; export function LoginHandler(services: Services): RequestHandler { - return async (req, res) => { - return res.send('Hey!\n'); + const login = Login(services); + + return async (req, res, next) => { + const user = await login(getId(req), req.body); + user + ? res.status(200).send(user) + : next({ status: 404, message: 'bad user or password' }); }; } export function CreateHandler(services: Services): RequestHandler { const createUser = Create(services); - return async (req, res) => { + return async (req, res, next) => { const user = await createUser(getId(req), req.body); - - res.send(user); + user ? res.status(201).send(user) : next(); }; } export function ReadHandler(services: Services): RequestHandler { const readUser = Read(services); - return async (req, res) => { + return async (req, res, next) => { const user = await readUser(getId(req), req.params.uuid); - - res.send(user); + user + ? res.status(200).send(user) + : next({ status: 404, message: 'user not found' }); }; } diff --git a/api/src/framework/mongo/user.ts b/api/src/framework/mongo/user.ts index 8780f60..7109aad 100644 --- a/api/src/framework/mongo/user.ts +++ b/api/src/framework/mongo/user.ts @@ -1,5 +1,8 @@ import { Collection, Db } from 'mongodb'; +import { LoginUserBody } from '../../../../core/src/user'; import { User, UserWithPassword } from '../../entities/user'; +import log from '../../functions/logger'; +import { generateHash } from '../../functions/password'; class UserModel { private collection: Collection; @@ -8,14 +11,42 @@ class UserModel { this.collection = db.collection('users'); } + public async login(tracker: string, data: LoginUserBody) { + const checkUser = await this.collection.findOne({ + username: data.username, + }); + if (checkUser === null) { + log(tracker, 'User Not Found'); + return null; + } + + const user = await this.collection.findOne({ + uuid: checkUser.uuid, + password: generateHash(checkUser.uuid, data.password), + }); + if (user === null) { + log(tracker, 'Wrong Password'); + return null; + } + + log(tracker, 'LOG IN', user); + return new User(user); + } + public async create(tracker: string, user: UserWithPassword) { await this.collection.insertOne(user); + log(tracker, 'CREATE USER', user); return this.read(tracker, user.uuid); } public async read(tracker: string, uuid: string) { const user = await this.collection.findOne({ uuid }); - return new User(user || { name: 'not found' }); + if (user === null) { + log(tracker, 'User Not Found'); + return null; + } + log(tracker, 'READ USER', user); + return new User(user); } } diff --git a/api/src/functions/logger.ts b/api/src/functions/logger.ts new file mode 100644 index 0000000..26faa55 --- /dev/null +++ b/api/src/functions/logger.ts @@ -0,0 +1,11 @@ +export function log(tracker: string, ...message: unknown[]) { + message.forEach((obj, index) => { + try { + message[index] = JSON.stringify(obj); + } catch {} + }); + + tracker ? console.log(`[${tracker}]`, ...message) : console.log(...message); +} + +export default log; diff --git a/api/src/functions/password.ts b/api/src/functions/password.ts new file mode 100644 index 0000000..5a3ac14 --- /dev/null +++ b/api/src/functions/password.ts @@ -0,0 +1,5 @@ +import { createHash } from 'crypto'; + +export function generateHash(...values: string[]) { + return createHash('sha256').update(values.join('')).digest('hex'); +} diff --git a/api/src/functions/user.ts b/api/src/functions/user.ts index fbee4ae..92cbe74 100644 --- a/api/src/functions/user.ts +++ b/api/src/functions/user.ts @@ -1,25 +1,36 @@ -import { CreateUserBody, UserInfo } from '@core'; +import { CreateUserBody, LoginUserBody, UserInfo } from '@core'; import { Services } from '../app'; -import { User, UserWithPassword } from '../entities/user'; +import { UserWithPassword } from '../entities/user'; + +export function Login( + services: Services, +): (tracker: string, raw: LoginUserBody) => Promise { + const { userModel } = services; + + return async (tracker, raw) => { + const user = await userModel.login(tracker, raw); + return user ? user.Info() : null; + }; +} export function Create( services: Services, -): (tracker: string, raw: CreateUserBody) => Promise { +): (tracker: string, raw: CreateUserBody) => Promise { const { userModel } = services; return async (tracker, raw) => { const user = await userModel.create(tracker, new UserWithPassword(raw)); - return user.Info(); + return user ? user.Info() : null; }; } export function Read( services: Services, -): (tracker: string, uuid: string) => Promise { +): (tracker: string, uuid: string) => Promise { const { userModel } = services; return async (tracker, uuid) => { const user = await userModel.read(tracker, uuid); - return user.Info(); + return user ? user.Info() : null; }; } diff --git a/core/src/user.ts b/core/src/user.ts index 34ddf82..f3c27e8 100644 --- a/core/src/user.ts +++ b/core/src/user.ts @@ -2,7 +2,7 @@ import { EntityInfo } from './entity'; export type UserInfo = { uuid: string; - name: string; + username: string; }; export type UserInfoWithPassword = { @@ -14,11 +14,14 @@ export type User = UserInfo & EntityInfo; export type UserWithPassword = UserInfoWithPassword & EntityInfo; export type CreateUserBody = { - name: string; + username: string; password: string; }; -export type LoginUserBody = CreateUserBody; +export type LoginUserBody = { + username: string; + password: string; +}; export type UserCtor = Partial;