diff --git a/.env.demo b/.env.demo index f520dbc17..c73e721c6 100644 --- a/.env.demo +++ b/.env.demo @@ -156,7 +156,7 @@ OTEL_LOGS_OTLP_ENDPOINT='http://localhost:4318/v1/logs' OTEL_HEADERS_KEY=88ca6b1XXXXXXXXXXXXXXXXXXXXXXXXXXX OTEL_LOGGER_NAME='credebl-platform-logger' HOSTNAME='localhost' - +SESSIONS_LIMIT=10 # SSO # To add more clients, simply add comma separated values of client names SUPPORTED_SSO_CLIENTS=CREDEBL diff --git a/.env.sample b/.env.sample index 7b50c2c6a..a041eba5d 100644 --- a/.env.sample +++ b/.env.sample @@ -153,7 +153,7 @@ SCHEMA_FILE_SERVER_URL= // Please provide schema URL SCHEMA_FILE_SERVER_TOKEN=xxxxxxxx // Please provide schema file server token for polygon FILEUPLOAD_CACHE_TTL= //Provide file upload cache ttl - +SESSIONS_LIMIT= //Provide limits of sessions FIELD_UPLOAD_SIZE= //Provide field upload size IS_ECOSYSTEM_ENABLE= //Set this flag to `true` to enable the ecosystem, or `false` to disable it. @@ -180,6 +180,7 @@ HOSTNAME='localhost' # Hostname or unique identifier # SSO # To add more clients, simply add comma separated values of client names SUPPORTED_SSO_CLIENTS=CREDEBL +NEXTAUTH_PROTOCOL= # Key for agent base wallet AGENT_API_KEY='supersecret-that-too-16chars' diff --git a/apps/api-gateway/src/authz/authz.controller.ts b/apps/api-gateway/src/authz/authz.controller.ts index ef55abec9..c31c8a1f1 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -7,18 +7,20 @@ import { Param, Post, Query, + Req, Res, UnauthorizedException, - UseFilters + UseFilters, + UseGuards } from '@nestjs/common'; import { AuthzService } from './authz.service'; import { CommonService } from '../../../../libs/common/src/common.service'; -import { ApiBody, ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiBody, ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UserEmailVerificationDto } from '../user/dto/create-user.dto'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { Response } from 'express'; +import { Response, Request } from 'express'; import { EmailVerificationDto } from '../user/dto/email-verify.dto'; import { AuthTokenResponse } from './dtos/auth-token-res.dto'; import { LoginUserDto } from '../user/dto/login-user.dto'; @@ -30,7 +32,10 @@ import { ResetTokenPasswordDto } from './dtos/reset-token-password'; import { RefreshTokenDto } from './dtos/refresh-token.dto'; import { getDefaultClient } from '../user/utils'; import { ClientAliasValidationPipe } from './decorators/user-auth-client'; - +import { SessionGuard } from './guards/session.guard'; +import { UserLogoutDto } from './dtos/user-logout.dto'; +import { AuthGuard } from '@nestjs/passport'; +import { ISessionData } from 'apps/user/interfaces/user.interface'; @Controller('auth') @ApiTags('auth') @UseFilters(CustomExceptionFilter) @@ -139,6 +144,7 @@ export class AuthzController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } + /** * Authenticates a user and returns an access token. * @@ -168,6 +174,42 @@ export class AuthzController { } } + /** + * Fetch session details + * + * @returns User's access token details + */ + @Get('/sessionDetails') + @UseGuards(SessionGuard) + @ApiOperation({ + summary: 'Fetch session details', + description: 'Fetch session details against logged in user' + }) + @ApiQuery({ + name: 'sessionId', + required: false + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: AuthTokenResponse }) + async sessionDetails(@Res() res: Response, @Req() req: Request, @Query() sessionId: ISessionData): Promise { + this.logger.debug(`in authz controller`); + + let sessionDetails; + if (0 < Object.keys(sessionId).length) { + sessionDetails = await this.authzService.getSession(sessionId); + } + if (req.user) { + sessionDetails = req.user; + } + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.fetchSession, + data: sessionDetails + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } + /** * Resets user's password. * @@ -263,4 +305,30 @@ export class AuthzController { return res.status(HttpStatus.OK).json(finalResponse); } + + /** + * Log out user. + * + * @body LogoutUserDto + * @returns Logged out user from current session + */ + @Post('/signout') + @ApiOperation({ + summary: 'Logout user', + description: 'Logout user from current session.' + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + @ApiBody({ type: UserLogoutDto }) + async logout(@Body() logoutUserDto: UserLogoutDto, @Res() res: Response): Promise { + await this.authzService.logout(logoutUserDto); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.logout + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } } diff --git a/apps/api-gateway/src/authz/authz.module.ts b/apps/api-gateway/src/authz/authz.module.ts index 78b862ca0..de7c42222 100644 --- a/apps/api-gateway/src/authz/authz.module.ts +++ b/apps/api-gateway/src/authz/authz.module.ts @@ -1,25 +1,27 @@ import { ClientsModule, Transport } from '@nestjs/microservices'; +import { Logger, Module } from '@nestjs/common'; import { AgentService } from '../agent/agent.service'; import { AuthzController } from './authz.controller'; import { AuthzService } from './authz.service'; +import { CommonConstants } from '@credebl/common/common.constant'; import { CommonModule } from '../../../../libs/common/src/common.module'; import { CommonService } from '../../../../libs/common/src/common.service'; import { ConnectionService } from '../connection/connection.service'; import { HttpModule } from '@nestjs/axios'; import { JwtStrategy } from './jwt.strategy'; import { MobileJwtStrategy } from './mobile-jwt.strategy'; -import { Module } from '@nestjs/common'; +import { NATSClient } from '@credebl/common/NATSClient'; +import { OrganizationService } from '../organization/organization.service'; import { PassportModule } from '@nestjs/passport'; +import { PrismaServiceModule } from '@credebl/prisma-service'; import { SocketGateway } from './socket.gateway'; import { SupabaseService } from '@credebl/supabase'; import { UserModule } from '../user/user.module'; +import { UserRepository } from 'apps/user/repositories/user.repository'; import { UserService } from '../user/user.service'; import { VerificationService } from '../verification/verification.service'; import { getNatsOptions } from '@credebl/common/nats.config'; -import { OrganizationService } from '../organization/organization.service'; -import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -36,7 +38,8 @@ import { NATSClient } from '@credebl/common/NATSClient'; }, CommonModule ]), - UserModule + UserModule, + PrismaServiceModule ], providers: [ JwtStrategy, @@ -50,12 +53,11 @@ import { NATSClient } from '@credebl/common/NATSClient'; CommonService, UserService, SupabaseService, - OrganizationService - ], - exports: [ - PassportModule, - AuthzService + OrganizationService, + UserRepository, + Logger ], + exports: [PassportModule, AuthzService], controllers: [AuthzController] }) -export class AuthzModule { } \ No newline at end of file +export class AuthzModule {} diff --git a/apps/api-gateway/src/authz/authz.service.ts b/apps/api-gateway/src/authz/authz.service.ts index 8ee8a5b4d..f9dc909f6 100644 --- a/apps/api-gateway/src/authz/authz.service.ts +++ b/apps/api-gateway/src/authz/authz.service.ts @@ -17,6 +17,8 @@ import { ForgotPasswordDto } from './dtos/forgot-password.dto'; import { ResetTokenPasswordDto } from './dtos/reset-token-password'; import { NATSClient } from '@credebl/common/NATSClient'; import { user } from '@prisma/client'; +import { ISessionDetails } from 'apps/user/interfaces/user.interface'; +import { UserLogoutDto } from './dtos/user-logout.dto'; @Injectable() @WebSocketGateway() export class AuthzService extends BaseService { @@ -53,6 +55,11 @@ export class AuthzService extends BaseService { return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-holder-login', payload); } + async getSession(sessionId): Promise { + const payload = { ...sessionId }; + return this.natsClient.sendNatsMessage(this.authServiceProxy, 'fetch-session-details', payload); + } + async resetPassword(resetPasswordDto: ResetPasswordDto): Promise { return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-reset-password', resetPasswordDto); } @@ -73,4 +80,8 @@ export class AuthzService extends BaseService { const payload = { userInfo }; return this.natsClient.sendNatsMessage(this.authServiceProxy, 'add-user', payload); } + + async logout(logoutUserDto: UserLogoutDto): Promise { + return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-logout', logoutUserDto); + } } diff --git a/apps/api-gateway/src/authz/dtos/user-logout.dto.ts b/apps/api-gateway/src/authz/dtos/user-logout.dto.ts new file mode 100644 index 000000000..ec836eebb --- /dev/null +++ b/apps/api-gateway/src/authz/dtos/user-logout.dto.ts @@ -0,0 +1,15 @@ +import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +import { ApiPropertyOptional } from '@nestjs/swagger'; + +export class UserLogoutDto { + @ApiPropertyOptional({ + description: 'List of session IDs to log out', + type: [String] + }) + @IsOptional() + @IsArray({ message: 'sessions must be an array' }) + @IsString({ each: true, message: 'each session Id must be a string' }) + @IsNotEmpty({ each: true, message: 'session Id must not be empty' }) + sessions?: string[]; +} diff --git a/apps/api-gateway/src/authz/guards/session.guard.ts b/apps/api-gateway/src/authz/guards/session.guard.ts new file mode 100644 index 000000000..fafa1f76e --- /dev/null +++ b/apps/api-gateway/src/authz/guards/session.guard.ts @@ -0,0 +1,27 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; + +import { Request } from 'express'; +import { UserRepository } from 'apps/user/repositories/user.repository'; + +@Injectable() +export class SessionGuard implements CanActivate { + constructor(private userRepository: UserRepository) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const sessionId = request.cookies['session_id']; + + // if (!sessionId) { + // throw new UnauthorizedException('Missing session cookie'); + // } + if (sessionId) { + const user = await this.userRepository.validateSession(sessionId); + request.user = user; + } + + // if (!user) { + // throw new UnauthorizedException('Invalid session'); + // } + return true; + } +} diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index 3bbe4b317..aabe4b3a5 100644 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -4,7 +4,7 @@ import * as express from 'express'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { Logger, VERSION_NEUTRAL, VersioningType } from '@nestjs/common'; - +import * as cookieParser from 'cookie-parser'; import { AppModule } from './app.module'; import { HttpAdapterHost, NestFactory, Reflector } from '@nestjs/core'; import { AllExceptionsFilter } from '@credebl/common/exception-handler'; @@ -45,6 +45,7 @@ async function bootstrap(): Promise { expressApp.set('x-powered-by', false); app.use(express.json({ limit: '100mb' })); app.use(express.urlencoded({ limit: '100mb', extended: true })); + app.use(cookieParser()); app.use((req, res, next) => { let err = null; diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 72f012fe4..22fba3876 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -550,11 +550,18 @@ export class OrganizationController { } const orgCredentials = await this.organizationService.clientLoginCredentials(clientCredentialsDto); + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.organisation.success.clientCredentials, data: orgCredentials }; + res.cookie('session_id', orgCredentials.sessionId, { + httpOnly: true, + sameSite: 'none', + secure: 'http' !== process.env.NEXTAUTH_PROTOCOL + }); + return res.status(HttpStatus.OK).json(finalResponse); } /** diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index d5dab6222..deed11a4f 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -760,7 +760,29 @@ export class OrganizationRepository { throw error; } } - + async getOrgAndOwnerUser(orgId: string): Promise { + try { + return this.prisma.user_org_roles.findFirst({ + where: { + orgId, + orgRole: { + name: 'owner' + } + }, + include: { + user: { + select: { + id: true, + keycloakUserId: true + } + } + } + }); + } catch (error) { + this.logger.error(`Error in fetch in organization with admin details`); + throw error; + } + } async getCredDefByOrg(orgId: string): Promise< { tag: string; diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index edf05c322..b24257f49 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -27,8 +27,19 @@ import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { CreateOrganizationDto } from '../dtos/create-organization.dto'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { UpdateInvitationDto } from '../dtos/update-invitation.dt'; -import { DidMethod, Invitation, Ledgers, PrismaTables, transition } from '@credebl/enum/enum'; -import { IGetOrgById, IGetOrganization, IUpdateOrganization, IClientCredentials, ICreateConnectionUrl, IOrgRole, IDidList, IPrimaryDidDetails, IEcosystemOrgStatus, IOrgDetails } from '../interfaces/organization.interface'; +import { DidMethod, Invitation, Ledgers, PrismaTables, SessionType, TokenType, transition } from '@credebl/enum/enum'; +import { + IGetOrgById, + IGetOrganization, + IUpdateOrganization, + IClientCredentials, + ICreateConnectionUrl, + IOrgRole, + IDidList, + IPrimaryDidDetails, + IEcosystemOrgStatus, + IOrgDetails +} from '../interfaces/organization.interface'; import { UserActivityService } from '@credebl/user-activity'; import { ClientRegistrationService } from '@credebl/client-registration/client-registration.service'; import { map } from 'rxjs/operators'; @@ -52,6 +63,7 @@ import { UserActivityRepository } from 'libs/user-activity/repositories'; import { DeleteOrgInvitationsEmail } from '../templates/delete-organization-invitations.template'; import { IOrgRoles } from 'libs/org-roles/interfaces/org-roles.interface'; import { NATSClient } from '@credebl/common/NATSClient'; +import { UserRepository } from 'apps/user/repositories/user.repository'; @Injectable() export class OrganizationService { constructor( @@ -67,9 +79,10 @@ export class OrganizationService { @Inject(CACHE_MANAGER) private cacheService: Cache, private readonly clientRegistrationService: ClientRegistrationService, private readonly userActivityRepository: UserActivityRepository, - private readonly natsClient : NATSClient + private readonly natsClient: NATSClient, + private readonly userRepository: UserRepository ) {} - + async getPlatformConfigDetails(): Promise { try { const getPlatformDetails = await this.organizationRepository.getPlatformConfigDetails(); @@ -79,7 +92,7 @@ export class OrganizationService { throw new RpcException(error.response ? error.response : error); } } - + /** * * @param registerOrgDto @@ -93,10 +106,10 @@ export class OrganizationService { keycloakUserId: string ): Promise { try { - const userOrgCount = await this.organizationRepository.userOrganizationCount(userId); - + const userOrgCount = await this.organizationRepository.userOrganizationCount(userId); + if (userOrgCount >= toNumber(`${process.env.MAX_ORG_LIMIT}`)) { - throw new BadRequestException(ResponseMessages.organisation.error.MaximumOrgsLimit); + throw new BadRequestException(ResponseMessages.organisation.error.MaximumOrgsLimit); } const organizationExist = await this.organizationRepository.checkOrganizationNameExist(createOrgDto.name); @@ -111,7 +124,7 @@ export class OrganizationService { if (isOrgSlugExist) { throw new ConflictException(ResponseMessages.organisation.error.exists); - } + } createOrgDto.orgSlug = orgSlug; createOrgDto.createdBy = userId; @@ -124,7 +137,6 @@ export class OrganizationService { createOrgDto.logo = ''; } - const organizationDetails = await this.organizationRepository.createOrganization(createOrgDto); // To return selective object data @@ -148,12 +160,12 @@ export class OrganizationService { clientId, idpId }; - + const updatedOrg = await this.organizationRepository.updateOrganizationById( updateOrgData, organizationDetails.id ); - + if (!updatedOrg) { throw new InternalServerErrorException(ResponseMessages.organisation.error.credentialsNotUpdate); } @@ -180,18 +192,14 @@ export class OrganizationService { } } - /** + /** * * @param registerOrgDto * @returns */ // eslint-disable-next-line camelcase - async setPrimaryDid( - orgId:string, - did:string, - id:string - ): Promise { + async setPrimaryDid(orgId: string, did: string, id: string): Promise { try { const organizationExist = await this.organizationRepository.getOrgProfile(orgId); if (!organizationExist) { @@ -204,7 +212,7 @@ export class OrganizationService { //check user DID exist in the organization's did list const organizationDidList = await this.organizationRepository.getAllOrganizationDid(orgId); - const isDidMatch = organizationDidList.some(item => item.did === did); + const isDidMatch = organizationDidList.some((item) => item.did === did); if (!isDidMatch) { throw new NotFoundException(ResponseMessages.organisation.error.didNotFound); @@ -214,25 +222,25 @@ export class OrganizationService { if (!didDetails) { throw new NotFoundException(ResponseMessages.organisation.error.didNotFound); } - + const dids = await this.organizationRepository.getDids(orgId); - const noPrimaryDid = dids.every(orgDids => false === orgDids.isPrimaryDid); + const noPrimaryDid = dids.every((orgDids) => false === orgDids.isPrimaryDid); let existingPrimaryDid; let priviousDidFalse; if (!noPrimaryDid) { existingPrimaryDid = await this.organizationRepository.getPerviousPrimaryDid(orgId); - + if (!existingPrimaryDid) { throw new NotFoundException(ResponseMessages.organisation.error.didNotFound); } - + priviousDidFalse = await this.organizationRepository.setPreviousDidFlase(existingPrimaryDid.id); - } + } const didParts = did.split(':'); let nameSpace: string | null = null; - + // This condition will handle the multi-ledger support if (DidMethod.INDY === didParts[1]) { nameSpace = `${didParts[2]}:${didParts[3]}`; @@ -264,9 +272,7 @@ export class OrganizationService { await Promise.all([setPrimaryDid, existingPrimaryDid, priviousDidFalse]); - return ResponseMessages.organisation.success.primaryDid; - } catch (error) { this.logger.error(`In setPrimaryDid method: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -290,9 +296,11 @@ export class OrganizationService { let generatedClientSecret = ''; if (organizationDetails.idpId) { - const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); + const token = await this.clientRegistrationService.getManagementToken( + userDetails.clientId, + userDetails.clientSecret + ); generatedClientSecret = await this.clientRegistrationService.generateClientSecret( organizationDetails.idpId, @@ -303,7 +311,6 @@ export class OrganizationService { clientSecret: this.maskString(generatedClientSecret) }; } else { - try { const orgCredentials = await this.registerToKeycloak( organizationDetails.name, @@ -312,11 +319,11 @@ export class OrganizationService { userId, true ); - + const { clientId, idpId, clientSecret } = orgCredentials; - + generatedClientSecret = clientSecret; - + updateOrgData = { clientId, clientSecret: this.maskString(clientSecret), @@ -359,14 +366,17 @@ export class OrganizationService { shouldUpdateRole: boolean ): Promise { const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); + const token = await this.clientRegistrationService.getManagementToken( + userDetails.clientId, + userDetails.clientSecret + ); const orgDetails = await this.clientRegistrationService.createClient(orgName, orgId, token); const orgRolesList = [OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER]; - for (const role of orgRolesList) { - await this.clientRegistrationService.createClientRole(orgDetails.idpId, token, role, role); - } + for (const role of orgRolesList) { + await this.clientRegistrationService.createClientRole(orgDetails.idpId, token, role, role); + } const ownerRoleClient = await this.clientRegistrationService.getClientSpecificRoles( orgDetails.idpId, @@ -384,20 +394,18 @@ export class OrganizationService { const ownerRoleData = await this.orgRoleService.getRole(OrgRoles.OWNER); if (!shouldUpdateRole) { - await Promise.all([ this.clientRegistrationService.createUserClientRole(orgDetails.idpId, token, keycloakUserId, payload), this.userOrgRoleService.createUserOrgRole(userId, ownerRoleData.id, orgId, ownerRoleClient.id) ]); - } else { const roleIdList = [ { roleId: ownerRoleData.id, idpRoleId: ownerRoleClient.id } - ]; - + ]; + await Promise.all([ this.clientRegistrationService.createUserClientRole(orgDetails.idpId, token, keycloakUserId, payload), this.userOrgRoleService.deleteOrgRoles(userId, orgId), @@ -509,7 +517,6 @@ export class OrganizationService { // eslint-disable-next-line camelcase async updateOrganization(updateOrgDto: IUpdateOrganization, userId: string, orgId: string): Promise { try { - const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); if (organizationExist && organizationExist.id !== orgId) { @@ -531,13 +538,24 @@ export class OrganizationService { const checkAgentIsExists = await this.organizationRepository.getAgentInvitationDetails(orgId); if (!checkAgentIsExists?.connectionInvitation && !checkAgentIsExists?.agentId) { - organizationDetails = await this.organizationRepository.updateOrganization(updateOrgDto); - } else if (organizationDetails?.logoUrl !== organizationExist?.logoUrl || organizationDetails?.name !== organizationExist?.name) { + organizationDetails = await this.organizationRepository.updateOrganization(updateOrgDto); + } else if ( + organizationDetails?.logoUrl !== organizationExist?.logoUrl || + organizationDetails?.name !== organizationExist?.name + ) { const invitationData = await this._createConnection(updateOrgDto?.logo, updateOrgDto?.name, orgId); - await this.organizationRepository.updateConnectionInvitationDetails(orgId, invitationData?.connectionInvitation); + await this.organizationRepository.updateConnectionInvitationDetails( + orgId, + invitationData?.connectionInvitation + ); } - await this.userActivityService.createActivity(userId, organizationDetails.id, `${organizationDetails.name} organization updated`, 'Organization details updated successfully'); + await this.userActivityService.createActivity( + userId, + organizationDetails.id, + `${organizationDetails.name} organization updated`, + 'Organization details updated successfully' + ); return organizationDetails; } catch (error) { this.logger.error(`In update organization : ${JSON.stringify(error)}`); @@ -545,11 +563,7 @@ export class OrganizationService { } } - async _createConnection( - orgName: string, - logoUrl: string, - orgId: string - ): Promise { + async _createConnection(orgName: string, logoUrl: string, orgId: string): Promise { const pattern = { cmd: 'create-connection-invitation' }; const payload = { @@ -561,7 +575,7 @@ export class OrganizationService { }; const connectionInvitationData = await this.natsClient .send(this.organizationServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -576,12 +590,8 @@ export class OrganizationService { return connectionInvitationData; } - async countTotalOrgs( - userId: string - - ): Promise { + async countTotalOrgs(userId: string): Promise { try { - const getOrgs = await this.organizationRepository.userOrganizationCount(userId); return getOrgs; } catch (error) { @@ -589,11 +599,11 @@ export class OrganizationService { throw new RpcException(error.response ? error.response : error); } } - + /** * @returns Get created organizations details */ - + async getOrganizations( userId: string, pageNumber: number, @@ -611,11 +621,11 @@ export class OrganizationService { { description: { contains: search, mode: 'insensitive' } } ] }; - + const filterOptions = { userId }; - + const getOrgs = await this.organizationRepository.getOrganizations( query, filterOptions, @@ -626,7 +636,7 @@ export class OrganizationService { ); const { organizations } = getOrgs; - + if (0 === organizations?.length) { throw new NotFoundException(ResponseMessages.organisation.error.organizationNotFound); } @@ -635,25 +645,25 @@ export class OrganizationService { let updatedOrgs; if ('true' === process.env.IS_ECOSYSTEM_ENABLE) { - orgIds = organizations?.map(item => item.id); - + orgIds = organizations?.map((item) => item.id); + const orgEcosystemDetails = await this._getOrgEcosystems(orgIds); - - updatedOrgs = getOrgs.organizations.map(org => { + + updatedOrgs = getOrgs.organizations.map((org) => { const matchingEcosystems = orgEcosystemDetails - .filter(ecosystem => ecosystem.orgId === org.id) - .map(ecosystem => ({ ecosystemId: ecosystem.ecosystemId })); + .filter((ecosystem) => ecosystem.orgId === org.id) + .map((ecosystem) => ({ ecosystemId: ecosystem.ecosystemId })); return { ...org, ecosystemOrgs: 0 < matchingEcosystems.length ? matchingEcosystems : [] }; }); } else { - updatedOrgs = getOrgs?.organizations?.map(org => ({ + updatedOrgs = getOrgs?.organizations?.map((org) => ({ ...org })); } - + return { totalCount: getOrgs.totalCount, totalPages: getOrgs.totalPages, @@ -686,27 +696,85 @@ export class OrganizationService { return response; } + /** + * Method used for generate access token based on client-id and client secret + * @param clientCredentials + * @returns session and access token both + */ async clientLoginCredentails(clientCredentials: IClientCredentials): Promise { - const {clientId, clientSecret} = clientCredentials; - return this.authenticateClientKeycloak(clientId, clientSecret); -} + const { clientId, clientSecret } = clientCredentials; + // This method used to authenticate the requested user on keycloak + const authenticationResult = await this.authenticateClientKeycloak(clientId, clientSecret); + let addSessionDetails; + // Fetch owner organization details for getting the user id + const orgRoleDetails = await this.organizationRepository.getOrgAndOwnerUser(clientId); + // Fetch the total number of sessions for the requested user to check and restrict the creation of multiple sessions. + const userSessionDetails = await this.userRepository.fetchUserSessions(orgRoleDetails['user'].id); + if (Number(process.env.SESSIONS_LIMIT) <= userSessionDetails?.length) { + throw new BadRequestException(ResponseMessages.user.error.sessionLimitReached); + } + // Session payload + const sessionData = { + sessionToken: authenticationResult?.access_token, + userId: orgRoleDetails['user'].id, + expires: authenticationResult?.expires_in, + sessionType: SessionType.ORG_SESSION + }; + // Note: + // Fetch account details to check whether the requested user account exists + // If the account exists, update it with the latest details and create a new session + // Otherwise, create a new account and also create the new session + const fetchAccountDetails = await this.userRepository.checkAccountDetails(orgRoleDetails['user'].id); + if (fetchAccountDetails) { + const accountData = { + sessionToken: authenticationResult?.access_token, + userId: orgRoleDetails['user'].id, + expires: authenticationResult?.expires_in + }; - async authenticateClientKeycloak(clientId: string, clientSecret: string): Promise { - - try { - const payload = new ClientCredentialTokenPayloadDto(); - // eslint-disable-next-line camelcase - payload.client_id = clientId; - // eslint-disable-next-line camelcase - payload.client_secret = clientSecret; + await this.userRepository.updateAccountDetails(accountData).then(async (response) => { + const finalSessionData = { ...sessionData, accountId: response.id }; + addSessionDetails = await this.userRepository.createSession(finalSessionData); + }); + } else { + // Note: + // This else block is mostly used for already registered users on the platform to create their account & session in the database. + // Once all users are migrated or created their accounts and sessions in the DB, this code can be removed. + const accountData = { + sessionToken: authenticationResult?.access_token, + userId: orgRoleDetails['user'].id, + expires: authenticationResult?.expires_in, + keycloakUserId: orgRoleDetails['user'].keycloakUserId, + type: TokenType.BEARER_TOKEN + }; - try { - const mgmtTokenResponse = await this.clientRegistrationService.getToken(payload); - return mgmtTokenResponse; - } catch (error) { - throw new UnauthorizedException(ResponseMessages.organisation.error.invalidClient); + await this.userRepository.addAccountDetails(accountData).then(async (response) => { + const finalSessionData = { ...sessionData, accountId: response.id }; + addSessionDetails = await this.userRepository.createSession(finalSessionData); + }); } + // Response: add session id + const finalResponse = { + ...authenticationResult, + sessionId: addSessionDetails.id + }; + return finalResponse; + } + + async authenticateClientKeycloak(clientId: string, clientSecret: string): Promise { + try { + const payload = new ClientCredentialTokenPayloadDto(); + // eslint-disable-next-line camelcase + payload.client_id = clientId; + // eslint-disable-next-line camelcase + payload.client_secret = clientSecret; + try { + const mgmtTokenResponse = await this.clientRegistrationService.getToken(payload); + return mgmtTokenResponse; + } catch (error) { + throw new UnauthorizedException(ResponseMessages.organisation.error.invalidClient); + } } catch (error) { this.logger.error(`Error in authenticateClientKeycloak : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -883,36 +951,35 @@ export class OrganizationService { userEmail: string, userId: string, orgName: string - ): Promise { + ): Promise { const { invitations, orgId } = bulkInvitationDto; - for (const invitation of invitations) { - const { orgRoleId, email } = invitation; + for (const invitation of invitations) { + const { orgRoleId, email } = invitation; - const isUserExist = await this.checkUserExistInPlatform(email); + const isUserExist = await this.checkUserExistInPlatform(email); - const userData = await this.getUserFirstName(userEmail); - - const {firstName} = userData; - const orgRolesDetails = await this.orgRoleService.getOrgRolesByIds(orgRoleId); - - if (0 === orgRolesDetails.length) { - throw new NotFoundException(ResponseMessages.organisation.error.orgRoleIdNotFound); - } + const userData = await this.getUserFirstName(userEmail); - const isInvitationExist = await this.checkInvitationExist(email, orgId); + const { firstName } = userData; + const orgRolesDetails = await this.orgRoleService.getOrgRolesByIds(orgRoleId); - if (!isInvitationExist && userEmail !== invitation.email) { + if (0 === orgRolesDetails.length) { + throw new NotFoundException(ResponseMessages.organisation.error.orgRoleIdNotFound); + } - await this.organizationRepository.createSendInvitation(email, String(orgId), String(userId), orgRoleId); + const isInvitationExist = await this.checkInvitationExist(email, orgId); - try { - await this.sendInviteEmailTemplate(email, orgName, orgRolesDetails, firstName, isUserExist); - } catch (error) { - throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); - } + if (!isInvitationExist && userEmail !== invitation.email) { + await this.organizationRepository.createSendInvitation(email, String(orgId), String(userId), orgRoleId); + + try { + await this.sendInviteEmailTemplate(email, orgName, orgRolesDetails, firstName, isUserExist); + } catch (error) { + throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); } } + } } async createInvitationByClientRoles( @@ -921,11 +988,14 @@ export class OrganizationService { userId: string, orgName: string, idpId: string - ): Promise { + ): Promise { const { invitations, orgId } = bulkInvitationDto; const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); + const token = await this.clientRegistrationService.getManagementToken( + userDetails.clientId, + userDetails.clientSecret + ); const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); const orgRoles = await this.orgRoleService.getOrgRoles(); @@ -951,7 +1021,6 @@ export class OrganizationService { const isInvitationExist = await this.checkInvitationExist(email, orgId); if (!isInvitationExist && userEmail !== invitation.email) { - await this.organizationRepository.createSendInvitation( email, String(orgId), @@ -960,13 +1029,7 @@ export class OrganizationService { ); try { - await this.sendInviteEmailTemplate( - email, - orgName, - filteredOrgRoles, - firstName, - isUserExist - ); + await this.sendInviteEmailTemplate(email, orgName, filteredOrgRoles, firstName, isUserExist); } catch (error) { throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); } @@ -991,12 +1054,7 @@ export class OrganizationService { } if (!organizationDetails.idpId) { - await this.createInvitationByOrgRoles( - bulkInvitationDto, - userEmail, - userId, - organizationDetails.name - ); + await this.createInvitationByOrgRoles(bulkInvitationDto, userEmail, userId, organizationDetails.name); } else { await this.createInvitationByClientRoles( bulkInvitationDto, @@ -1063,7 +1121,7 @@ export class OrganizationService { const userData: user = await this.natsClient .send(this.organizationServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1086,7 +1144,7 @@ export class OrganizationService { const userData = await this.natsClient .send(this.organizationServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1104,19 +1162,17 @@ export class OrganizationService { const pattern = { cmd: 'get-user-by-user-id' }; // const payload = { id: userId }; - const userData = await this.natsClient - .send(this.organizationServiceProxy, pattern, userId) - .catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.error, - message: error.message - }, - error.status - ); - }); + const userData = await this.natsClient.send(this.organizationServiceProxy, pattern, userId).catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.error, + message: error.message + }, + error.status + ); + }); return userData; } @@ -1145,38 +1201,45 @@ export class OrganizationService { status: string ): Promise { const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); - const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); + const token = await this.clientRegistrationService.getManagementToken( + userDetails.clientId, + userDetails.clientSecret + ); + const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); - const orgRoles = await this.orgRoleService.getOrgRolesByIds(invitation.orgRoles); + const orgRoles = await this.orgRoleService.getOrgRolesByIds(invitation.orgRoles); - const rolesPayload: { roleId: string; name: string; idpRoleId: string }[] = orgRoles.map((orgRole: IOrgRole) => { - let roleObj: { roleId: string; name: string; idpRoleId: string} = null; + const rolesPayload: { roleId: string; name: string; idpRoleId: string }[] = orgRoles.map((orgRole: IOrgRole) => { + let roleObj: { roleId: string; name: string; idpRoleId: string } = null; - for (let index = 0; index < clientRolesList.length; index++) { - if (clientRolesList[index].name === orgRole.name) { - roleObj = { - roleId: orgRole.id, - name: orgRole.name, - idpRoleId: clientRolesList[index].id - }; - break; - } + for (let index = 0; index < clientRolesList.length; index++) { + if (clientRolesList[index].name === orgRole.name) { + roleObj = { + roleId: orgRole.id, + name: orgRole.name, + idpRoleId: clientRolesList[index].id + }; + break; } + } - return roleObj; - }); - - const data = { - status - }; + return roleObj; + }); - await Promise.all([ - this.organizationRepository.updateOrgInvitation(invitation.id, data), - this.clientRegistrationService.createUserClientRole(idpId, token, keycloakUserId, rolesPayload.map(role => ({id: role.idpRoleId, name: role.name}))), - this.userOrgRoleService.updateUserOrgRole(userId, orgId, rolesPayload) - ]); + const data = { + status + }; + await Promise.all([ + this.organizationRepository.updateOrgInvitation(invitation.id, data), + this.clientRegistrationService.createUserClientRole( + idpId, + token, + keycloakUserId, + rolesPayload.map((role) => ({ id: role.idpRoleId, name: role.name })) + ), + this.userOrgRoleService.updateUserOrgRole(userId, orgId, rolesPayload) + ]); } /** @@ -1251,11 +1314,11 @@ export class OrganizationService { orgId: string ): Promise { const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); - const clientRolesList = await this.clientRegistrationService.getAllClientRoles( - idpId, - token + const token = await this.clientRegistrationService.getManagementToken( + userDetails.clientId, + userDetails.clientSecret ); + const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); const orgRoles = await this.orgRoleService.getOrgRoles(); const matchedClientRoles = clientRolesList.filter((role) => roleIds.includes(role.id.trim())); @@ -1286,11 +1349,7 @@ export class OrganizationService { const userData = await this.getUserUserId(userId); const [, deletedUserRoleRecords] = await Promise.all([ - this.clientRegistrationService.deleteUserClientRoles( - idpId, - token, - userData.keycloakUserId - ), + this.clientRegistrationService.deleteUserClientRoles(idpId, token, userData.keycloakUserId), this.userOrgRoleService.deleteOrgRoles(userId, orgId) ]); @@ -1351,15 +1410,8 @@ export class OrganizationService { return true; } else { - - return this.updateUserClientRoles( - roleIds, - organizationDetails.idpId, - userId, - organizationDetails.id - ); + return this.updateUserClientRoles(roleIds, organizationDetails.idpId, userId, organizationDetails.id); } - } catch (error) { this.logger.error(`Error in updateUserRoles: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -1375,26 +1427,26 @@ export class OrganizationService { } } - async getOrganizationActivityCount(orgId: string, userId: string): Promise { try { - const [ - verificationRecordsCount, - issuanceRecordsCount, - connectionRecordsCount, - orgInvitationsCount, - orgUsers - ] = await Promise.all([ - this._getVerificationRecordsCount(orgId, userId), - this._getIssuanceRecordsCount(orgId, userId), - this._getConnectionRecordsCount(orgId, userId), - this.organizationRepository.getOrgInvitationsCount(orgId), - this.organizationRepository.getOrgDashboard(orgId) - ]); + const [verificationRecordsCount, issuanceRecordsCount, connectionRecordsCount, orgInvitationsCount, orgUsers] = + await Promise.all([ + this._getVerificationRecordsCount(orgId, userId), + this._getIssuanceRecordsCount(orgId, userId), + this._getConnectionRecordsCount(orgId, userId), + this.organizationRepository.getOrgInvitationsCount(orgId), + this.organizationRepository.getOrgDashboard(orgId) + ]); const orgUsersCount = orgUsers?.['usersCount']; - return {verificationRecordsCount, issuanceRecordsCount, connectionRecordsCount, orgUsersCount, orgInvitationsCount}; + return { + verificationRecordsCount, + issuanceRecordsCount, + connectionRecordsCount, + orgUsersCount, + orgInvitationsCount + }; } catch (error) { this.logger.error(`In fetch organization references count : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -1408,24 +1460,22 @@ export class OrganizationService { orgId, userId }; - const ecosystemsCount = await (this.natsClient - .send(this.organizationServiceProxy, pattern, payload) as unknown as Promise) - - .catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); - }); + const ecosystemsCount = await ( + this.natsClient.send(this.organizationServiceProxy, pattern, payload) as unknown as Promise + ).catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); return ecosystemsCount; } - async _getConnectionRecordsCount(orgId: string, userId: string): Promise { const pattern = { cmd: 'get-connection-records' }; @@ -1435,7 +1485,7 @@ export class OrganizationService { }; const connectionsCount = await this.natsClient .send(this.organizationServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1450,7 +1500,6 @@ export class OrganizationService { return connectionsCount; } - async _getIssuanceRecordsCount(orgId: string, userId: string): Promise { const pattern = { cmd: 'get-issuance-records' }; @@ -1460,7 +1509,7 @@ export class OrganizationService { }; const issuanceCount = await this.natsClient .send(this.organizationServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1484,7 +1533,7 @@ export class OrganizationService { }; const verificationCount = await this.natsClient .send(this.organizationServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1547,7 +1596,7 @@ export class OrganizationService { throw new RpcException(error.response ? error.response : error); } } - + async deleteOrganization(orgId: string, user: user): Promise { try { const getUser = await this.organizationRepository.getUser(user?.id); @@ -1556,113 +1605,128 @@ export class OrganizationService { this.clientRegistrationService.getManagementToken(getUser?.clientId, getUser?.clientSecret), this.organizationRepository.getOrganizationDetails(orgId) ]); - + if (!organizationDetails) { throw new NotFoundException(ResponseMessages.organisation.error.orgNotFound); } - + const organizationInvitationDetails = await this.organizationRepository.getOrgInvitationsByOrg(orgId); - - const arrayEmail = organizationInvitationDetails.map(userData => userData.email); + + const arrayEmail = organizationInvitationDetails.map((userData) => userData.email); this.logger.debug(`arrayEmail ::: ${JSON.stringify(arrayEmail)}`); - + // Fetch Keycloak IDs only if there are emails to process - const keycloakUserIds = 0 < arrayEmail.length - ? (await this.getUserKeycloakIdByEmail(arrayEmail)).response.map(user => user.keycloakUserId) - : []; - + const keycloakUserIds = + 0 < arrayEmail.length + ? (await this.getUserKeycloakIdByEmail(arrayEmail)).response.map((user) => user.keycloakUserId) + : []; + this.logger.log('Keycloak User Ids'); // Delete user client roles in parallel - const deleteUserRolesPromises = keycloakUserIds.map(keycloakUserId => this.clientRegistrationService.deleteUserClientRoles(organizationDetails?.idpId, token, keycloakUserId) + const deleteUserRolesPromises = keycloakUserIds.map((keycloakUserId) => + this.clientRegistrationService.deleteUserClientRoles(organizationDetails?.idpId, token, keycloakUserId) ); deleteUserRolesPromises.push( this.clientRegistrationService.deleteUserClientRoles(organizationDetails?.idpId, token, getUser?.keycloakUserId) ); - + this.logger.debug(`deleteUserRolesPromises ::: ${JSON.stringify(deleteUserRolesPromises)}`); const deleteUserRolesResults = await Promise.allSettled(deleteUserRolesPromises); - + // Check for failures in deleting user roles - const deletionFailures = deleteUserRolesResults.filter(result => 'rejected' === result?.status); - + const deletionFailures = deleteUserRolesResults.filter((result) => 'rejected' === result?.status); + if (0 < deletionFailures.length) { this.logger.error(`deletionFailures ::: ${JSON.stringify(deletionFailures)}`); throw new NotFoundException(ResponseMessages.organisation.error.orgDataNotFoundInkeycloak); } - - const deletedOrgInvitationInfo: { email?: string, orgName?: string, orgRoleNames?: string[] }[] = []; - const userIds = (await this.getUserKeycloakIdByEmail(arrayEmail)).response.map(user => user.id); - await Promise.all(userIds.map(async (userId) => { - const userOrgRoleIds = await this.organizationRepository.getUserOrgRole(userId, orgId); - this.logger.debug(`userOrgRoleIds ::::: ${JSON.stringify(userOrgRoleIds)}`); - const userDetails = await this.organizationRepository.getUser(userId); - this.logger.debug(`userDetails ::::: ${JSON.stringify(userDetails)}`); - - const orgRoles = await this.organizationRepository.getOrgRole(userOrgRoleIds); - this.logger.debug(`orgRoles ::::: ${JSON.stringify(orgRoles)}`); - - const orgRoleNames = orgRoles.map(orgRoleName => orgRoleName.name); - const sendEmail = await this.sendEmailForOrgInvitationsMember(userDetails?.email, organizationDetails?.name, orgRoleNames); - const newInvitation = { - email: userDetails.email, - orgName: organizationDetails?.name, - orgRoleNames - }; - - // Step 3: Push the data into the array - deletedOrgInvitationInfo.push(newInvitation); - - this.logger.log(`email: ${userDetails.email}, orgName: ${organizationDetails?.name}, orgRoles: ${JSON.stringify(orgRoleNames)}, sendEmail: ${sendEmail}`); - })); - + const deletedOrgInvitationInfo: { email?: string; orgName?: string; orgRoleNames?: string[] }[] = []; + const userIds = (await this.getUserKeycloakIdByEmail(arrayEmail)).response.map((user) => user.id); + await Promise.all( + userIds.map(async (userId) => { + const userOrgRoleIds = await this.organizationRepository.getUserOrgRole(userId, orgId); + this.logger.debug(`userOrgRoleIds ::::: ${JSON.stringify(userOrgRoleIds)}`); + + const userDetails = await this.organizationRepository.getUser(userId); + this.logger.debug(`userDetails ::::: ${JSON.stringify(userDetails)}`); + + const orgRoles = await this.organizationRepository.getOrgRole(userOrgRoleIds); + this.logger.debug(`orgRoles ::::: ${JSON.stringify(orgRoles)}`); + + const orgRoleNames = orgRoles.map((orgRoleName) => orgRoleName.name); + const sendEmail = await this.sendEmailForOrgInvitationsMember( + userDetails?.email, + organizationDetails?.name, + orgRoleNames + ); + const newInvitation = { + email: userDetails.email, + orgName: organizationDetails?.name, + orgRoleNames + }; + + // Step 3: Push the data into the array + deletedOrgInvitationInfo.push(newInvitation); + + this.logger.log( + `email: ${userDetails.email}, orgName: ${organizationDetails?.name}, orgRoles: ${JSON.stringify(orgRoleNames)}, sendEmail: ${sendEmail}` + ); + }) + ); + // Delete organization data - const { deletedUserActivity, deletedUserOrgRole, deleteOrg, deletedOrgInvitations, deletedNotification } = await this.organizationRepository.deleteOrg(orgId); - + const { deletedUserActivity, deletedUserOrgRole, deleteOrg, deletedOrgInvitations, deletedNotification } = + await this.organizationRepository.deleteOrg(orgId); + this.logger.debug(`deletedUserActivity ::: ${JSON.stringify(deletedUserActivity)}`); this.logger.debug(`deletedUserOrgRole ::: ${JSON.stringify(deletedUserOrgRole)}`); this.logger.debug(`deleteOrg ::: ${JSON.stringify(deleteOrg)}`); this.logger.debug(`deletedOrgInvitations ::: ${JSON.stringify(deletedOrgInvitations)}`); - + const deletions = [ { records: deletedUserActivity.count, tableName: `${PrismaTables.USER_ACTIVITY}` }, { records: deletedUserOrgRole.count, tableName: `${PrismaTables.USER_ORG_ROLES}` }, - { records: deletedOrgInvitations.count, deletedOrgInvitationInfo, tableName: `${PrismaTables.ORG_INVITATIONS}` }, + { + records: deletedOrgInvitations.count, + deletedOrgInvitationInfo, + tableName: `${PrismaTables.ORG_INVITATIONS}` + }, { records: deletedNotification.count, tableName: `${PrismaTables.NOTIFICATION}` }, { records: deleteOrg ? 1 : 0, tableName: `${PrismaTables.ORGANIZATION}` } ]; - + // Log deletion activities in parallel - await Promise.all(deletions.map(async ({ records, tableName, deletedOrgInvitationInfo }) => { - if (records) { - const txnMetadata: { - deletedRecordsCount: number; - deletedRecordInTable: string; - deletedOrgInvitationInfo?: object[] - } = { - deletedRecordsCount: records, - deletedRecordInTable: tableName - }; - - if (deletedOrgInvitationInfo) { - txnMetadata.deletedOrgInvitationInfo = deletedOrgInvitationInfo; + await Promise.all( + deletions.map(async ({ records, tableName, deletedOrgInvitationInfo }) => { + if (records) { + const txnMetadata: { + deletedRecordsCount: number; + deletedRecordInTable: string; + deletedOrgInvitationInfo?: object[]; + } = { + deletedRecordsCount: records, + deletedRecordInTable: tableName + }; + + if (deletedOrgInvitationInfo) { + txnMetadata.deletedOrgInvitationInfo = deletedOrgInvitationInfo; + } + + const recordType = RecordType.ORGANIZATION; + await this.userActivityRepository._orgDeletedActivity(orgId, user, txnMetadata, recordType); } - - const recordType = RecordType.ORGANIZATION; - await this.userActivityRepository._orgDeletedActivity(orgId, user, txnMetadata, recordType); - } - })); - + }) + ); + return deleteOrg; - } catch (error) { this.logger.error(`delete organization: ${JSON.stringify(error)}`); throw new RpcException(error.response ?? error); } } - async sendEmailForOrgInvitationsMember(email: string, orgName: string, orgRole: string[]): Promise { const platformConfigData = await this.prisma.platform_config.findMany(); @@ -1672,11 +1736,7 @@ export class OrganizationService { emailData.emailTo = email; emailData.emailSubject = `Removal of participation of “${orgName}”`; - emailData.emailHtml = await urlEmailTemplate.sendDeleteOrgMemberEmailTemplate( - email, - orgName, - orgRole - ); + emailData.emailHtml = await urlEmailTemplate.sendDeleteOrgMemberEmailTemplate(email, orgName, orgRole); //Email is sent to user for the verification through emailData const isEmailSent = await sendEmail(emailData); @@ -1737,20 +1797,19 @@ export class OrganizationService { } async registerOrgsMapUsers(): Promise { - try { - const unregisteredOrgsList = await this.organizationRepository.getUnregisteredClientOrgs(); - + if (!unregisteredOrgsList || 0 === unregisteredOrgsList.length) { throw new NotFoundException('Unregistered client organizations not found'); - } + } for (const org of unregisteredOrgsList) { const userOrgRoles = 0 < org['userOrgRoles'].length && org['userOrgRoles']; - const ownerUserList = 0 < org['userOrgRoles'].length - && userOrgRoles.filter(userOrgRole => userOrgRole.orgRole.name === OrgRoles.OWNER); + const ownerUserList = + 0 < org['userOrgRoles'].length && + userOrgRoles.filter((userOrgRole) => userOrgRole.orgRole.name === OrgRoles.OWNER); const ownerUser = 0 < ownerUserList.length && ownerUserList[0].user; @@ -1773,7 +1832,7 @@ export class OrganizationService { ); const { clientId, idpId, clientSecret } = orgCredentials; - + const updateOrgData = { clientId, clientSecret: this.maskString(clientSecret), @@ -1781,58 +1840,59 @@ export class OrganizationService { }; const updatedOrg = await this.organizationRepository.updateOrganizationById(updateOrgData, orgObj.id); - + this.logger.log(`updatedOrg::`, updatedOrg); - const usersToRegisterList = userOrgRoles.filter(userOrgRole => null !== userOrgRole.user.keycloakUserId); - - const userDetails = await this.organizationRepository.getUser(orgObj.ownerId); - const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); - const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); - - const deletedUserDetails: string[] = []; - for (const userRole of usersToRegisterList) { - const user = userRole.user; - - const matchedClientRoles = clientRolesList.filter((role) => userRole.orgRole.name === role.name) - .map(clientRole => ({roleId: userRole.orgRole.id, idpRoleId: clientRole.id, name: clientRole.name})); - - if (!deletedUserDetails.includes(user.id)) { - const [, deletedUserRoleRecords] = await Promise.all([ - this.clientRegistrationService.deleteUserClientRoles(idpId, token, user.keycloakUserId), - this.userOrgRoleService.deleteOrgRoles(user.id, orgObj.id) - ]); - - this.logger.log(`deletedUserRoleRecords::`, deletedUserRoleRecords); - - deletedUserDetails.push(user.id); - } - - - await Promise.all([ - this.clientRegistrationService.createUserClientRole( - idpId, - token, - user.keycloakUserId, - matchedClientRoles.map((role) => ({ id: role.idpRoleId, name: role.name })) - ), - this.userOrgRoleService.updateUserOrgRole( - user.id, - orgObj.id, - matchedClientRoles.map((role) => ({ roleId: role.roleId, idpRoleId: role.idpRoleId })) - ) + const usersToRegisterList = userOrgRoles.filter((userOrgRole) => null !== userOrgRole.user.keycloakUserId); + + const userDetails = await this.organizationRepository.getUser(orgObj.ownerId); + const token = await this.clientRegistrationService.getManagementToken( + userDetails.clientId, + userDetails.clientSecret + ); + const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); + + const deletedUserDetails: string[] = []; + for (const userRole of usersToRegisterList) { + const user = userRole.user; + + const matchedClientRoles = clientRolesList + .filter((role) => userRole.orgRole.name === role.name) + .map((clientRole) => ({ roleId: userRole.orgRole.id, idpRoleId: clientRole.id, name: clientRole.name })); + + if (!deletedUserDetails.includes(user.id)) { + const [, deletedUserRoleRecords] = await Promise.all([ + this.clientRegistrationService.deleteUserClientRoles(idpId, token, user.keycloakUserId), + this.userOrgRoleService.deleteOrgRoles(user.id, orgObj.id) ]); - this.logger.log(`Organization client created and users mapped to roles`); - } - } + this.logger.log(`deletedUserRoleRecords::`, deletedUserRoleRecords); + + deletedUserDetails.push(user.id); + } + + await Promise.all([ + this.clientRegistrationService.createUserClientRole( + idpId, + token, + user.keycloakUserId, + matchedClientRoles.map((role) => ({ id: role.idpRoleId, name: role.name })) + ), + this.userOrgRoleService.updateUserOrgRole( + user.id, + orgObj.id, + matchedClientRoles.map((role) => ({ roleId: role.roleId, idpRoleId: role.idpRoleId })) + ) + ]); + this.logger.log(`Organization client created and users mapped to roles`); + } + } } - + return ''; } catch (error) { this.logger.error(`Error in registerOrgsMapUsers: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); - } } @@ -1948,18 +2008,18 @@ export class OrganizationService { } } - async getOrgAgentDetailsForEcosystem(data: {orgIds: string[], search: string}): Promise { + async getOrgAgentDetailsForEcosystem(data: { orgIds: string[]; search: string }): Promise { try { - const getAllOrganizationDetails = await this.organizationRepository.handleGetOrganisationData(data); + const getAllOrganizationDetails = await this.organizationRepository.handleGetOrganisationData(data); - if (!getAllOrganizationDetails) { - throw new NotFoundException(ResponseMessages.ledger.error.NotFound); - } + if (!getAllOrganizationDetails) { + throw new NotFoundException(ResponseMessages.ledger.error.NotFound); + } - return getAllOrganizationDetails; + return getAllOrganizationDetails; } catch (error) { - this.logger.error(`Error in getOrgAgentDetailsForEcosystem: ${error}`); - throw new RpcException(error.response ? error.response : error); + this.logger.error(`Error in getOrgAgentDetailsForEcosystem: ${error}`); + throw new RpcException(error.response ? error.response : error); } + } } -} \ No newline at end of file diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index dd6c1d60d..9f17b54fb 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -12,20 +12,20 @@ export interface IUsersProfile { } interface IUserOrgRole { - id: string; - userId: string; - orgRoleId: string; - orgId: string; - orgRole :IOrgRole; - organisation:IOrganisation; -} - export interface IOrgRole{ - id: string; + id: string; + userId: string; + orgRoleId: string; + orgId: string; + orgRole: IOrgRole; + organisation: IOrganisation; +} +export interface IOrgRole { + id: string; name: string; description: string; - }; - export interface IOrganisation{ - id: string; +} +export interface IOrganisation { + id: string; name: string; description: string; orgSlug: string; @@ -106,12 +106,12 @@ export interface ICheckUserDetails { isFidoVerified?: boolean; isRegistrationCompleted?: boolean; userId?: number; - message?:string; + message?: string; } export interface IOrgUsers { - totalPages: number, - users: OrgUser[] + totalPages: number; + users: OrgUser[]; } export interface IDidList { @@ -137,7 +137,7 @@ interface UserOrgRoles { orgId: string; orgRoleId: string; orgRole: OrgRole; - organisation: Organization + organisation: Organization; } interface OrgRole { id: string; @@ -146,22 +146,22 @@ interface OrgRole { } interface Organization { - id: string, - name: string, - description: string, - orgSlug: string, - logoUrl: string, + id: string; + name: string; + description: string; + orgSlug: string; + logoUrl: string; org_agents: OrgAgents[]; } interface OrgAgents { - id: string, - orgDid: string, - walletName: string, - agentSpinUpStatus: number, - agentsTypeId: string, - createDateTime: Date, - orgAgentTypeId:string + id: string; + orgDid: string; + walletName: string; + agentSpinUpStatus: number; + agentsTypeId: string; + createDateTime: Date; + orgAgentTypeId: string; } export interface Payload { @@ -170,29 +170,53 @@ export interface Payload { search: string; } -export interface IVerifyUserEmail{ +export interface IVerifyUserEmail { email: string; verificationCode: string; } -export interface IUserSignIn{ +export interface IUserSignIn { email: string; password: string; isPasskey?: boolean; } -export interface IUserResetPassword{ +export interface ISession { + sessionToken?: string; + userId?: string; + expires?: number; + refreshToken?: string; + keycloakUserId?: string; + type?: string; + accountId?: string; + sessionType?: string; +} + +export interface IUpdateAccountDetails { + accessToken: string; + refreshToken?: string; + expiresAt: number; + accountId: string; +} + +export interface ISessionDetails extends ISession { + id: string; + createdAt: Date; + updatedAt: Date; +} + +export interface IUserResetPassword { email: string; oldPassword?: string; newPassword?: string; token?: string; password?: string; } -export interface IUserForgotPassword{ +export interface IUserForgotPassword { email: string; - brandLogoUrl?: string, - platformName?: string, - endpoint?: string + brandLogoUrl?: string; + platformName?: string; + endpoint?: string; } export interface IIssueCertificate { courseCode: string; @@ -202,7 +226,7 @@ export interface IIssueCertificate { practicalGradeCredits: string; practicalObtainedEarned: string; } -export interface IPuppeteerOption{ +export interface IPuppeteerOption { width: number; height: number; } @@ -229,7 +253,10 @@ export interface UserRoleMapping { userRoleId: string; } -export interface UserRoleDetails{ +export interface ISessions { + sessions: string[]; +} +export interface UserRoleDetails { id: string; role: $Enums.UserRole; } @@ -243,4 +270,21 @@ export interface IEcosystemConfig { lastChangedDateTime: Date; lastChangedBy: string; deletedAt: Date | null; -} \ No newline at end of file +} + +export interface IAccountDetails { + userId: string; + type?: string; + provider?: string; + providerAccountId?: string; + refresh_token?: string; + access_token?: string; + expires_at?: string; + scope?: string; + token_type?: string; + id_token?: string; + session_state?: string; +} +export interface ISessionData { + sessionId: string; +} diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 2e21c4ec8..c02ef0d99 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -3,7 +3,9 @@ import { IOrgUsers, ISendVerificationEmail, + ISession, IShareUserCertificate, + IUpdateAccountDetails, IUserDeletedActivity, IUserInformation, IUsersProfile, @@ -16,10 +18,20 @@ import { } from '../interfaces/user.interface'; import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; // eslint-disable-next-line camelcase -import { RecordType, client_aliases, schema, token, user, user_org_roles } from '@prisma/client'; +import { + Prisma, + RecordType, + account, + client_aliases, + schema, + session, + token, + user, + user_org_roles +} from '@prisma/client'; +import { ProviderType, UserRole } from '@credebl/enum/enum'; import { PrismaService } from '@credebl/prisma-service'; -import { UserRole } from '@credebl/enum/enum'; interface UserQueryOptions { id?: string; // Use the appropriate type based on your data model @@ -119,6 +131,33 @@ export class UserRepository { } } + /** + * + * @param sessionId + * @returns Session details + */ + async getSession(sessionId: string): Promise { + try { + return this.prisma.session.findUnique({ + where: { + id: sessionId + } + }); + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } + } + + async validateSession(sessionId: string): Promise { + const session = await this.prisma.session.findUnique({ + where: { id: sessionId }, + include: { user: true } + }); + // if (!session || new Date() > session.expires) return null; + return session; + } + /** * * @param id @@ -638,6 +677,125 @@ export class UserRepository { } } + async createSession(tokenDetails: ISession): Promise { + try { + const { sessionToken, userId, expires, refreshToken, accountId, sessionType } = tokenDetails; + const sessionResponse = await this.prisma.session.create({ + data: { + sessionToken, + expires, + userId, + refreshToken, + accountId, + sessionType + } + }); + return sessionResponse; + } catch (error) { + this.logger.error(`Error in creating session: ${error.message} `); + throw error; + } + } + + async fetchUserSessions(userId: string): Promise { + try { + const userSessionCount = await this.prisma.session.findMany({ + where: { + userId + } + }); + return userSessionCount; + } catch (error) { + this.logger.error(`Error in getting user session details: ${error.message} `); + throw error; + } + } + + async checkAccountDetails(userId: string): Promise { + try { + const accountDetails = await this.prisma.account.findUnique({ + where: { + userId + } + }); + return accountDetails; + } catch (error) { + this.logger.error(`Error in getting account details: ${error.message} `); + throw error; + } + } + + async fetchAccountByRefreshToken(userId: string, refreshToken: string): Promise { + try { + return await this.prisma.account.findUnique({ + where: { + userId, + refreshToken + } + }); + } catch (error) { + this.logger.error(`Error in getting account details: ${error.message} `); + throw error; + } + } + + async updateAccountDetailsById(accountDetails: IUpdateAccountDetails): Promise { + try { + return await this.prisma.account.update({ + where: { + id: accountDetails.accountId + }, + data: { + accessToken: accountDetails.accessToken, + refreshToken: accountDetails.refreshToken, + expiresAt: accountDetails.expiresAt + } + }); + } catch (error) { + this.logger.error(`Error in getting account details: ${error.message} `); + throw error; + } + } + + async updateAccountDetails(accountDetails: ISession): Promise { + try { + const userAccountDetails = await this.prisma.account.update({ + where: { + userId: accountDetails.userId + }, + data: { + accessToken: accountDetails.sessionToken, + refreshToken: accountDetails.refreshToken, + expiresAt: accountDetails.expires + } + }); + return userAccountDetails; + } catch (error) { + this.logger.error(`Error in updateAccountDetails: ${error.message}`); + throw error; + } + } + + async addAccountDetails(accountDetails: ISession): Promise { + try { + const userAccountDetails = await this.prisma.account.create({ + data: { + userId: accountDetails.userId, + provider: ProviderType.KEYCLOAK, + providerAccountId: accountDetails.keycloakUserId, + accessToken: accountDetails.sessionToken, + refreshToken: accountDetails.refreshToken, + expiresAt: accountDetails.expires, + tokenType: accountDetails.type + } + }); + return userAccountDetails; + } catch (error) { + this.logger.error(`Error in creating account: ${error.message}`); + throw error; + } + } + /** * * @param userId @@ -838,4 +996,35 @@ export class UserRepository { throw error; } } + + async destroySession(sessions: string[]): Promise { + try { + const userSessions = await this.prisma.session.deleteMany({ + where: { + id: { + in: sessions + } + } + }); + + return userSessions; + } catch (error) { + this.logger.error(`Error in logging out user: ${error.message}`); + throw error; + } + } + + async deleteSessionRecordByRefreshToken(refreshToken: string): Promise { + try { + const userSession = await this.prisma.session.delete({ + where: { + refreshToken + } + }); + return userSession; + } catch (error) { + this.logger.error(`Error in logging out user: ${error.message}`); + throw error; + } + } } diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 580de2c4f..a539d8a99 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -1,6 +1,8 @@ import { ICheckUserDetails, IOrgUsers, + ISessionDetails, + ISessions, IUserDeletedActivity, IUserForgotPassword, IUserInformation, @@ -77,6 +79,11 @@ export class UserController { return loginRes; } + @MessagePattern({ cmd: 'fetch-session-details' }) + async getSession(payload: { sessionId: string }): Promise { + return this.userService.getSession(payload?.sessionId); + } + @MessagePattern({ cmd: 'refresh-token-details' }) async refreshTokenDetails(refreshToken: string): Promise { return this.userService.refreshTokenDetails(refreshToken); @@ -257,4 +264,9 @@ export class UserController { async getuserOrganizationByUserId(payload: { userId: string }): Promise { return this.userService.getuserOrganizationByUserId(payload.userId); } + + @MessagePattern({ cmd: 'user-logout' }) + async logout(logoutUserDto: ISessions): Promise { + return this.userService.logout(logoutUserDto); + } } diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index e102079de..c3941e745 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -39,14 +39,17 @@ import { IUserDeletedActivity, UserKeycloakId, IEcosystemConfig, - IUserForgotPassword + IUserForgotPassword, + ISessionDetails, + ISessions, + IUpdateAccountDetails } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; import { v4 as uuidv4 } from 'uuid'; -import { Invitation, UserRole } from '@credebl/enum/enum'; +import { Invitation, ProviderType, SessionType, TokenType, UserRole } from '@credebl/enum/enum'; import validator from 'validator'; import { DISALLOWED_EMAIL_DOMAIN } from '@credebl/common/common.constant'; import { AwsService } from '@credebl/aws'; @@ -377,6 +380,15 @@ export class UserService { ); const holderOrgRole = await this.orgRoleService.getRole(OrgRoles.HOLDER); await this.userOrgRoleService.createUserOrgRole(userDetails.id, holderOrgRole.id, null, holderRoleData.id); + const userAccountDetails = { + userId: userDetails?.id, + provider: ProviderType.KEYCLOAK, + keycloakUserId: keycloakDetails.keycloakUserId, + // eslint-disable-next-line camelcase + type: TokenType.BEARER_TOKEN + }; + + await this.userRepository.addAccountDetails(userAccountDetails); return { userId: userDetails?.id }; } catch (error) { @@ -437,6 +449,13 @@ export class UserService { try { this.validateEmail(email.toLowerCase()); const userData = await this.userRepository.checkUserExist(email.toLowerCase()); + + const userSessionDetails = await this.userRepository.fetchUserSessions(userData?.id); + + if (Number(process.env.SESSIONS_LIMIT) <= userSessionDetails?.length) { + throw new BadRequestException(ResponseMessages.user.error.sessionLimitReached); + } + if (!userData) { throw new NotFoundException(ResponseMessages.user.error.notFound); } @@ -455,7 +474,53 @@ export class UserService { return await this.generateToken(email.toLowerCase(), decryptedPassword, userData); } else { const decryptedPassword = await this.commonService.decryptPassword(password); - return await this.generateToken(email.toLowerCase(), decryptedPassword, userData); + const tokenDetails = await this.generateToken(email.toLowerCase(), decryptedPassword, userData); + + const sessionData = { + sessionToken: tokenDetails?.access_token, + userId: userData?.id, + expires: tokenDetails?.expires_in, + refreshToken: tokenDetails?.refresh_token, + sessionType: SessionType.USER_SESSION + }; + + const fetchAccountDetails = await this.userRepository.checkAccountDetails(userData?.id); + let addSessionDetails; + let accountData; + if (null === fetchAccountDetails) { + accountData = { + sessionToken: tokenDetails?.access_token, + userId: userData?.id, + expires: tokenDetails?.expires_in, + refreshToken: tokenDetails?.refresh_token, + keycloakUserId: userData?.keycloakUserId, + type: TokenType.BEARER_TOKEN + }; + + await this.userRepository.addAccountDetails(accountData).then(async (response) => { + const finalSessionData = { ...sessionData, accountId: response.id }; + addSessionDetails = await this.userRepository.createSession(finalSessionData); + }); + } else { + accountData = { + sessionToken: tokenDetails?.access_token, + userId: userData?.id, + expires: tokenDetails?.expires_in, + refreshToken: tokenDetails?.refresh_token + }; + + await this.userRepository.updateAccountDetails(accountData).then(async (response) => { + const finalSessionData = { ...sessionData, accountId: response.id }; + addSessionDetails = await this.userRepository.createSession(finalSessionData); + }); + } + + const finalResponse = { + ...tokenDetails, + sessionId: addSessionDetails.id + }; + + return finalResponse; } } catch (error) { this.logger.error(`In Login User : ${JSON.stringify(error)}`); @@ -463,16 +528,68 @@ export class UserService { } } + async getSession(sessionId: string): Promise { + try { + const onceDecoded = decodeURIComponent(sessionId); + const decodedSessionId = decodeURIComponent(onceDecoded); + const decryptedSessionId = await this.commonService.decryptPassword(decodedSessionId); + const sessionDetails = await this.userRepository.getSession(decryptedSessionId); + return sessionDetails; + } catch (error) { + this.logger.error(`In fetching session details : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + async refreshTokenDetails(refreshToken: string): Promise { try { try { const data = jwt.decode(refreshToken) as jwt.JwtPayload; const userByKeycloakId = await this.userRepository.getUserByKeycloakId(data?.sub); + this.logger.debug(`User details::;${JSON.stringify(userByKeycloakId)}`); const tokenResponse = await this.clientRegistrationService.getAccessToken( refreshToken, userByKeycloakId?.['clientId'], userByKeycloakId?.['clientSecret'] ); + this.logger.debug(`tokenResponse::::${JSON.stringify(tokenResponse)}`); + // Fetch the details from account table based on userid and refresh token + const userAccountDetails = await this.userRepository.fetchAccountByRefreshToken( + userByKeycloakId?.['id'], + refreshToken + ); + // Update the account details with latest access token, refresh token and exp date + if (!userAccountDetails) { + throw new NotFoundException(ResponseMessages.user.error.userAccountNotFound); + } + const updateAccountDetails: IUpdateAccountDetails = { + accessToken: tokenResponse.access_token, + refreshToken: tokenResponse.refresh_token, + expiresAt: tokenResponse.expires_in, + accountId: userAccountDetails.id + }; + const updateAccountDetailsResponse = await this.userRepository.updateAccountDetailsById(updateAccountDetails); + // Delete the preveious session record and create new one + if (!updateAccountDetailsResponse) { + throw new InternalServerErrorException(ResponseMessages.user.error.errorInUpdateAccountDetails); + } + const deletePreviousSession = await this.userRepository.deleteSessionRecordByRefreshToken(refreshToken); + if (!deletePreviousSession) { + throw new InternalServerErrorException(ResponseMessages.user.error.errorInDeleteSession); + } + const sessionData = { + sessionToken: tokenResponse.access_token, + userId: userByKeycloakId?.['id'], + expires: tokenResponse.expires_in, + refreshToken: tokenResponse.refresh_token, + sessionType: SessionType.USER_SESSION, + accountId: updateAccountDetailsResponse.id + }; + const addSessionDetails = await this.userRepository.createSession(sessionData); + if (!addSessionDetails) { + throw new InternalServerErrorException(ResponseMessages.user.error.errorInSessionCreation); + } + return tokenResponse; } catch (error) { throw new BadRequestException(ResponseMessages.user.error.invalidRefreshToken); @@ -1227,4 +1344,17 @@ export class UserService { throw new RpcException(error.response ? error.response : error); } } + + async logout(logoutUserDto: ISessions): Promise { + try { + if (logoutUserDto?.sessions) { + await this.userRepository.destroySession(logoutUserDto?.sessions); + } + + return 'user logged out successfully'; + } catch (error) { + this.logger.error(`Error in logging out session: ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } } diff --git a/libs/common/src/interfaces/interface.ts b/libs/common/src/interfaces/interface.ts index 4116d21f8..802654cbe 100644 --- a/libs/common/src/interfaces/interface.ts +++ b/libs/common/src/interfaces/interface.ts @@ -13,6 +13,7 @@ export interface IAccessTokenData { refresh_expires_in: number; token_type: string; scope: string; + sessionId?: string; } export interface IOptionalParams { diff --git a/libs/common/src/interfaces/user.interface.ts b/libs/common/src/interfaces/user.interface.ts index 900a1a843..041458bff 100644 --- a/libs/common/src/interfaces/user.interface.ts +++ b/libs/common/src/interfaces/user.interface.ts @@ -5,6 +5,7 @@ export interface ISignInUser { expires_at?: number; refresh_token?: string; isRegisteredToSupabase?: boolean; + sessionId?: string; } export interface IVerifyUserEmail { email: string; diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 3205a4a24..e0bacaac2 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -6,6 +6,7 @@ export const ResponseMessages = { emaiVerified: 'Email verified successfully', fetchClientAliases: 'Client aliases fetched successfully', login: 'User login successfully', + fetchSession: 'Session details fetched successfully', fetchProfile: 'User fetched successfully', fetchInvitations: 'Org invitations fetched successfully', invitationReject: 'Organization invitation rejected', @@ -24,7 +25,8 @@ export const ResponseMessages = { refreshToken: 'Token details fetched successfully', countriesVerificationCode: 'All countries has been fetched successfully', stateVerificationCode: 'All states has been fetched successfully', - cityVerificationCode: 'All cities has been fetched successfully' + cityVerificationCode: 'All cities has been fetched successfully', + logout: 'User logout successfully' }, error: { exists: 'User already exists', @@ -37,6 +39,8 @@ export const ResponseMessages = { invalidEmailUrl: 'Invalid verification code or EmailId!', verifiedEmail: 'Email already verified', notFound: 'User not found', + sessionLimitReached: + 'You have reached the maximum number of allowed sessions. Please remove an existing session to add a new one', verifyMail: 'Please verify your email', invalidCredentials: 'Invalid Credentials', registerFido: 'Please complete your fido registration', @@ -62,7 +66,11 @@ export const ResponseMessages = { invalidResetLink: 'Invalid or expired reset password link', invalidAccessToken: 'Authentication failed', invalidRefreshToken: 'Invalid refreshToken provided', - userOrgsLimit: 'Limit reached: You can be associated with or create maximum 10 organizations.' + userOrgsLimit: 'Limit reached: You can be associated with or create maximum 10 organizations.', + errorInUpdateAccountDetails: 'Error in updating the account details', + errorInDeleteSession: 'Error in deleting the session', + errorInSessionCreation: 'Error in create session', + userAccountNotFound: 'User account not found' } }, organisation: { diff --git a/libs/enum/src/enum.ts b/libs/enum/src/enum.ts index fa0eba90b..9dc8b31ca 100644 --- a/libs/enum/src/enum.ts +++ b/libs/enum/src/enum.ts @@ -257,3 +257,17 @@ export enum ProofType { POLYGON_PROOFTYPE = 'EcdsaSecp256k1Signature2019', NO_LEDGER_PROOFTYPE = 'Ed25519Signature2018' } + +export enum TokenType { + BEARER_TOKEN = 'Bearer' +} + +export enum SessionType { + USER_SESSION = 'user-session', + ORG_SESSION = 'organization-session' +} + +export enum ProviderType { + KEYCLOAK = 'keycloak', + SUPABASE = 'supabase' +} diff --git a/libs/prisma-service/prisma/migrations/20250724130424_added_session_account/migration.sql b/libs/prisma-service/prisma/migrations/20250724130424_added_session_account/migration.sql new file mode 100644 index 000000000..ddaf77772 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20250724130424_added_session_account/migration.sql @@ -0,0 +1,56 @@ +-- CreateTable +CREATE TABLE "account" ( + "id" UUID NOT NULL, + "userId" UUID NOT NULL, + "type" TEXT, + "provider" TEXT NOT NULL, + "providerAccountId" TEXT NOT NULL, + "refresh_token" TEXT, + "access_token" TEXT, + "expires_at" INTEGER, + "token_type" TEXT, + "scope" TEXT, + "id_token" TEXT, + "session_state" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "account_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "session" ( + "id" UUID NOT NULL, + "sessionToken" TEXT NOT NULL, + "userId" UUID NOT NULL, + "expires" INTEGER NOT NULL, + "refresh_token" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "session_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "account_userId_key" ON "account"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "account_refresh_token_key" ON "account"("refresh_token"); + +-- CreateIndex +CREATE UNIQUE INDEX "account_access_token_key" ON "account"("access_token"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_sessionToken_key" ON "session"("sessionToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_userId_key" ON "session"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_refresh_token_key" ON "session"("refresh_token"); + +-- AddForeignKey +ALTER TABLE "account" ADD CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "session" ADD CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/migrations/20250729083628_reltion_between_account_and_session/migration.sql b/libs/prisma-service/prisma/migrations/20250729083628_reltion_between_account_and_session/migration.sql new file mode 100644 index 000000000..0e6137796 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20250729083628_reltion_between_account_and_session/migration.sql @@ -0,0 +1,14 @@ +/* + Warnings: + + - A unique constraint covering the columns `[accountId]` on the table `session` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "session" ADD COLUMN "accountId" UUID; + +-- CreateIndex +CREATE UNIQUE INDEX "session_accountId_key" ON "session"("accountId"); + +-- AddForeignKey +ALTER TABLE "session" ADD CONSTRAINT "session_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "account"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/migrations/20250729090416_removed_userid_unique_constraint/migration.sql b/libs/prisma-service/prisma/migrations/20250729090416_removed_userid_unique_constraint/migration.sql new file mode 100644 index 000000000..5c5bf63c5 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20250729090416_removed_userid_unique_constraint/migration.sql @@ -0,0 +1,2 @@ +-- DropIndex +DROP INDEX "session_userId_key"; diff --git a/libs/prisma-service/prisma/migrations/20250730104449_remove_unique_constraint_for_acoountid/migration.sql b/libs/prisma-service/prisma/migrations/20250730104449_remove_unique_constraint_for_acoountid/migration.sql new file mode 100644 index 000000000..bb1c4da87 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20250730104449_remove_unique_constraint_for_acoountid/migration.sql @@ -0,0 +1,2 @@ +-- DropIndex +DROP INDEX "session_accountId_key"; diff --git a/libs/prisma-service/prisma/migrations/20250731092810_account_session_table_column_name_changes/migration.sql b/libs/prisma-service/prisma/migrations/20250731092810_account_session_table_column_name_changes/migration.sql new file mode 100644 index 000000000..8921a348a --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20250731092810_account_session_table_column_name_changes/migration.sql @@ -0,0 +1,51 @@ +/* + Warnings: + + - You are about to drop the column `access_token` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `expires_at` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `id_token` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `refresh_token` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `session_state` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `token_type` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `refresh_token` on the `session` table. All the data in the column will be lost. + - A unique constraint covering the columns `[refreshToken]` on the table `account` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[accessToken]` on the table `account` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[refreshToken]` on the table `session` will be added. If there are existing duplicate values, this will fail. + +*/ +-- DropIndex +DROP INDEX "account_access_token_key"; + +-- DropIndex +DROP INDEX "account_refresh_token_key"; + +-- DropIndex +DROP INDEX "session_refresh_token_key"; + +-- AlterTable +ALTER TABLE "account" DROP COLUMN "access_token", +DROP COLUMN "expires_at", +DROP COLUMN "id_token", +DROP COLUMN "refresh_token", +DROP COLUMN "session_state", +DROP COLUMN "token_type", +ADD COLUMN "accessToken" TEXT, +ADD COLUMN "expiresAt" INTEGER, +ADD COLUMN "idToken" TEXT, +ADD COLUMN "refreshToken" TEXT, +ADD COLUMN "sessionState" TEXT, +ADD COLUMN "tokenType" TEXT; + +-- AlterTable +ALTER TABLE "session" DROP COLUMN "refresh_token", +ADD COLUMN "refreshToken" TEXT, +ADD COLUMN "sessionType" TEXT; + +-- CreateIndex +CREATE UNIQUE INDEX "account_refreshToken_key" ON "account"("refreshToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "account_accessToken_key" ON "account"("accessToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_refreshToken_key" ON "session"("refreshToken"); diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 02bcbb91d..a06926c6f 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -34,6 +34,41 @@ model user { token token[] user_role_mapping user_role_mapping[] cloud_wallet_user_info cloud_wallet_user_info[] + accounts account[] + sessions session[] +} + +model account { + id String @id @default(uuid()) @db.Uuid + userId String @unique @db.Uuid + type String? + provider String + providerAccountId String + refreshToken String? @unique + accessToken String? @unique + expiresAt Int? + tokenType String? + scope String? + idToken String? + sessionState String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user user @relation(fields: [userId], references: [id]) + sessions session[] + } + +model session { + id String @id @default(uuid()) @db.Uuid + sessionToken String @unique + userId String @db.Uuid + expires Int + refreshToken String? @unique + user user @relation(fields: [userId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + accountId String? @db.Uuid + sessionType String? + account account? @relation(fields: [accountId], references:[id]) } model token { diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index 22919fec1..c3983f250 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -6,7 +6,7 @@ import { CommonConstants } from '../../common/src/common.constant'; import * as CryptoJS from 'crypto-js'; import { exec } from 'child_process'; import * as util from 'util'; -const execPromise = util.promisify(exec); +const execPromise = util.promisify(exec); const prisma = new PrismaClient(); const logger = new Logger('Init seed DB'); @@ -14,494 +14,478 @@ let platformUserId = ''; const configData = fs.readFileSync(`${process.cwd()}/prisma/data/credebl-master-table.json`, 'utf8'); const createPlatformConfig = async (): Promise => { - try { - const existPlatformAdmin = await prisma.platform_config.findMany(); - - if (0 === existPlatformAdmin.length) { - const { platformConfigData } = JSON.parse(configData); - const platformConfig = await prisma.platform_config.create({ - data: platformConfigData - }); - - logger.log(platformConfig); - } else { - logger.log('Already seeding in platform config'); - } - } catch (error) { - logger.error('An error occurred seeding platformConfig:', error); - throw error; + try { + const existPlatformAdmin = await prisma.platform_config.findMany(); + + if (0 === existPlatformAdmin.length) { + const { platformConfigData } = JSON.parse(configData); + const platformConfig = await prisma.platform_config.create({ + data: platformConfigData + }); + + logger.log(platformConfig); + } else { + logger.log('Already seeding in platform config'); } + } catch (error) { + logger.error('An error occurred seeding platformConfig:', error); + throw error; + } }; const createOrgRoles = async (): Promise => { - try { - const { orgRoleData } = JSON.parse(configData); - const roleNames = orgRoleData.map(role => role.name); - const existOrgRole = await prisma.org_roles.findMany({ - where: { - name: { - in: roleNames - } - } - }); - - if (0 === existOrgRole.length) { - const orgRoles = await prisma.org_roles.createMany({ - data: orgRoleData - }); - - logger.log(orgRoles); - } else { - logger.log('Already seeding in org role'); + try { + const { orgRoleData } = JSON.parse(configData); + const roleNames = orgRoleData.map((role) => role.name); + const existOrgRole = await prisma.org_roles.findMany({ + where: { + name: { + in: roleNames } + } + }); + + if (0 === existOrgRole.length) { + const orgRoles = await prisma.org_roles.createMany({ + data: orgRoleData + }); - } catch (error) { - logger.error('An error occurred seeding orgRoles:', error); - throw error; + logger.log(orgRoles); + } else { + logger.log('Already seeding in org role'); } + } catch (error) { + logger.error('An error occurred seeding orgRoles:', error); + throw error; + } }; const createAgentTypes = async (): Promise => { - try { - const { agentTypeData } = JSON.parse(configData); - - const agentType = agentTypeData.map(agentType => agentType.agent); - const existAgentType = await prisma.agents_type.findMany({ - where: { - agent: { - in: agentType - } - } - }); - - if (0 === existAgentType.length) { - const agentTypes = await prisma.agents_type.createMany({ - data: agentTypeData - }); - - logger.log(agentTypes); - } else { - logger.log('Already seeding in agent type'); + try { + const { agentTypeData } = JSON.parse(configData); + + const agentType = agentTypeData.map((agentType) => agentType.agent); + const existAgentType = await prisma.agents_type.findMany({ + where: { + agent: { + in: agentType } + } + }); + if (0 === existAgentType.length) { + const agentTypes = await prisma.agents_type.createMany({ + data: agentTypeData + }); - } catch (error) { - logger.error('An error occurred seeding agentTypes:', error); - throw error; + logger.log(agentTypes); + } else { + logger.log('Already seeding in agent type'); } + } catch (error) { + logger.error('An error occurred seeding agentTypes:', error); + throw error; + } }; const createOrgAgentTypes = async (): Promise => { - try { - const { orgAgentTypeData } = JSON.parse(configData); - const orgAgentType = orgAgentTypeData.map(orgAgentType => orgAgentType.agent); - const existAgentType = await prisma.org_agents_type.findMany({ - where: { - agent: { - in: orgAgentType - } - } - }); - - if (0 === existAgentType.length) { - const orgAgentTypes = await prisma.org_agents_type.createMany({ - data: orgAgentTypeData - }); - - logger.log(orgAgentTypes); - } else { - logger.log('Already seeding in org agent type'); + try { + const { orgAgentTypeData } = JSON.parse(configData); + const orgAgentType = orgAgentTypeData.map((orgAgentType) => orgAgentType.agent); + const existAgentType = await prisma.org_agents_type.findMany({ + where: { + agent: { + in: orgAgentType } + } + }); + if (0 === existAgentType.length) { + const orgAgentTypes = await prisma.org_agents_type.createMany({ + data: orgAgentTypeData + }); - } catch (error) { - logger.error('An error occurred seeding orgAgentTypes:', error); - throw error; + logger.log(orgAgentTypes); + } else { + logger.log('Already seeding in org agent type'); } + } catch (error) { + logger.error('An error occurred seeding orgAgentTypes:', error); + throw error; + } }; const createPlatformUser = async (): Promise => { - try { - const { platformAdminData } = JSON.parse(configData); - platformAdminData.email = process.env.PLATFORM_ADMIN_EMAIL; - platformAdminData.username = process.env.PLATFORM_ADMIN_EMAIL; - - const existPlatformAdminUser = await prisma.user.findMany({ - where: { - email: platformAdminData.email - } - }); + try { + const { platformAdminData } = JSON.parse(configData); + platformAdminData.email = process.env.PLATFORM_ADMIN_EMAIL; + platformAdminData.username = process.env.PLATFORM_ADMIN_EMAIL; + + const existPlatformAdminUser = await prisma.user.findMany({ + where: { + email: platformAdminData.email + } + }); - if (0 === existPlatformAdminUser.length) { - const platformUser = await prisma.user.create({ - data: platformAdminData - }); + if (0 === existPlatformAdminUser.length) { + const platformUser = await prisma.user.create({ + data: platformAdminData + }); - platformUserId = platformUser.id; + platformUserId = platformUser.id; - logger.log(platformUser); - } else { - logger.log('Already seeding in user'); - } - - } catch (error) { - logger.error('An error occurred seeding platformUser:', error); - throw error; + logger.log(platformUser); + } else { + logger.log('Already seeding in user'); } + } catch (error) { + logger.error('An error occurred seeding platformUser:', error); + throw error; + } }; - const createPlatformOrganization = async (): Promise => { - try { - const { platformAdminOrganizationData } = JSON.parse(configData); - platformAdminOrganizationData.createdBy = platformUserId; - platformAdminOrganizationData.lastChangedBy = platformUserId; - - const existPlatformAdminUser = await prisma.organisation.findMany({ - where: { - name: platformAdminOrganizationData.name - } - }); - - if (0 === existPlatformAdminUser.length) { - const platformOrganization = await prisma.organisation.create({ - data: platformAdminOrganizationData - }); - - logger.log(platformOrganization); - } else { - logger.log('Already seeding in organization'); - } + try { + const { platformAdminOrganizationData } = JSON.parse(configData); + platformAdminOrganizationData.createdBy = platformUserId; + platformAdminOrganizationData.lastChangedBy = platformUserId; + + const existPlatformAdminUser = await prisma.organisation.findMany({ + where: { + name: platformAdminOrganizationData.name + } + }); + + if (0 === existPlatformAdminUser.length) { + const platformOrganization = await prisma.organisation.create({ + data: platformAdminOrganizationData + }); - } catch (error) { - logger.error('An error occurred seeding platformOrganization:', error); - throw error; + logger.log(platformOrganization); + } else { + logger.log('Already seeding in organization'); } + } catch (error) { + logger.error('An error occurred seeding platformOrganization:', error); + throw error; + } }; const createPlatformUserOrgRoles = async (): Promise => { - try { - - const userId = await prisma.user.findUnique({ - where: { - email: `${CommonConstants.PLATFORM_ADMIN_EMAIL}` - } - }); - - const orgId = await prisma.organisation.findFirst({ - where: { - name: `${CommonConstants.PLATFORM_ADMIN_ORG}` - } - }); - - const orgRoleId = await prisma.org_roles.findUnique({ - where: { - name: `${CommonConstants.PLATFORM_ADMIN_ORG_ROLE}` - } - }); - - if (!userId && !orgId && !orgRoleId) { - const platformOrganization = await prisma.user_org_roles.create({ - data: { - userId: userId.id, - orgRoleId: orgRoleId.id, - orgId: orgId.id - } - }); - logger.log(platformOrganization); - } else { - logger.log('Already seeding in org_roles'); - } + try { + const userId = await prisma.user.findUnique({ + where: { + email: `${process.env.PLATFORM_ADMIN_EMAIL}` + } + }); + const orgId = await prisma.organisation.findFirst({ + where: { + name: `${CommonConstants.PLATFORM_ADMIN_ORG}` + } + }); - } catch (error) { - logger.error('An error occurred seeding platformOrganization:', error); - throw error; + const orgRoleId = await prisma.org_roles.findUnique({ + where: { + name: `${CommonConstants.PLATFORM_ADMIN_ORG_ROLE}` + } + }); + + if (!userId && !orgId && !orgRoleId) { + const platformOrganization = await prisma.user_org_roles.create({ + data: { + userId: userId.id, + orgRoleId: orgRoleId.id, + orgId: orgId.id + } + }); + logger.log(platformOrganization); + } else { + logger.log('Already seeding in org_roles'); } + } catch (error) { + logger.error('An error occurred seeding platformOrganization:', error); + throw error; + } }; const createLedger = async (): Promise => { - try { - const { ledgerData } = JSON.parse(configData); - - const existingLedgers = await prisma.ledgers.findMany(); - - if (0 === existingLedgers.length) { - const createLedger = await prisma.ledgers.createMany({ - data: ledgerData - }); - logger.log('All ledgers inserted:', createLedger); - } else { - const updatesNeeded = []; - - if (existingLedgers.length !== ledgerData.length) { - updatesNeeded.push(ledgerData); - if (0 < updatesNeeded.length) { - await prisma.ledgers.deleteMany(); - - const createLedger = await prisma.ledgers.createMany({ - data: ledgerData - }); - logger.log('Updated ledgers:', createLedger); - } else { - logger.log('No changes in ledger data'); - } + try { + const { ledgerData } = JSON.parse(configData); + + const existingLedgers = await prisma.ledgers.findMany(); + + if (0 === existingLedgers.length) { + const createLedger = await prisma.ledgers.createMany({ + data: ledgerData + }); + logger.log('All ledgers inserted:', createLedger); + } else { + const updatesNeeded = []; + + if (existingLedgers.length !== ledgerData.length) { + updatesNeeded.push(ledgerData); + if (0 < updatesNeeded.length) { + await prisma.ledgers.deleteMany(); + + const createLedger = await prisma.ledgers.createMany({ + data: ledgerData + }); + logger.log('Updated ledgers:', createLedger); } else { - logger.log('No changes in ledger data'); + logger.log('No changes in ledger data'); } + } else { + logger.log('No changes in ledger data'); } - } catch (error) { - logger.error('An error occurred seeding createLedger:', error); - throw error; } - }; + } catch (error) { + logger.error('An error occurred seeding createLedger:', error); + throw error; + } +}; const createLedgerConfig = async (): Promise => { - try { - const { ledgerConfig } = JSON.parse(configData); - - const ledgerConfigList = await prisma.ledgerConfig.findMany(); - - const checkDataIsEqual = (ledgerConfig, ledgerConfigList): boolean => { - if (ledgerConfig.length !== ledgerConfigList.length) { - return false; - } - - for (let i = 0; i < ledgerConfig.length; i++) { - const config1 = ledgerConfig[i]; - const config2 = ledgerConfigList.find(item => item.name === config1.name && JSON.stringify(item.details) === JSON.stringify(config1.details)); - - if (!config2) { - return false; - } - } - return true; - }; - - if (0 === ledgerConfigList.length) { - const configDetails = await prisma.ledgerConfig.createMany({ - data: ledgerConfig - }); - logger.log('Ledger config created:', configDetails); - - } else if (!checkDataIsEqual(ledgerConfig, ledgerConfigList)) { - await prisma.ledgerConfig.deleteMany({}); - const configDetails = await prisma.ledgerConfig.createMany({ - data: ledgerConfig - }); - logger.log('Existing ledger config deleted and new ones created:', configDetails); - } else { - logger.log('Already seeding in ledger config'); + try { + const { ledgerConfig } = JSON.parse(configData); + + const ledgerConfigList = await prisma.ledgerConfig.findMany(); + + const checkDataIsEqual = (ledgerConfig, ledgerConfigList): boolean => { + if (ledgerConfig.length !== ledgerConfigList.length) { + return false; + } + + for (let i = 0; i < ledgerConfig.length; i++) { + const config1 = ledgerConfig[i]; + const config2 = ledgerConfigList.find( + (item) => item.name === config1.name && JSON.stringify(item.details) === JSON.stringify(config1.details) + ); + + if (!config2) { + return false; } - } catch (error) { - logger.error('An error occurred while configuring ledger:', error); - throw error; + } + return true; + }; + + if (0 === ledgerConfigList.length) { + const configDetails = await prisma.ledgerConfig.createMany({ + data: ledgerConfig + }); + logger.log('Ledger config created:', configDetails); + } else if (!checkDataIsEqual(ledgerConfig, ledgerConfigList)) { + await prisma.ledgerConfig.deleteMany({}); + const configDetails = await prisma.ledgerConfig.createMany({ + data: ledgerConfig + }); + logger.log('Existing ledger config deleted and new ones created:', configDetails); + } else { + logger.log('Already seeding in ledger config'); } + } catch (error) { + logger.error('An error occurred while configuring ledger:', error); + throw error; + } }; const createUserRole = async (): Promise => { - try { - const { userRoleData } = JSON.parse(configData); - - const userRoleDetails = userRoleData.map(userRole => userRole.role); - const existUserRole = await prisma.user_role.findMany({ - where: { - role: { - in: userRoleDetails - } - } - }); - - if (0 === existUserRole.length) { - const userRole = await prisma.user_role.createMany({ - data: userRoleData - }); - - logger.log(userRole); - } else { - logger.log('Already seeding in user role'); + try { + const { userRoleData } = JSON.parse(configData); + + const userRoleDetails = userRoleData.map((userRole) => userRole.role); + const existUserRole = await prisma.user_role.findMany({ + where: { + role: { + in: userRoleDetails } + } + }); + if (0 === existUserRole.length) { + const userRole = await prisma.user_role.createMany({ + data: userRoleData + }); - } catch (error) { - logger.error('An error occurred seeding user role:', error); - throw error; + logger.log(userRole); + } else { + logger.log('Already seeding in user role'); } + } catch (error) { + logger.error('An error occurred seeding user role:', error); + throw error; + } }; const migrateOrgAgentDids = async (): Promise => { - try { - const orgAgents = await prisma.org_agents.findMany({ - where: { - walletName: { - not: 'platform-admin' - } - } - }); - - const orgDids = orgAgents.map((agent) => agent.orgDid).filter((did) => null !== did && '' !== did); - const existingDids = await prisma.org_dids.findMany({ - where: { - did: { - in: orgDids - } - } - }); - - const filteredOrgAgents = orgAgents.filter( - (agent) => null !== agent.orgDid && '' !== agent.orgDid - ); + try { + const orgAgents = await prisma.org_agents.findMany({ + where: { + walletName: { + not: 'platform-admin' + } + } + }); - // If there are org DIDs that do not exist in org_dids table - if (orgDids.length !== existingDids.length) { - const newOrgAgents = filteredOrgAgents.filter( - (agent) => !existingDids.some((did) => did.did === agent.orgDid) - ); - - const newDidRecords = newOrgAgents.map((agent) => ({ - orgId: agent.orgId, - did: agent.orgDid, - didDocument: agent.didDocument, - isPrimaryDid: true, - createdBy: agent.createdBy, - lastChangedBy: agent.lastChangedBy, - orgAgentId: agent.id - })); - - const didInsertResult = await prisma.org_dids.createMany({ - data: newDidRecords - }); - - logger.log(didInsertResult); - } else { - logger.log('No new DIDs to migrate in migrateOrgAgentDids'); + const orgDids = orgAgents.map((agent) => agent.orgDid).filter((did) => null !== did && '' !== did); + const existingDids = await prisma.org_dids.findMany({ + where: { + did: { + in: orgDids } - } catch (error) { - logger.error('An error occurred during migrateOrgAgentDids:', error); - throw error; + } + }); + + const filteredOrgAgents = orgAgents.filter((agent) => null !== agent.orgDid && '' !== agent.orgDid); + + // If there are org DIDs that do not exist in org_dids table + if (orgDids.length !== existingDids.length) { + const newOrgAgents = filteredOrgAgents.filter((agent) => !existingDids.some((did) => did.did === agent.orgDid)); + + const newDidRecords = newOrgAgents.map((agent) => ({ + orgId: agent.orgId, + did: agent.orgDid, + didDocument: agent.didDocument, + isPrimaryDid: true, + createdBy: agent.createdBy, + lastChangedBy: agent.lastChangedBy, + orgAgentId: agent.id + })); + + const didInsertResult = await prisma.org_dids.createMany({ + data: newDidRecords + }); + + logger.log(didInsertResult); + } else { + logger.log('No new DIDs to migrate in migrateOrgAgentDids'); } + } catch (error) { + logger.error('An error occurred during migrateOrgAgentDids:', error); + throw error; + } }; const addSchemaType = async (): Promise => { - try { - const emptyTypeSchemaList = await prisma.schema.findMany({ - where: { - OR: [ - { type: null }, - { type: '' } - ] - } - }); - if (0 < emptyTypeSchemaList.length) { - const updatePromises = emptyTypeSchemaList.map((schema) => prisma.schema.update({ - where: { id: schema.id }, - data: { type: 'indy' } - }) - ); - await Promise.all(updatePromises); - - logger.log('Schemas updated successfully'); - } else { - logger.log('No schemas to update'); - } - } catch (error) { - logger.error('An error occurred during addSchemaType:', error); - throw error; + try { + const emptyTypeSchemaList = await prisma.schema.findMany({ + where: { + OR: [{ type: null }, { type: '' }] + } + }); + if (0 < emptyTypeSchemaList.length) { + const updatePromises = emptyTypeSchemaList.map((schema) => + prisma.schema.update({ + where: { id: schema.id }, + data: { type: 'indy' } + }) + ); + await Promise.all(updatePromises); + + logger.log('Schemas updated successfully'); + } else { + logger.log('No schemas to update'); } + } catch (error) { + logger.error('An error occurred during addSchemaType:', error); + throw error; + } }; const importGeoLocationMasterData = async (): Promise => { - try { - const scriptPath = process.env.GEO_LOCATION_MASTER_DATA_IMPORT_SCRIPT; - const dbUrl = process.env.DATABASE_URL; - - if (!scriptPath || !dbUrl) { - throw new Error('Environment variables GEO_LOCATION_MASTER_DATA_IMPORT_SCRIPT or DATABASE_URL are not set.'); - } - - const command = `${process.cwd()}/${scriptPath} ${dbUrl}`; - - const { stdout, stderr } = await execPromise(command); - - if (stdout) { - logger.log(`Shell script output: ${stdout}`); - } - if (stderr) { - logger.error(`Shell script error: ${stderr}`); - } - } catch (error) { - logger.error('An error occurred during importGeoLocationMasterData:', error); - throw error; + try { + const scriptPath = process.env.GEO_LOCATION_MASTER_DATA_IMPORT_SCRIPT; + const dbUrl = process.env.DATABASE_URL; + + if (!scriptPath || !dbUrl) { + throw new Error('Environment variables GEO_LOCATION_MASTER_DATA_IMPORT_SCRIPT or DATABASE_URL are not set.'); } - }; -const encryptClientCredential = async (clientCredential: string): Promise => { - try { - const encryptedToken = CryptoJS.AES.encrypt(JSON.stringify(clientCredential), process.env.CRYPTO_PRIVATE_KEY).toString(); + const command = `${process.cwd()}/${scriptPath} ${dbUrl}`; + + const { stdout, stderr } = await execPromise(command); - return encryptedToken; - } catch (error) { - logger.error('An error occurred during encryptClientCredential:', error); - throw error; + if (stdout) { + logger.log(`Shell script output: ${stdout}`); + } + if (stderr) { + logger.error(`Shell script error: ${stderr}`); } + } catch (error) { + logger.error('An error occurred during importGeoLocationMasterData:', error); + throw error; + } +}; + +const encryptClientCredential = async (clientCredential: string): Promise => { + try { + const encryptedToken = CryptoJS.AES.encrypt( + JSON.stringify(clientCredential), + process.env.CRYPTO_PRIVATE_KEY + ).toString(); + + return encryptedToken; + } catch (error) { + logger.error('An error occurred during encryptClientCredential:', error); + throw error; + } }; const updateClientCredential = async (): Promise => { - try { - const scriptPath = process.env.UPDATE_CLIENT_CREDENTIAL_SCRIPT; - const dbUrl = process.env.DATABASE_URL; - const clientId = process.env.KEYCLOAK_MANAGEMENT_CLIENT_ID; - const clientSecret = process.env.KEYCLOAK_MANAGEMENT_CLIENT_SECRET; - - if (!scriptPath || !dbUrl || !clientId || !clientSecret) { - throw new Error('Environment variables UPDATE_CLIENT_CREDENTIAL_SCRIPT or DATABASE_URL or clientId or clientSecret are not set.'); - } - - const encryptedClientId = await encryptClientCredential(process.env.KEYCLOAK_MANAGEMENT_CLIENT_ID); - const encryptedClientSecret = await encryptClientCredential(process.env.KEYCLOAK_MANAGEMENT_CLIENT_SECRET); - - const command = `${process.cwd()}/${scriptPath} ${dbUrl} ${encryptedClientId} ${encryptedClientSecret}`; - - const { stdout, stderr } = await execPromise(command); - - if (stdout) { - logger.log(`Shell script output: ${stdout}`); - } - if (stderr) { - logger.error(`Shell script error: ${stderr}`); - } - - } catch (error) { - logger.error('An error occurred during updateClientCredential:', error); - throw error; + try { + const scriptPath = process.env.UPDATE_CLIENT_CREDENTIAL_SCRIPT; + const dbUrl = process.env.DATABASE_URL; + const clientId = process.env.KEYCLOAK_MANAGEMENT_CLIENT_ID; + const clientSecret = process.env.KEYCLOAK_MANAGEMENT_CLIENT_SECRET; + + if (!scriptPath || !dbUrl || !clientId || !clientSecret) { + throw new Error( + 'Environment variables UPDATE_CLIENT_CREDENTIAL_SCRIPT or DATABASE_URL or clientId or clientSecret are not set.' + ); } -}; + const encryptedClientId = await encryptClientCredential(process.env.KEYCLOAK_MANAGEMENT_CLIENT_ID); + const encryptedClientSecret = await encryptClientCredential(process.env.KEYCLOAK_MANAGEMENT_CLIENT_SECRET); -async function main(): Promise { + const command = `${process.cwd()}/${scriptPath} ${dbUrl} ${encryptedClientId} ${encryptedClientSecret}`; + + const { stdout, stderr } = await execPromise(command); + + if (stdout) { + logger.log(`Shell script output: ${stdout}`); + } + if (stderr) { + logger.error(`Shell script error: ${stderr}`); + } + } catch (error) { + logger.error('An error occurred during updateClientCredential:', error); + throw error; + } +}; - await createPlatformConfig(); - await createOrgRoles(); - await createAgentTypes(); - await createPlatformUser(); - await createPlatformOrganization(); - await createPlatformUserOrgRoles(); - await createOrgAgentTypes(); - await createLedger(); - await createLedgerConfig(); - await createUserRole(); - await migrateOrgAgentDids(); - await addSchemaType(); - await importGeoLocationMasterData(); - await updateClientCredential(); +async function main(): Promise { + await createPlatformConfig(); + await createOrgRoles(); + await createAgentTypes(); + await createPlatformUser(); + await createPlatformOrganization(); + await createPlatformUserOrgRoles(); + await createOrgAgentTypes(); + await createLedger(); + await createLedgerConfig(); + await createUserRole(); + await migrateOrgAgentDids(); + await addSchemaType(); + await importGeoLocationMasterData(); + await updateClientCredential(); } main() - .then(async () => { - await prisma.$disconnect(); - }) - .catch(async (error) => { - logger.error(`In prisma seed initialize`, error); - await prisma.$disconnect(); - process.exit(1); - }); \ No newline at end of file + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (error) => { + logger.error(`In prisma seed initialize`, error); + await prisma.$disconnect(); + process.exit(1); + }); diff --git a/package.json b/package.json index 3bae65e70..214141f89 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "cache-manager-redis-store": "^2.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "cookie-parser": "^1.4.7", "crypto-js": "^4.1.1", "crypto-random-string": "^5.0.0", "dotenv": "^16.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05fd8cd04..3c325bcf7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,10 +16,10 @@ importers: version: 3.1.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@0.26.1)(rxjs@7.8.2) '@nestjs/bull': specifier: ^10.0.1 - version: 10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(bull@4.16.5) + version: 10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(bull@4.16.5) '@nestjs/cache-manager': specifier: ^2.1.0 - version: 2.3.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(cache-manager@5.7.6)(rxjs@7.8.2) + version: 2.3.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(cache-manager@5.7.6)(rxjs@7.8.2) '@nestjs/common': specifier: ^10.2.7 version: 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -46,13 +46,13 @@ importers: version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/websockets@10.4.19)(rxjs@7.8.2) '@nestjs/schedule': specifier: ^3.0.1 - version: 3.0.4(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14) + version: 3.0.4(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(reflect-metadata@0.1.14) '@nestjs/swagger': specifier: ^7.1.6 - version: 7.4.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14) + version: 7.4.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14) '@nestjs/typeorm': specifier: ^10.0.0 - version: 10.0.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.25(ioredis@5.6.1)(pg@8.16.2)(redis@3.1.2)(reflect-metadata@0.1.14)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3))) + version: 10.0.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.25(ioredis@5.6.1)(pg@8.16.2)(redis@3.1.2)(reflect-metadata@0.1.14)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3))) '@nestjs/websockets': specifier: ^10.1.3 version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/platform-socket.io@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -143,6 +143,9 @@ importers: class-validator: specifier: ^0.14.0 version: 0.14.2 + cookie-parser: + specifier: ^1.4.7 + version: 1.4.7 crypto-js: specifier: ^4.1.1 version: 4.2.0 @@ -199,7 +202,7 @@ importers: version: 2.29.3 nestjs-cls: specifier: ^4.3.0 - version: 4.5.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 4.5.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) nestjs-rate-limiter: specifier: ^3.1.0 version: 3.1.0 @@ -302,7 +305,7 @@ importers: version: 10.2.3(chokidar@3.6.0)(typescript@5.8.3) '@nestjs/testing': specifier: ^10.1.3 - version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)) + version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19) '@types/express': specifier: ^4.17.17 version: 4.17.23 @@ -395,7 +398,7 @@ importers: version: 10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/testing': specifier: ^10.1.3 - version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)) + version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19) aws-sdk: specifier: ^2.1510.0 version: 2.1692.0 @@ -426,10 +429,10 @@ importers: version: 10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/swagger': specifier: ^7.1.6 - version: 7.4.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14) + version: 7.4.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14) '@nestjs/testing': specifier: ^10.1.3 - version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)) + version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19) '@prisma/client': specifier: ^5.1.1 version: 5.22.0(prisma@5.22.0) @@ -2402,6 +2405,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-parser@1.4.7: + resolution: {integrity: sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==} + engines: {node: '>= 0.8.0'} + cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} @@ -6617,21 +6624,21 @@ snapshots: axios: 0.26.1 rxjs: 7.8.2 - '@nestjs/bull-shared@10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))': + '@nestjs/bull-shared@10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)': dependencies: '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) tslib: 2.8.1 - '@nestjs/bull@10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(bull@4.16.5)': + '@nestjs/bull@10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(bull@4.16.5)': dependencies: - '@nestjs/bull-shared': 10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)) + '@nestjs/bull-shared': 10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19) '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) bull: 4.16.5 tslib: 2.8.1 - '@nestjs/cache-manager@2.3.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(cache-manager@5.7.6)(rxjs@7.8.2)': + '@nestjs/cache-manager@2.3.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(cache-manager@5.7.6)(rxjs@7.8.2)': dependencies: '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -6762,7 +6769,7 @@ snapshots: - supports-color - utf-8-validate - '@nestjs/schedule@3.0.4(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)': + '@nestjs/schedule@3.0.4(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(reflect-metadata@0.1.14)': dependencies: '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -6792,7 +6799,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@7.4.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)': + '@nestjs/swagger@7.4.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)': dependencies: '@microsoft/tsdoc': 0.15.1 '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -6807,7 +6814,7 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.2 - '@nestjs/testing@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19))': + '@nestjs/testing@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)': dependencies: '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -6816,7 +6823,7 @@ snapshots: '@nestjs/microservices': 10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/platform-express': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19) - '@nestjs/typeorm@10.0.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.25(ioredis@5.6.1)(pg@8.16.2)(redis@3.1.2)(reflect-metadata@0.1.14)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)))': + '@nestjs/typeorm@10.0.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.25(ioredis@5.6.1)(pg@8.16.2)(redis@3.1.2)(reflect-metadata@0.1.14)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)))': dependencies: '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -8439,6 +8446,11 @@ snapshots: convert-source-map@2.0.0: {} + cookie-parser@1.4.7: + dependencies: + cookie: 0.7.2 + cookie-signature: 1.0.6 + cookie-signature@1.0.6: {} cookie@0.5.0: @@ -10845,7 +10857,7 @@ snapshots: neo-async@2.6.2: {} - nestjs-cls@4.5.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2): + nestjs-cls@4.5.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2): dependencies: '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)