diff --git a/.env.demo b/.env.demo index fe2554c9f..36f57d8a3 100644 --- a/.env.demo +++ b/.env.demo @@ -103,6 +103,28 @@ CLOUD_WALLET_NKEY_SEED= GEOLOCATION_NKEY_SEED= NOTIFICATION_NKEY_SEED= +# SSO +# To add more clients, simply add comma separated values of client names +SUPPORTED_SSO_CLIENTS=CREDEBL + +# To add more client add the following variables for each additional client. +# Replace the `CLIENT-NAME` with the appropriate client name as added in `SUPPORTED_SSO_CLIENTS` +# Default client will not need the following details + +# CLIENT-NAME_CLIENT_ALIAS=VERIFIER + # # Domain represents the redirection url once the client logs-in + # # TODO: Can be taken from keycloak instead +# CLIENT-NAME_DOMAIN=htts://VERIFIER-domain.com + # # Encrypted client credentials using the `CRYPTO_PRIVATE_KEY` +# CLIENT-NAME_KEYCLOAK_MANAGEMENT_CLIENT_ID= +# CLIENT-NAME_KEYCLOAK_MANAGEMENT_CLIENT_SECRET= + +# Sample values: +# VERIFIER_CLIENT_ALIAS=VERIFIER +# VERIFIER_DOMAIN=htts://VERIFIER-domain.com +# VERIFIER_KEYCLOAK_MANAGEMENT_CLIENT_ID=encryptedKeyCloakClientId +# VERIFIER_KEYCLOAK_MANAGEMENT_CLIENT_SECRET=encryptedKeyCloakClientSecret + KEYCLOAK_DOMAIN=http://localhost:8080/ KEYCLOAK_ADMIN_URL=http://localhost:8080 KEYCLOAK_MASTER_REALM=master diff --git a/.env.sample b/.env.sample index b9b0c5252..9df90a6e2 100644 --- a/.env.sample +++ b/.env.sample @@ -148,6 +148,28 @@ KEYCLOAK_MANAGEMENT_CLIENT_ID=xxxxxxx KEYCLOAK_MANAGEMENT_CLIENT_SECRET=xxxxxxx KEYCLOAK_REALM=xxxxxxx +# SSO +# To add more clients, simply add comma separated values of client names +SUPPORTED_SSO_CLIENTS=CREDEBL + +# To add more client add the following variables for each additional client. +# Replace the `CLIENT-NAME` with the appropriate client name as added in `SUPPORTED_SSO_CLIENTS` +# Default client will not need the following details + +# CLIENT-NAME_CLIENT_ALIAS=MYAPP + # # Domain represents the redirection url once the client logs-in + # # TODO: Can be taken from keycloak instead +# CLIENT-NAME_DOMAIN=htts://myapp.com + # # Encrypted client credentials using the `CRYPTO_PRIVATE_KEY` +# CLIENT-NAME_KEYCLOAK_MANAGEMENT_CLIENT_ID= +# CLIENT-NAME_KEYCLOAK_MANAGEMENT_CLIENT_SECRET + +# Sample values: +# VERIFIER_CLIENT_ALIAS=VERIFIER +# VERIFIER_DOMAIN=htts://VERIFIER-domain.com +# VERIFIER_KEYCLOAK_MANAGEMENT_CLIENT_ID=encryptedKeyCloakClientId +# VERIFIER_KEYCLOAK_MANAGEMENT_CLIENT_SECRET=encryptedKeyCloakClientSecret + ENABLE_CORS_IP_LIST="" # Provide a list of domains that are allowed to use this server SCHEMA_FILE_SERVER_URL= // Please provide schema URL SCHEMA_FILE_SERVER_TOKEN=xxxxxxxx // Please provide schema file server token for polygon diff --git a/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml index 4a95540b9..2adca8445 100644 --- a/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml +++ b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml @@ -70,4 +70,4 @@ body: - Docs updated - Tests written and passing validations: - required: false + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a9f326c77..b35aba57e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -87,4 +87,4 @@ body: - Console output - Stack traces validations: - required: false + required: false \ No newline at end of file diff --git a/apps/agent-service/src/repositories/agent-service.repository.ts b/apps/agent-service/src/repositories/agent-service.repository.ts index 4eb239932..4c9be4050 100644 --- a/apps/agent-service/src/repositories/agent-service.repository.ts +++ b/apps/agent-service/src/repositories/agent-service.repository.ts @@ -1,17 +1,6 @@ -import { PrismaService } from '@credebl/prisma-service'; +/* eslint-disable camelcase */ +import { AgentType, PrismaTables } from '@credebl/enum/enum'; import { ConflictException, Injectable, Logger } from '@nestjs/common'; -// eslint-disable-next-line camelcase -import { - Prisma, - ledgerConfig, - ledgers, - org_agents, - org_agents_type, - org_dids, - organisation, - platform_config, - user -} from '@prisma/client'; import { ICreateOrgAgent, ILedgers, @@ -24,7 +13,19 @@ import { LedgerNameSpace, OrgDid } from '../interface/agent-service.interface'; -import { AgentType, PrismaTables } from '@credebl/enum/enum'; +import { + Prisma, + ledgerConfig, + ledgers, + org_agents, + org_agents_type, + org_dids, + organisation, + platform_config, + user +} from '@prisma/client'; + +import { PrismaService } from '@credebl/prisma-service'; @Injectable() export class AgentServiceRepository { diff --git a/apps/api-gateway/src/authz/authz.controller.ts b/apps/api-gateway/src/authz/authz.controller.ts index 5821d6bed..fe43aebea 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -13,7 +13,7 @@ import { } from '@nestjs/common'; import { AuthzService } from './authz.service'; import { CommonService } from '../../../../libs/common/src/common.service'; -import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { 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'; @@ -28,7 +28,8 @@ import { ResetPasswordDto } from './dtos/reset-password.dto'; import { ForgotPasswordDto } from './dtos/forgot-password.dto'; import { ResetTokenPasswordDto } from './dtos/reset-token-password'; import { RefreshTokenDto } from './dtos/refresh-token.dto'; - +import { ClientAliasValidationPipe } from './decorators/user-auth-client'; +import { getDefaultClient } from '../user/utils'; @Controller('auth') @ApiTags('auth') @@ -36,19 +37,46 @@ import { RefreshTokenDto } from './dtos/refresh-token.dto'; export class AuthzController { private logger = new Logger('AuthzController'); - constructor(private readonly authzService: AuthzService, - private readonly commonService: CommonService) { } + constructor( + private readonly authzService: AuthzService, + private readonly commonService: CommonService + ) {} + + /** + * Fetch client aliase. + * + * @returns Returns client alias and its url. + */ + @Get('/clientAliases') + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ + summary: 'Get client aliases', + description: 'Fetch client aliases and itr url' + }) + async getClientAlias(@Res() res: Response): Promise { + const clientAliases = await this.authzService.getClientAlias(); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.fetchClientAlises, + data: clientAliases + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } /** * Verify user’s email address. - * + * * @param email The email address of the user. * @param verificationcode The verification code sent to the user's email. - * @returns Returns the email verification status. + * @returns Returns the email verification status. */ @Get('/verify') @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ summary: 'Verify user’s email', description: 'Checks if the provided verification code is valid for the given email.' }) + @ApiOperation({ + summary: 'Verify user’s email', + description: 'Checks if the provided verification code is valid for the given email.' + }) async verifyEmail(@Query() query: EmailVerificationDto, @Res() res: Response): Promise { await this.authzService.verifyEmail(query); const finalResponse: IResponseType = { @@ -60,15 +88,28 @@ export class AuthzController { } /** - * Sends a verification email to the user. - * - * @body UserEmailVerificationDto. - * @returns The status of the verification email. - */ + * Sends a verification email to the user. + * + * @body UserEmailVerificationDto. + * @returns The status of the verification email. + */ @Post('/verification-mail') @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @ApiQuery({ + name: 'clientAlias', + required: false, + enum: (process.env.SUPPORTED_SSO_CLIENTS || '') + .split(',') + .map((alias) => alias.trim()?.toUpperCase()) + .filter(Boolean) + }) @ApiOperation({ summary: 'Send verification email', description: 'Send verification email to new user' }) - async create(@Body() userEmailVerification: UserEmailVerificationDto, @Res() res: Response): Promise { + async create( + @Query('clientAlias', ClientAliasValidationPipe) clientAlias: string, + @Body() userEmailVerification: UserEmailVerificationDto, + @Res() res: Response + ): Promise { + userEmailVerification.clientAlias = clientAlias ?? (await getDefaultClient()).alias; await this.authzService.sendVerificationMail(userEmailVerification); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -78,30 +119,32 @@ export class AuthzController { } /** - * Registers a new user on the platform. - * - * @body AddUserDetailsDto - * @returns User's registration status and user details - */ + * Registers a new user on the platform. + * + * @body AddUserDetailsDto + * @returns User's registration status and user details + */ @Post('/signup') @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) - @ApiOperation({ summary: 'Register new user to platform', description: 'Register new user to platform with the provided details.' }) + @ApiOperation({ + summary: 'Register new user to platform', + description: 'Register new user to platform with the provided details.' + }) async addUserDetails(@Body() userInfo: AddUserDetailsDto, @Res() res: Response): Promise { const userData = await this.authzService.addUserDetails(userInfo); - const finalResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, - data: userData - }; + const finalResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userData + }; return res.status(HttpStatus.CREATED).json(finalResponse); - } /** - * Authenticates a user and returns an access token. - * - * @body LoginUserDto - * @returns User's access token details - */ + * Authenticates a user and returns an access token. + * + * @body LoginUserDto + * @returns User's access token details + */ @Post('/signin') @ApiOperation({ summary: 'Authenticate the user for the access', @@ -110,7 +153,6 @@ export class AuthzController { @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: AuthTokenResponse }) @ApiBody({ type: LoginUserDto }) async login(@Body() loginUserDto: LoginUserDto, @Res() res: Response): Promise { - if (loginUserDto.email) { const userData = await this.authzService.login(loginUserDto.email, loginUserDto.password); @@ -126,36 +168,34 @@ export class AuthzController { } } - /** - * Resets user's password. - * - * @body ResetPasswordDto - * @returns The password reset status. - */ + * Resets user's password. + * + * @body ResetPasswordDto + * @returns The password reset status. + */ @Post('/reset-password') @ApiOperation({ summary: 'Reset password', description: 'Allows users to reset a new password which should be different form existing password.' - }) + }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async resetPassword(@Body() resetPasswordDto: ResetPasswordDto, @Res() res: Response): Promise { - - const userData = await this.authzService.resetPassword(resetPasswordDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.resetPassword, - data: userData - }; - return res.status(HttpStatus.OK).json(finalResponse); + const userData = await this.authzService.resetPassword(resetPasswordDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.resetPassword, + data: userData + }; + return res.status(HttpStatus.OK).json(finalResponse); } -/** - * Initiates the password reset process by sending a reset link to the user's email. - * - * @body ForgotPasswordDto - * @returns Status message indicating whether the reset link was sent successfully. - */ + /** + * Initiates the password reset process by sending a reset link to the user's email. + * + * @body ForgotPasswordDto + * @returns Status message indicating whether the reset link was sent successfully. + */ @Post('/forgot-password') @ApiOperation({ summary: 'Forgot password', @@ -163,23 +203,23 @@ export class AuthzController { }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async forgotPassword(@Body() forgotPasswordDto: ForgotPasswordDto, @Res() res: Response): Promise { - const userData = await this.authzService.forgotPassword(forgotPasswordDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.resetPasswordLink, - data: userData - }; + const userData = await this.authzService.forgotPassword(forgotPasswordDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.resetPasswordLink, + data: userData + }; - return res.status(HttpStatus.OK).json(finalResponse); + return res.status(HttpStatus.OK).json(finalResponse); } -/** - * Resets the user's password using a verification token. - * - * @param email The email address of the user. - * @body ResetTokenPasswordDto - * @returns Status message indicating whether the password reset was successful. - */ + /** + * Resets the user's password using a verification token. + * + * @param email The email address of the user. + * @body ResetTokenPasswordDto + * @returns Status message indicating whether the password reset was successful. + */ @Post('/password-reset/:email') @ApiOperation({ summary: 'Reset password with verification token', @@ -189,41 +229,38 @@ export class AuthzController { async resetNewPassword( @Param('email') email: string, @Body() resetTokenPasswordDto: ResetTokenPasswordDto, - @Res() res: Response): Promise { - resetTokenPasswordDto.email = email.trim(); - const userData = await this.authzService.resetNewPassword(resetTokenPasswordDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.resetPassword, - data: userData - }; - return res.status(HttpStatus.OK).json(finalResponse); + @Res() res: Response + ): Promise { + resetTokenPasswordDto.email = email.trim(); + const userData = await this.authzService.resetNewPassword(resetTokenPasswordDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.resetPassword, + data: userData + }; + return res.status(HttpStatus.OK).json(finalResponse); } -/** - * Generates a new access token using a refresh token. - * - * @body RefreshTokenDto - * @returns New access token and its details. - */ + /** + * Generates a new access token using a refresh token. + * + * @body RefreshTokenDto + * @returns New access token and its details. + */ @Post('/refresh-token') @ApiOperation({ summary: 'Token from refresh token', description: 'Generates a new access token using a refresh token.' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - async refreshToken( - @Body() refreshTokenDto: RefreshTokenDto, - @Res() res: Response): Promise { - const tokenData = await this.authzService.refreshToken(refreshTokenDto.refreshToken); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.refreshToken, - data: tokenData - }; + async refreshToken(@Body() refreshTokenDto: RefreshTokenDto, @Res() res: Response): Promise { + const tokenData = await this.authzService.refreshToken(refreshTokenDto.refreshToken); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.refreshToken, + data: tokenData + }; - return res.status(HttpStatus.OK).json(finalResponse); - + return res.status(HttpStatus.OK).json(finalResponse); } - -} \ No newline at end of file +} diff --git a/apps/api-gateway/src/authz/authz.service.ts b/apps/api-gateway/src/authz/authz.service.ts index 83d4d8358..4e2b8ecd0 100644 --- a/apps/api-gateway/src/authz/authz.service.ts +++ b/apps/api-gateway/src/authz/authz.service.ts @@ -1,19 +1,22 @@ import { Injectable, Inject } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from '../../../../libs/service/base.service'; -import { - WebSocketGateway, - WebSocketServer - -} from '@nestjs/websockets'; +import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets'; import { UserEmailVerificationDto } from '../user/dto/create-user.dto'; import { EmailVerificationDto } from '../user/dto/email-verify.dto'; import { AddUserDetailsDto } from '../user/dto/add-user.dto'; -import { IResetPasswordResponse, ISendVerificationEmail, ISignInUser, ISignUpUserResponse, IVerifyUserEmail } from '@credebl/common/interfaces/user.interface'; +import { + IClientAliases, + IResetPasswordResponse, + ISignInUser, + ISignUpUserResponse, + IVerifyUserEmail +} from '@credebl/common/interfaces/user.interface'; import { ResetPasswordDto } from './dtos/reset-password.dto'; 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'; @Injectable() @WebSocketGateway() @@ -22,18 +25,21 @@ export class AuthzService extends BaseService { @WebSocketServer() server; constructor( @Inject('NATS_CLIENT') private readonly authServiceProxy: ClientProxy, - private readonly natsClient : NATSClient + private readonly natsClient: NATSClient ) { - super('AuthzService'); } + getClientAlias(): Promise { + return this.natsClient.sendNatsMessage(this.authServiceProxy, 'get-client-alias-and-url', ''); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any getUserByKeycloakUserId(keycloakUserId: string): Promise { return this.natsClient.sendNats(this.authServiceProxy, 'get-user-by-keycloakUserId', keycloakUserId); } - async sendVerificationMail(userEmailVerification: UserEmailVerificationDto): Promise { + async sendVerificationMail(userEmailVerification: UserEmailVerificationDto): Promise { const payload = { userEmailVerification }; return this.natsClient.sendNatsMessage(this.authServiceProxy, 'send-verification-mail', payload); } @@ -47,11 +53,11 @@ export class AuthzService extends BaseService { const payload = { email, password, isPasskey }; return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-holder-login', payload); } - + async resetPassword(resetPasswordDto: ResetPasswordDto): Promise { return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-reset-password', resetPasswordDto); } - + async forgotPassword(forgotPasswordDto: ForgotPasswordDto): Promise { return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-forgot-password', forgotPasswordDto); } @@ -68,4 +74,4 @@ export class AuthzService extends BaseService { const payload = { userInfo }; return this.natsClient.sendNatsMessage(this.authServiceProxy, 'add-user', payload); } -} \ No newline at end of file +} diff --git a/apps/api-gateway/src/authz/decorators/user-auth-client.ts b/apps/api-gateway/src/authz/decorators/user-auth-client.ts new file mode 100644 index 000000000..8b6c68dff --- /dev/null +++ b/apps/api-gateway/src/authz/decorators/user-auth-client.ts @@ -0,0 +1,26 @@ +import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common'; + +@Injectable() +export class ClientAliasValidationPipe implements PipeTransform { + private readonly allowedAliases: string[]; + + constructor() { + this.allowedAliases = (process.env.SUPPORTED_SSO_CLIENTS || '') + .split(',') + .map((alias) => alias.trim()) + .filter(Boolean); + } + + transform(value: string): string { + if (!value) { + return value; + } // allow empty if it's optional + const upperValue = value.toUpperCase(); + + if (!this.allowedAliases.includes(upperValue)) { + throw new BadRequestException(`Invalid clientAlias. Allowed values are: ${this.allowedAliases.join(', ')}`); + } + + return upperValue; + } +} diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index 3bbe4b317..576bf81b9 100644 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -3,19 +3,19 @@ import * as dotenv from 'dotenv'; import * as express from 'express'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; -import { Logger, VERSION_NEUTRAL, VersioningType } from '@nestjs/common'; - -import { AppModule } from './app.module'; import { HttpAdapterHost, NestFactory, Reflector } from '@nestjs/core'; -import { AllExceptionsFilter } from '@credebl/common/exception-handler'; +import { Logger, VERSION_NEUTRAL, VersioningType } from '@nestjs/common'; import { type MicroserviceOptions, Transport } from '@nestjs/microservices'; -import { getNatsOptions } from '@credebl/common/nats.config'; -import helmet from 'helmet'; +import { AllExceptionsFilter } from '@credebl/common/exception-handler'; +import { AppModule } from './app.module'; import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { NatsInterceptor } from '@credebl/common'; +import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { UpdatableValidationPipe } from '@credebl/common/custom-overrideable-validation-pipe'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import helmet from 'helmet'; + dotenv.config(); async function bootstrap(): Promise { diff --git a/apps/api-gateway/src/user/dto/create-user.dto.ts b/apps/api-gateway/src/user/dto/create-user.dto.ts index d3bf0855e..a9c91793e 100644 --- a/apps/api-gateway/src/user/dto/create-user.dto.ts +++ b/apps/api-gateway/src/user/dto/create-user.dto.ts @@ -5,37 +5,33 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; export class UserEmailVerificationDto { - @ApiProperty({ example: 'awqx@yopmail.com' }) - @Transform(({ value }) => trim(value)) - @Transform(({ value }) => toLowerCase(value)) - @IsNotEmpty({ message: 'Email is required.' }) - @MaxLength(256, { message: 'Email must be at most 256 character.' }) - @IsEmail({}, { message: 'Please provide a valid email' }) - email: string; - - @ApiProperty({ example: 'xxxx-xxxx-xxxx' }) - @IsString({ message: 'clientId should be string' }) - clientId: string; - - @ApiProperty({ example: 'xxxx-xxxxx-xxxxx' }) - @IsString({ message: 'clientSecret should be string' }) - clientSecret: string; + @ApiProperty({ example: 'awqx@yopmail.com' }) + @Transform(({ value }) => trim(value)) + @Transform(({ value }) => toLowerCase(value)) + @IsNotEmpty({ message: 'Email is required.' }) + @MaxLength(256, { message: 'Email must be at most 256 character.' }) + @IsEmail({}, { message: 'Please provide a valid email' }) + email: string; - @ApiPropertyOptional({ example: 'https://example.com/logo.png' }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsUrl({ - // eslint-disable-next-line camelcase - require_protocol: true, - // eslint-disable-next-line camelcase + @ApiPropertyOptional({ example: 'https://example.com/logo.png' }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsUrl( + { + // eslint-disable-next-line camelcase + require_protocol: true, + // eslint-disable-next-line camelcase require_tld: true }, - { message: 'brandLogoUrl should be a valid URL' }) - brandLogoUrl?: string; + { message: 'brandLogoUrl should be a valid URL' } + ) + brandLogoUrl?: string; @ApiPropertyOptional({ example: 'MyPlatform' }) @Transform(({ value }) => trim(value)) @IsOptional() @IsString({ message: 'platformName should be string' }) platformName?: string; + + clientAlias?: string; } diff --git a/apps/api-gateway/src/user/utils/index.ts b/apps/api-gateway/src/user/utils/index.ts new file mode 100644 index 000000000..68193f49d --- /dev/null +++ b/apps/api-gateway/src/user/utils/index.ts @@ -0,0 +1,46 @@ +import { IClientDetailsSSO } from '@credebl/common/interfaces/user.interface'; +import { encryptClientCredential } from '@credebl/common/cast.helper'; + +export const getDefaultClient = async (): Promise => ({ + alias: process.env.PLATFORM_NAME?.toUpperCase(), + domain: process.env.FRONT_END_URL, + clientId: await encryptClientCredential(process.env.KEYCLOAK_MANAGEMENT_CLIENT_ID), + clientSecret: await encryptClientCredential(process.env.KEYCLOAK_MANAGEMENT_CLIENT_SECRET) +}); + +// Now getting from env, but can get from DB +function getClientDetails(alias: string): IClientDetailsSSO { + const clientIdKey = `${alias}_KEYCLOAK_MANAGEMENT_CLIENT_ID`; + const clientSecretKey = `${alias}_KEYCLOAK_MANAGEMENT_CLIENT_SECRET`; + const domainKey = `${alias}_DOMAIN`; + const aliasNameKey = `${alias}_ALIAS`; + + const clientId = process.env[clientIdKey]; + const clientSecret = process.env[clientSecretKey]; + const domain = process.env[domainKey]; + const aliasName = process.env[aliasNameKey] || alias; + + const clientDetails: IClientDetailsSSO = { + clientId, + clientSecret, + domain, + alias: aliasName + }; + + return clientDetails; +} + +export async function getCredentialsByAlias(alias: string): Promise { + const defaultClient = await getDefaultClient(); + if (alias.toUpperCase() === defaultClient.alias) { + return defaultClient; + } + + const clientDetails = getClientDetails(alias); + + if (!clientDetails.clientId || !clientDetails.clientSecret || !clientDetails.domain) { + throw new Error(`Missing configuration for SSO client: ${alias}`); + } + + return clientDetails; +} diff --git a/apps/issuance/src/issuance.repository.ts b/apps/issuance/src/issuance.repository.ts index c825cb96a..856bb359e 100644 --- a/apps/issuance/src/issuance.repository.ts +++ b/apps/issuance/src/issuance.repository.ts @@ -681,7 +681,9 @@ export class IssuanceRepository { .filter(Boolean); if (0 < referencedTables.length) { - let errorMessage = `Organization ID ${orgId} is referenced in the following table(s): ${referencedTables.join(', ')}`; + let errorMessage = `Organization ID ${orgId} is referenced in the following table(s): ${referencedTables.join( + ', ' + )}`; if (1 === referencedTables.length) { if (referencedTables.includes(`${PrismaTables.PRESENTATIONS}`)) { diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index 7030d2b0c..e03595043 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -150,8 +150,9 @@ export class IssuanceService { const { orgId, credentialDefinitionId, comment, credentialData, isValidateSchema } = payload || {}; if (payload.credentialType === IssueCredentialType.INDY) { - const schemaResponse: SchemaDetails = - await this.issuanceRepository.getCredentialDefinitionDetails(credentialDefinitionId); + const schemaResponse: SchemaDetails = await this.issuanceRepository.getCredentialDefinitionDetails( + credentialDefinitionId + ); if (schemaResponse?.attributes) { const schemaResponseError = []; const attributesArray: IAttributes[] = JSON.parse(schemaResponse.attributes); @@ -331,8 +332,9 @@ export class IssuanceService { isValidateSchema } = payload; if (credentialType === IssueCredentialType.INDY) { - const schemadetailsResponse: SchemaDetails = - await this.issuanceRepository.getCredentialDefinitionDetails(credentialDefinitionId); + const schemadetailsResponse: SchemaDetails = await this.issuanceRepository.getCredentialDefinitionDetails( + credentialDefinitionId + ); if (schemadetailsResponse?.attributes) { const schemadetailsResponseError = []; @@ -479,7 +481,9 @@ export class IssuanceService { return message.response; } catch (error) { this.logger.error( - `[storeIssuanceObjectReturnUrl] [NATS call]- error in storing object and returning url : ${JSON.stringify(error)}` + `[storeIssuanceObjectReturnUrl] [NATS call]- error in storing object and returning url : ${JSON.stringify( + error + )}` ); throw error; } @@ -789,8 +793,9 @@ export class IssuanceService { } if (IssueCredentialType.INDY === credentialType) { - const schemaResponse: SchemaDetails = - await this.issuanceRepository.getCredentialDefinitionDetails(credentialDefinitionId); + const schemaResponse: SchemaDetails = await this.issuanceRepository.getCredentialDefinitionDetails( + credentialDefinitionId + ); let attributesArray: IAttributes[] = []; if (schemaResponse?.attributes) { @@ -1151,8 +1156,8 @@ export class IssuanceService { orgAgentType === OrgAgentType.DEDICATED ? `${agentEndPoint}${CommonConstants.URL_ISSUE_CREATE_CRED_OFFER_AFJ}` : orgAgentType === OrgAgentType.SHARED - ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_OFFER}`.replace('#', tenantId) - : null; + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_OFFER}`.replace('#', tenantId) + : null; break; } @@ -1161,8 +1166,8 @@ export class IssuanceService { orgAgentType === OrgAgentType.DEDICATED ? `${agentEndPoint}${CommonConstants.URL_OUT_OF_BAND_CREDENTIAL_OFFER}` : orgAgentType === OrgAgentType.SHARED - ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_OFFER_OUT_OF_BAND}`.replace('#', tenantId) - : null; + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_OFFER_OUT_OF_BAND}`.replace('#', tenantId) + : null; break; } @@ -1171,8 +1176,8 @@ export class IssuanceService { orgAgentType === OrgAgentType.DEDICATED ? `${agentEndPoint}${CommonConstants.URL_ISSUE_GET_CREDS_AFJ}` : orgAgentType === OrgAgentType.SHARED - ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CREDENTIALS}`.replace('#', tenantId) - : null; + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CREDENTIALS}`.replace('#', tenantId) + : null; break; } @@ -1181,10 +1186,10 @@ export class IssuanceService { orgAgentType === OrgAgentType.DEDICATED ? `${agentEndPoint}${CommonConstants.URL_ISSUE_GET_CREDS_AFJ_BY_CRED_REC_ID}/${credentialRecordId}` : orgAgentType === OrgAgentType.SHARED - ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CREDENTIALS_BY_CREDENTIAL_ID}` - .replace('#', credentialRecordId) - .replace('@', tenantId) - : null; + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CREDENTIALS_BY_CREDENTIAL_ID}` + .replace('#', credentialRecordId) + .replace('@', tenantId) + : null; break; } diff --git a/apps/ledger/src/schema/schema.service.ts b/apps/ledger/src/schema/schema.service.ts index a2fc33c63..198678295 100644 --- a/apps/ledger/src/schema/schema.service.ts +++ b/apps/ledger/src/schema/schema.service.ts @@ -360,7 +360,6 @@ export class SchemaService extends BaseService { } private async storeW3CSchemas(schemaDetails, user, orgId, attributes, alias): Promise { - let ledgerDetails; const schemaServerUrl = `${process.env.SCHEMA_FILE_SERVER_URL}${schemaDetails.schemaId}`; const schemaRequest = await this.commonService.httpGet(schemaServerUrl).then(async (response) => response); diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 017c6816f..d5dab6222 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -682,7 +682,8 @@ export class OrganizationRepository { }, org_agents: { select: { - orgDid: true + orgDid: true, + tenantId: true } } }, diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 1cc3efad2..384d3260a 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -1,23 +1,24 @@ /* eslint-disable prefer-destructuring */ -import { Injectable, Logger, NotFoundException, InternalServerErrorException } from '@nestjs/common'; import { IOrgUsers, - PlatformSettings, - IShareUserCertificate, - UpdateUserProfile, ISendVerificationEmail, - IUsersProfile, + IShareUserCertificate, + IUserDeletedActivity, IUserInformation, + IUsersProfile, IVerifyUserEmail, - IUserDeletedActivity, + PlatformSettings, + UpdateUserProfile, UserKeycloakId, - UserRoleMapping, - UserRoleDetails + UserRoleDetails, + UserRoleMapping } from '../interfaces/user.interface'; -import { PrismaService } from '@credebl/prisma-service'; +import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; // eslint-disable-next-line camelcase -import { RecordType, schema, token, user, user_org_roles } from '@prisma/client'; +import { RecordType, client_aliases, schema, token, user, user_org_roles } from '@prisma/client'; + +import { PrismaService } from '@credebl/prisma-service'; import { UserRole } from '@credebl/enum/enum'; interface UserQueryOptions { @@ -34,6 +35,21 @@ export class UserRepository { private readonly logger: Logger ) {} + /** + * + * @returns Client alias and its url + */ + + // eslint-disable-next-line camelcase + async fetchClientAliases(): Promise { + try { + return this.prisma.client_aliases.findMany(); + } catch (error) { + this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); + throw new error(); + } + } + /** * * @param userEmailVerification @@ -700,7 +716,14 @@ export class UserRepository { } } - async updateOrgDeletedActivity(orgId: string, userId: string, deletedBy: string, recordType: RecordType, userEmail: string, txnMetadata: object): Promise { + async updateOrgDeletedActivity( + orgId: string, + userId: string, + deletedBy: string, + recordType: RecordType, + userEmail: string, + txnMetadata: object + ): Promise { try { const orgDeletedActivity = await this.prisma.user_org_delete_activity.create({ data: { @@ -754,10 +777,12 @@ export class UserRepository { }); // Create a map for quick lookup of keycloakUserId, id, and email by email - const userMap = new Map(users.map(user => [user.email, { id: user.id, keycloakUserId: user.keycloakUserId, email: user.email }])); + const userMap = new Map( + users.map((user) => [user.email, { id: user.id, keycloakUserId: user.keycloakUserId, email: user.email }]) + ); // Collect the keycloakUserId, id, and email in the order of input emails - const result = userEmails.map(email => { + const result = userEmails.map((email) => { const user = userMap.get(email); return { id: user?.id || null, keycloakUserId: user?.keycloakUserId || null, email }; }); @@ -768,7 +793,7 @@ export class UserRepository { throw error; } } - + async storeUserRole(userId: string, userRoleId: string): Promise { try { const userRoleMapping = await this.prisma.user_role_mapping.create({ @@ -798,21 +823,19 @@ export class UserRepository { } } - // eslint-disable-next-line camelcase - async handleGetUserOrganizations(userId: string): Promise { - try { + // eslint-disable-next-line camelcase + async handleGetUserOrganizations(userId: string): Promise { + try { const getUserOrgs = await this.prisma.user_org_roles.findMany({ where: { userId } }); - + return getUserOrgs; } catch (error) { - this.logger.error( - `Error in handleGetUserOrganizations: ${error.message}` - ); + this.logger.error(`Error in handleGetUserOrganizations: ${error.message}`); throw error; } } -} \ No newline at end of file +} diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 3616b2d92..580de2c4f 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -1,26 +1,58 @@ -import { IOrgUsers, Payload, ICheckUserDetails, PlatformSettings, UpdateUserProfile, IUsersProfile, IUserInformation, IUserSignIn, IUserResetPassword, IUserDeletedActivity, UserKeycloakId, IUserForgotPassword} from '../interfaces/user.interface'; +import { + ICheckUserDetails, + IOrgUsers, + IUserDeletedActivity, + IUserForgotPassword, + IUserInformation, + IUserResetPassword, + IUserSignIn, + IUsersProfile, + Payload, + PlatformSettings, + UpdateUserProfile, + UserKeycloakId +} from '../interfaces/user.interface'; +import { + IResetPasswordResponse, + ISendVerificationEmail, + ISignInUser, + ISignUpUserResponse, + IUserInvitations, + IVerifyUserEmail +} from '@credebl/common/interfaces/user.interface'; +// eslint-disable-next-line camelcase +import { client_aliases, user, user_org_roles } from '@prisma/client'; + import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; +import { AddPasskeyDetailsDto } from 'apps/api-gateway/src/user/dto/add-user.dto'; import { Controller } from '@nestjs/common'; +import { IUsersActivity } from 'libs/user-activity/interface'; import { MessagePattern } from '@nestjs/microservices'; import { UserService } from './user.service'; import { VerifyEmailTokenDto } from '../dtos/verify-email.dto'; -// eslint-disable-next-line camelcase -import { user, user_org_roles } from '@prisma/client'; -import { IUsersActivity } from 'libs/user-activity/interface'; -import { ISendVerificationEmail, ISignInUser, IVerifyUserEmail, IUserInvitations, IResetPasswordResponse, ISignUpUserResponse } from '@credebl/common/interfaces/user.interface'; -import { AddPasskeyDetailsDto } from 'apps/api-gateway/src/user/dto/add-user.dto'; @Controller() export class UserController { - constructor(private readonly userService: UserService) { } + constructor(private readonly userService: UserService) {} + + /** + * Description: Fetch client aliases are its url + * @param email + * @returns Client alias and its url + */ + @MessagePattern({ cmd: 'get-client-alias-and-url' }) + // eslint-disable-next-line camelcase + async getClientAliases(): Promise { + return this.userService.getClientAliases(); + } /** * Description: Registers new user - * @param email + * @param email * @returns User's verification email sent status */ @MessagePattern({ cmd: 'send-verification-mail' }) - async sendVerificationMail(payload: { userEmailVerification: ISendVerificationEmail }): Promise { + async sendVerificationMail(payload: { userEmailVerification: ISendVerificationEmail }): Promise { return this.userService.sendVerificationMail(payload.userEmailVerification); } @@ -28,41 +60,41 @@ export class UserController { * Description: Verify user's email * @param email * @param verificationcode - * @returns User's email verification status + * @returns User's email verification status */ @MessagePattern({ cmd: 'user-email-verification' }) async verifyEmail(payload: { param: VerifyEmailTokenDto }): Promise { return this.userService.verifyEmail(payload.param); } - /** - * @body loginUserDto - * @returns User's access token details - */ + /** + * @body loginUserDto + * @returns User's access token details + */ @MessagePattern({ cmd: 'user-holder-login' }) async login(payload: IUserSignIn): Promise { - const loginRes = await this.userService.login(payload); - return loginRes; + const loginRes = await this.userService.login(payload); + return loginRes; } @MessagePattern({ cmd: 'refresh-token-details' }) async refreshTokenDetails(refreshToken: string): Promise { - return this.userService.refreshTokenDetails(refreshToken); + return this.userService.refreshTokenDetails(refreshToken); } @MessagePattern({ cmd: 'user-reset-password' }) async resetPassword(payload: IUserResetPassword): Promise { - return this.userService.resetPassword(payload); + return this.userService.resetPassword(payload); } @MessagePattern({ cmd: 'user-set-token-password' }) - async resetTokenPassword(payload: IUserResetPassword): Promise { + async resetTokenPassword(payload: IUserResetPassword): Promise { return this.userService.resetTokenPassword(payload); } @MessagePattern({ cmd: 'user-forgot-password' }) async forgotPassword(payload: IUserForgotPassword): Promise { - return this.userService.forgotPassword(payload); + return this.userService.forgotPassword(payload); } @MessagePattern({ cmd: 'get-user-profile' }) @@ -74,7 +106,7 @@ export class UserController { async getPublicProfile(payload: { username }): Promise { return this.userService.getPublicProfile(payload); } - /** + /** * @returns User details */ @MessagePattern({ cmd: 'update-user-profile' }) @@ -106,10 +138,10 @@ export class UserController { * @returns Organization invitation data */ @MessagePattern({ cmd: 'get-org-invitations' }) - async invitations(payload: { id; status; pageNumber; pageSize; search; }): Promise { - return this.userService.invitations(payload); + async invitations(payload: { id; status; pageNumber; pageSize; search }): Promise { + return this.userService.invitations(payload); } - + /** * * @param payload @@ -129,32 +161,32 @@ export class UserController { * @returns organization users list */ @MessagePattern({ cmd: 'fetch-organization-user' }) - async getOrganizationUsers(payload: {orgId:string} & Payload): Promise { + async getOrganizationUsers(payload: { orgId: string } & Payload): Promise { return this.userService.getOrgUsers(payload.orgId, payload.pageNumber, payload.pageSize, payload.search); } /** - * @param payload - * @returns organization users list - */ + * @param payload + * @returns organization users list + */ @MessagePattern({ cmd: 'fetch-users' }) - async get(payload: { pageNumber: number, pageSize: number, search: string }): Promise { + async get(payload: { pageNumber: number; pageSize: number; search: string }): Promise { const users = this.userService.get(payload.pageNumber, payload.pageSize, payload.search); return users; } - - /** - * @param email - * @returns User's email exist status - * */ + + /** + * @param email + * @returns User's email exist status + * */ @MessagePattern({ cmd: 'check-user-exist' }) async checkUserExist(payload: { userEmail: string }): Promise { return this.userService.checkUserExist(payload.userEmail); } /** - * @body userInfo - * @returns User's registration status - */ + * @body userInfo + * @returns User's registration status + */ @MessagePattern({ cmd: 'add-user' }) async addUserDetailsInKeyCloak(payload: { userInfo: IUserInformation }): Promise { return this.userService.createUserForToken(payload.userInfo); @@ -162,15 +194,15 @@ export class UserController { // Fetch Users recent activities @MessagePattern({ cmd: 'get-user-activity' }) - async getUserActivity(payload: { userId: string, limit: number }): Promise { + async getUserActivity(payload: { userId: string; limit: number }): Promise { return this.userService.getUserActivity(payload.userId, payload.limit); } @MessagePattern({ cmd: 'add-passkey' }) - async addPasskey(payload: { userEmail: string, userInfo: AddPasskeyDetailsDto }): Promise { + async addPasskey(payload: { userEmail: string; userInfo: AddPasskeyDetailsDto }): Promise { return this.userService.addPasskey(payload.userEmail, payload.userInfo); } - /** + /** * @returns platform settings updated status */ @MessagePattern({ cmd: 'update-platform-settings' }) @@ -186,8 +218,22 @@ export class UserController { } @MessagePattern({ cmd: 'org-deleted-activity' }) - async updateOrgDeletedActivity(payload: { orgId, userId, deletedBy, recordType, userEmail, txnMetadata }): Promise { - return this.userService.updateOrgDeletedActivity(payload.orgId, payload.userId, payload.deletedBy, payload.recordType, payload.userEmail, payload.txnMetadata); + async updateOrgDeletedActivity(payload: { + orgId; + userId; + deletedBy; + recordType; + userEmail; + txnMetadata; + }): Promise { + return this.userService.updateOrgDeletedActivity( + payload.orgId, + payload.userId, + payload.deletedBy, + payload.recordType, + payload.userEmail, + payload.txnMetadata + ); } @MessagePattern({ cmd: 'get-user-details-by-userId' }) @@ -202,13 +248,13 @@ export class UserController { } @MessagePattern({ cmd: 'get-user-info-by-user-email-keycloak' }) - async getUserByUserIdInKeycloak(payload: {email}): Promise { + async getUserByUserIdInKeycloak(payload: { email }): Promise { return this.userService.getUserByUserIdInKeycloak(payload.email); } @MessagePattern({ cmd: 'get-user-organizations' }) // eslint-disable-next-line camelcase - async getuserOrganizationByUserId(payload: {userId: string}): Promise { + async getuserOrganizationByUserId(payload: { userId: string }): Promise { return this.userService.getuserOrganizationByUserId(payload.userId); } -} \ No newline at end of file +} diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index aec67200e..6503e775d 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -11,7 +11,6 @@ import { HttpException } from '@nestjs/common'; - import { ClientRegistrationService } from '@credebl/client-registration'; import { CommonService } from '@credebl/common'; import { EmailDto } from '@credebl/common/dtos/email.dto'; @@ -27,20 +26,20 @@ import { UserRepository } from '../repositories/user.repository'; import { VerifyEmailTokenDto } from '../dtos/verify-email.dto'; import { sendEmail } from '@credebl/common/send-grid-helper-file'; // eslint-disable-next-line camelcase -import { RecordType, user, user_org_roles } from '@prisma/client'; +import { client_aliases, RecordType, user, user_org_roles } from '@prisma/client'; import { ICheckUserDetails, OrgInvitations, PlatformSettings, IOrgUsers, UpdateUserProfile, - IUserInformation, - IUsersProfile, - IUserResetPassword, - IUserDeletedActivity, - UserKeycloakId, - IEcosystemConfig, - IUserForgotPassword + IUserInformation, + IUsersProfile, + IUserResetPassword, + IUserDeletedActivity, + UserKeycloakId, + IEcosystemConfig, + IUserForgotPassword } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; @@ -52,12 +51,20 @@ import validator from 'validator'; import { DISALLOWED_EMAIL_DOMAIN } from '@credebl/common/common.constant'; import { AwsService } from '@credebl/aws'; import { IUsersActivity } from 'libs/user-activity/interface'; -import { ISendVerificationEmail, ISignInUser, IVerifyUserEmail, IUserInvitations, IResetPasswordResponse, ISignUpUserResponse } from '@credebl/common/interfaces/user.interface'; +import { + ISendVerificationEmail, + ISignInUser, + IVerifyUserEmail, + IUserInvitations, + IResetPasswordResponse, + ISignUpUserResponse +} from '@credebl/common/interfaces/user.interface'; import { AddPasskeyDetailsDto } from 'apps/api-gateway/src/user/dto/add-user.dto'; import { URLUserResetPasswordTemplate } from '../templates/reset-password-template'; import { toNumber } from '@credebl/common/cast.helper'; import * as jwt from 'jsonwebtoken'; import { NATSClient } from '@credebl/common/NATSClient'; +import { getCredentialsByAlias } from 'apps/api-gateway/src/user/utils'; @Injectable() export class UserService { @@ -74,9 +81,25 @@ export class UserService { private readonly userDevicesRepository: UserDevicesRepository, private readonly logger: Logger, @Inject('NATS_CLIENT') private readonly userServiceProxy: ClientProxy, - private readonly natsClient : NATSClient + private readonly natsClient: NATSClient ) {} + /** + * + * @returns client alias and its url + */ + + // eslint-disable-next-line camelcase + async getClientAliases(): Promise { + try { + const clientAliases = await this.userRepository.fetchClientAliases(); + return clientAliases; + } catch (error) { + this.logger.error(`In Create User : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + /** * * @param userEmailVerification @@ -85,8 +108,8 @@ export class UserService { async sendVerificationMail(userEmailVerification: ISendVerificationEmail): Promise { try { - const { email, brandLogoUrl, platformName, clientId, clientSecret } = userEmailVerification; - + const { email, brandLogoUrl, platformName, clientAlias } = userEmailVerification; + if ('PROD' === process.env.PLATFORM_PROFILE_MODE) { // eslint-disable-next-line prefer-destructuring const domain = email.split('@')[1]; @@ -94,9 +117,9 @@ export class UserService { throw new BadRequestException(ResponseMessages.user.error.InvalidEmailDomain); } } - + const userDetails = await this.userRepository.checkUserExist(email); - + if (userDetails) { if (userDetails.isEmailVerified) { throw new ConflictException(ResponseMessages.user.error.exists); @@ -104,34 +127,46 @@ export class UserService { throw new ConflictException(ResponseMessages.user.error.verificationAlreadySent); } } - + const verifyCode = uuidv4(); let sendVerificationMail: boolean; + const clientDetails = await getCredentialsByAlias(clientAlias); try { - - const token = await this.clientRegistrationService.getManagementToken(clientId, clientSecret); - const getClientData = await this.clientRegistrationService.getClientRedirectUrl(clientId, token); + const token = await this.clientRegistrationService.getManagementToken( + clientDetails.clientId, + clientDetails.clientSecret + ); + const getClientData = await this.clientRegistrationService.getClientRedirectUrl(clientDetails.clientId, token); const [redirectUrl] = getClientData[0]?.redirectUris || []; - + if (!redirectUrl) { throw new NotFoundException(ResponseMessages.user.error.redirectUrlNotFound); } - - sendVerificationMail = await this.sendEmailForVerification(email, verifyCode, redirectUrl, clientId, brandLogoUrl, platformName); + + sendVerificationMail = await this.sendEmailForVerification( + email, + verifyCode, + redirectUrl, + clientDetails.clientId, + brandLogoUrl, + platformName, + clientDetails.domain, + clientAlias + ); } catch (error) { throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); } - + if (sendVerificationMail) { const uniqueUsername = await this.createUsername(email, verifyCode); userEmailVerification.username = uniqueUsername; - userEmailVerification.clientId = clientId; - userEmailVerification.clientSecret = clientSecret; + userEmailVerification.clientId = clientDetails.clientId; + userEmailVerification.clientSecret = clientDetails.clientSecret; const resUser = await this.userRepository.createUser(userEmailVerification, verifyCode); return resUser; - } + } } catch (error) { this.logger.error(`In Create User : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -168,11 +203,20 @@ export class UserService { * @returns */ - async sendEmailForVerification(email: string, verificationCode: string, redirectUrl: string, clientId: string, brandLogoUrl:string, platformName: string): Promise { + async sendEmailForVerification( + email: string, + verificationCode: string, + redirectUrl: string, + clientId: string, + brandLogoUrl: string, + platformName: string, + redirectTo?: string, + clientAlias?: string + ): Promise { try { const platformConfigData = await this.prisma.platform_config.findMany(); - const decryptClientId = await this.commonService.decryptPassword(clientId); + const decryptedClientId = await this.commonService.decryptPassword(clientId); const urlEmailTemplate = new URLUserEmailTemplate(); const emailData = new EmailDto(); emailData.emailFrom = platformConfigData[0].emailFrom; @@ -180,7 +224,16 @@ export class UserService { const platform = platformName || process.env.PLATFORM_NAME; emailData.emailSubject = `[${platform}] Verify your email to activate your account`; - emailData.emailHtml = await urlEmailTemplate.getUserURLTemplate(email, verificationCode, redirectUrl, decryptClientId, brandLogoUrl, platformName); + emailData.emailHtml = await urlEmailTemplate.getUserURLTemplate( + email, + verificationCode, + redirectUrl, + decryptedClientId, + brandLogoUrl, + platformName, + redirectTo, + clientAlias + ); const isEmailSent = await sendEmail(emailData); if (isEmailSent) { return isEmailSent; @@ -252,9 +305,12 @@ export class UserService { if (!userDetails) { throw new NotFoundException(ResponseMessages.user.error.adduser); } - let keycloakDetails = null; - - const token = await this.clientRegistrationService.getManagementToken(checkUserDetails.clientId, checkUserDetails.clientSecret); + let keycloakDetails = null; + + const token = await this.clientRegistrationService.getManagementToken( + checkUserDetails.clientId, + checkUserDetails.clientSecret + ); if (userInfo.isPasskey) { const resUser = await this.userRepository.addUserPassword(email.toLowerCase(), userInfo.password); const userDetails = await this.userRepository.getUserDetails(email.toLowerCase()); @@ -265,8 +321,12 @@ export class UserService { } userInfo.password = decryptedPassword; - try { - keycloakDetails = await this.clientRegistrationService.createUser(userInfo, process.env.KEYCLOAK_REALM, token); + try { + keycloakDetails = await this.clientRegistrationService.createUser( + userInfo, + process.env.KEYCLOAK_REALM, + token + ); } catch (error) { throw new InternalServerErrorException('Error while registering user on keycloak'); } @@ -275,16 +335,18 @@ export class UserService { userInfo.password = decryptedPassword; - try { - keycloakDetails = await this.clientRegistrationService.createUser(userInfo, process.env.KEYCLOAK_REALM, token); + try { + keycloakDetails = await this.clientRegistrationService.createUser( + userInfo, + process.env.KEYCLOAK_REALM, + token + ); } catch (error) { throw new InternalServerErrorException('Error while registering user on keycloak'); } } - await this.userRepository.updateUserDetails(userDetails.id, - keycloakDetails.keycloakUserId.toString() - ); + await this.userRepository.updateUserDetails(userDetails.id, keycloakDetails.keycloakUserId.toString()); if (userInfo?.isHolder) { const getUserRole = await this.userRepository.getUserRole(UserRole.HOLDER); @@ -296,9 +358,9 @@ export class UserService { } const realmRoles = await this.clientRegistrationService.getAllRealmRoles(token); - - const holderRole = realmRoles.filter(role => role.name === OrgRoles.HOLDER); - const holderRoleData = 0 < holderRole.length && holderRole[0]; + + const holderRole = realmRoles.filter((role) => role.name === OrgRoles.HOLDER); + const holderRoleData = 0 < holderRole.length && holderRole[0]; const payload = [ { @@ -307,7 +369,11 @@ export class UserService { } ]; - await this.clientRegistrationService.createUserHolderRole(token, keycloakDetails.keycloakUserId.toString(), payload); + await this.clientRegistrationService.createUserHolderRole( + token, + keycloakDetails.keycloakUserId.toString(), + payload + ); const holderOrgRole = await this.orgRoleService.getRole(OrgRoles.HOLDER); await this.userOrgRoleService.createUserOrgRole(userDetails.id, holderOrgRole.id, null, holderRoleData.id); @@ -368,7 +434,6 @@ export class UserService { const { email, password, isPasskey } = loginUserDto; try { - this.validateEmail(email.toLowerCase()); const userData = await this.userRepository.checkUserExist(email.toLowerCase()); if (!userData) { @@ -388,9 +453,8 @@ export class UserService { const decryptedPassword = await this.commonService.decryptPassword(getUserDetails.password); return await this.generateToken(email.toLowerCase(), decryptedPassword, userData); } else { - const decryptedPassword = await this.commonService.decryptPassword(password); - return await this.generateToken(email.toLowerCase(), decryptedPassword, userData); + return await this.generateToken(email.toLowerCase(), decryptedPassword, userData); } } catch (error) { this.logger.error(`In Login User : ${JSON.stringify(error)}`); @@ -399,21 +463,22 @@ export class UserService { } async refreshTokenDetails(refreshToken: string): Promise { - try { - try { - const data = jwt.decode(refreshToken) as jwt.JwtPayload; - const userByKeycloakId = await this.userRepository.getUserByKeycloakId(data?.sub); - const tokenResponse = await this.clientRegistrationService.getAccessToken(refreshToken, userByKeycloakId?.['clientId'], userByKeycloakId?.['clientSecret']); - return tokenResponse; - } catch (error) { - throw new BadRequestException(ResponseMessages.user.error.invalidRefreshToken); - } - + try { + const data = jwt.decode(refreshToken) as jwt.JwtPayload; + const userByKeycloakId = await this.userRepository.getUserByKeycloakId(data?.sub); + const tokenResponse = await this.clientRegistrationService.getAccessToken( + refreshToken, + userByKeycloakId?.['clientId'], + userByKeycloakId?.['clientSecret'] + ); + return tokenResponse; + } catch (error) { + throw new BadRequestException(ResponseMessages.user.error.invalidRefreshToken); + } } catch (error) { this.logger.error(`In refreshTokenDetails : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); - } } @@ -426,8 +491,8 @@ export class UserService { /** * Forgot password - * @param forgotPasswordDto - * @returns + * @param forgotPasswordDto + * @returns */ async forgotPassword(forgotPasswordDto: IUserForgotPassword): Promise { const { email, brandLogoUrl, platformName, endpoint } = forgotPasswordDto; @@ -445,7 +510,7 @@ export class UserService { const token = uuidv4(); const expirationTime = new Date(); expirationTime.setHours(expirationTime.getHours() + 1); // Set expiration time to 1 hour from now - + const tokenCreated = await this.userRepository.createTokenForResetPassword(userData.id, token, expirationTime); if (!tokenCreated) { @@ -462,7 +527,6 @@ export class UserService { id: tokenCreated.id, email: userData.email }; - } catch (error) { this.logger.error(`Error In forgotPassword : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -471,11 +535,17 @@ export class UserService { /** * Send email for token verification of reset password - * @param email - * @param verificationCode - * @returns + * @param email + * @param verificationCode + * @returns */ - async sendEmailForResetPassword(email: string, brandLogoUrl: string, platformName: string, endpoint: string, verificationCode: string): Promise { + async sendEmailForResetPassword( + email: string, + brandLogoUrl: string, + platformName: string, + endpoint: string, + verificationCode: string + ): Promise { try { const platformConfigData = await this.prisma.platform_config.findMany(); @@ -487,7 +557,13 @@ export class UserService { const platform = platformName || process.env.PLATFORM_NAME; emailData.emailSubject = `[${platform}] Important: Password Reset Request`; - emailData.emailHtml = await urlEmailTemplate.getUserResetPasswordTemplate(email, platform, brandLogoUrl, endpoint, verificationCode); + emailData.emailHtml = await urlEmailTemplate.getUserResetPasswordTemplate( + email, + platform, + brandLogoUrl, + endpoint, + verificationCode + ); const isEmailSent = await sendEmail(emailData); if (isEmailSent) { return isEmailSent; @@ -502,11 +578,10 @@ export class UserService { /** * Create reset password token - * @param resetPasswordDto + * @param resetPasswordDto * @returns user details */ async resetTokenPassword(resetPasswordDto: IUserResetPassword): Promise { - const { email, password, token } = resetPasswordDto; try { @@ -519,30 +594,32 @@ export class UserService { if (userData && !userData.isEmailVerified) { throw new BadRequestException(ResponseMessages.user.error.verifyMail); } - + const tokenDetails = await this.userRepository.getResetPasswordTokenDetails(userData.id, token); - if (!tokenDetails || (new Date() > tokenDetails.expiresAt)) { + if (!tokenDetails || new Date() > tokenDetails.expiresAt) { throw new BadRequestException(ResponseMessages.user.error.invalidResetLink); } const decryptedPassword = await this.commonService.decryptPassword(password); - try { - - - const authToken = await this.clientRegistrationService.getManagementToken(userData.clientId, userData.clientSecret); + try { + const authToken = await this.clientRegistrationService.getManagementToken( + userData.clientId, + userData.clientSecret + ); userData.password = decryptedPassword; if (userData.keycloakUserId) { await this.clientRegistrationService.resetPasswordOfUser(userData, process.env.KEYCLOAK_REALM, authToken); - } else { - const keycloakDetails = await this.clientRegistrationService.createUser(userData, process.env.KEYCLOAK_REALM, authToken); - await this.userRepository.updateUserDetails(userData.id, - keycloakDetails.keycloakUserId.toString() + } else { + const keycloakDetails = await this.clientRegistrationService.createUser( + userData, + process.env.KEYCLOAK_REALM, + authToken ); + await this.userRepository.updateUserDetails(userData.id, keycloakDetails.keycloakUserId.toString()); } await this.updateFidoVerifiedUser(email.toLowerCase(), userData.isFidoVerified, password); - } catch (error) { this.logger.error(`Error reseting the password`, error); throw new InternalServerErrorException('Error while reseting user password'); @@ -554,7 +631,6 @@ export class UserService { id: userData.id, email: userData.email }; - } catch (error) { this.logger.error(`Error In resetTokenPassword : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -563,7 +639,6 @@ export class UserService { findUserByUserId(id: string): Promise { return this.userRepository.getUserById(id); - } async resetPassword(resetPasswordDto: IUserResetPassword): Promise { @@ -588,23 +663,30 @@ export class UserService { } const tokenResponse = await this.generateToken(email.toLowerCase(), oldDecryptedPassword, userData); - + if (tokenResponse) { userData.password = newDecryptedPassword; - try { - let keycloakDetails = null; - const token = await this.clientRegistrationService.getManagementToken(userData.clientId, userData.clientSecret); + try { + let keycloakDetails = null; + const token = await this.clientRegistrationService.getManagementToken( + userData.clientId, + userData.clientSecret + ); if (userData.keycloakUserId) { - - keycloakDetails = await this.clientRegistrationService.resetPasswordOfUser(userData, process.env.KEYCLOAK_REALM, token); + keycloakDetails = await this.clientRegistrationService.resetPasswordOfUser( + userData, + process.env.KEYCLOAK_REALM, + token + ); await this.updateFidoVerifiedUser(email.toLowerCase(), userData.isFidoVerified, newPassword); - } else { - keycloakDetails = await this.clientRegistrationService.createUser(userData, process.env.KEYCLOAK_REALM, token); - await this.userRepository.updateUserDetails(userData.id, - keycloakDetails.keycloakUserId.toString() + keycloakDetails = await this.clientRegistrationService.createUser( + userData, + process.env.KEYCLOAK_REALM, + token ); + await this.userRepository.updateUserDetails(userData.id, keycloakDetails.keycloakUserId.toString()); await this.updateFidoVerifiedUser(email.toLowerCase(), userData.isFidoVerified, newPassword); } @@ -612,14 +694,12 @@ export class UserService { id: userData.id, email: userData.email }; - } catch (error) { throw new InternalServerErrorException('Error while registering user on keycloak'); } } else { throw new BadRequestException(ResponseMessages.user.error.invalidCredentials); } - } catch (error) { this.logger.error(`In Login User : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -627,44 +707,46 @@ export class UserService { } async generateToken(email: string, password: string, userData: user): Promise { - - if (userData.keycloakUserId) { - - try { - const tokenResponse = await this.clientRegistrationService.getUserToken(email, password, userData.clientId, userData.clientSecret); - tokenResponse.isRegisteredToSupabase = false; - return tokenResponse; - } catch (error) { - throw new UnauthorizedException(ResponseMessages.user.error.invalidCredentials); - } - - } else { - const supaInstance = await this.supabaseService.getClient(); - const { data, error } = await supaInstance.auth.signInWithPassword({ + if (userData.keycloakUserId) { + try { + const tokenResponse = await this.clientRegistrationService.getUserToken( email, - password - }); - - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); - - if (error) { - throw new BadRequestException(error?.message); - } - - const token = data?.session; + password, + userData.clientId, + userData.clientSecret + ); + tokenResponse.isRegisteredToSupabase = false; + return tokenResponse; + } catch (error) { + throw new UnauthorizedException(ResponseMessages.user.error.invalidCredentials); + } + } else { + const supaInstance = await this.supabaseService.getClient(); + const { data, error } = await supaInstance.auth.signInWithPassword({ + email, + password + }); - return { - // eslint-disable-next-line camelcase - access_token: token.access_token, - // eslint-disable-next-line camelcase - token_type: token.token_type, - // eslint-disable-next-line camelcase - expires_in: token.expires_in, - // eslint-disable-next-line camelcase - expires_at: token.expires_at, - isRegisteredToSupabase: true - }; + this.logger.error(`Supa Login Error::`, JSON.stringify(error)); + + if (error) { + throw new BadRequestException(error?.message); } + + const token = data?.session; + + return { + // eslint-disable-next-line camelcase + access_token: token.access_token, + // eslint-disable-next-line camelcase + token_type: token.token_type, + // eslint-disable-next-line camelcase + expires_in: token.expires_in, + // eslint-disable-next-line camelcase + expires_at: token.expires_at, + isRegisteredToSupabase: true + }; + } } async getProfile(payload: { id }): Promise { @@ -677,7 +759,7 @@ export class UserService { userData[setting.key] = 'true' === setting.value; } } - + return userData; } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); @@ -685,9 +767,9 @@ export class UserService { } } - async _getEcosystemConfig(): Promise { + async _getEcosystemConfig(): Promise { const pattern = { cmd: 'get-ecosystem-config-details' }; - const payload = { }; + const payload = {}; const getEcosystemConfigDetails = await this.userServiceProxy .send(pattern, payload) @@ -779,13 +861,12 @@ export class UserService { payload.pageNumber, payload.pageSize, payload.search - ); - - const invitations: OrgInvitations[] = await this.updateOrgInvitations(invitationsData['invitations']); - invitationsData['invitations'] = invitations; + ); + + const invitations: OrgInvitations[] = await this.updateOrgInvitations(invitationsData['invitations']); + invitationsData['invitations'] = invitations; return invitationsData; - } catch (error) { this.logger.error(`Error in get invitations: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -810,7 +891,7 @@ export class UserService { const invitationsData = await this.natsClient .send(this.userServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -826,8 +907,6 @@ export class UserService { } async updateOrgInvitations(invitations: OrgInvitations[]): Promise { - - const updatedInvitations = []; for (const invitation of invitations) { @@ -854,17 +933,20 @@ export class UserService { * @param userId * @returns Organization invitation status */ - async acceptRejectInvitations(acceptRejectInvitation: AcceptRejectInvitationDto, userId: string): Promise { + async acceptRejectInvitations( + acceptRejectInvitation: AcceptRejectInvitationDto, + userId: string + ): Promise { try { const userData = await this.userRepository.getUserById(userId); - + if (Invitation.ACCEPTED === acceptRejectInvitation.status) { - const payload = {userId}; + const payload = { userId }; const TotalOrgs = await this._getTotalOrgCount(payload); - + if (TotalOrgs >= toNumber(`${process.env.MAX_ORG_LIMIT}`)) { - throw new BadRequestException(ResponseMessages.user.error.userOrgsLimit); - } + throw new BadRequestException(ResponseMessages.user.error.userOrgsLimit); + } } return this.fetchInvitationsStatus(acceptRejectInvitation, userData.keycloakUserId, userData.email, userId); } catch (error) { @@ -873,12 +955,12 @@ export class UserService { } } - async _getTotalOrgCount(payload): Promise { + async _getTotalOrgCount(payload): Promise { const pattern = { cmd: 'get-organizations-count' }; const getOrganizationCount = await this.natsClient .send(this.userServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -915,7 +997,7 @@ export class UserService { const invitationsData = await this.natsClient .send(this.userServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -942,7 +1024,6 @@ export class UserService { */ async getOrgUsers(orgId: string, pageNumber: number, pageSize: number, search: string): Promise { try { - const query = { userOrgRoles: { some: { orgId } @@ -990,16 +1071,15 @@ export class UserService { async checkUserExist(email: string): Promise { try { const userDetails = await this.userRepository.checkUniqueUserExist(email.toLowerCase()); - let userVerificationDetails; + let userVerificationDetails; if (userDetails) { userVerificationDetails = { isEmailVerified: userDetails.isEmailVerified, isFidoVerified: userDetails.isFidoVerified, isRegistrationCompleted: null !== userDetails.keycloakUserId && undefined !== userDetails.keycloakUserId, - message:'', + message: '', userId: userDetails.id }; - } if (userDetails && !userDetails.isEmailVerified) { userVerificationDetails.message = ResponseMessages.user.error.verificationAlreadySent; @@ -1011,11 +1091,11 @@ export class UserService { userVerificationDetails.message = ResponseMessages.user.error.exists; return userVerificationDetails; } else if (null === userDetails) { - return { + return { isRegistrationCompleted: false, - isEmailVerified: false, - userId:null, - message: ResponseMessages.user.error.notFound + isEmailVerified: false, + userId: null, + message: ResponseMessages.user.error.notFound }; } else { return userVerificationDetails; @@ -1069,9 +1149,23 @@ export class UserService { } } - async updateOrgDeletedActivity(orgId: string, userId: string, deletedBy: string, recordType: RecordType, userEmail: string, txnMetadata: object): Promise { + async updateOrgDeletedActivity( + orgId: string, + userId: string, + deletedBy: string, + recordType: RecordType, + userEmail: string, + txnMetadata: object + ): Promise { try { - return await this.userRepository.updateOrgDeletedActivity(orgId, userId, deletedBy, recordType, userEmail, txnMetadata); + return await this.userRepository.updateOrgDeletedActivity( + orgId, + userId, + deletedBy, + recordType, + userEmail, + txnMetadata + ); } catch (error) { this.logger.error(`In updateOrgDeletedActivity : ${JSON.stringify(error)}`); throw error; @@ -1091,7 +1185,6 @@ export class UserService { async getUserKeycloakIdByEmail(userEmails: string[]): Promise { try { - const getkeycloakUserIds = await this.userRepository.getUserKeycloak(userEmails); return getkeycloakUserIds; } catch (error) { @@ -1102,7 +1195,6 @@ export class UserService { async getUserByUserIdInKeycloak(email: string): Promise { try { - const userData = await this.userRepository.checkUserExist(email.toLowerCase()); if (!userData) { @@ -1119,19 +1211,19 @@ export class UserService { } } - // eslint-disable-next-line camelcase - async getuserOrganizationByUserId(userId: string): Promise { + // eslint-disable-next-line camelcase + async getuserOrganizationByUserId(userId: string): Promise { try { - const getOrganizationDetails = await this.userRepository.handleGetUserOrganizations(userId); + const getOrganizationDetails = await this.userRepository.handleGetUserOrganizations(userId); - if (!getOrganizationDetails) { - throw new NotFoundException(ResponseMessages.ledger.error.NotFound); - } + if (!getOrganizationDetails) { + throw new NotFoundException(ResponseMessages.ledger.error.NotFound); + } - return getOrganizationDetails; + return getOrganizationDetails; } catch (error) { - this.logger.error(`Error in getuserOrganizationByUserId: ${error}`); - throw new RpcException(error.response ? error.response : error); + this.logger.error(`Error in getuserOrganizationByUserId: ${error}`); + throw new RpcException(error.response ? error.response : error); } + } } -} \ No newline at end of file diff --git a/apps/user/templates/user-email-template.ts b/apps/user/templates/user-email-template.ts index 31cfbcdab..17bfc4a09 100644 --- a/apps/user/templates/user-email-template.ts +++ b/apps/user/templates/user-email-template.ts @@ -1,14 +1,27 @@ export class URLUserEmailTemplate { - public getUserURLTemplate(email: string, verificationCode: string, redirectUrl: string, clientId: string, brandLogoUrl:string, platformName:string): string { - - const apiUrl = new URL( - clientId === process.env.KEYCLOAK_MANAGEMENT_CLIENT_ID ? '/verify-email-success' : '', - redirectUrl - ); + public getUserURLTemplate( + email: string, + verificationCode: string, + redirectUrl: string, + clientId: string, + brandLogoUrl: string, + platformName: string, + redirectTo?: string, + clientAlias?: string + ): string { + const apiUrl = new URL(clientId ? '/verify-email-success' : '', redirectUrl); apiUrl.searchParams.append('verificationCode', verificationCode); apiUrl.searchParams.append('email', encodeURIComponent(email)); + if (redirectTo) { + apiUrl.searchParams.append('redirectTo', redirectTo); + } + + if (clientAlias) { + apiUrl.searchParams.append('clientAlias', clientAlias); + } + const validUrl = apiUrl.href; const logoUrl = brandLogoUrl || process.env.BRAND_LOGO; @@ -64,6 +77,8 @@ export class URLUserEmailTemplate { `; - } catch (error) {} + } catch (error) { + throw new Error('Error creating email verification template'); + } } } diff --git a/apps/verification/src/verification.service.ts b/apps/verification/src/verification.service.ts index fe99e8cf8..50afe404b 100644 --- a/apps/verification/src/verification.service.ts +++ b/apps/verification/src/verification.service.ts @@ -243,7 +243,9 @@ export class VerificationService { const getProofPresentationById = await this._getProofPresentationById(payload); return getProofPresentationById?.response; } catch (error) { - this.logger.error(`[getProofPresentationById] - error in get proof presentation by proofId : ${JSON.stringify(error)}`); + this.logger.error( + `[getProofPresentationById] - error in get proof presentation by proofId : ${JSON.stringify(error)}` + ); const errorMessage = error?.response?.error?.reason || error?.message; if (errorMessage?.includes('not found')) { @@ -805,15 +807,12 @@ export class VerificationService { orgAgentType === OrgAgentType.DEDICATED && threadId ? `${agentEndPoint}${CommonConstants.URL_GET_PROOF_PRESENTATIONS}?threadId=${threadId}` : orgAgentType === OrgAgentType.SHARED && threadId - ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_PROOFS}?threadId=${threadId}`.replace( - '#', - tenantId - ) - : orgAgentType === OrgAgentType.DEDICATED - ? `${agentEndPoint}${CommonConstants.URL_GET_PROOF_PRESENTATIONS}` - : orgAgentType === OrgAgentType.SHARED - ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_PROOFS}`.replace('#', tenantId) - : null; + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_PROOFS}?threadId=${threadId}`.replace('#', tenantId) + : orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_GET_PROOF_PRESENTATIONS}` + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_PROOFS}`.replace('#', tenantId) + : null; break; } @@ -822,10 +821,10 @@ export class VerificationService { orgAgentType === OrgAgentType.DEDICATED ? `${agentEndPoint}${CommonConstants.URL_GET_PROOF_PRESENTATION_BY_ID}`.replace('#', proofPresentationId) : orgAgentType === OrgAgentType.SHARED - ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_PROOFS_BY_PRESENTATION_ID}` - .replace('#', proofPresentationId) - .replace('@', tenantId) - : null; + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_PROOFS_BY_PRESENTATION_ID}` + .replace('#', proofPresentationId) + .replace('@', tenantId) + : null; break; } @@ -834,8 +833,8 @@ export class VerificationService { orgAgentType === OrgAgentType.DEDICATED ? `${agentEndPoint}${CommonConstants.URL_SEND_PROOF_REQUEST}` : orgAgentType === OrgAgentType.SHARED - ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_REQUEST_PROOF}`.replace('#', tenantId) - : null; + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_REQUEST_PROOF}`.replace('#', tenantId) + : null; break; } @@ -844,10 +843,10 @@ export class VerificationService { orgAgentType === OrgAgentType.DEDICATED ? `${agentEndPoint}${CommonConstants.URL_VERIFY_PRESENTATION}`.replace('#', proofPresentationId) : orgAgentType === OrgAgentType.SHARED - ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_ACCEPT_PRESENTATION}` - .replace('@', proofPresentationId) - .replace('#', tenantId) - : null; + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_ACCEPT_PRESENTATION}` + .replace('@', proofPresentationId) + .replace('#', tenantId) + : null; break; } @@ -856,8 +855,8 @@ export class VerificationService { orgAgentType === OrgAgentType.DEDICATED ? `${agentEndPoint}${CommonConstants.URL_SEND_OUT_OF_BAND_CREATE_REQUEST}` : orgAgentType === OrgAgentType.SHARED - ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_OUT_OF_BAND_CREATE_REQUEST}`.replace('#', tenantId) - : null; + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_OUT_OF_BAND_CREATE_REQUEST}`.replace('#', tenantId) + : null; break; } @@ -866,10 +865,10 @@ export class VerificationService { orgAgentType === OrgAgentType.DEDICATED ? `${agentEndPoint}${CommonConstants.URL_PROOF_FORM_DATA}`.replace('#', proofPresentationId) : orgAgentType === OrgAgentType.SHARED - ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_PROOF_FORM_DATA}` - .replace('@', proofPresentationId) - .replace('#', tenantId) - : null; + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_PROOF_FORM_DATA}` + .replace('@', proofPresentationId) + .replace('#', tenantId) + : null; break; } diff --git a/docker-compose.yml b/docker-compose.yml index 854b63eb3..1f102c9ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -61,6 +61,7 @@ services: issuance: depends_on: - nats # Use depends_on instead of needs + - redis - api-gateway - user - connection @@ -165,7 +166,7 @@ services: webhook: depends_on: - nats - - api-gateway + - api-gateway image: ghcr.io/credebl/webhook:latest env_file: - ./.env diff --git a/libs/common/src/cast.helper.ts b/libs/common/src/cast.helper.ts index a425f7dbf..f4f8ba20b 100644 --- a/libs/common/src/cast.helper.ts +++ b/libs/common/src/cast.helper.ts @@ -1,4 +1,6 @@ -import { BadRequestException, PipeTransform } from '@nestjs/common'; +import * as CryptoJS from 'crypto-js'; + +import { BadRequestException, Logger, PipeTransform } from '@nestjs/common'; import { DidMethod, JSONSchemaType, @@ -23,6 +25,8 @@ import { ISchemaFields } from './interfaces/schema.interface'; import { ResponseMessages } from './response-messages'; import { plainToClass } from 'class-transformer'; +const logger = new Logger(); + interface ToNumberOptions { default?: number; min?: number; @@ -436,6 +440,22 @@ export function validateAndUpdateIssuanceDates(data: ICredentialData[]): ICreden }); } +export const encryptClientCredential = async (clientCredential: string): Promise => { + try { + const encryptedToken = CryptoJS.AES.encrypt( + JSON.stringify(clientCredential), + process.env.CRYPTO_PRIVATE_KEY + ).toString(); + + logger.debug('Client credentials encrypted successfully'); + + return encryptedToken; + } catch (error) { + logger.error('An error occurred during encryptClientCredential:', error); + throw error; + } +}; + export function ValidateNestedStructureFields(validationOptions?: ValidationOptions) { return function (object: object, propertyName: string): void { registerDecorator({ diff --git a/libs/common/src/common.module.ts b/libs/common/src/common.module.ts index 2b7967c30..0345877d2 100644 --- a/libs/common/src/common.module.ts +++ b/libs/common/src/common.module.ts @@ -1,11 +1,12 @@ import { HttpModule } from '@nestjs/axios'; -import { Module } from '@nestjs/common'; +import { Logger, Module } from '@nestjs/common'; +import { LoggerModule } from '@credebl/logger/logger.module'; import { CommonService } from './common.service'; @Module({ - imports: [HttpModule], - providers: [CommonService], + imports: [HttpModule, LoggerModule], + providers: [CommonService, Logger], exports: [CommonService] }) export class CommonModule {} diff --git a/libs/common/src/interfaces/user.interface.ts b/libs/common/src/interfaces/user.interface.ts index 998be0e70..974921ed9 100644 --- a/libs/common/src/interfaces/user.interface.ts +++ b/libs/common/src/interfaces/user.interface.ts @@ -1,53 +1,70 @@ export interface ISignInUser { - access_token: string; - token_type?: string; - expires_in?: number; - expires_at?: number; - refresh_token?: string; - isRegisteredToSupabase?: boolean; - } - export interface IVerifyUserEmail{ - email: string; - verificationCode: string; - } - export interface ISendVerificationEmail { - email: string; - clientId?: string; - clientSecret?: string; - username?: string; - brandLogoUrl?: string; - platformName?: string; - } - - export interface IUserInvitations { - totalPages:number; - userInvitationsData:IUserInvitationsData[]; - } - export interface IUserInvitationsData { - orgRoles: IOrgRole[]; - status: string; - id: string; - orgId: string; - organisation: IOrganisation; - userId: string; - } - export interface IOrgRole { - id: string; - name: string; - description: string; - } - - export interface IOrganisation { - id: string; - name: string; - logoUrl: string; - } - - export interface IResetPasswordResponse { - id: string; - email: string; - } + access_token: string; + token_type?: string; + expires_in?: number; + expires_at?: number; + refresh_token?: string; + isRegisteredToSupabase?: boolean; +} +export interface IVerifyUserEmail { + email: string; + verificationCode: string; +} +export interface ISendVerificationEmail { + email: string; + clientId?: string; + clientSecret?: string; + username?: string; + brandLogoUrl?: string; + platformName?: string; + redirectTo?: string; + clientAlias: string; +} + +export interface IClientDetailsSSO { + alias: string; + domain: string; + clientId: string; + clientSecret: string; +} + +export interface IUserInvitations { + totalPages: number; + userInvitationsData: IUserInvitationsData[]; +} +export interface IUserInvitationsData { + orgRoles: IOrgRole[]; + status: string; + id: string; + orgId: string; + organisation: IOrganisation; + userId: string; +} +export interface IOrgRole { + id: string; + name: string; + description: string; +} + +export interface IOrganisation { + id: string; + name: string; + logoUrl: string; +} + +export interface IResetPasswordResponse { + id: string; + email: string; +} export interface ISignUpUserResponse { - userId: string -} \ No newline at end of file + userId: string; +} + +export interface IClientAliases { + id: string; + createDateTime: string; + lastChangedDateTime: string; + clientAlias: string; + clientUrl: string; +} diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 3a2457f08..38ee5065c 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -4,6 +4,7 @@ export const ResponseMessages = { create: 'User registered successfully', update: 'User details updated successfully', emaiVerified: 'Email verified successfully', + fetchClientAlises: 'Client aliases fetched successfully', login: 'User login successfully', fetchProfile: 'User fetched successfully', fetchInvitations: 'Org invitations fetched successfully', diff --git a/libs/logger/src/logger.service.ts b/libs/logger/src/logger.service.ts index ca92fd1f6..33ace92c0 100644 --- a/libs/logger/src/logger.service.ts +++ b/libs/logger/src/logger.service.ts @@ -89,14 +89,14 @@ export default class LoggerService implements Logger { 'string' === typeof data.error ? data.error : data.error instanceof Error - ? { - name: data.error.name, - message: data.error.message, - stack: data.error.stack - } - : 'object' === typeof data.error - ? JSON.parse(JSON.stringify(data.error)) - : String(data.error); + ? { + name: data.error.name, + message: data.error.message, + stack: data.error.stack + } + : 'object' === typeof data.error + ? JSON.parse(JSON.stringify(data.error)) + : String(data.error); } otelLogger.emit({ diff --git a/libs/prisma-service/prisma/migrations/20250603101813_added_client_alias/migration.sql b/libs/prisma-service/prisma/migrations/20250603101813_added_client_alias/migration.sql new file mode 100644 index 000000000..998dfa1a6 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20250603101813_added_client_alias/migration.sql @@ -0,0 +1,10 @@ +-- CreateTable +CREATE TABLE "client_aliases" ( + "id" UUID NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "clientAlias" TEXT, + "clientUrl" TEXT NOT NULL, + + CONSTRAINT "client_aliases_pkey" PRIMARY KEY ("id") +); diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index f5c842b4a..89fee4629 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -519,4 +519,12 @@ model cloud_wallet_user_info { enum CloudWalletType { CLOUD_BASE_WALLET CLOUD_SUB_WALLET +} + +model client_aliases { + id String @id @default(uuid()) @db.Uuid + createDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + clientAlias String? + clientUrl String } \ No newline at end of file diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index 22919fec1..95cbd88e3 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -3,10 +3,10 @@ import * as fs from 'fs'; import { Logger } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; 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); +import { encryptClientCredential } from '@credebl/common/cast.helper'; +const execPromise = util.promisify(exec); const prisma = new PrismaClient(); const logger = new Logger('Init seed DB'); @@ -14,494 +14,464 @@ 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 - } - }); - - if (0 === existPlatformAdminUser.length) { - const platformUser = await prisma.user.create({ - data: platformAdminData - }); + 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 + } + }); - platformUserId = platformUser.id; + if (0 === existPlatformAdminUser.length) { + const platformUser = await prisma.user.create({ + data: platformAdminData + }); - logger.log(platformUser); - } else { - logger.log('Already seeding in user'); - } + platformUserId = platformUser.id; - } 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: `${CommonConstants.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 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); + });