diff --git a/src/controllers/auth.ts b/src/controllers/auth.ts index fb86ee7..34dcca7 100644 --- a/src/controllers/auth.ts +++ b/src/controllers/auth.ts @@ -37,20 +37,22 @@ export class AuthController { try { const schema = Joi.object().keys({ username: Joi.string().max(64).required().label('Username'), - password: Joi.string().min(8).required().label('Password') + password: Joi.string().min(8).required().label('Password'), + application: Joi.string().required().label('Application'), }); - const param: User = await schema.validateAsync(req.body); + const param: User & { application: string } = await schema.validateAsync(req.body); const userRepository = OrmHelper.DB.getRepository(User); const userRefreshTokenRepository = OrmHelper.DB.getRepository(UserRefreshToken); const user = await userRepository.findOne({ - select: ['id', 'email', 'password', 'username', 'id_role'], + relations: ['roles', 'roles.application'], + select: ['id', 'email', 'password', 'username', 'roles'], where: { username: param.username }, }) - if (user != null && user.checkIfPasswordMatch(param.password)) { + if (user != null && user.roles != null && user.roles.length > 0 && user.checkIfPasswordMatch(param.password)) { const { browser, device, os } = UAParser(req.get('User-Agent')); @@ -66,48 +68,67 @@ export class AuthController { await userRefreshTokenRepository.save(refresh_token); - const userRoleRepository = OrmHelper.DB.getRepository(UserRole); - const userRole = await userRoleRepository.findOneBy({ id: user.id_role }) + // const userRoleRepository = OrmHelper.DB.getRepository(UserRole); - var privateKey = fs.readFileSync('src/helpers/key/private.key'); + let role_id = ''; + let roles: UserRole; - var token = jwt.sign( - { - exp: Math.floor(Date.now() / 1000) + (Number(config.get('auth.access_token_lifetime'))), - data: { - id: user.id, - username: user.username, - id_role: user.id_role, - roles: userRole.roles + if (user.roles) { + for (let r of user.roles) { + if (r.application.id == param.application) { + role_id = r.id; + roles = r; } - }, - privateKey, - { algorithm: 'RS256' } - ); - - delete user.password; - - - - const data = { - user, - role_name: userRole.name, - roles_list: await AuthController.getListRole(userRole.roles), - token: { - access_token: token, - refresh_token: refresh_token.refresh_token } } - return ReturnHelper.successResponseAny(res, 200, Language.lang.success_login, data); + if (role_id == '') { + return ReturnHelper.errorResponse(res, 403, 403, Language.lang.failed_access + ', you don\'t have any roles in this application'); + } else { + // const userRole = await userRoleRepository.findOneBy({ id: role_id }) + + var privateKey = fs.readFileSync('src/helpers/key/private.key'); + + var token = jwt.sign( + { + exp: Math.floor(Date.now() / 1000) + (Number(config.get('auth.access_token_lifetime'))), + data: { + id: user.id, + username: user.username, + id_role: role_id, + roles: roles.roles + } + }, + privateKey, + { algorithm: 'RS256' } + ); + + delete user.password; + + const data = { + user, + role_name: roles.name, + roles_list: await AuthController.getListRole(roles.roles), + token: { + access_token: token, + refresh_token: refresh_token.refresh_token + } + } + + return ReturnHelper.successResponseAny(res, 200, Language.lang.success_login, data); + } } - return ReturnHelper.errorResponse(res, 403, 401, Language.lang.failed_access); + if (user.roles == null || user.roles.length == 0) { + return ReturnHelper.errorResponse(res, 403, 401, Language.lang.failed_access + ', you don\t have any roles in this application'); + } else { + return ReturnHelper.errorResponse(res, 403, 402, Language.lang.failed_access); + } } catch (e: unknown) { log.error(e); const err = e as Error; - return ReturnHelper.errorResponse(res, 500, 402, Language.lang.failed_access, err.message); + return ReturnHelper.errorResponse(res, 500, 403, Language.lang.failed_access, err.message); } } @@ -309,9 +330,10 @@ export class AuthController { try { const schema = Joi.object().keys({ refresh_token: Joi.string().uuid().required().label('Refresh Token'), + application: Joi.string().required().label('Application'), }); - const param: UserRefreshToken = await schema.validateAsync(req.params); + const param: UserRefreshToken & { application: string } = await schema.validateAsync(req.params); const userRefreshTokenRepo = OrmHelper.DB.getRepository(UserRefreshToken); @@ -320,40 +342,59 @@ export class AuthController { if (refresh_token != null) { const userRepo = OrmHelper.DB.getRepository(User); - const user = await userRepo.findOneBy({ id: refresh_token.id_user }); + const user = await userRepo.findOne({ relations: ['roles', 'roles.application'], where: { id: refresh_token.id_user } }); - const userRoleRepository = OrmHelper.DB.getRepository(UserRole); - const userRole = await userRoleRepository.findOneBy({ id: user.id_role }); + let role_id = ''; + let roles: UserRole; - var privateKey = fs.readFileSync('src/helpers/key/private.key'); + if (user.roles != null && user.roles.length > 0) { + for (let r of user.roles) { + if (r.application.id == param.application) { + role_id = r.id; - var token = jwt.sign( - { - exp: Math.floor(Date.now() / 1000) + (Number(config.get('auth.access_token_lifetime'))), - data: { - id: user.id, - username: user.username, - id_role: user.id_role, - roles: userRole.roles + roles = r; } - }, - privateKey, - { algorithm: 'RS256' } - ); + } + } else { + return ReturnHelper.errorResponse(res, 403, 401, Language.lang.failed_access + ', you don\'t have any roles in this application'); + } - return ReturnHelper.successResponseAny(res, 200, Language.lang.success, { - token, - role_name: userRole.name, - roles_list: await AuthController.getListRole(userRole.roles) - }); + // const userRoleRepository = OrmHelper.DB.getRepository(UserRole); + // const userRole = await userRoleRepository.findOneBy({ id: role_id }); + + if (roles) { + var privateKey = fs.readFileSync('src/helpers/key/private.key'); + + var token = jwt.sign( + { + exp: Math.floor(Date.now() / 1000) + (Number(config.get('auth.access_token_lifetime'))), + data: { + id: user.id, + username: user.username, + id_role: role_id, + roles: roles.roles + } + }, + privateKey, + { algorithm: 'RS256' } + ); + + return ReturnHelper.successResponseAny(res, 200, Language.lang.success, { + token, + role_name: roles.name, + roles_list: await AuthController.getListRole(roles.roles) + }); + } else { + return ReturnHelper.errorResponse(res, 403, 402, Language.lang.failed_access + ', you don\'t have any roles in this application'); + } } else { - return ReturnHelper.errorResponse(res, 404, 401, Language.lang.failed, ""); + return ReturnHelper.errorResponse(res, 404, 403, Language.lang.failed, ""); } } catch (e: unknown) { log.error(e); const err = e as Error; - return ReturnHelper.errorResponse(res, 500, 402, Language.lang.failed, err.message); + return ReturnHelper.errorResponse(res, 500, 404, Language.lang.failed, err.message); } } diff --git a/src/controllers/user.ts b/src/controllers/user.ts index 4925587..7a1ef0a 100644 --- a/src/controllers/user.ts +++ b/src/controllers/user.ts @@ -2,7 +2,7 @@ import { Response, NextFunction, Application } from "express"; import { Request } from "express-jwt"; import { ReturnHelper } from "../helpers/express/return"; import { OrmHelper } from "../helpers/orm"; -import { User, UserRefreshToken } from "entity"; +import { User, UserRefreshToken, UserRole } from "entity"; import Joi from "joi"; import { ILogObj, Logger } from "tslog"; import { Language } from "../langs/lang"; @@ -10,7 +10,7 @@ import { Paging } from "entity"; import CommonHelper from "../helpers/common"; import * as fastcsv from 'fast-csv'; import dayjs from "dayjs"; -import { validate } from 'uuid'; +import exceljs from "exceljs"; const log: Logger = new Logger({ name: '[UserController]', type: 'pretty' }); @@ -85,8 +85,8 @@ export class UserController { .where(whereAttr, whereVal); const res_list = userRepository.createQueryBuilder() .where(whereAttr, whereVal) - .select('id,email,username,name,id_role,status,created_at,updated_at') - .addSelect('(select role.name from user_roles role where role.id=User.id_role )', 'role_name') + .leftJoinAndSelect("User.roles", "roles") + .leftJoinAndSelect("roles.application", "application") .orderBy(param.order_field, param.order_direction) .offset(offset) .limit(param.limit); @@ -98,7 +98,7 @@ export class UserController { const current_page = param.page; const total_count_data = await res_count.getCount(); - const list_data = await res_list.getRawMany(); + const list_data = await res_list.getMany(); const count_data = CommonHelper.countObject(list_data); return ReturnHelper.successResponselist(res, 200, Language.lang.success_view, count_data, current_page, total_count_data, list_data); @@ -159,20 +159,24 @@ export class UserController { col_any_like: ['name', 'username'] }); - const filename = "user.csv"; + const filename = "user.xlsx"; - res.setHeader('Content-Type', 'text/csv'); + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') res.setHeader('Content-Disposition', 'attachment; filename=' + filename); - const csvStream = fastcsv.format({ - headers: true, - writeHeaders: true, - transform: (row: User): any => ({ - ...row, - created_at: dayjs(row.created_at).format('DD-MM-YYYY HH:MM:ss'), - }) - }); - csvStream.pipe(res); + const workbook = new exceljs.stream.xlsx.WorkbookWriter({ stream: res }); + const sheet = workbook.addWorksheet('Data'); + + sheet.columns = [ + { header: "ID", key: "id", width: 10 }, + { header: "Name", key: "name", width: 20 }, + { header: "Email", key: "email", width: 20 }, + { header: "Username", key: "username", width: 20 }, + { header: "Status", key: "status", width: 10 }, + { header: "Created At", key: "created_at", width: 15 }, + { header: "Roles", key: "role_name", width: 15 }, + { header: "Application", key: "application", width: 15 }, + ]; const limit = 50; @@ -182,27 +186,27 @@ export class UserController { const data = await userRepository.createQueryBuilder() .where(whereAttr, whereVal) .select([ - 'id', - 'name', - 'email', - 'username', - 'status', - 'created_at']) - .addSelect('(select role.name from user_roles role where role.id=User.id_role )', 'role_name') + 'User.id as id', + 'User.name as name', + 'User.email as email', + 'User.username as username', + 'User.status as status', + 'User.created_at as created_at', + 'roles.name as role_name', + 'application.id as application']) + .leftJoin("User.roles", "roles") + .leftJoin("roles.application", "application") .orderBy(param.order_field, param.order_direction) .offset(offset) .limit(limit).getRawMany(); - if (CommonHelper.countObject(data) === 0) { - csvStream.end(); - } else { - data.forEach((item: any) => csvStream.write(item)); + data.forEach((item: any) => sheet.addRow(item).commit()); - if (CommonHelper.countObject(data) == limit) { - fetchAndWrite(page + 1); - } else { - csvStream.end(); - } + if (CommonHelper.countObject(data) == limit) { + fetchAndWrite(page + 1); + } else { + sheet.commit(); + workbook.commit(); } }; @@ -241,7 +245,6 @@ export class UserController { password: Joi.string().pattern(new RegExp('^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$')).min(8).required().label('Password'), retype_password: Joi.ref('password'), name: Joi.string().max(64).required().label('Name'), - id_role: Joi.string().uuid().required().label('Role'), status: Joi.string().required().label('Status'), }); @@ -252,7 +255,6 @@ export class UserController { data.username = param.username data.password = param.password data.email = param.email - data.id_role = param.id_role data.status = param.status data.hashPassword(); @@ -268,6 +270,149 @@ export class UserController { } } + static async addRole(req: Request, res: Response, next: NextFunction): Promise { + /* + #swagger.tags = ['User'] + #swagger.security = [{ + "bearerAuth": [] + }] + + #swagger.parameters['id'] = { + in: 'path', + description: 'User ID.', + required: true, + type: 'string' + } + + #swagger.parameters['id_role'] = { + in: 'path', + description: 'User Role ID.', + required: true, + type: 'string' + } + */ + + try { + const schema = Joi.object().keys({ + id: Joi.string().uuid().required().label('ID'), + id_role: Joi.string().uuid().required().label('ID'), + }); + + const param: { id: string, id_role: string } = await schema.validateAsync(req.params); + + const userRepository = OrmHelper.DB.getRepository(User); + const repo_role = OrmHelper.DB.getRepository(UserRole); + + const data = await userRepository.findOne({ + relations: { + roles: true, + }, + where: { id: param.id } + }); + + if (data != null) { + const new_role = await repo_role.findOneBy({ id: param.id_role }); + + if (data.roles) { + for (let r of data.roles) { + if (r.id == param.id_role) { + //already added + return ReturnHelper.errorResponse(res, 409, 401, Language.lang.failed_insert + ", roles already exists ", ""); + } + } + } + + if (!data.roles) { + data.roles = []; + } + + data.roles.push(new_role); + + await userRepository.save(data); + + return ReturnHelper.successResponseAny(res, 200, Language.lang.success_update, data); + } else { + return ReturnHelper.errorResponse(res, 404, 402, Language.lang.failed_not_found, ""); + } + } catch (e: unknown) { + log.error(e); + const err = e as Error; + + return ReturnHelper.errorResponse(res, 500, 403, Language.lang.failed_update, err.message); + } + } + + static async deleteRole(req: Request, res: Response, next: NextFunction): Promise { + /* + #swagger.tags = ['User'] + #swagger.security = [{ + "bearerAuth": [] + }] + + #swagger.parameters['id'] = { + in: 'path', + description: 'User ID.', + required: true, + type: 'string' + } + + #swagger.parameters['id_role'] = { + in: 'path', + description: 'User Role ID.', + required: true, + type: 'string' + } + */ + + try { + const schema = Joi.object().keys({ + id: Joi.string().uuid().required().label('ID'), + id_role: Joi.string().uuid().required().label('ID'), + }); + + const param: { id: string, id_role: string } = await schema.validateAsync(req.params); + + const userRepository = OrmHelper.DB.getRepository(User); + + const data = await userRepository.findOne({ + relations: { + roles: true, + }, + where: { id: param.id } + }); + + if (data != null && data.roles) { + let found = false; + let new_roles: UserRole[] = []; + + for (let r of data.roles) { + if (r.id != param.id_role) { + new_roles.push(r); + } else if (r.id == param.id_role) { + found = true; + } + } + + data.roles = new_roles; + + if (found) { + await userRepository.save(data); + + return ReturnHelper.successResponseAny(res, 200, Language.lang.success_delete, data); + } else { + return ReturnHelper.errorResponse(res, 404, 401, Language.lang.failed_not_found + ", role not found", ""); + } + } else { + return ReturnHelper.errorResponse(res, 404, 402, Language.lang.failed_not_found, ""); + } + } catch (e: unknown) { + log.error(e); + const err = e as Error; + + return ReturnHelper.errorResponse(res, 500, 403, Language.lang.failed_delete, err.message); + } + } + static async updateProfile(req: Request, res: Response, next: NextFunction): Promise { /* #swagger.tags = ['User'] @@ -411,7 +556,6 @@ export class UserController { password: Joi.string().allow('').optional().label('Password'), retype_password: Joi.ref('password'), name: Joi.string().max(64).required().label('Name'), - id_role: Joi.string().uuid().required().label('Role'), status: Joi.string().required().label('Status'), }); @@ -427,7 +571,6 @@ export class UserController { data.name = param.name data.username = param.username data.email = param.email - data.id_role = param.id_role data.status = param.status if (param.password) { @@ -525,7 +668,10 @@ export class UserController { const userRepository = OrmHelper.DB.getRepository(User); - const detail = (await userRepository.findOneBy({ id: param.id })); + const detail = await userRepository.findOne({ + relations: ['roles', 'roles.application'], + where: { id: param.id } + }); if (detail != null) { return ReturnHelper.successResponseAny(res, 200, Language.lang.success_view, detail); diff --git a/src/controllers/user_roles.ts b/src/controllers/user_roles.ts index a2a8f44..796e524 100644 --- a/src/controllers/user_roles.ts +++ b/src/controllers/user_roles.ts @@ -374,21 +374,21 @@ export class UserRoleController { const param: { id: string, hard: boolean } = await schema.validateAsync(req.params); const repo_role = OrmHelper.DB.getRepository(UserRole); - const repo_user = OrmHelper.DB.getRepository(User); + // const repo_user = OrmHelper.DB.getRepository(User); - const check_used = await repo_user.findOneBy({ id_role: param.id }) + // const check_used = await repo_user.findOneBy({ id_role: param.id }) - if (check_used == null) { - const affected = (!param.hard ? await repo_role.softDelete({ id: param.id }) : await repo_role.delete({ id: param.id })).affected; + // if (check_used == null) { + const affected = (!param.hard ? await repo_role.softDelete({ id: param.id }) : await repo_role.delete({ id: param.id })).affected; - if (affected > 0) { - return ReturnHelper.successResponseAny(res, 200, Language.lang.success_delete, {}); - } else { - return ReturnHelper.errorResponse(res, 404, 401, Language.lang.failed_not_found, ""); - } + if (affected > 0) { + return ReturnHelper.successResponseAny(res, 200, Language.lang.success_delete, {}); } else { - return ReturnHelper.errorResponse(res, 404, 402, Language.lang.failed_related, ""); + return ReturnHelper.errorResponse(res, 404, 401, Language.lang.failed_not_found, ""); } + // } else { + // return ReturnHelper.errorResponse(res, 404, 402, Language.lang.failed_related, ""); + // } } catch (e: unknown) { log.error(e); const err = e as Error; diff --git a/src/routes/private.ts b/src/routes/private.ts index a71f69c..29b6f54 100644 --- a/src/routes/private.ts +++ b/src/routes/private.ts @@ -17,6 +17,8 @@ export class RoutePrivate { app.get('/api/user/export', UserController.export) app.post('/api/user/create', UserController.create) app.put('/api/user/update/:id', UserController.update) + app.put('/api/user/add_role/:id/:id_role', UserController.addRole) + app.put('/api/user/delete_role/:id/:id_role', UserController.deleteRole) app.put('/api/user/update_profile', UserController.updateProfile) app.put('/api/user/update_password', UserController.updatePassword) app.get('/api/user/detail/:id', UserController.detail) diff --git a/src/routes/public.ts b/src/routes/public.ts index ac17cd0..29de73a 100644 --- a/src/routes/public.ts +++ b/src/routes/public.ts @@ -12,7 +12,7 @@ export class RoutePublic { app.put('/api/reset_password/:email', AuthController.resetPassword) app.put('/api/update_password/:token', AuthController.updatePassword) app.put('/api/logout/:refresh_token', AuthController.logout) - app.put('/api/renew_token/:refresh_token', AuthController.renewToken) + app.put('/api/renew_token/:refresh_token/:application', AuthController.renewToken) } } \ No newline at end of file diff --git a/src/swagger/builder.js b/src/swagger/builder.js index 4f7d2b0..97fe9b2 100644 --- a/src/swagger/builder.js +++ b/src/swagger/builder.js @@ -17,7 +17,8 @@ const doc = { schemas: { login: { $username: "admin", - $password: "12345678" + $password: "12345678", + $application: "ukln", }, user: { $nik: "123456789", @@ -31,7 +32,6 @@ const doc = { $organisasi: "4a6991b6-78a0-4b3b-bf5c-fa0af5f69be8", $divisi: "4a6991b6-78a0-4b3b-bf5c-fa0af5f69be8", $unit: "4a6991b6-78a0-4b3b-bf5c-fa0af5f69be8", - $id_role: "4a6991b6-78a0-4b3b-bf5c-fa0af5f69be8", $status: "Y" }, user_profile: {