Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add authentication system #4

Merged
merged 99 commits into from Apr 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
8500373
fix: this context in controller instance undefined
riflowth Apr 12, 2022
43c74cb
feat: add cookie management
riflowth Apr 12, 2022
940ee33
feat: add sidebar
Porping Apr 12, 2022
24d2742
feat:add searchbar
Porping Apr 12, 2022
fceede6
feat: fix searchbar
Porping Apr 12, 2022
336ab9f
Merge branch 'main' into frontend
Porping Apr 12, 2022
cf64b61
feat:finish searchbar ♥
Porping Apr 12, 2022
409d4fe
feat:add layout home page , TwobarChart, AreaChart
Porping Apr 12, 2022
7505bbf
refactor: cookie implementation and document
riflowth Apr 12, 2022
64d8ce4
feat:add headless ui and add responesive
Porping Apr 12, 2022
88f6867
feat:fix MachineLog error
Porping Apr 12, 2022
b412eb1
feat:fix case bug
Porping Apr 12, 2022
53d02bd
feat:delete MachineLog and and Progressing circle
Porping Apr 13, 2022
65c1237
feat: remove gridline form areachart and twobar
Porping Apr 13, 2022
6e75a7c
feat:add Pagination Errorlog but dont no type of pagination
Porping Apr 13, 2022
6dc98a9
feat:fix misspell
Porping Apr 13, 2022
33e244e
refactor: eliminate type 'any
Porping Apr 13, 2022
7f2f842
feat:fix prevent loading component
Porping Apr 13, 2022
8027959
feat: reuse components
Porping Apr 18, 2022
9c721b4
feat:add Loginpage
Porping Apr 21, 2022
6ce1eb2
feat: add database connector and prepared auth service (#3)
riflowth Apr 22, 2022
1b89f55
Merge branch 'feat/session' into feat/auth
riflowth Apr 22, 2022
54e14c7
feat: add not found 404 fallback middleware
riflowth Apr 22, 2022
93dce5c
feat: add request decorator for basic validation
riflowth Apr 22, 2022
081455a
refactor: remove next function parameter
riflowth Apr 22, 2022
69bcae4
fix: missing get connection methods
riflowth Apr 22, 2022
da33a31
refactor: login method return cookie with sid
riflowth Apr 22, 2022
2095041
refactor: invoke auth service instead of mock
riflowth Apr 22, 2022
1e8cc4c
refactor: call express url encoding & json parser
riflowth Apr 22, 2022
e92040f
fix: wrong expect (forget to fix after changed)
riflowth Apr 22, 2022
0930de0
feat: add auth controller unit test
riflowth Apr 22, 2022
8fa4bdb
feat: add DateUtil class
eltfshr Apr 22, 2022
fa516ab
feat: add repository interface
eltfshr Apr 22, 2022
aa106aa
feat: add staff repository, add password in staff
eltfshr Apr 22, 2022
8ecf7c0
build: add bcrypt depedency
riflowth Apr 22, 2022
fe83656
refactor: dependecy inversion for testability
riflowth Apr 22, 2022
a9d15c2
refactor: complete auth service for logging in
riflowth Apr 22, 2022
0da2f87
refactor: auth service tester with new interface
riflowth Apr 22, 2022
587d20a
refactor: entity interface
riflowth Apr 23, 2022
e5e9d2f
refactor: database connection for repository
riflowth Apr 23, 2022
781ee54
fix: tsconfig annoying warning
riflowth Apr 23, 2022
578deee
feat: add parameter 'readOptions' to read method
XiaoXuxxxx Apr 23, 2022
b81c05f
refactor: convert to marker interface
riflowth Apr 23, 2022
b81c5df
feat: add test script on top-level module
riflowth Apr 23, 2022
c195471
Merge remote-tracking branch 'origin/feat/frontend' into feat/auth
riflowth Apr 23, 2022
6562728
feat: add address repository
Porping Apr 23, 2022
a41f7bf
feat: add zone repository
Porping Apr 23, 2022
00ce517
refactor: clean yarn.lock with yarn install
riflowth Apr 23, 2022
e35a34c
feat: add machine repository
Porping Apr 23, 2022
0e72549
fix: linting issue
riflowth Apr 23, 2022
a3a63ea
fix: add dateutil
Porping Apr 23, 2022
303cf35
feat:add order repository
Porping Apr 23, 2022
9b06e1b
fix: add dateutil
Porping Apr 23, 2022
d4a852b
refactor: convert to marker interface
riflowth Apr 23, 2022
2c022bc
feat: add maintenancepart repository
Porping Apr 23, 2022
92a8c29
feat: add maintenancelog repository
Porping Apr 23, 2022
fbd2689
fix: variable same in database
Porping Apr 23, 2022
8f4dfab
Merge branch 'feat/repository' of https://github.com/CPE34-KMUTT/mod-…
Porping Apr 23, 2022
bdc88ed
fix: method typo
riflowth Apr 23, 2022
dd9ca50
refactor: register all repositories
riflowth Apr 23, 2022
ba6fa1c
Merge branch 'feat/repository' of https://github.com/CPE34-KMUTT/mod-…
riflowth Apr 23, 2022
5a2fb4d
feat: add machinepart repository
XiaoXuxxxx Apr 23, 2022
1bf6d6c
feat: add branch repository
XiaoXuxxxx Apr 23, 2022
29477e7
feat: add bill repository
XiaoXuxxxx Apr 23, 2022
1de61a8
refactor: change return type of update and delete
XiaoXuxxxx Apr 23, 2022
65bb4b9
refactor: change return type of concrete classes
XiaoXuxxxx Apr 23, 2022
4da00cc
refactor: change return type related to interface
XiaoXuxxxx Apr 23, 2022
55034e7
typo: fix misspelling words
XiaoXuxxxx Apr 23, 2022
2e95da6
feat: register alll repositories
XiaoXuxxxx Apr 23, 2022
b03e298
Merge branch 'feat/repository' into feat/auth
riflowth Apr 23, 2022
42a3411
refactor: pool typing as mysql like redis
riflowth Apr 23, 2022
ddb52bd
fix: annoying new line
riflowth Apr 23, 2022
a16b66e
feat: add session entity/repository
eltfshr Apr 23, 2022
b70d9ef
Merge branch 'feat/auth' of https://github.com/CPE34-KMUTT/mod-tham-n…
eltfshr Apr 23, 2022
d91e6cd
fix: fix typos
eltfshr Apr 23, 2022
763d07f
fix: wrong casing on import
riflowth Apr 23, 2022
f37670e
refactor: add sql builder for utility
riflowth Apr 23, 2022
e7bafd3
refactor: use new sql builder for example
riflowth Apr 23, 2022
bc06a10
refactor: use new sql builder for example
riflowth Apr 23, 2022
e050d1d
Merge branch 'feat/auth' of https://github.com/CPE34-KMUTT/mod-tham-n…
XiaoXuxxxx Apr 23, 2022
61d9faf
refactor: remove unnecessary json parse
riflowth Apr 23, 2022
fc3bfd4
feat: add axios and login state
Porping Apr 23, 2022
b3db63f
fix: easy fix when pass undefined into the method
riflowth Apr 23, 2022
f649c26
refactor: implement session and logout
riflowth Apr 23, 2022
46e4cbd
Merge branch 'feat/frontend' into feat/auth
riflowth Apr 23, 2022
d3fedbd
refactor: util raw cookie and signed cookie
riflowth Apr 24, 2022
0a75ab7
refactor: controller registration for middleware
riflowth Apr 24, 2022
8969c6e
build: clean yarn.lock for ci
riflowth Apr 24, 2022
201a01b
refactor: auth service unit test
riflowth Apr 24, 2022
903a117
feat: add get staff info route
riflowth Apr 24, 2022
a912f4c
feat: add validation for readoptions
XiaoXuxxxx Apr 24, 2022
b790408
refactor: remove unnecessary logging
XiaoXuxxxx Apr 24, 2022
03a6360
fix: limit and offset must be integer
XiaoXuxxxx Apr 24, 2022
745ebb0
refactor: clean sourcecode
riflowth Apr 24, 2022
56b96c7
feat: add number utility class
XiaoXuxxxx Apr 24, 2022
cb1005b
feat: add optional request body
XiaoXuxxxx Apr 24, 2022
3df77a0
Merge branch 'feat/auth' of https://github.com/CPE34-KMUTT/mod-tham-n…
riflowth Apr 24, 2022
ef7563a
feat: implement authorization with decorator
riflowth Apr 24, 2022
7bb4c48
refactor: throw meaningful exceception instead
riflowth Apr 24, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions .vscode/settings.json
@@ -0,0 +1,4 @@
{
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.quoteStyle": "single",
}
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -3,7 +3,8 @@
"private": true,
"workspaces": ["packages/*"],
"scripts": {
"dev": "lerna run dev --stream"
"dev": "lerna run dev --stream",
"test": "lerna run test --stream"
},
"devDependencies": {
"lerna": "3.22.1"
Expand Down
10 changes: 10 additions & 0 deletions packages/backend/.env.example
@@ -0,0 +1,10 @@
MYSQL_HOST=
MYSQL_PORT=
MYSQL_USER=
MYSQL_PASSWORD=
MYSQL_DATABASE=

REDIS_HOST=
REDIS_PORT=
REDIS_USER=
REDIS_PASSWORD=
8 changes: 8 additions & 0 deletions packages/backend/declarations/express.d.ts
@@ -0,0 +1,8 @@
declare namespace Express {
export interface Request {
session?: {
sessionId: string,
staffId: number,
}
}
}
9 changes: 7 additions & 2 deletions packages/backend/package.json
Expand Up @@ -9,21 +9,26 @@
"build": "yarn run build:prod",
"dev": "npm-run-all --silent --parallel build:dev start:dev",
"lint": "eslint . --ext .ts",
"start:dev": "nodemon dist/app.js -q -w dist -e js",
"start:prod": "node dist/app.js",
"start:dev": "nodemon -r dotenv/config dist/app.js -q -w dist -e js",
"start:prod": "node -r dotenv/config dist/app.js",
"start": "yarn run start:prod",
"test": "jest --silent"
},
"dependencies": {
"bcrypt": "^5.0.1",
"express": "^4.17.3",
"mysql2": "^2.3.3",
"redis": "^4.0.6",
"reflect-metadata": "^0.1.13"
},
"devDependencies": {
"@types/bcrypt": "^5.0.0",
"@types/express": "^4.17.13",
"@types/jest": "^27.4.1",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^5.12.1",
"@typescript-eslint/parser": "^5.12.1",
"dotenv": "^16.0.0",
"eslint": "^8.9.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^16.1.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/App.ts
Expand Up @@ -4,7 +4,7 @@ try {
console.log('Starting up the application...');
const server = new Server(4000);
server.run();
} catch (error: Error | unknown) {
} catch (error: unknown) {
if (error instanceof Error) {
console.log(`${error.name}: ${error.message}`);
console.log('Closing the application...');
Expand Down
115 changes: 106 additions & 9 deletions packages/backend/src/Server.ts
Expand Up @@ -3,28 +3,73 @@ import http from 'http';
import express, { Application } from 'express';
import { IndexController } from '@/controllers/IndexController';
import { ControllerRegistry } from '@/controllers/ControllerRegistry';
import { DatabaseConnector } from '@/utils/database/DatabaseConnector';
import { DatabaseException } from '@/exceptions/DatabaseException';
import { CookieProvider } from '@/utils/cookie/CookieProvider';
import { AuthController } from '@/controllers/AuthController';
import { AuthService } from '@/services/AuthService';
import { AddressRepository } from '@/repositories/address/AddressRepository';
import { BillRepository } from '@/repositories/bill/BillRepository';
import { BranchRepository } from '@/repositories/branch/BranchRepository';
import { MachineRepository } from '@/repositories/machine/MachineRepository';
import { MachinePartRepository } from '@/repositories/machinePart/MachinePartRepository';
import { MaintenanceLogRepository } from '@/repositories/maintenancelog/MaintenanceLogRepository';
import { MaintenancePartRepository } from '@/repositories/maintenancepart/MaintenancePartRepository';
import { OrderRepository } from '@/repositories/order/OrderRepository';
import { StaffRepository } from '@/repositories/staff/StaffRepository';
import { ZoneRepository } from '@/repositories/zone/ZoneRepository';
import { DefaultAddressRepository } from '@/repositories/address/DefaultAddressRepository';
import { DefaultBillRepository } from '@/repositories/bill/DefaultBillRepository';
import { DefaultBranchRepository } from '@/repositories/branch/DefaultBranchRepository';
import { DefaultMachineRepository } from '@/repositories/machine/DefaultMachineRepository';
import { DefaultMachinePartRepository } from '@/repositories/machinePart/DefaultMachinePartRepository';
import { DefaultMaintenanceLogRepository } from '@/repositories/maintenancelog/DefaultMaintenanceLogRepository';
import { DefaultMaintenancePartRepository } from '@/repositories/maintenancepart/DefaultMaintenancePartRepository';
import { DefaultOrderRepository } from '@/repositories/order/DefaultOrderRepository';
import { DefaultStaffRepository } from '@/repositories/staff/DefaultStaffRepository';
import { DefaultZoneRepository } from '@/repositories/zone/DefaultZoneRepository';
import { SessionRepository } from '@/repositories/session/SessionRepository';
import { DefaultSessionRepository } from '@/repositories/session/DefaultSessionRepository';
import { StaffController } from '@/controllers/StaffController';

export class Server {

private readonly app: Application;
private readonly port: number;
private readonly controllerRegistry: ControllerRegistry;

private databaseConnector: DatabaseConnector;
private controllerRegistry: ControllerRegistry;
private cookieProvider: CookieProvider;

private addressRepository: AddressRepository;
private billRepository: BillRepository;
private branchRepository: BranchRepository;
private machineRepository: MachineRepository;
private machinePartRepository: MachinePartRepository;
private maintenanceLogRepository: MaintenanceLogRepository;
private maintenancePartRepository: MaintenancePartRepository;
private orderRepository: OrderRepository;
private sessionRepository: SessionRepository;
private staffRepository: StaffRepository;
private zoneRepository: ZoneRepository;

private authService: AuthService;

public constructor(port: number) {
this.app = express();
this.port = port;
this.controllerRegistry = new ControllerRegistry(this.app);
this.databaseConnector = new DatabaseConnector();
}

public run(): http.Server {
this.controllerRegistry.loadControllers([
new IndexController(),
]);

public async run(): Promise<http.Server> {
this.app.use(express.urlencoded({ extended: true }));
this.app.use(express.json());
this.app.disable('x-powered-by');

const controllerCount = this.controllerRegistry.size();
console.log(`Registered ${controllerCount} controller${controllerCount > 1 ? 's' : ''}`);
await this.connectDatabase();
await this.registerRepository();
await this.registerServices();
await this.loadControllers();

return this.app.listen(this.port, this.onStartup.bind(this));
}
Expand All @@ -33,4 +78,56 @@ export class Server {
console.log(`Listening on http://localhost:${this.port}/`);
}

private async registerRepository(): Promise<void> {
const defaultDb = await this.databaseConnector.getDefaultDatabase();
const cachingDb = await this.databaseConnector.getCachingDatabase();

this.addressRepository = new DefaultAddressRepository(defaultDb, cachingDb);
this.billRepository = new DefaultBillRepository(defaultDb, cachingDb);
this.branchRepository = new DefaultBranchRepository(defaultDb, cachingDb);
this.machineRepository = new DefaultMachineRepository(defaultDb, cachingDb);
this.machinePartRepository = new DefaultMachinePartRepository(defaultDb, cachingDb);
this.maintenanceLogRepository = new DefaultMaintenanceLogRepository(defaultDb, cachingDb);
this.maintenancePartRepository = new DefaultMaintenancePartRepository(defaultDb, cachingDb);
this.orderRepository = new DefaultOrderRepository(defaultDb, cachingDb);
this.sessionRepository = new DefaultSessionRepository(defaultDb, cachingDb);
this.staffRepository = new DefaultStaffRepository(defaultDb, cachingDb);
this.zoneRepository = new DefaultZoneRepository(defaultDb, cachingDb);
}

private async registerServices(): Promise<void> {
this.authService = new AuthService(this.staffRepository, this.sessionRepository);
}

private async loadControllers(): Promise<void> {
this.cookieProvider = new CookieProvider('this-is-the-secret-just-keep-it');

this.controllerRegistry = new ControllerRegistry(
this.app,
this.cookieProvider,
this.sessionRepository,
this.staffRepository,
);

this.controllerRegistry.loadControllers([
new IndexController(),
new AuthController(this.cookieProvider, this.authService),
new StaffController(this.staffRepository),
]);

const controllerCount = this.controllerRegistry.size();
console.log(`Registered ${controllerCount} controller${controllerCount > 1 ? 's' : ''}`);
}

private async connectDatabase(): Promise<void> {
try {
await this.databaseConnector.connect();
} catch (error: unknown) {
if (error instanceof DatabaseException) {
console.log(error.message);
}
process.exit(1);
}
}

}
49 changes: 49 additions & 0 deletions packages/backend/src/controllers/AuthController.ts
@@ -0,0 +1,49 @@
import { Controller } from '@/controllers/Controller';
import { Methods } from '@/controllers/Route';
import { ControllerMapping } from '@/decorators/ControllerDecorator';
import { RouteMapping } from '@/decorators/RouteDecorator';
import { CookieProvider } from '@/utils/cookie/CookieProvider';
import { Request, Response } from 'express';
import { RequestBody } from '@/decorators/RequestDecorator';
import { AuthService } from '@/services/AuthService';

@ControllerMapping('/auth')
export class AuthController extends Controller {

private readonly cookieProvider: CookieProvider;
private readonly authService: AuthService;

public constructor(cookieProvider: CookieProvider, authService: AuthService) {
super();
this.cookieProvider = cookieProvider;
this.authService = authService;
}

@RouteMapping('/login', Methods.POST)
@RequestBody('username', 'password')
private async loginRoute(req: Request, res: Response): Promise<void> {
const { username, password } = req.body;

const cookie = await this.authService.login(username, password);

if (cookie) {
this.cookieProvider.setSignedCookie(res, cookie);

res.status(200).json({
message: 'Logged in successfully',
});
}
}

@RouteMapping('/logout', Methods.GET)
private async logoutRoute(req: Request, res: Response): Promise<void> {
const sessionId = this.cookieProvider.getSignedCookie(req, 'sid');

await this.authService.logout(sessionId);

res.status(200).json({
message: 'Logged out successfully',
});
}

}
24 changes: 11 additions & 13 deletions packages/backend/src/controllers/Controller.ts
@@ -1,15 +1,11 @@
import { RouteHandler } from '@/controllers/Route';
import { RouteMetadata } from '@/decorators/RouteDecorator';
import { ErrorHandler } from '@/exceptions/ErrorHandler';
import { Router } from 'express';
import { Route } from '@/controllers/Route';

export class Controller {

/**
* Injected with {@link ControllerDecorator} on class declaration
*/
private readonly path: string;
private readonly router: Router = Router();
private readonly hasAvailable: boolean;

/**
Expand All @@ -28,19 +24,21 @@ export class Controller {
}

/**
* Returns the modular express router of the controller instance
* @returns The express {@link Router} instance
* Returns the router structure with handler and metadata of the controller
* for converting to the modular express router
* @returns The router structure (array of {@link Route})
*/
public getRouter(): Router {
public getRouter(): Route[] {
const routes = Reflect.getMetadataKeys(this);

routes.forEach((route) => {
const routeHandler: RouteHandler = ErrorHandler.wrap(this[route]);
const routeProperty: RouteMetadata = Reflect.getMetadata(route, this);
this.router[routeProperty.method.toLowerCase()](routeProperty.path, routeHandler);
const router: Route[] = routes.map((route) => {
return {
handler: this[route].bind(this),
metadata: Reflect.getMetadata(route, this),
};
});

return this.router;
return router;
}

}