From f78d6efe2730f45cb5ccdbe14daca01a2a0567f3 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 8 May 2025 18:10:02 +0200 Subject: [PATCH] refactor(nestjs): hook up auth endpoints --- .../interceptors/to-json.interceptor.ts | 24 +++++++++ packages/server/src/main.ts | 3 ++ .../src/modules/Auth/Auth.controller.ts | 21 ++++++-- .../server/src/modules/Auth/Auth.module.ts | 8 ++- .../src/modules/Auth/Authed.controller.ts | 23 ++++++++ .../Auth/commands/AuthSignin.service.ts | 2 +- .../Auth/queries/GetAuthedAccount.service.ts | 21 ++++++++ .../queries/GetAuthedAccount.transformer.ts | 19 +++++++ .../modules/Dashboard/Dashboard.controller.ts | 4 +- .../modules/Dashboard/Dashboard.service.ts | 12 ++--- .../Organization/Organization.controller.ts | 31 +++++------ .../Organization/Organization.module.ts | 4 ++ .../SyncSystemUserToTenant.service.ts | 46 ++++++++++++++++ .../queries/GetCurrentOrganization.service.ts | 3 +- .../SyncSystemUserToTenant.subscriber.ts | 19 +++++++ .../EnsureTenantIsInitialized.guard.ts | 17 +++--- .../Tenancy/EnsureTenantIsSeeded.guards.ts | 19 ++++--- .../modules/Tenancy/TenancyContext.service.ts | 2 +- .../TenancyModels/models/TenantUser.model.ts | 4 +- .../TransformerInjectable.service.ts | 5 +- .../src/containers/Authentication/Login.tsx | 30 +++++------ .../containers/Authentication/Register.tsx | 28 +++++----- .../webapp/src/hooks/query/authentication.tsx | 52 +++++++++++-------- .../webapp/src/hooks/query/organization.tsx | 9 +++- packages/webapp/src/hooks/query/users.tsx | 7 +-- packages/webapp/src/hooks/useRequest.tsx | 2 +- 26 files changed, 304 insertions(+), 111 deletions(-) create mode 100644 packages/server/src/common/interceptors/to-json.interceptor.ts create mode 100644 packages/server/src/modules/Auth/Authed.controller.ts create mode 100644 packages/server/src/modules/Auth/queries/GetAuthedAccount.service.ts create mode 100644 packages/server/src/modules/Auth/queries/GetAuthedAccount.transformer.ts create mode 100644 packages/server/src/modules/Organization/commands/SyncSystemUserToTenant.service.ts create mode 100644 packages/server/src/modules/Organization/subscribers/SyncSystemUserToTenant.subscriber.ts diff --git a/packages/server/src/common/interceptors/to-json.interceptor.ts b/packages/server/src/common/interceptors/to-json.interceptor.ts new file mode 100644 index 000000000..cd0f03bdc --- /dev/null +++ b/packages/server/src/common/interceptors/to-json.interceptor.ts @@ -0,0 +1,24 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { Observable, map } from 'rxjs'; +import { mapValues, mapValuesDeep } from '@/utils/deepdash'; + +@Injectable() +export class ToJsonInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + return next.handle().pipe( + map((data) => { + return mapValuesDeep(data, (value) => { + if (value && typeof value.toJSON === 'function') { + return value.toJSON(); + } + return value; + }); + }), + ); + } +} diff --git a/packages/server/src/main.ts b/packages/server/src/main.ts index c6a9ddcc0..2bfe99fce 100644 --- a/packages/server/src/main.ts +++ b/packages/server/src/main.ts @@ -6,6 +6,7 @@ import './utils/moment-mysql'; import { AppModule } from './modules/App/App.module'; import { ServiceErrorFilter } from './common/filters/service-error.filter'; import { ValidationPipe } from './common/pipes/ClassValidation.pipe'; +import { ToJsonInterceptor } from './common/interceptors/to-json.interceptor'; global.__static_dirname = path.join(__dirname, '../static'); global.__views_dirname = path.join(global.__static_dirname, '/views'); @@ -18,6 +19,8 @@ async function bootstrap() { // create and mount the middleware manually here app.use(new ClsMiddleware({}).use); + app.useGlobalInterceptors(new ToJsonInterceptor()); + // use the validation pipe globally app.useGlobalPipes(new ValidationPipe()); diff --git a/packages/server/src/modules/Auth/Auth.controller.ts b/packages/server/src/modules/Auth/Auth.controller.ts index bf1bb6798..eff81e6f8 100644 --- a/packages/server/src/modules/Auth/Auth.controller.ts +++ b/packages/server/src/modules/Auth/Auth.controller.ts @@ -1,21 +1,22 @@ -// @ts-nocheck import { Body, Controller, Get, + Inject, Param, Post, Request, UseGuards, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBody, ApiParam } from '@nestjs/swagger'; -import { JwtAuthGuard, PublicRoute } from './guards/jwt.guard'; +import { PublicRoute } from './guards/jwt.guard'; import { AuthenticationApplication } from './AuthApplication.sevice'; import { AuthSignupDto } from './dtos/AuthSignup.dto'; import { AuthSigninDto } from './dtos/AuthSignin.dto'; import { LocalAuthGuard } from './guards/local.guard'; -import { JwtService } from '@nestjs/jwt'; import { AuthSigninService } from './commands/AuthSignin.service'; +import { TenantModel } from '../System/models/TenantModel'; +import { SystemUser } from '../System/models/SystemUser'; @Controller('/auth') @ApiTags('Auth') @@ -24,15 +25,25 @@ export class AuthController { constructor( private readonly authApp: AuthenticationApplication, private readonly authSignin: AuthSigninService, + + @Inject(TenantModel.name) + private readonly tenantModel: typeof TenantModel, ) {} @Post('/signin') @UseGuards(LocalAuthGuard) @ApiOperation({ summary: 'Sign in a user' }) @ApiBody({ type: AuthSigninDto }) - signin(@Request() req: Request, @Body() signinDto: AuthSigninDto) { + async signin(@Request() req: Request & { user: SystemUser }, @Body() signinDto: AuthSigninDto) { const { user } = req; - return { access_token: this.authSignin.signToken(user) }; + const tenant = await this.tenantModel.query().findById(user.tenantId); + + return { + accessToken: this.authSignin.signToken(user), + organizationId: tenant.organizationId, + tenantId: tenant.id, + userId: user.id, + }; } @Post('/signup') diff --git a/packages/server/src/modules/Auth/Auth.module.ts b/packages/server/src/modules/Auth/Auth.module.ts index acb08c881..b220ced4c 100644 --- a/packages/server/src/modules/Auth/Auth.module.ts +++ b/packages/server/src/modules/Auth/Auth.module.ts @@ -28,11 +28,14 @@ import { MailModule } from '../Mail/Mail.module'; import { ConfigService } from '@nestjs/config'; import { InjectSystemModel } from '../System/SystemModels/SystemModels.module'; import { GetAuthMetaService } from './queries/GetAuthMeta.service'; +import { AuthedController } from './Authed.controller'; +import { GetAuthenticatedAccount } from './queries/GetAuthedAccount.service'; +import { TenancyModule } from '../Tenancy/Tenancy.module'; const models = [InjectSystemModel(PasswordReset)]; @Module({ - controllers: [AuthController], + controllers: [AuthController, AuthedController], imports: [ MailModule, PassportModule.register({ defaultStrategy: 'jwt' }), @@ -45,9 +48,9 @@ const models = [InjectSystemModel(PasswordReset)]; }), }), TenantDBManagerModule, + TenancyModule, BullModule.registerQueue({ name: SendResetPasswordMailQueue }), BullModule.registerQueue({ name: SendSignupVerificationMailQueue }), - ], exports: [...models], providers: [ @@ -65,6 +68,7 @@ const models = [InjectSystemModel(PasswordReset)]; SendResetPasswordMailProcessor, SendSignupVerificationMailProcessor, GetAuthMetaService, + GetAuthenticatedAccount, { provide: APP_GUARD, useClass: JwtAuthGuard, diff --git a/packages/server/src/modules/Auth/Authed.controller.ts b/packages/server/src/modules/Auth/Authed.controller.ts new file mode 100644 index 000000000..846ab9dab --- /dev/null +++ b/packages/server/src/modules/Auth/Authed.controller.ts @@ -0,0 +1,23 @@ +import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { GetAuthenticatedAccount } from './queries/GetAuthedAccount.service'; +import { Controller, Get } from '@nestjs/common'; +import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards'; +import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard'; + +@Controller('/auth') +@ApiTags('Auth') +@IgnoreTenantSeededRoute() +@IgnoreTenantInitializedRoute() +export class AuthedController { + constructor( + private readonly getAuthedAccountService: GetAuthenticatedAccount, + ) {} + + @Get('/account') + @ApiOperation({ summary: 'Retrieve the authenticated account' }) + async getAuthedAcccount() { + const data = await this.getAuthedAccountService.getAccount(); + + return { data }; + } +} diff --git a/packages/server/src/modules/Auth/commands/AuthSignin.service.ts b/packages/server/src/modules/Auth/commands/AuthSignin.service.ts index 375a6bfad..836f1b507 100644 --- a/packages/server/src/modules/Auth/commands/AuthSignin.service.ts +++ b/packages/server/src/modules/Auth/commands/AuthSignin.service.ts @@ -43,7 +43,7 @@ export class AuthSigninService { } if (!user.verified) { throw new UnauthorizedException( - `The user is not verified yet, check out your mail inbox.` + `The user is not verified yet, check out your mail inbox.`, ); } return user; diff --git a/packages/server/src/modules/Auth/queries/GetAuthedAccount.service.ts b/packages/server/src/modules/Auth/queries/GetAuthedAccount.service.ts new file mode 100644 index 000000000..e507ccc32 --- /dev/null +++ b/packages/server/src/modules/Auth/queries/GetAuthedAccount.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; +import { GetAuthedAccountTransformer } from './GetAuthedAccount.transformer'; + +@Injectable() +export class GetAuthenticatedAccount { + constructor( + private readonly tenancyContext: TenancyContext, + private readonly transformer: TransformerInjectable, + ) {} + + async getAccount() { + const account = await this.tenancyContext.getSystemUser(); + + return this.transformer.transform( + account, + new GetAuthedAccountTransformer(), + ); + } +} diff --git a/packages/server/src/modules/Auth/queries/GetAuthedAccount.transformer.ts b/packages/server/src/modules/Auth/queries/GetAuthedAccount.transformer.ts new file mode 100644 index 000000000..03535c1b0 --- /dev/null +++ b/packages/server/src/modules/Auth/queries/GetAuthedAccount.transformer.ts @@ -0,0 +1,19 @@ +import { Transformer } from '@/modules/Transformer/Transformer'; + +export class GetAuthedAccountTransformer extends Transformer { + /** + * Include these attributes to sale invoice object. + * @returns {Array} + */ + public includeAttributes = (): string[] => { + return [ + 'firstName', + 'lastName', + 'email', + 'active', + 'language', + 'tenantId', + 'verified', + ]; + }; +} diff --git a/packages/server/src/modules/Dashboard/Dashboard.controller.ts b/packages/server/src/modules/Dashboard/Dashboard.controller.ts index ea715996e..aa5354e36 100644 --- a/packages/server/src/modules/Dashboard/Dashboard.controller.ts +++ b/packages/server/src/modules/Dashboard/Dashboard.controller.ts @@ -1,6 +1,6 @@ -import { DashboardService } from './Dashboard.service'; -import { Controller, Get } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { Controller, Get } from '@nestjs/common'; +import { DashboardService } from './Dashboard.service'; @ApiTags('dashboard') @Controller('dashboard') diff --git a/packages/server/src/modules/Dashboard/Dashboard.service.ts b/packages/server/src/modules/Dashboard/Dashboard.service.ts index 8f6987a10..05408ff6b 100644 --- a/packages/server/src/modules/Dashboard/Dashboard.service.ts +++ b/packages/server/src/modules/Dashboard/Dashboard.service.ts @@ -29,8 +29,6 @@ export class DashboardService { /** * Retrieve dashboard meta. - * @param {number} tenantId - * @param {number} authorizedUser */ public getBootMeta = async (): Promise => { // Retrieves all orgnaization abilities. @@ -60,17 +58,19 @@ export class DashboardService { /** * Retrieve the boot abilities. - * @returns + * @returns {Promise} */ private getBootAbilities = async (): Promise => { const authorizedUser = await this.tenancyContext.getSystemUser(); - const tenantUser = await this.tenantUserModel().query() + const tenantUser = await this.tenantUserModel() + .query() .findOne('systemUserId', authorizedUser.id) - .withGraphFetched('role.permissions'); + .withGraphFetched('role.permissions') + .throwIfNotFound(); return tenantUser.role.slug === 'admin' - ? [{ subject: 'all', action: 'manage' }] + ? [{ subject: 'all', ability: 'manage' }] : this.transformRoleAbility(tenantUser.role.permissions); }; } diff --git a/packages/server/src/modules/Organization/Organization.controller.ts b/packages/server/src/modules/Organization/Organization.controller.ts index aafcced04..6fcca3d98 100644 --- a/packages/server/src/modules/Organization/Organization.controller.ts +++ b/packages/server/src/modules/Organization/Organization.controller.ts @@ -8,6 +8,7 @@ import { Req, Res, Next, + HttpCode, } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; import { BuildOrganizationService } from './commands/BuildOrganization.service'; @@ -32,60 +33,52 @@ export class OrganizationController { ) {} @Post('build') + @HttpCode(200) @ApiOperation({ summary: 'Build organization database' }) @ApiBody({ type: BuildOrganizationDto }) @ApiResponse({ status: 200, description: 'The organization database has been initialized', }) - async build( - @Body() buildDTO: BuildOrganizationDto, - @Req() req: Request, - @Res() res: Response, - ) { + async build(@Body() buildDTO: BuildOrganizationDto) { const result = await this.buildOrganizationService.buildRunJob(buildDTO); - return res.status(200).send({ + return { type: 'success', code: 'ORGANIZATION.DATABASE.INITIALIZED', message: 'The organization database has been initialized.', data: result, - }); + }; } @Get('current') + @HttpCode(200) @ApiOperation({ summary: 'Get current organization' }) @ApiResponse({ status: 200, description: 'Returns the current organization', }) - async currentOrganization( - @Req() req: Request, - @Res() res: Response, - @Next() next: NextFunction, - ) { + async currentOrganization() { const organization = await this.getCurrentOrgService.getCurrentOrganization(); - return res.status(200).send({ organization }); + return { organization }; } @Put() + @HttpCode(200) @ApiOperation({ summary: 'Update organization information' }) @ApiBody({ type: UpdateOrganizationDto }) @ApiResponse({ status: 200, description: 'Organization information has been updated successfully', }) - async updateOrganization( - @Body() updateDTO: UpdateOrganizationDto, - @Res() res: Response, - ) { + async updateOrganization(@Body() updateDTO: UpdateOrganizationDto) { await this.updateOrganizationService.execute(updateDTO); - return res.status(200).send({ + return { code: 200, message: 'Organization information has been updated successfully.', - }); + }; } } diff --git a/packages/server/src/modules/Organization/Organization.module.ts b/packages/server/src/modules/Organization/Organization.module.ts index 43f957151..8987b29af 100644 --- a/packages/server/src/modules/Organization/Organization.module.ts +++ b/packages/server/src/modules/Organization/Organization.module.ts @@ -10,6 +10,8 @@ import { CommandOrganizationValidators } from './commands/CommandOrganizationVal import { TenancyContext } from '../Tenancy/TenancyContext.service'; import { TenantDBManagerModule } from '../TenantDBManager/TenantDBManager.module'; import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBaseCurrencyLocking.service'; +import { SyncSystemUserToTenantService } from './commands/SyncSystemUserToTenant.service'; +import { SyncSystemUserToTenantSubscriber } from './subscribers/SyncSystemUserToTenant.subscriber'; @Module({ providers: [ @@ -20,6 +22,8 @@ import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBase OrganizationBuildProcessor, CommandOrganizationValidators, OrganizationBaseCurrencyLocking, + SyncSystemUserToTenantService, + SyncSystemUserToTenantSubscriber ], imports: [ BullModule.registerQueue({ name: OrganizationBuildQueue }), diff --git a/packages/server/src/modules/Organization/commands/SyncSystemUserToTenant.service.ts b/packages/server/src/modules/Organization/commands/SyncSystemUserToTenant.service.ts new file mode 100644 index 000000000..f3e07c318 --- /dev/null +++ b/packages/server/src/modules/Organization/commands/SyncSystemUserToTenant.service.ts @@ -0,0 +1,46 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { pick } from 'lodash'; +import { Role } from '@/modules/Roles/models/Role.model'; +import { SystemUser } from '@/modules/System/models/SystemUser'; +import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { TenantUser } from '@/modules/Tenancy/TenancyModels/models/TenantUser.model'; + +@Injectable() +export class SyncSystemUserToTenantService { + constructor( + @Inject(TenantUser.name) + private readonly tenantUserModel: TenantModelProxy, + + @Inject(Role.name) + private readonly roleModel: TenantModelProxy, + + @Inject(SystemUser.name) + private readonly systemUserModel: typeof SystemUser, + ) {} + + /** + * Syncs system user to tenant user. + * @param {number} systemUserId - System user id. + */ + public async syncSystemUserToTenant(systemUserId: number) { + const adminRole = await this.roleModel().query().findOne('slug', 'admin'); + const systemUser = await this.systemUserModel + .query() + .findById(systemUserId); + + await this.tenantUserModel() + .query() + .insert({ + ...pick(systemUser, [ + 'firstName', + 'lastName', + 'phoneNumber', + 'email', + 'active', + 'inviteAcceptedAt', + ]), + systemUserId: systemUser.id, + roleId: adminRole.id, + }); + } +} diff --git a/packages/server/src/modules/Organization/queries/GetCurrentOrganization.service.ts b/packages/server/src/modules/Organization/queries/GetCurrentOrganization.service.ts index 6c981a600..f23909dd2 100644 --- a/packages/server/src/modules/Organization/queries/GetCurrentOrganization.service.ts +++ b/packages/server/src/modules/Organization/queries/GetCurrentOrganization.service.ts @@ -2,6 +2,7 @@ import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { throwIfTenantNotExists } from '../Organization/_utils'; import { TenantModel } from '@/modules/System/models/TenantModel'; import { Injectable } from '@nestjs/common'; +import { ModelObject } from 'objection'; @Injectable() export class GetCurrentOrganizationService { @@ -12,7 +13,7 @@ export class GetCurrentOrganizationService { * @param {number} tenantId * @returns {Promise} */ - async getCurrentOrganization(): Promise { + async getCurrentOrganization(): Promise> { const tenant = await this.tenancyContext .getTenant() .withGraphFetched('subscriptions') diff --git a/packages/server/src/modules/Organization/subscribers/SyncSystemUserToTenant.subscriber.ts b/packages/server/src/modules/Organization/subscribers/SyncSystemUserToTenant.subscriber.ts new file mode 100644 index 000000000..80d81a1b2 --- /dev/null +++ b/packages/server/src/modules/Organization/subscribers/SyncSystemUserToTenant.subscriber.ts @@ -0,0 +1,19 @@ +import { OnEvent } from '@nestjs/event-emitter'; +import { SyncSystemUserToTenantService } from '../commands/SyncSystemUserToTenant.service'; +import { events } from '@/common/events/events'; +import { IOrganizationBuildEventPayload } from '../Organization.types'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class SyncSystemUserToTenantSubscriber { + constructor( + private readonly syncSystemUserToTenantService: SyncSystemUserToTenantService, + ) {} + + @OnEvent(events.organization.build) + async onOrgBuildSyncSystemUser({ systemUser }: IOrganizationBuildEventPayload) { + await this.syncSystemUserToTenantService.syncSystemUserToTenant( + systemUser.id, + ); + } +} diff --git a/packages/server/src/modules/Tenancy/EnsureTenantIsInitialized.guard.ts b/packages/server/src/modules/Tenancy/EnsureTenantIsInitialized.guard.ts index 4c05573fc..eca9b58a0 100644 --- a/packages/server/src/modules/Tenancy/EnsureTenantIsInitialized.guard.ts +++ b/packages/server/src/modules/Tenancy/EnsureTenantIsInitialized.guard.ts @@ -10,11 +10,15 @@ import { Reflector } from '@nestjs/core'; import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants'; export const IS_IGNORE_TENANT_INITIALIZED = 'IS_IGNORE_TENANT_INITIALIZED'; -export const IgnoreTenantInitializedRoute = () => SetMetadata(IS_IGNORE_TENANT_INITIALIZED, true); +export const IgnoreTenantInitializedRoute = () => + SetMetadata(IS_IGNORE_TENANT_INITIALIZED, true); @Injectable() export class EnsureTenantIsInitializedGuard implements CanActivate { - constructor(private readonly tenancyContext: TenancyContext, private reflector: Reflector) {} + constructor( + private readonly tenancyContext: TenancyContext, + private reflector: Reflector, + ) {} /** * Validate the tenant of the current request is initialized.. @@ -22,10 +26,11 @@ export class EnsureTenantIsInitializedGuard implements CanActivate { * @returns {Promise} */ async canActivate(context: ExecutionContext): Promise { - const isIgnoreEnsureTenantInitialized = this.reflector.getAllAndOverride( - IS_IGNORE_TENANT_INITIALIZED, - [context.getHandler(), context.getClass()], - ); + const isIgnoreEnsureTenantInitialized = + this.reflector.getAllAndOverride(IS_IGNORE_TENANT_INITIALIZED, [ + context.getHandler(), + context.getClass(), + ]); const isPublic = this.reflector.getAllAndOverride( IS_PUBLIC_ROUTE, [context.getHandler(), context.getClass()], diff --git a/packages/server/src/modules/Tenancy/EnsureTenantIsSeeded.guards.ts b/packages/server/src/modules/Tenancy/EnsureTenantIsSeeded.guards.ts index b9929db92..17a1e2e76 100644 --- a/packages/server/src/modules/Tenancy/EnsureTenantIsSeeded.guards.ts +++ b/packages/server/src/modules/Tenancy/EnsureTenantIsSeeded.guards.ts @@ -11,11 +11,15 @@ import { Reflector } from '@nestjs/core'; import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants'; export const IS_IGNORE_TENANT_SEEDED = 'IS_IGNORE_TENANT_SEEDED'; -export const IgnoreTenantSeededRoute = () => SetMetadata(IS_IGNORE_TENANT_SEEDED, true); +export const IgnoreTenantSeededRoute = () => + SetMetadata(IS_IGNORE_TENANT_SEEDED, true); @Injectable() export class EnsureTenantIsSeededGuard implements CanActivate { - constructor(private readonly tenancyContext: TenancyContext, private reflector: Reflector) {} + constructor( + private readonly tenancyContext: TenancyContext, + private reflector: Reflector, + ) {} /** * Validate the tenant of the current request is seeded. @@ -27,15 +31,16 @@ export class EnsureTenantIsSeededGuard implements CanActivate { IS_PUBLIC_ROUTE, [context.getHandler(), context.getClass()], ); - const isIgnoreEnsureTenantSeeded = this.reflector.getAllAndOverride( - IS_IGNORE_TENANT_SEEDED, - [context.getHandler(), context.getClass()], - ); + const isIgnoreEnsureTenantSeeded = + this.reflector.getAllAndOverride(IS_IGNORE_TENANT_SEEDED, [ + context.getHandler(), + context.getClass(), + ]); if (isPublic || isIgnoreEnsureTenantSeeded) { return true; } const tenant = await this.tenancyContext.getTenant(); - + if (!tenant.seededAt) { throw new UnauthorizedException({ message: 'Tenant database is not seeded with initial data yet.', diff --git a/packages/server/src/modules/Tenancy/TenancyContext.service.ts b/packages/server/src/modules/Tenancy/TenancyContext.service.ts index e294e6695..93b35662e 100644 --- a/packages/server/src/modules/Tenancy/TenancyContext.service.ts +++ b/packages/server/src/modules/Tenancy/TenancyContext.service.ts @@ -49,6 +49,6 @@ export class TenancyContext { // Get the user from the request headers. const userId = this.cls.get('userId'); - return this.systemUserModel.query().findOne({ id: userId }); + return this.systemUserModel.query().findById(userId); } } diff --git a/packages/server/src/modules/Tenancy/TenancyModels/models/TenantUser.model.ts b/packages/server/src/modules/Tenancy/TenancyModels/models/TenantUser.model.ts index 03fb05ab4..ec7b54b3f 100644 --- a/packages/server/src/modules/Tenancy/TenancyModels/models/TenantUser.model.ts +++ b/packages/server/src/modules/Tenancy/TenancyModels/models/TenantUser.model.ts @@ -49,7 +49,7 @@ export class TenantUser extends TenantBaseModel { * Relationship mapping. */ static get relationMappings() { - const Role = require('models/Role'); + const { Role } = require('../../../Roles/models/Role.model'); return { /** @@ -57,7 +57,7 @@ export class TenantUser extends TenantBaseModel { */ role: { relation: Model.BelongsToOneRelation, - modelClass: Role.default, + modelClass: Role, join: { from: 'users.roleId', to: 'roles.id', diff --git a/packages/server/src/modules/Transformer/TransformerInjectable.service.ts b/packages/server/src/modules/Transformer/TransformerInjectable.service.ts index 125e659e4..f80da66a2 100644 --- a/packages/server/src/modules/Transformer/TransformerInjectable.service.ts +++ b/packages/server/src/modules/Transformer/TransformerInjectable.service.ts @@ -36,7 +36,7 @@ export class TransformerInjectable { */ async getTenantDateFormat() { const tenant = await this.tenancyContext.getTenant(true); - return tenant.metadata.dateFormat; + return tenant.metadata?.dateFormat; } /** @@ -55,7 +55,8 @@ export class TransformerInjectable { transformer.setContext(context); const dateFormat = await this.getTenantDateFormat(); - transformer.setDateFormat(dateFormat); + + transformer.setDateFormat(dateFormat || 'DD-MM-YYYY'); transformer.setOptions(options); return transformer.work(object); diff --git a/packages/webapp/src/containers/Authentication/Login.tsx b/packages/webapp/src/containers/Authentication/Login.tsx index 0d80d4306..d904d05c2 100644 --- a/packages/webapp/src/containers/Authentication/Login.tsx +++ b/packages/webapp/src/containers/Authentication/Login.tsx @@ -29,22 +29,19 @@ export default function Login() { const handleSubmit = (values, { setSubmitting }) => { loginMutate({ - crediential: values.crediential, + email: values.crediential, password: values.password, - }).catch( - ({ - response: { - data: { errors }, - }, - }) => { - const toastBuilders = transformLoginErrorsToToasts(errors); + }).catch(({ response }) => { + const { + data: { errors }, + } = response; + const toastBuilders = transformLoginErrorsToToasts(errors); - toastBuilders.forEach((builder) => { - Toaster.show(builder); - }); - setSubmitting(false); - }, - ); + toastBuilders.forEach((builder) => { + Toaster.show(builder); + }); + setSubmitting(false); + }); }; return ( @@ -70,7 +67,10 @@ function LoginFooterLinks() { {!signupDisabled && ( - + {' '} + + + )} diff --git a/packages/webapp/src/containers/Authentication/Register.tsx b/packages/webapp/src/containers/Authentication/Register.tsx index ccdd3eaa5..9df270d21 100644 --- a/packages/webapp/src/containers/Authentication/Register.tsx +++ b/packages/webapp/src/containers/Authentication/Register.tsx @@ -53,22 +53,20 @@ export default function RegisterUserForm() { }, ); }) - .catch( - ({ - response: { - data: { errors }, - }, - }) => { - const formErrors = transformRegisterErrorsToForm(errors); - const toastMessages = transformRegisterToastMessages(errors); + .catch(({ response }) => { + const { + data: { errors }, + } = response; + + const formErrors = transformRegisterErrorsToForm(errors); + const toastMessages = transformRegisterToastMessages(errors); - toastMessages.forEach((toastMessage) => { - AppToaster.show(toastMessage); - }); - setErrors(formErrors); - setSubmitting(false); - }, - ); + toastMessages.forEach((toastMessage) => { + AppToaster.show(toastMessage); + }); + setErrors(formErrors); + setSubmitting(false); + }); }; return ( diff --git a/packages/webapp/src/hooks/query/authentication.tsx b/packages/webapp/src/hooks/query/authentication.tsx index 5d91dbffe..9235cfe81 100644 --- a/packages/webapp/src/hooks/query/authentication.tsx +++ b/packages/webapp/src/hooks/query/authentication.tsx @@ -13,17 +13,27 @@ import { useSetTenantId, } from '../state'; +const AuthRoute = { + Signin: 'auth/signin', + Signup: 'auth/signup', + SignupVerify: 'auth/signup/verify', + SignupVerifyResend: 'auth/signup/verify/resend', + SendResetPassword: 'auth/send_reset_password', + ForgetPassword: 'auth/reset_password/:token', + AuthMeta: 'auth/meta', +}; + /** * Saves the response data to cookies. */ export function setAuthLoginCookies(data) { - setCookie('token', data.token); - setCookie('authenticated_user_id', data.user.id); - setCookie('organization_id', data.tenant.organization_id); - setCookie('tenant_id', data.tenant.id); + setCookie('token', data.access_token); + setCookie('authenticated_user_id', data.user_id); + setCookie('organization_id', data.organization_id); + setCookie('tenant_id', data.tenant_id); - if (data?.tenant?.metadata?.language) - setCookie('locale', data.tenant.metadata.language); + // if (data?.tenant?.metadata?.language) + // setCookie('locale', data.tenant.metadata.language); } /** @@ -38,7 +48,7 @@ export const useAuthLogin = (props) => { const setTenantId = useSetTenantId(); const setLocale = useSetLocale(); - return useMutation((values) => apiRequest.post('auth/login', values), { + return useMutation((values) => apiRequest.post(AuthRoute.Signin, values), { select: (res) => res.data, onSuccess: (res) => { // Set authentication cookies. @@ -46,14 +56,14 @@ export const useAuthLogin = (props) => { batch(() => { // Sets the auth metadata to global state. - setAuthToken(res.data.token); - setOrganizationId(res.data.tenant.organization_id); - setUserId(res.data.user.id); - setTenantId(res.data.tenant.id); + setAuthToken(res.data.access_token); + setOrganizationId(res.data.organization_id); + setTenantId(res.data.tenant_id); + setUserId(res.data.user_id); - if (res.data?.tenant?.metadata?.language) { - setLocale(res.data?.tenant?.metadata?.language); - } + // if (res.data?.tenant?.metadata?.language) { + // setLocale(res.data?.tenant?.metadata?.language); + // } }); props?.onSuccess && props?.onSuccess(...args); }, @@ -68,7 +78,7 @@ export const useAuthRegister = (props) => { const apiRequest = useApiRequest(); return useMutation( - (values) => apiRequest.post('auth/register', values), + (values) => apiRequest.post(AuthRoute.Signup, values), props, ); }; @@ -80,7 +90,7 @@ export const useAuthSendResetPassword = (props) => { const apiRequest = useApiRequest(); return useMutation( - (email) => apiRequest.post('auth/send_reset_password', email), + (values) => apiRequest.post(AuthRoute.SendResetPassword, values), props, ); }; @@ -105,7 +115,7 @@ export const useAuthMetadata = (props = {}) => { [t.AUTH_METADATA_PAGE], { method: 'get', - url: `auth/meta`, + url: AuthRoute.AuthMeta, }, { select: (res) => res.data, @@ -116,13 +126,13 @@ export const useAuthMetadata = (props = {}) => { }; /** - * + * Resend the mail of signup verification. */ export const useAuthSignUpVerifyResendMail = (props) => { const apiRequest = useApiRequest(); return useMutation( - () => apiRequest.post('auth/register/verify/resend'), + () => apiRequest.post(AuthRoute.SignupVerifyResend), props, ); }; @@ -133,14 +143,14 @@ interface AuthSignUpVerifyValues { } /** - * + * Signup verification. */ export const useAuthSignUpVerify = (props) => { const apiRequest = useApiRequest(); return useMutation( (values: AuthSignUpVerifyValues) => - apiRequest.post('auth/register/verify', values), + apiRequest.post(AuthRoute.SignupVerify, values), props, ); }; diff --git a/packages/webapp/src/hooks/query/organization.tsx b/packages/webapp/src/hooks/query/organization.tsx index 72aaad94a..14a40cda3 100644 --- a/packages/webapp/src/hooks/query/organization.tsx +++ b/packages/webapp/src/hooks/query/organization.tsx @@ -7,6 +7,11 @@ import useApiRequest from '../useRequest'; import { useRequestQuery } from '../useQueryRequest'; import { useSetOrganizations, useSetSubscriptions } from '../state'; +const OrganizationRoute = { + Current: '/organization/current', + Build: '/organization/build', +}; + /** * Retrieve organizations of the authenticated user. */ @@ -36,7 +41,7 @@ export function useCurrentOrganization(props) { return useRequestQuery( [t.ORGANIZATION_CURRENT], - { method: 'get', url: `organization` }, + { method: 'get', url: OrganizationRoute.Current }, { select: (res) => res.data.organization, defaultData: {}, @@ -64,7 +69,7 @@ export function useOrganizationSetup() { const queryClient = useQueryClient(); return useMutation( - (values) => apiRequest.post(`organization/build`, values), + (values) => apiRequest.post(OrganizationRoute.Build, values), { onSuccess: (res) => { queryClient.invalidateQueries(t.ORGANIZATION_CURRENT); diff --git a/packages/webapp/src/hooks/query/users.tsx b/packages/webapp/src/hooks/query/users.tsx index 00452091a..c91bf0f39 100644 --- a/packages/webapp/src/hooks/query/users.tsx +++ b/packages/webapp/src/hooks/query/users.tsx @@ -137,13 +137,14 @@ export function useAuthenticatedAccount(props) { ['AuthenticatedAccount'], { method: 'get', - url: `account`, + url: `auth/account`, }, { select: (response) => response.data.data, defaultData: {}, onSuccess: (data) => { - setEmailConfirmed(data.is_verified, data.email); + debugger; + setEmailConfirmed(data.verified, data.email); }, ...props, }, @@ -160,7 +161,7 @@ export const useDashboardMeta = (props) => { [t.DASHBOARD_META], { method: 'get', url: 'dashboard/boot' }, { - select: (res) => res.data.meta, + select: (res) => res.data, defaultData: {}, ...props, }, diff --git a/packages/webapp/src/hooks/useRequest.tsx b/packages/webapp/src/hooks/useRequest.tsx index 542ccb81d..b3d249674 100644 --- a/packages/webapp/src/hooks/useRequest.tsx +++ b/packages/webapp/src/hooks/useRequest.tsx @@ -30,7 +30,7 @@ export default function useApiRequest() { const locale = currentLocale; if (token) { - request.headers['X-Access-Token'] = token; + request.headers['Authorization'] = `Bearer ${token}`; } if (organizationId) { request.headers['organization-id'] = organizationId;