update + schema validate
This commit is contained in:
parent
5c21712344
commit
0bb28ce89a
@ -1,19 +1,19 @@
|
||||
import './paths';
|
||||
import Server from './framework/express/server';
|
||||
import ip from 'ip';
|
||||
import UserModel from './framework/mongo/user';
|
||||
import { MongoClient } from 'mongodb';
|
||||
import ip from 'ip';
|
||||
|
||||
import Server from './framework/express/server';
|
||||
import UserModel from './framework/mongo/user';
|
||||
import { Config } from './config';
|
||||
import { exit, env } from 'process';
|
||||
|
||||
export type Services = {
|
||||
userModel: UserModel;
|
||||
};
|
||||
|
||||
const configFile = env.CONFIGFILE;
|
||||
const configFile = process.env.CONFIGFILE;
|
||||
if (configFile === undefined) {
|
||||
console.log('env var CONFIGFILE not set');
|
||||
exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config = new Config(configFile);
|
||||
@ -27,6 +27,13 @@ const services: Services = {
|
||||
|
||||
const server = new Server(config.server, config.mongo, services);
|
||||
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('\nClosing Api...');
|
||||
await mongo.close();
|
||||
await server.close();
|
||||
process.exit();
|
||||
});
|
||||
|
||||
server.start(() =>
|
||||
console.log(
|
||||
`Running on http://127.0.0.1:${config.server.port} http://${ip.address()}:${
|
||||
|
@ -16,6 +16,15 @@ export class Config {
|
||||
|
||||
constructor(configFile: string) {
|
||||
const config = JSON.parse(readFileSync(configFile).toString()) as Config;
|
||||
if (
|
||||
config.mongo.dbName === undefined ||
|
||||
config.mongo.uri === undefined ||
|
||||
config.server.origin === undefined ||
|
||||
config.server.port === undefined
|
||||
) {
|
||||
console.log('Error in Config File');
|
||||
process.exit(1);
|
||||
}
|
||||
this.server = config.server;
|
||||
this.mongo = config.mongo;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
UserRoles,
|
||||
UserWithPasswordCtor,
|
||||
} from '@core';
|
||||
import { generateHash } from '../functions/password';
|
||||
import { getHashedPassword } from '../functions/password';
|
||||
import { Entity } from './entity';
|
||||
|
||||
export class User extends Entity implements UserInfo {
|
||||
@ -34,7 +34,7 @@ export class UserWithPassword extends User implements UserInfoWithPassword {
|
||||
constructor(raw: UserWithPasswordCtor) {
|
||||
super(raw);
|
||||
|
||||
this.password = generateHash(this.uuid, raw.password || '');
|
||||
this.password = getHashedPassword(this.uuid, raw.password || '');
|
||||
}
|
||||
|
||||
Info(): UserInfoWithPassword {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ErrorRequestHandler, Request, RequestHandler } from 'express';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { validationResult } from 'express-validator';
|
||||
import { validationResult, matchedData } from 'express-validator';
|
||||
import { UserInfo, UserRoles } from '@core';
|
||||
|
||||
declare module 'express-session' {
|
||||
@ -10,22 +10,21 @@ declare module 'express-session' {
|
||||
}
|
||||
|
||||
export function getRequestId(req: Request): string {
|
||||
return req.header('request-id') || 'unknown';
|
||||
return req.header('x-request-id') || 'unknown';
|
||||
}
|
||||
|
||||
export function RequestId(): RequestHandler {
|
||||
return (req, res, next) => {
|
||||
req.headers['request-id'] = randomUUID();
|
||||
req.headers['x-request-id'] = randomUUID();
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
export function CheckPermissions(): RequestHandler {
|
||||
function getResourceId(req: Request): string | null {
|
||||
if (req.method === 'GET' && req.params.uuid) return req.params.uuid;
|
||||
if ((req.method === 'POST' || req.method === 'PUT') && req.body.uuid)
|
||||
return req.body.uuid;
|
||||
return null;
|
||||
function getResourceId(req: Request): string | undefined {
|
||||
if (req.params.uuid) return req.params.uuid;
|
||||
if (req.body.uuid) return req.body.uuid;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function canAccessRessource(user: UserInfo, uuid: string): boolean {
|
||||
@ -55,15 +54,19 @@ export function CheckPermissions(): RequestHandler {
|
||||
};
|
||||
}
|
||||
|
||||
export function SchemaValidator(keys: number = 0): RequestHandler {
|
||||
export function ValidateSchema(): RequestHandler {
|
||||
return (req, res, next) => {
|
||||
if (Object.keys(req.body).length > keys)
|
||||
const error = validationResult(req);
|
||||
|
||||
const oldBody = req.body;
|
||||
req.body = matchedData(req, { locations: ['body'] });
|
||||
|
||||
if (JSON.stringify(oldBody) !== JSON.stringify(req.body))
|
||||
return next({
|
||||
status: 400,
|
||||
message: `Found ${Object.keys(req.body).length} keys expected ${keys}`,
|
||||
status: 422,
|
||||
message: 'Unprocessable Entity',
|
||||
});
|
||||
|
||||
const error = validationResult(req);
|
||||
error.isEmpty()
|
||||
? next()
|
||||
: next({
|
||||
|
@ -36,3 +36,21 @@ export const CreateUserSchema = () =>
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const UpdateUserSchema = () =>
|
||||
checkSchema({
|
||||
username: {
|
||||
isString: true,
|
||||
optional: true,
|
||||
},
|
||||
password: {
|
||||
isString: true,
|
||||
optional: true,
|
||||
},
|
||||
role: {
|
||||
isIn: {
|
||||
options: [Object.values(UserRoles)],
|
||||
},
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
@ -1,15 +1,18 @@
|
||||
import express, { Express } from 'express';
|
||||
import cors from 'cors';
|
||||
import session from 'express-session';
|
||||
import MongoStore from 'connect-mongo';
|
||||
import { randomUUID } from 'crypto';
|
||||
import * as http from 'http';
|
||||
|
||||
import { Routes } from './router';
|
||||
import { RequestId, ErrorHandler } from './middleware';
|
||||
import { Services } from '../../app';
|
||||
import { MongoConfig, ServerConfig } from '../../config';
|
||||
import { randomUUID } from 'crypto';
|
||||
import MongoStore from 'connect-mongo';
|
||||
|
||||
class Server {
|
||||
private app: Express;
|
||||
private server: http.Server;
|
||||
private config: ServerConfig;
|
||||
|
||||
constructor(
|
||||
@ -54,7 +57,16 @@ class Server {
|
||||
}
|
||||
|
||||
start(func: () => void): void {
|
||||
this.app.listen(this.config.port, func);
|
||||
this.server = this.app.listen(this.config.port, func);
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.server.close((error) => {
|
||||
if (error) reject(error);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,19 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
import { Services } from '../../app';
|
||||
import { CreateUser, ReadUser, LoginUser } from '../../functions/user';
|
||||
import {
|
||||
CreateUser,
|
||||
ReadUser,
|
||||
LoginUser,
|
||||
UpdateUser,
|
||||
} from '../../functions/user';
|
||||
import {
|
||||
LoginUserSchema,
|
||||
CreateUserSchema,
|
||||
ReadUserSchema,
|
||||
LogoutUserSchema,
|
||||
UpdateUserSchema,
|
||||
} from './schema/user';
|
||||
import { CheckPermissions, getRequestId, SchemaValidator } from './middleware';
|
||||
import { CheckPermissions, getRequestId, ValidateSchema } from './middleware';
|
||||
|
||||
function LoginHandler(services: Services): RequestHandler {
|
||||
const login = LoginUser(services);
|
||||
@ -60,26 +66,43 @@ function ReadHandler(services: Services): RequestHandler {
|
||||
};
|
||||
}
|
||||
|
||||
function UpdateHandler(services: Services): RequestHandler {
|
||||
const updateUser = UpdateUser(services);
|
||||
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const user = await updateUser(
|
||||
getRequestId(req),
|
||||
req.params.uuid,
|
||||
req.body,
|
||||
);
|
||||
res.status(200).send(user);
|
||||
} catch (error) {
|
||||
next({ status: 404, message: 'User Not Found' });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function Routes(services: Services) {
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
'/login',
|
||||
LoginUserSchema(),
|
||||
SchemaValidator(2),
|
||||
ValidateSchema(),
|
||||
LoginHandler(services),
|
||||
);
|
||||
router.post(
|
||||
'/logout',
|
||||
LogoutUserSchema(),
|
||||
SchemaValidator(),
|
||||
ValidateSchema(),
|
||||
LogoutHandler(services),
|
||||
);
|
||||
router.get(
|
||||
'/read/:uuid',
|
||||
CheckPermissions(),
|
||||
ReadUserSchema(),
|
||||
SchemaValidator(),
|
||||
ValidateSchema(),
|
||||
ReadHandler(services),
|
||||
);
|
||||
|
||||
@ -87,9 +110,17 @@ export function Routes(services: Services) {
|
||||
'/create',
|
||||
CheckPermissions(),
|
||||
CreateUserSchema(),
|
||||
SchemaValidator(3),
|
||||
ValidateSchema(),
|
||||
CreateHandler(services),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/update/:uuid',
|
||||
CheckPermissions(),
|
||||
UpdateUserSchema(),
|
||||
ValidateSchema(),
|
||||
UpdateHandler(services),
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Collection, Db } from 'mongodb';
|
||||
import { LoginUserBody, UserInfo } from '@core';
|
||||
import { LoginUserBody, UpdateUserBody } from '@core';
|
||||
import { User, UserWithPassword } from '../../entities/user';
|
||||
import log from '../../functions/logger';
|
||||
import { generateHash } from '../../functions/password';
|
||||
import { log } from '../../functions/logger';
|
||||
import { getHashedPassword } from '../../functions/password';
|
||||
|
||||
class UserModel {
|
||||
private collection: Collection<User>;
|
||||
@ -22,7 +22,7 @@ class UserModel {
|
||||
|
||||
const userDocument = await this.collection.findOne({
|
||||
uuid: checkUser.uuid,
|
||||
password: generateHash(checkUser.uuid, data.password),
|
||||
password: getHashedPassword(checkUser.uuid, data.password),
|
||||
});
|
||||
if (!userDocument) {
|
||||
log(tracker, 'Wrong Password');
|
||||
@ -31,7 +31,7 @@ class UserModel {
|
||||
|
||||
const user = new User(userDocument);
|
||||
log(tracker, 'LOG IN', user);
|
||||
return new User(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
public async create(
|
||||
@ -64,6 +64,35 @@ class UserModel {
|
||||
log(tracker, 'READ USER', user);
|
||||
return user;
|
||||
}
|
||||
|
||||
public async update(
|
||||
tracker: string,
|
||||
uuid: string,
|
||||
userInfo: UpdateUserBody,
|
||||
): Promise<User> {
|
||||
const checkUser = await this.collection.findOne({ uuid });
|
||||
if (!checkUser) {
|
||||
log(tracker, 'User Does Not Exist');
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if (userInfo.password)
|
||||
userInfo.password = getHashedPassword(uuid, userInfo.password);
|
||||
|
||||
await this.collection.updateOne(
|
||||
{ uuid },
|
||||
{
|
||||
$set: {
|
||||
...userInfo,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const user = await this.read(tracker, uuid);
|
||||
log(tracker, 'UPDATE USER', user);
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
export default UserModel;
|
||||
|
@ -6,7 +6,5 @@ export function log(tracker: string, ...message: unknown[]) {
|
||||
} catch {}
|
||||
});
|
||||
|
||||
message ? console.log(`[${tracker}]`, ...message) : console.log(...tracker);
|
||||
message ? console.log(`[${tracker}]`, ...message) : console.log(tracker);
|
||||
}
|
||||
|
||||
export default log;
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { createHash } from 'crypto';
|
||||
|
||||
export function getHashedPassword(uuid: string, password: string) {
|
||||
return generateHash(uuid, password);
|
||||
}
|
||||
|
||||
export function generateHash(...values: string[]) {
|
||||
return createHash('sha256').update(values.join('')).digest('hex');
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CreateUserBody, LoginUserBody, UserInfo } from '@core';
|
||||
import { CreateUserBody, LoginUserBody, UserInfo, UpdateUserBody } from '@core';
|
||||
import { Services } from '../app';
|
||||
import { UserWithPassword } from '../entities/user';
|
||||
|
||||
@ -34,3 +34,14 @@ export function ReadUser(
|
||||
return user.Info();
|
||||
};
|
||||
}
|
||||
|
||||
export function UpdateUser(
|
||||
services: Services,
|
||||
): (tracker: string, uuid: string, raw: UpdateUserBody) => Promise<UserInfo> {
|
||||
const { userModel } = services;
|
||||
|
||||
return async (tracker, uuid, raw) => {
|
||||
const user = await userModel.update(tracker, uuid, raw);
|
||||
return user.Info();
|
||||
};
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ export type CreateUserBody = {
|
||||
role: keyof typeof UserRoles;
|
||||
};
|
||||
|
||||
export type UpdateUserBody = Partial<Omit<UserInfoWithPassword, 'uuid'>>;
|
||||
|
||||
export type LoginUserBody = {
|
||||
username: string;
|
||||
password: string;
|
||||
|
Loading…
x
Reference in New Issue
Block a user