diff --git a/.env b/.env deleted file mode 100644 index 56cbd483a..000000000 --- a/.env +++ /dev/null @@ -1,14 +0,0 @@ -MAIL_HOST= -MAIL_USERNAME= -MAIL_PASSWORD= -MAIL_PORT= -MAIL_SECURE=false - -MAIL_FROM_ADDRESS= -MAIL_FROM_NAME= - -DB_CLIENT=mysql -DB_HOST=127.0.0.1 -DB_USER=root -DB_PASSWORD=root -DB_NAME=ratteb diff --git a/server/.env.example b/server/.env.example new file mode 100644 index 000000000..ea99a35cc --- /dev/null +++ b/server/.env.example @@ -0,0 +1,43 @@ +MAIL_HOST=smtp.mailtrap.io +MAIL_USERNAME=842f331d3dc005 +MAIL_PASSWORD=172f97b34f1a17 +MAIL_PORT=587 +MAIL_SECURE=false + +MAIL_FROM_ADDRESS= +MAIL_FROM_NAME= + +SYSTEM_DB_CLIENT=mysql +SYSTEM_DB_HOST=127.0.0.1 +SYSTEM_DB_USER=root +SYSTEM_DB_PASSWORD=root +SYSTEM_DB_NAME=bigcapital_system +SYSTEM_MIGRATIONS_DIR=./src/system/migrations +SYSTEM_SEEDS_DIR=./src/system/seeds + +TENANT_DB_CLIENT=mysql +TENANT_DB_NAME_PERFIX=bigcapital_tenant_ +TENANT_DB_HOST=127.0.0.1 +TENANT_DB_PASSWORD=root +TEANNT_DB_USER=root +TENANT_DB_CHARSET=charset +TENANT_MIGRATIONS_DIR=src/database/migrations +TENANT_SEEDS_DIR=src/database/seeds/core + +DB_MANAGER_SUPER_USER=root +DB_MANAGER_SUPER_PASSWORD=root + +MONGODB_DATABASE_URL=mongodb://localhost/bigcapital + +EASY_SMS_TOKEN=b0JDZW56RnV6aEthb0RGPXVEcUI + +JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI + +CONTACT_US_MAIL=support@bigcapital.ly +BASE_URL=https://bigcapital.ly + +LICENSES_AUTH_USER=root +LICENSES_AUTH_PASSWORD=root + +AGENDASH_AUTH_USER=agendash +AGENDASH_AUTH_PASSWORD=123123 \ No newline at end of file diff --git a/server/src/api/controllers/Authentication.ts b/server/src/api/controllers/Authentication.ts index 1285574e0..e5d162333 100644 --- a/server/src/api/controllers/Authentication.ts +++ b/server/src/api/controllers/Authentication.ts @@ -119,7 +119,7 @@ export default class AuthenticationController extends BaseController{ } if (error.errorType === 'user_inactive') { return res.boom.badRequest(null, { - errors: [{ type: 'INVALID_DETAILS', code: 200 }], + errors: [{ type: 'USER_INACTIVE', code: 200 }], }); } } diff --git a/server/src/api/controllers/Expenses.ts b/server/src/api/controllers/Expenses.ts index c2afcce26..c69b752bd 100644 --- a/server/src/api/controllers/Expenses.ts +++ b/server/src/api/controllers/Expenses.ts @@ -249,32 +249,32 @@ export default class ExpensesController extends BaseController { if (error instanceof ServiceError) { if (error.errorType === 'expense_not_found') { return res.boom.badRequest(null, { - errors: [{ type: 'EXPENSE_NOT_FOUND' }], + errors: [{ type: 'EXPENSE_NOT_FOUND', code: 100 }], }); } if (error.errorType === 'total_amount_equals_zero') { return res.boom.badRequest(null, { - errors: [{ type: 'TOTAL.AMOUNT.EQUALS.ZERO' }], + errors: [{ type: 'TOTAL.AMOUNT.EQUALS.ZERO', code: 200 }], }); } if (error.errorType === 'payment_account_not_found') { return res.boom.badRequest(null, { - errors: [{ type: 'PAYMENT.ACCOUNT.NOT.FOUND', }], + errors: [{ type: 'PAYMENT.ACCOUNT.NOT.FOUND', code: 300 }], }); } if (error.errorType === 'some_expenses_not_found') { return res.boom.badRequest(null, { - errors: [{ type: 'SOME.EXPENSE.ACCOUNTS.NOT.FOUND', code: 200 }] + errors: [{ type: 'SOME.EXPENSE.ACCOUNTS.NOT.FOUND', code: 400 }] }) } if (error.errorType === 'payment_account_has_invalid_type') { return res.boom.badRequest(null, { - errors: [{ type: 'PAYMENT.ACCOUNT.HAS.INVALID.TYPE' }], + errors: [{ type: 'PAYMENT.ACCOUNT.HAS.INVALID.TYPE', code: 500 }], }); } if (error.errorType === 'expenses_account_has_invalid_type') { return res.boom.badRequest(null, { - errors: [{ type: 'EXPENSES.ACCOUNT.HAS.INVALID.TYPE' }] + errors: [{ type: 'EXPENSES.ACCOUNT.HAS.INVALID.TYPE', code: 600 }] }); } } diff --git a/server/src/api/controllers/Users.ts b/server/src/api/controllers/Users.ts index 35e4ae2af..0dc47c8ae 100644 --- a/server/src/api/controllers/Users.ts +++ b/server/src/api/controllers/Users.ts @@ -26,37 +26,42 @@ export default class UsersController extends BaseController{ ...this.specificUserSchema, ], this.validationResult, - asyncMiddleware(this.inactivateUser.bind(this)) + asyncMiddleware(this.inactivateUser.bind(this)), + this.catchServiceErrors, ); router.put('/:id/activate', [ ...this.specificUserSchema ], this.validationResult, - asyncMiddleware(this.activateUser.bind(this)) + asyncMiddleware(this.activateUser.bind(this)), + this.catchServiceErrors, ); router.post('/:id', [ ...this.userDTOSchema, ...this.specificUserSchema, ], this.validationResult, - asyncMiddleware(this.editUser.bind(this)) + asyncMiddleware(this.editUser.bind(this)), + this.catchServiceErrors, ); router.get('/', this.listUsersSchema, this.validationResult, - asyncMiddleware(this.listUsers.bind(this)) + asyncMiddleware(this.listUsers.bind(this)), ); router.get('/:id', [ ...this.specificUserSchema, ], this.validationResult, - asyncMiddleware(this.getUser.bind(this)) + asyncMiddleware(this.getUser.bind(this)), + this.catchServiceErrors, ); router.delete('/:id', [ ...this.specificUserSchema ], this.validationResult, - asyncMiddleware(this.deleteUser.bind(this)) + asyncMiddleware(this.deleteUser.bind(this)), + this.catchServiceErrors, ); return router; } @@ -101,19 +106,6 @@ export default class UsersController extends BaseController{ await this.usersService.editUser(tenantId, userId, userDTO); return res.status(200).send({ id: userId }); } catch (error) { - if (error instanceof ServiceErrors) { - const errorReasons = []; - - if (error.errorType === 'email_already_exists') { - errorReasons.push({ type: 'EMAIL_ALREADY_EXIST', code: 100 }); - } - if (error.errorType === 'phone_number_already_exist') { - errorReasons.push({ type: 'PHONE_NUMBER_ALREADY_EXIST', code: 200 }); - } - if (errorReasons.length > 0) { - return res.status(400).send({ errors: errorReasons }); - } - } next(error); } } @@ -128,19 +120,10 @@ export default class UsersController extends BaseController{ const { id } = req.params; const { tenantId } = req; - debugger; - try { await this.usersService.deleteUser(tenantId, id); return res.status(200).send({ id }); } catch (error) { - if (error instanceof ServiceError) { - if (error.errorType === 'user_not_found') { - return res.boom.notFound(null, { - errors: [{ type: 'USER_NOT_FOUND', code: 100 }], - }); - } - } next(error); } } @@ -159,15 +142,8 @@ export default class UsersController extends BaseController{ const user = await this.usersService.getUser(tenantId, userId); return res.status(200).send({ user }); } catch (error) { - if (error instanceof ServiceError) { - if (error.errorType === 'user_not_found') { - return res.boom.notFound(null, { - errors: [{ type: 'USER_NOT_FOUND', code: 100 }], - }); - } - } next(error); - } + } } /** @@ -194,25 +170,13 @@ export default class UsersController extends BaseController{ * @param {NextFunction} next */ async activateUser(req: Request, res: Response, next: NextFunction) { - const { tenantId } = req; + const { tenantId, user } = req; const { id: userId } = req.params; try { - await this.usersService.activateUser(tenantId, userId); + await this.usersService.activateUser(tenantId, userId, user); return res.status(200).send({ id: userId }); } catch(error) { - if (error instanceof ServiceError) { - if (error.errorType === 'user_not_found') { - return res.status(404).send({ - errors: [{ type: 'USER.NOT.FOUND', code: 100 }], - }); - } - if (error.errorType === 'user_already_active') { - return res.status(404).send({ - errors: [{ type: 'USER.ALREADY.ACTIVE', code: 200 }], - }); - } - } next(error); } } @@ -228,22 +192,57 @@ export default class UsersController extends BaseController{ const { id: userId } = req.params; try { - await this.usersService.inactivateUser(tenantId, userId); + await this.usersService.inactivateUser(tenantId, userId, user); return res.status(200).send({ id: userId }); } catch(error) { - if (error instanceof ServiceError) { - if (error.errorType === 'user_not_found') { - return res.status(404).send({ - errors: [{ type: 'USER.NOT.FOUND', code: 100 }], - }); - } - if (error.errorType === 'user_already_inactive') { - return res.status(404).send({ - errors: [{ type: 'USER.ALREADY.INACTIVE', code: 200 }], - }); - } - } next(error); } } + + /** + * Catches all users service errors. + * @param {Error} error + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + catchServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) { + if (error instanceof ServiceErrors) { + const errorReasons = []; + + if (error.errorType === 'email_already_exists') { + errorReasons.push({ type: 'EMAIL_ALREADY_EXIST', code: 100 }); + } + if (error.errorType === 'phone_number_already_exist') { + errorReasons.push({ type: 'PHONE_NUMBER_ALREADY_EXIST', code: 200 }); + } + if (errorReasons.length > 0) { + return res.status(400).send({ errors: errorReasons }); + } + } + if (error instanceof ServiceError) { + if (error.errorType === 'user_not_found') { + return res.status(404).send({ + errors: [{ type: 'USER.NOT.FOUND', code: 100 }], + }); + } + if (error.errorType === 'user_already_active') { + return res.status(404).send({ + errors: [{ type: 'USER.ALREADY.ACTIVE', code: 200 }], + }); + } + if (error.errorType === 'user_already_inactive') { + return res.status(404).send({ + errors: [{ type: 'USER.ALREADY.INACTIVE', code: 200 }], + }); + } + if (error.errorType === 'user_same_the_authorized_user') { + return res.boom.badRequest( + 'You could not activate/inactivate the same authorized user.', + { errors: [{ type: 'CANNOT.TOGGLE.ACTIVATE.AUTHORIZED.USER', code: 300 }] }, + ) + } + } + next(error); + } }; \ No newline at end of file diff --git a/server/src/api/middleware/AttachCurrentTenantUser.ts b/server/src/api/middleware/AttachCurrentTenantUser.ts index 20c626e82..ee43de37f 100644 --- a/server/src/api/middleware/AttachCurrentTenantUser.ts +++ b/server/src/api/middleware/AttachCurrentTenantUser.ts @@ -14,11 +14,19 @@ const attachCurrentUser = async (req: Request, res: Response, next: Function) => try { Logger.info('[attach_user_middleware] finding system user by id.'); const user = await systemUserRepository.getById(req.token.id); + console.log(user); if (!user) { Logger.info('[attach_user_middleware] the system user not found.'); return res.boom.unauthorized(); } + if (!user.active) { + Logger.info('[attach_user_middleware] the system user not found.'); + return res.boom.badRequest( + 'The authorized user is inactivated.', + { errors: [{ type: 'USER_INACTIVE', code: 100, }] }, + ); + } // Delete password property from user object. Reflect.deleteProperty(user, 'password'); req.user = user; diff --git a/server/src/data/ResourceFieldsKeys.js b/server/src/data/ResourceFieldsKeys.js index 8f46e8a5f..462142dfd 100644 --- a/server/src/data/ResourceFieldsKeys.js +++ b/server/src/data/ResourceFieldsKeys.js @@ -31,26 +31,26 @@ export default { // Accounts 'accounts': { - 'name': { + name: { column: 'name', }, - 'type': { + type: { column: 'account_type_id', relation: 'account_types.id', relationColumn: 'account_types.key', }, - 'description': { + description: { column: 'description', }, - 'code': { + code: { column: 'code', }, - 'root_type': { + root_type: { column: 'account_type_id', relation: 'account_types.id', relationColumn: 'account_types.root_type', }, - 'created_at': { + created_at: { column: 'created_at', columnType: 'date', }, diff --git a/server/src/services/Authentication/index.ts b/server/src/services/Authentication/index.ts index 565863480..20414a7ae 100644 --- a/server/src/services/Authentication/index.ts +++ b/server/src/services/Authentication/index.ts @@ -59,20 +59,20 @@ export default class AuthenticationService { this.logger.info('[login] invalid data'); throw new ServiceError('invalid_details'); } - this.logger.info('[login] check password validation.'); + this.logger.info('[login] check password validation.', { emailOrPhone, password }); if (!user.verifyPassword(password)) { throw new ServiceError('invalid_password'); } if (!user.active) { - this.logger.info('[login] user inactive.'); + this.logger.info('[login] user inactive.', { userId: user.id }); throw new ServiceError('user_inactive'); } - this.logger.info('[login] generating JWT token.'); + this.logger.info('[login] generating JWT token.', { userId: user.id }); const token = this.generateToken(user); - this.logger.info('[login] updating user last login at.'); + this.logger.info('[login] updating user last login at.', { userId: user.id }); await systemUserRepository.patchLastLoginAt(user.id); this.logger.info('[login] Logging success.', { user, token }); diff --git a/server/src/services/Users/UsersService.ts b/server/src/services/Users/UsersService.ts index 953ad00c2..172078f73 100644 --- a/server/src/services/Users/UsersService.ts +++ b/server/src/services/Users/UsersService.ts @@ -53,7 +53,7 @@ export default class UsersService { * @param {number} userId - * @returns {ISystemUser} */ - async getUserOrThrowError(tenantId: number, userId: number): void { + async getUserOrThrowError(tenantId: number, userId: number): Promise { const { systemUserRepository } = this.repositories; const user = await systemUserRepository.getByIdAndTenant(userId, tenantId); @@ -72,7 +72,7 @@ export default class UsersService { async deleteUser(tenantId: number, userId: number): Promise { const { systemUserRepository } = this.repositories; await this.getUserOrThrowError(tenantId, userId); - + this.logger.info('[users] trying to delete the given user.', { tenantId, userId }); await systemUserRepository.deleteById(userId); @@ -84,7 +84,8 @@ export default class UsersService { * @param {number} tenantId * @param {number} userId */ - async activateUser(tenantId: number, userId: number): Promise { + async activateUser(tenantId: number, userId: number, authorizedUser: ISystemUser): Promise { + this.throwErrorIfUserIdSameAuthorizedUser(userId, authorizedUser); const { systemUserRepository } = this.repositories; const user = await this.getUserOrThrowError(tenantId, userId); @@ -99,8 +100,10 @@ export default class UsersService { * @param {number} userId * @return {Promise} */ - async inactivateUser(tenantId: number, userId: number): Promise { + async inactivateUser(tenantId: number, userId: number, authorizedUser: ISystemUser): Promise { + this.throwErrorIfUserIdSameAuthorizedUser(userId, authorizedUser); const { systemUserRepository } = this.repositories; + const user = await this.getUserOrThrowError(tenantId, userId); this.throwErrorIfUserInactive(user); @@ -114,6 +117,7 @@ export default class UsersService { */ async getList(tenantId: number) { const users = await SystemUser.query() + .whereNotDeleted() .where('tenant_id', tenantId); return users; @@ -149,4 +153,15 @@ export default class UsersService { throw new ServiceError('user_already_inactive'); } } + + /** + * Throw service error in case the given user same the authorized user. + * @param {number} userId + * @param {ISystemUser} authorizedUser + */ + throwErrorIfUserIdSameAuthorizedUser(userId: number, authorizedUser: ISystemUser) { + if (userId === authorizedUser.id) { + throw new ServiceError('user_same_the_authorized_user'); + } + } } \ No newline at end of file diff --git a/server/src/system/migrations/20190822214242_create_users_table.js b/server/src/system/migrations/20190822214242_create_users_table.js index 9b75f438b..50fa5e42c 100644 --- a/server/src/system/migrations/20190822214242_create_users_table.js +++ b/server/src/system/migrations/20190822214242_create_users_table.js @@ -8,9 +8,8 @@ exports.up = function (knex) { table.string('phone_number').unique(); table.string('password'); table.boolean('active'); - table.integer('role_id').unique(); table.string('language'); - + table.integer('tenant_id').unsigned(); table.date('invite_accepted_at'); @@ -18,10 +17,6 @@ exports.up = function (knex) { table.dateTime('deleted_at'); table.timestamps(); - }).then(() => { - // knex.seed.run({ - // specific: 'seed_users.js', - // }) }); }; diff --git a/server/src/system/repositories/SystemUserRepository.ts b/server/src/system/repositories/SystemUserRepository.ts index 5e90f86ad..ccfa76fa4 100644 --- a/server/src/system/repositories/SystemUserRepository.ts +++ b/server/src/system/repositories/SystemUserRepository.ts @@ -98,7 +98,7 @@ export default class SystemUserRepository extends SystemRepository { * @param {number} userId */ async deleteById(userId: number) { - const user = this.getById(userId); + const user = await this.getById(userId); await SystemUser.query().where('id', userId).delete(); this.flushUserCache(user); }