diff --git a/.env.sample b/.env.sample index 17873880c..e36d252e7 100644 --- a/.env.sample +++ b/.env.sample @@ -1,7 +1,6 @@ MODE=DEV - -SUPABASE_URL= // Please specify your Supabase Url +SUPABASE_URL= // Please specify your Supabase URL SUPABASE_KEY= // Please specify your Supabase Anon key SUPABASE_JWT_SECRET= // Please specify your Supabase jwt secret @@ -9,7 +8,19 @@ API_GATEWAY_PROTOCOL=http API_GATEWAY_HOST='0.0.0.0' API_GATEWAY_PORT=5000 -PLATFORM_NAME=CREDEBL +## +PLATFORM_NAME= // Please specify your paltform name +PUBLIC_PLATFORM_SUPPORT_EMAIL= // Please specify your support email +POWERED_BY= // Please specify your powered by org name +PLATFORM_WEB_URL= // Please specify your platform web URL +POWERED_BY_URL= // Please specify your support URL + +PUBLIC_LOCALHOST_URL= // Please specify your localhost URL +PUBLIC_DEV_API_URL= // Please specify your DEV environment api URL +PUBLIC_QA_API_URL= // Please specify your your QA environment api URL +PUBLIC_PRODUCTION_API_URL= // Please specify your PRODUCTION environment api URL +PUBLIC_SANDBOX_API_URL= // Please specify your sandbox environment URL + AGENT_HOST=username@0.0.0.0 // Please specify your agent host VM and IP address AWS_ACCOUNT_ID=xxxxx // Please provide your AWS account Id @@ -47,8 +58,8 @@ PLATFORM_SEED= // The seed should consist of 32 characters. PLATFORM_ID= AFJ_AGENT_ENDPOINT_PATH=/apps/agent-provisioning/AFJ/endpoints/ -DATABASE_URL="postgresql://postgres:xxxxxx@localhost:5432/postgres?schema=public" #Provide supabase postgres url and Use the correct user/pwd, IP Address -POOL_DATABASE_URL="" #Provide pooler supabase postgres url +DATABASE_URL="postgresql://postgres:xxxxxx@localhost:5432/postgres?schema=public" #Provide supabase postgres URL and Use the correct user/pwd, IP Address +POOL_DATABASE_URL="" #Provide pooler supabase postgres URL CLUSTER_NAME="" # ecs cluster TESKDEFINITION_FAMILY="" # ecs task-definition AGENT_PROTOCOL=http @@ -68,4 +79,11 @@ export DEBUG="prisma:engine" export DEBUG="prisma:client" # enable both prisma-client- and engine-level debugging output -export DEBUG="prisma:client,prisma:engine" \ No newline at end of file +export DEBUG="prisma:client,prisma:engine" + +KEYCLOAK_DOMAIN=http://localhost:8080/ +KEYCLOAK_ADMIN_URL=http://localhost:8080 +KEYCLOAK_MASTER_REALM=xxxxxxx +KEYCLOAK_MANAGEMENT_CLIENT_ID=xxxxxxx +KEYCLOAK_MANAGEMENT_CLIENT_SECRET=xxxxxxx +KEYCLOAK_REALM=xxxxxxx \ No newline at end of file diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index 55e6452d2..163c7edca 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -3,9 +3,9 @@ import { MessagePattern } from '@nestjs/microservices'; import { AgentServiceService } from './agent-service.service'; import { IAgentStatus, IConnectionDetails, IUserRequestInterface, ISendProofRequestPayload, IAgentSpinUpSatus, IGetCredDefAgentRedirection, IGetSchemaAgentRedirection, IAgentSpinupDto, IIssuanceCreateOffer, ITenantCredDef, ITenantDto, ITenantSchema, IOutOfBandCredentialOffer, IProofPresentation, IAgentProofRequest, IPresentation } from './interface/agent-service.interface'; import { user } from '@prisma/client'; -import { ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface'; import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; import { IProofPresentationDetails } from '@credebl/common/interfaces/verification.interface'; +import { InvitationMessage } from '@credebl/common/interfaces/agent-service.interface'; @Controller() export class AgentServiceController { @@ -54,7 +54,7 @@ export class AgentServiceController { //DONE @MessagePattern({ cmd: 'agent-create-connection-legacy-invitation' }) - async createLegacyConnectionInvitation(payload: { connectionPayload: IConnectionDetails, url: string, apiKey: string }): Promise { + async createLegacyConnectionInvitation(payload: { connectionPayload: IConnectionDetails, url: string, apiKey: string }): Promise { return this.agentServiceService.createLegacyConnectionInvitation(payload.connectionPayload, payload.url, payload.apiKey); } diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 6dca30728..36552d004 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -36,6 +36,7 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { IProofPresentationDetails } from '@credebl/common/interfaces/verification.interface'; import { ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface'; import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; +import { InvitationMessage } from '@credebl/common/interfaces/agent-service.interface'; @Injectable() @WebSocketGateway() @@ -952,7 +953,7 @@ export class AgentServiceService { } } - async createLegacyConnectionInvitation(connectionPayload: IConnectionDetails, url: string, apiKey: string): Promise { + async createLegacyConnectionInvitation(connectionPayload: IConnectionDetails, url: string, apiKey: string): Promise { try { diff --git a/apps/api-gateway/src/authz/guards/org-roles.guard.ts b/apps/api-gateway/src/authz/guards/org-roles.guard.ts index b4e71fb26..c109525fb 100644 --- a/apps/api-gateway/src/authz/guards/org-roles.guard.ts +++ b/apps/api-gateway/src/authz/guards/org-roles.guard.ts @@ -1,7 +1,5 @@ import { BadRequestException, CanActivate, ExecutionContext, ForbiddenException, Logger } from '@nestjs/common'; -import { HttpException } from '@nestjs/common'; -import { HttpStatus } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; import { OrgRoles } from 'libs/org-roles/enums'; import { ROLES_KEY } from '../decorators/roles.decorator'; @@ -34,15 +32,23 @@ export class OrgRolesGuard implements CanActivate { const orgId = req.params.orgId || req.query.orgId || req.body.orgId; - if (!orgId) { - throw new BadRequestException(ResponseMessages.organisation.error.orgIdIsRequired); - } + if (orgId) { if (!isValidUUID(orgId)) { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - - if (orgId) { + } + + + if (user.hasOwnProperty('resource_access') && user.resource_access[orgId]) { + const orgRoles: string[] = user.resource_access[orgId].roles; + const roleAccess = requiredRoles.some((role) => orgRoles.includes(role)); + + if (!roleAccess) { + throw new ForbiddenException(ResponseMessages.organisation.error.roleNotMatch, { cause: new Error(), description: ResponseMessages.errorMessages.forbidden }); + } + return roleAccess; + } + const specificOrg = user.userOrgRoles.find((orgDetails) => { if (!orgDetails.orgId) { return false; @@ -78,7 +84,7 @@ export class OrgRolesGuard implements CanActivate { return false; } else { - throw new HttpException('organization is required', HttpStatus.BAD_REQUEST); + throw new BadRequestException('organization is required'); } // Sending user friendly message if a user attempts to access an API that is inaccessible to their role diff --git a/apps/api-gateway/src/authz/guards/user-access-guard.ts b/apps/api-gateway/src/authz/guards/user-access-guard.ts new file mode 100644 index 000000000..ea53f63fc --- /dev/null +++ b/apps/api-gateway/src/authz/guards/user-access-guard.ts @@ -0,0 +1,16 @@ +import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common'; +import { Observable } from 'rxjs'; + +@Injectable() +export class UserAccessGuard implements CanActivate { + canActivate(context: ExecutionContext): boolean | Promise | Observable { + const request = context.switchToHttp().getRequest(); + + const { user } = request; + + if (user.hasOwnProperty('client_id')) { + throw new UnauthorizedException('You do not have access'); + } + return true; + } +} diff --git a/apps/api-gateway/src/connection/connection.controller.ts b/apps/api-gateway/src/connection/connection.controller.ts index 686b97075..9fc45bfb7 100644 --- a/apps/api-gateway/src/connection/connection.controller.ts +++ b/apps/api-gateway/src/connection/connection.controller.ts @@ -15,7 +15,7 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler import { OrgRoles } from 'libs/org-roles/enums'; import { Roles } from '../authz/decorators/roles.decorator'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; -import { GetAllConnectionsDto } from './dtos/get-all-connections.dto'; +import { GetAllAgentConnectionsDto, GetAllConnectionsDto } from './dtos/get-all-connections.dto'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; import { SortFields } from 'apps/connection/src/enum/connection.enum'; @@ -108,7 +108,40 @@ export class ConnectionController { return res.status(HttpStatus.OK).json(finalResponse); } - + /** + * Description: Get all connections from agent + * @param user + * @param orgId + * + */ + @Get('/orgs/:orgId/agent/connections') + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiOperation({ + summary: `Fetch all connections from agent by orgId`, + description: `Fetch all connections from agent by orgId` + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + async getConnectionListFromAgent( + @Query() getAllConnectionsDto: GetAllAgentConnectionsDto, + @Param('orgId') orgId: string, + @Res() res: Response + ): Promise { + + const connectionDetails = await this.connectionService.getConnectionListFromAgent( + getAllConnectionsDto, + orgId + ); + + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.connection.success.fetch, + data: connectionDetails + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + @Get('orgs/:orgId/question-answer/question') @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER, OrgRoles.SUPER_ADMIN, OrgRoles.PLATFORM_ADMIN) diff --git a/apps/api-gateway/src/connection/connection.service.ts b/apps/api-gateway/src/connection/connection.service.ts index c01321093..5b2998dc1 100644 --- a/apps/api-gateway/src/connection/connection.service.ts +++ b/apps/api-gateway/src/connection/connection.service.ts @@ -5,7 +5,7 @@ import { BaseService } from 'libs/service/base.service'; import { ConnectionDto, CreateConnectionDto, ReceiveInvitationDto, ReceiveInvitationUrlDto } from './dtos/connection.dto'; import { IReceiveInvitationRes, IUserRequestInterface } from './interfaces'; import { IConnectionList, ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface'; -import { IConnectionDetailsById, IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; +import { AgentConnectionSearchCriteria, IConnectionDetailsById, IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; import { QuestionDto } from './dtos/question-answer.dto'; @Injectable() @@ -77,6 +77,14 @@ export class ConnectionService extends BaseService { return this.sendNatsMessage(this.connectionServiceProxy, 'get-all-connections', payload); } + getConnectionListFromAgent( + connectionSearchCriteria: AgentConnectionSearchCriteria, + orgId: string + ): Promise { + const payload = { connectionSearchCriteria, orgId }; + return this.sendNatsMessage(this.connectionServiceProxy, 'get-all-agent-connection-list', payload); + } + getConnectionsById( user: IUserRequest, connectionId: string, diff --git a/apps/api-gateway/src/connection/dtos/get-all-connections.dto.ts b/apps/api-gateway/src/connection/dtos/get-all-connections.dto.ts index 553a8a5a2..ac5ddb329 100644 --- a/apps/api-gateway/src/connection/dtos/get-all-connections.dto.ts +++ b/apps/api-gateway/src/connection/dtos/get-all-connections.dto.ts @@ -1,41 +1,61 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { Transform, Type } from "class-transformer"; -import { IsEnum, IsOptional } from "class-validator"; -import { SortValue } from "../../enum"; -import { trim } from "@credebl/common/cast.helper"; -import { SortFields } from "apps/connection/src/enum/connection.enum"; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; +import { IsEnum, IsOptional } from 'class-validator'; +import { SortValue } from '../../enum'; +import { trim } from '@credebl/common/cast.helper'; +import { SortFields } from 'apps/connection/src/enum/connection.enum'; export class GetAllConnectionsDto { - - @ApiProperty({ required: false, example: '1' }) - @IsOptional() - pageNumber: number = 1; - - @ApiProperty({ required: false, example: '10' }) - @IsOptional() - pageSize: number = 10; - - @ApiProperty({ required: false }) - @IsOptional() - @Transform(({ value }) => trim(value)) - @Type(() => String) - searchByText: string = ''; - - @ApiProperty({ - required: false - }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsEnum(SortFields) - sortField: string = SortFields.CREATED_DATE_TIME; - - @ApiProperty({ - enum: [SortValue.DESC, SortValue.ASC], - required: false - }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsEnum(SortValue) - sortBy: string = SortValue.DESC; + @ApiProperty({ required: false, example: '1' }) + @IsOptional() + pageNumber: number = 1; + + @ApiProperty({ required: false, example: '10' }) + @IsOptional() + pageSize: number = 10; + + @ApiProperty({ required: false }) + @IsOptional() + @Transform(({ value }) => trim(value)) + @Type(() => String) + searchByText: string = ''; + + @ApiProperty({ + required: false + }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(SortFields) + sortField: string = SortFields.CREATED_DATE_TIME; + + @ApiProperty({ + enum: [SortValue.DESC, SortValue.ASC], + required: false + }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(SortValue) + sortBy: string = SortValue.DESC; } +export class GetAllAgentConnectionsDto { + @ApiProperty({ required: false, example: 'e315f30d-9beb-4068-aea4-abb5fe5eecb1' }) + @IsOptional() + outOfBandId: string = ''; + + @ApiProperty({ required: false, example: 'Test' }) + @IsOptional() + alias: string = ''; + + @ApiProperty({ required: false, example: 'did:example:e315f30d-9beb-4068-aea4-abb5fe5eecb1' }) + @IsOptional() + myDid: string = ''; + + @ApiProperty({ required: false, example: 'did:example:e315f30d-9beb-4068-aea4-abb5fe5eecb1' }) + @IsOptional() + theirDid: string = ''; + + @ApiProperty({ required: false, example: 'Bob' }) + @IsOptional() + theirLabel: string = ''; +} diff --git a/apps/api-gateway/src/interfaces/IConnectionSearch.interface.ts b/apps/api-gateway/src/interfaces/IConnectionSearch.interface.ts index 35d79949d..a228ae636 100644 --- a/apps/api-gateway/src/interfaces/IConnectionSearch.interface.ts +++ b/apps/api-gateway/src/interfaces/IConnectionSearch.interface.ts @@ -1,25 +1,34 @@ import { IUserRequestInterface } from './IUserRequestInterface'; export interface IConnectionSearchCriteria { - pageNumber: number; - pageSize: number; - sortField: string; - sortBy: string; - searchByText: string; - user?: IUserRequestInterface + pageNumber: number; + pageSize: number; + sortField: string; + sortBy: string; + searchByText: string; + user?: IUserRequestInterface; } export interface IConnectionDetailsById { - id: string; - createdAt: string; - did: string; - theirDid: string; - theirLabel: string; - state: string; - role: string; - autoAcceptConnection: boolean; - threadId: string; - protocol: string; - outOfBandId: string; - updatedAt: string; - } + id: string; + createdAt: string; + did: string; + theirDid: string; + theirLabel: string; + state: string; + role: string; + autoAcceptConnection: boolean; + threadId: string; + protocol: string; + outOfBandId: string; + updatedAt: string; +} + +export interface AgentConnectionSearchCriteria { + outOfBandId?: string; + alias?: string; + state?: string; + myDid?: string; + theirDid?: string; + theirLabel?: string; +} diff --git a/apps/api-gateway/src/interfaces/IUserRequestInterface.ts b/apps/api-gateway/src/interfaces/IUserRequestInterface.ts index 908cfe524..dbda90cec 100644 --- a/apps/api-gateway/src/interfaces/IUserRequestInterface.ts +++ b/apps/api-gateway/src/interfaces/IUserRequestInterface.ts @@ -1,6 +1,7 @@ import { UserRoleOrgPermsDto } from '../authz/dtos/user-role-org-perms.dto'; export interface IUserRequestInterface { + id: string; userId: string; email: string; orgId: string; diff --git a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts index b8381e531..390d402fc 100644 --- a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts +++ b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts @@ -1,14 +1,15 @@ /* eslint-disable @typescript-eslint/array-type */ -import { IsArray, IsNotEmpty, IsOptional, IsString, IsEmail, ArrayMaxSize, ValidateNested, ArrayMinSize, IsBoolean, IsDefined, MaxLength, IsEnum, IsObject} from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { ArrayMaxSize, ArrayMinSize, IsArray, IsBoolean, IsDefined, IsEmail, IsEnum, IsNotEmpty, IsObject, IsOptional, IsString, MaxLength, ValidateNested } from 'class-validator'; +import { IsCredentialJsonLdContext, SingleOrArray } from '../utils/helper'; +import { IssueCredentialType, JsonLdCredentialDetailCredentialStatusOptions, JsonLdCredentialDetailOptionsOptions, JsonObject } from '../interfaces'; import { Transform, Type } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; -import { SortValue } from '../../enum'; -import { SortFields } from 'apps/connection/src/enum/connection.enum'; + import { AutoAccept } from '@credebl/enum/enum'; -import { IssueCredentialType, JsonLdCredentialDetailCredentialStatusOptions, JsonLdCredentialDetailOptionsOptions, JsonObject } from '../interfaces'; -import { IsCredentialJsonLdContext, SingleOrArray } from '../utils/helper'; +import { SortFields } from 'apps/connection/src/enum/connection.enum'; +import { SortValue } from '../../enum'; +import { trim } from '@credebl/common/cast.helper'; class Issuer { @ApiProperty() @@ -209,6 +210,14 @@ export class OOBIssueCredentialDto extends CredentialsIssuanceDto { @IsNotEmpty({ message: 'Please provide valid attributes' }) @Type(() => Attribute) attributes?: Attribute[]; + + @ApiProperty({ + example: false + }) + @IsOptional() + @IsNotEmpty() + @IsBoolean({message: 'isShortenUrl must be boolean'}) + isShortenUrl?: boolean; @ApiProperty() diff --git a/apps/api-gateway/src/issuance/issuance.service.ts b/apps/api-gateway/src/issuance/issuance.service.ts index bd58cba92..e97e26ad7 100644 --- a/apps/api-gateway/src/issuance/issuance.service.ts +++ b/apps/api-gateway/src/issuance/issuance.service.ts @@ -31,12 +31,12 @@ export class IssuanceService extends BaseService { }> { let payload; if (IssueCredentialType.INDY === issueCredentialDto.credentialType) { - payload = { attributes: issueCredentialDto.attributes, comment: issueCredentialDto.comment, credentialDefinitionId: issueCredentialDto.credentialDefinitionId, orgId: issueCredentialDto.orgId, protocolVersion: issueCredentialDto.protocolVersion, goalCode: issueCredentialDto.goalCode, parentThreadId: issueCredentialDto.parentThreadId, willConfirm: issueCredentialDto.willConfirm, label: issueCredentialDto.label, autoAcceptCredential: issueCredentialDto.autoAcceptCredential, credentialType: issueCredentialDto.credentialType }; - } - if (IssueCredentialType.JSONLD === issueCredentialDto.credentialType) { - payload = { credential: issueCredentialDto.credential, options:issueCredentialDto.options, comment: issueCredentialDto.comment, orgId: issueCredentialDto.orgId, protocolVersion: issueCredentialDto.protocolVersion, goalCode: issueCredentialDto.goalCode, parentThreadId: issueCredentialDto.parentThreadId, willConfirm: issueCredentialDto.willConfirm, label: issueCredentialDto.label, autoAcceptCredential: issueCredentialDto.autoAcceptCredential, credentialType: issueCredentialDto.credentialType }; - } - + payload = { attributes: issueCredentialDto.attributes, comment: issueCredentialDto.comment, credentialDefinitionId: issueCredentialDto.credentialDefinitionId, orgId: issueCredentialDto.orgId, protocolVersion: issueCredentialDto.protocolVersion, goalCode: issueCredentialDto.goalCode, parentThreadId: issueCredentialDto.parentThreadId, willConfirm: issueCredentialDto.willConfirm, label: issueCredentialDto.label, autoAcceptCredential: issueCredentialDto.autoAcceptCredential, credentialType: issueCredentialDto.credentialType, isShortenUrl: issueCredentialDto.isShortenUrl }; + } + if (IssueCredentialType.JSONLD === issueCredentialDto.credentialType) { + payload = { credential: issueCredentialDto.credential, options: issueCredentialDto.options, comment: issueCredentialDto.comment, orgId: issueCredentialDto.orgId, protocolVersion: issueCredentialDto.protocolVersion, goalCode: issueCredentialDto.goalCode, parentThreadId: issueCredentialDto.parentThreadId, willConfirm: issueCredentialDto.willConfirm, label: issueCredentialDto.label, autoAcceptCredential: issueCredentialDto.autoAcceptCredential, credentialType: issueCredentialDto.credentialType, isShortenUrl: issueCredentialDto.isShortenUrl }; + } + return this.sendNats(this.issuanceProxy, 'send-credential-create-offer-oob', payload); } diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index 80d22c6ad..f8507b270 100644 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -36,11 +36,11 @@ async function bootstrap(): Promise { .setDescription(`${process.env.PLATFORM_NAME} Platform APIs`) .setVersion('1.0') .addBearerAuth() - .addServer('http://localhost:5000') - .addServer('https://devapi.credebl.id') - .addServer('https://qa-api.credebl.id') - .addServer('https://api.credebl.id') - .addServer('https://sandboxapi.credebl.id') + .addServer(`${process.env.PUBLIC_DEV_API_URL}`) + .addServer(`${process.env.PUBLIC_LOCALHOST_URL}`) + .addServer(`${process.env.PUBLIC_QA_API_URL}`) + .addServer(`${process.env.PUBLIC_PRODUCTION_API_URL}`) + .addServer(`${process.env.PUBLIC_SANDBOX_API_URL}`) .addServer(`${process.env.API_GATEWAY_PROTOCOL}://${process.env.API_ENDPOINT}`) .addServer(`${process.env.API_GATEWAY_PROTOCOL}://${process.env.API_GATEWAY_HOST}`) .build(); diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 20635a1f9..6884e5963 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -24,6 +24,7 @@ import { ImageServiceService } from '@credebl/image-service'; import { ClientCredentialsDto } from './dtos/client-credentials.dto'; import { PaginationDto } from '@credebl/common/dtos/pagination.dto'; import { validate as isValidUUID } from 'uuid'; +import { UserAccessGuard } from '../authz/guards/user-access-guard'; @UseFilters(CustomExceptionFilter) @Controller('orgs') @@ -95,7 +96,7 @@ export class OrganizationController { * @returns get organization roles */ - @Get('/roles') + @Get('/:orgId/roles') @ApiOperation({ summary: 'Fetch org-roles details', description: 'Fetch org-roles details' @@ -103,9 +104,9 @@ export class OrganizationController { @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getOrgRoles(@Res() res: Response): Promise { + async getOrgRoles(@Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @Res() res: Response): Promise { - const orgRoles = await this.organizationService.getOrgRoles(); + const orgRoles = await this.organizationService.getOrgRoles(orgId.trim()); const finalResponse: IResponse = { statusCode: HttpStatus.OK, @@ -211,7 +212,7 @@ export class OrganizationController { @Get('/') @ApiOperation({ summary: 'Get all organizations', description: 'Get all organizations' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), UserAccessGuard) @ApiBearerAuth() @ApiQuery({ name: 'pageNumber', @@ -324,10 +325,14 @@ export class OrganizationController { @Post('/') @ApiOperation({ summary: 'Create a new Organization', description: 'Create an organization' }) @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), UserAccessGuard) @ApiBearerAuth() async createOrganization(@Body() createOrgDto: CreateOrganizationDto, @Res() res: Response, @User() reqUser: user): Promise { - const orgData = await this.organizationService.createOrganization(createOrgDto, reqUser.id); + + // eslint-disable-next-line prefer-destructuring + const keycloakUserId = reqUser.keycloakUserId; + + const orgData = await this.organizationService.createOrganization(createOrgDto, reqUser.id, keycloakUserId); const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.organisation.success.create, @@ -350,7 +355,11 @@ export class OrganizationController { @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() async createOrgCredentials(@Param('orgId') orgId: string, @Res() res: Response, @User() reqUser: user): Promise { - const orgCredentials = await this.organizationService.createOrgCredentials(orgId, reqUser.id); + + // eslint-disable-next-line prefer-destructuring + const keycloakUserId = reqUser.keycloakUserId; + + const orgCredentials = await this.organizationService.createOrgCredentials(orgId, reqUser.id, keycloakUserId); const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.organisation.success.orgCredentials, @@ -381,6 +390,28 @@ export class OrganizationController { return res.status(HttpStatus.OK).json(finalResponse); } + @Post('/register-org-map-users') + @ApiOperation({ + summary: 'Register client and map users', + description: 'Register client and map users' + }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.PLATFORM_ADMIN) + @ApiBearerAuth() + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + async registerOrgsMapUsers(@Res() res: Response): Promise { + + await this.organizationService.registerOrgsMapUsers(); + + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: 'Organization client created and users mapped to client' + }; + + return res.status(HttpStatus.CREATED).json(finalResponse); + + } + @Post('/:orgId/invitations') @ApiOperation({ summary: 'Create organization invitation', @@ -482,6 +513,7 @@ export class OrganizationController { @ApiOperation({ summary: 'Delete Organization Client Credentials', description: 'Delete Organization Client Credentials' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() + @ApiExcludeEndpoint() @UseGuards(AuthGuard('jwt')) async deleteOrgClientCredentials(@Param('orgId') orgId: string, @Res() res: Response): Promise { diff --git a/apps/api-gateway/src/organization/organization.service.ts b/apps/api-gateway/src/organization/organization.service.ts index 1e4d377d8..0baf88fa0 100644 --- a/apps/api-gateway/src/organization/organization.service.ts +++ b/apps/api-gateway/src/organization/organization.service.ts @@ -6,7 +6,6 @@ import { CreateOrganizationDto } from './dtos/create-organization-dto'; import { BulkSendInvitationDto } from './dtos/send-invitation.dto'; import { UpdateUserRolesDto } from './dtos/update-user-roles.dto'; import { UpdateOrganizationDto } from './dtos/update-organization-dto'; -import { IOrgRoles } from 'libs/org-roles/interfaces/org-roles.interface'; import { organisation } from '@prisma/client'; import { IGetOrgById, IGetOrganization } from 'apps/organization/interfaces/organization.interface'; import { IOrgUsers } from 'apps/user/interfaces/user.interface'; @@ -14,6 +13,7 @@ import { IOrgCredentials, IOrganization, IOrganizationInvitations, IOrganization import { ClientCredentialsDto } from './dtos/client-credentials.dto'; import { IAccessTokenData } from '@credebl/common/interfaces/interface'; import { PaginationDto } from '@credebl/common/dtos/pagination.dto'; +import { IClientRoles } from '@credebl/client-registration/interfaces/client.interface'; @Injectable() export class OrganizationService extends BaseService { @@ -26,8 +26,8 @@ export class OrganizationService extends BaseService { * @param createOrgDto * @returns Organization creation Success */ - async createOrganization(createOrgDto: CreateOrganizationDto, userId: string): Promise { - const payload = { createOrgDto, userId }; + async createOrganization(createOrgDto: CreateOrganizationDto, userId: string, keycloakUserId: string): Promise { + const payload = { createOrgDto, userId, keycloakUserId }; return this.sendNatsMessage(this.serviceProxy, 'create-organization', payload); } @@ -37,8 +37,8 @@ export class OrganizationService extends BaseService { * @param userId * @returns Orgnization client credentials */ - async createOrgCredentials(orgId: string, userId: string): Promise { - const payload = { orgId, userId }; + async createOrgCredentials(orgId: string, userId: string, keycloakUserId: string): Promise { + const payload = { orgId, userId, keycloakUserId }; return this.sendNatsMessage(this.serviceProxy, 'create-org-credentials', payload); } @@ -133,8 +133,8 @@ export class OrganizationService extends BaseService { * @returns get organization roles */ - async getOrgRoles(): Promise { - const payload = {}; + async getOrgRoles(orgId: string): Promise { + const payload = {orgId}; return this.sendNatsMessage(this.serviceProxy, 'get-org-roles', payload); } @@ -148,6 +148,11 @@ export class OrganizationService extends BaseService { return this.sendNatsMessage(this.serviceProxy, 'send-invitation', payload); } + async registerOrgsMapUsers(): Promise { + const payload = {}; + return this.sendNatsMessage(this.serviceProxy, 'register-orgs-users-map', payload); + } + /** * * @param updateUserDto diff --git a/apps/api-gateway/src/schema/interfaces/index.ts b/apps/api-gateway/src/schema/interfaces/index.ts index a0edf7816..ace0f7e67 100644 --- a/apps/api-gateway/src/schema/interfaces/index.ts +++ b/apps/api-gateway/src/schema/interfaces/index.ts @@ -1,6 +1,7 @@ import { UserRoleOrgPermsDto } from '../../dtos/user-role-org-perms.dto'; export interface IUserRequestInterface { + id: string; userId: string; email: string; orgId: string; diff --git a/apps/api-gateway/src/user/dto/update-platform-settings.dto.ts b/apps/api-gateway/src/user/dto/update-platform-settings.dto.ts index 79d4cdb76..d452e213a 100644 --- a/apps/api-gateway/src/user/dto/update-platform-settings.dto.ts +++ b/apps/api-gateway/src/user/dto/update-platform-settings.dto.ts @@ -23,7 +23,7 @@ export class UpdatePlatformSettingsDto { @IsString({ message: 'emailFrom should be string' }) emailFrom: string; - @ApiProperty({ example: 'dev.credebl.id' }) + @ApiProperty({ example: `${process.env.UPLOAD_LOGO_HOST}` }) @IsOptional() @IsString({ message: 'API endpoint should be string' }) apiEndPoint: string; diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 8e9ee72a4..5e85bbfbc 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -50,6 +50,7 @@ import { OrgRoles } from 'libs/org-roles/enums'; import { AwsService } from '@credebl/aws/aws.service'; import { PaginationDto } from '@credebl/common/dtos/pagination.dto'; import { CreateCertificateDto } from './dto/share-certificate.dto'; +import { UserAccessGuard } from '../authz/guards/user-access-guard'; @UseFilters(CustomExceptionFilter) @Controller('users') @@ -132,7 +133,7 @@ export class UserController { summary: 'Fetch login user details', description: 'Fetch login user details' }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), UserAccessGuard) @ApiBearerAuth() async getProfile(@User() reqUser: user, @Res() res: Response): Promise { const userData = await this.userService.getProfile(reqUser.id); @@ -154,7 +155,7 @@ export class UserController { summary: 'Get all platform and ecosystem settings', description: 'Get all platform and ecosystem settings' }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard, UserAccessGuard) @Roles(OrgRoles.PLATFORM_ADMIN) @ApiBearerAuth() async getPlatformSettings(@Res() res: Response): Promise { @@ -174,7 +175,7 @@ export class UserController { summary: 'users activity', description: 'Fetch users activity' }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), UserAccessGuard) @ApiBearerAuth() @ApiQuery({ name: 'limit', required: true }) async getUserActivities( @@ -201,7 +202,7 @@ export class UserController { summary: 'organization invitations', description: 'Fetch organization invitations' }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), UserAccessGuard) @ApiBearerAuth() @ApiQuery({ name: 'pageNumber', @@ -293,7 +294,7 @@ export class UserController { summary: 'accept/reject organization invitation', description: 'Accept or Reject organization invitations' }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), UserAccessGuard) @ApiBearerAuth() async acceptRejectInvitaion( @Body() acceptRejectInvitation: AcceptRejectInvitationDto, @@ -349,7 +350,7 @@ export class UserController { }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), UserAccessGuard) async updateUserProfile( @Body() updateUserProfileDto: UpdateUserProfileDto, @User() reqUser: user, @@ -375,7 +376,7 @@ export class UserController { @ApiOperation({ summary: 'Store user password details', description: 'Store user password details' }) @ApiExcludeEndpoint() @ApiBearerAuth() - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), UserAccessGuard) async addPasskey( @Body() userInfo: AddPasskeyDetailsDto, @@ -403,7 +404,7 @@ export class UserController { summary: 'Update platform and ecosystem settings', description: 'Update platform and ecosystem settings' }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard, UserAccessGuard) @Roles(OrgRoles.PLATFORM_ADMIN) @ApiBearerAuth() async updatePlatformSettings( diff --git a/apps/api-gateway/src/utilities/dtos/shortening-url.dto.ts b/apps/api-gateway/src/utilities/dtos/shortening-url.dto.ts index 287778e40..f1467028b 100644 --- a/apps/api-gateway/src/utilities/dtos/shortening-url.dto.ts +++ b/apps/api-gateway/src/utilities/dtos/shortening-url.dto.ts @@ -39,3 +39,9 @@ interface Attribute { name: string; value: string; } + + export class GenericDto { + @ApiProperty() + @IsNotEmpty() + data: string | object; +} \ No newline at end of file diff --git a/apps/api-gateway/src/utilities/utilities.controller.ts b/apps/api-gateway/src/utilities/utilities.controller.ts index 35984c216..782b90c6e 100644 --- a/apps/api-gateway/src/utilities/utilities.controller.ts +++ b/apps/api-gateway/src/utilities/utilities.controller.ts @@ -1,5 +1,5 @@ import { ApiForbiddenResponse, ApiOperation, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; -import { Controller, UseFilters, Post, Body, Res, HttpStatus } from '@nestjs/common'; +import { Controller, UseFilters, Post, Body, Res, HttpStatus, Param } from '@nestjs/common'; import IResponse from '@credebl/common/interfaces/response.interface'; import { Response } from 'express'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; @@ -7,7 +7,7 @@ import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import { ResponseMessages } from '@credebl/common/response-messages'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; -import { UtilitiesDto } from './dtos/shortening-url.dto'; +import { GenericDto, UtilitiesDto } from './dtos/shortening-url.dto'; import { UtilitiesService } from './utilities.service'; @UseFilters(CustomExceptionFilter) @@ -20,7 +20,7 @@ export class UtilitiesController { constructor( private readonly utilitiesService: UtilitiesService ) { } - + @Post('/') @ApiOperation({ summary: 'Create a shorteningurl', description: 'Create a shortening url' }) @@ -35,5 +35,18 @@ export class UtilitiesController { return res.status(HttpStatus.CREATED).json(finalResponse); } + @Post('/store-object/:persist') + @ApiOperation({ summary: 'Store an object and return a short url to it', description: 'Create a short url representing the object' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + async storeObject(@Body() storeObjectDto: GenericDto, @Param('persist') persist: boolean, @Res() res: Response): Promise { + const shorteningUrl = await this.utilitiesService.storeObject(persist.valueOf(), storeObjectDto); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.storeObject.success.storeObject, + data: shorteningUrl + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + } diff --git a/apps/api-gateway/src/utilities/utilities.service.ts b/apps/api-gateway/src/utilities/utilities.service.ts index fae204616..08aa46f5f 100644 --- a/apps/api-gateway/src/utilities/utilities.service.ts +++ b/apps/api-gateway/src/utilities/utilities.service.ts @@ -12,4 +12,9 @@ export class UtilitiesService extends BaseService { async createShorteningUrl(shorteningUrlDto: UtilitiesDto): Promise { return this.sendNatsMessage(this.serviceProxy, 'create-shortening-url', shorteningUrlDto); } + + async storeObject(persistent: boolean, storeObj: unknown): Promise { + const payload = {persistent, storeObj}; + return this.sendNatsMessage(this.serviceProxy, 'store-object-return-url', payload); + } } diff --git a/apps/api-gateway/src/verification/dto/request-proof.dto.ts b/apps/api-gateway/src/verification/dto/request-proof.dto.ts index 3f16e005a..576d05854 100644 --- a/apps/api-gateway/src/verification/dto/request-proof.dto.ts +++ b/apps/api-gateway/src/verification/dto/request-proof.dto.ts @@ -1,11 +1,11 @@ -import { ArrayNotEmpty, IsArray, IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsNumberString, IsObject, IsOptional, IsString, ValidateIf, ValidateNested, IsUUID } from 'class-validator'; -import { toLowerCase, trim } from '@credebl/common/cast.helper'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { ArrayNotEmpty, IsArray, IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsNumberString, IsObject, IsOptional, IsString, IsUUID, ValidateIf, ValidateNested } from 'class-validator'; import { Transform, Type } from 'class-transformer'; +import { toLowerCase, trim } from '@credebl/common/cast.helper'; + import { AutoAccept } from '@credebl/enum/enum'; import { IProofFormats } from '../interfaces/verification.interface'; - export class ProofRequestAttribute { @ValidateIf((obj) => obj.attributeNames === undefined) @@ -16,7 +16,7 @@ export class ProofRequestAttribute { @ValidateIf((obj) => obj.attributeName === undefined) @IsArray({ message: 'attributeNames must be an array.' }) @ArrayNotEmpty({ message: 'array can not be empty' }) - @IsString({ each: true}) + @IsString({ each: true }) @IsNotEmpty({ each: true, message: 'each element cannot be empty' }) attributeNames?: string[]; @@ -127,7 +127,7 @@ export class OutOfBandRequestProof extends ProofPayload { type: () => [ProofRequestAttribute] }) @IsArray({ message: 'attributes must be in array' }) - @ValidateNested({each: true}) + @ValidateNested({ each: true }) @IsObject({ each: true }) @IsNotEmpty({ message: 'please provide valid attributes' }) @Type(() => ProofRequestAttribute) @@ -254,11 +254,11 @@ export class SendProofRequestPayload { requested_attributes: { verifynameAddress: { names: ['name', 'address'], - restrictions: [{'schema_id': 'KU583UbI4yAKfaBTSz1rqG:2:National ID:1.0.0'}] + restrictions: [{ 'schema_id': 'KU583UbI4yAKfaBTSz1rqG:2:National ID:1.0.0' }] }, verifyBirthPlace: { name: 'Place', - restrictions: [{'schema_id': 'KU583UbI4yAKfaBTSz1rqG:2:Birth Certificate:1.0.0'}] + restrictions: [{ 'schema_id': 'KU583UbI4yAKfaBTSz1rqG:2:Birth Certificate:1.0.0' }] } }, // eslint-disable-next-line camelcase @@ -325,6 +325,12 @@ export class SendProofRequestPayload { @IsOptional() @IsUUID() @IsNotEmpty({ message: 'please provide valid parentThreadId' }) - parentThreadId: string; + parentThreadId: string; + + @ApiProperty({ example: true }) + @IsBoolean() + @IsOptional() + @IsNotEmpty({message:'Please provide the flag for shorten url.'}) + isShortenUrl?: boolean; } diff --git a/apps/connection/src/connection.controller.ts b/apps/connection/src/connection.controller.ts index 923a15190..ca67a4149 100644 --- a/apps/connection/src/connection.controller.ts +++ b/apps/connection/src/connection.controller.ts @@ -2,6 +2,7 @@ import { Controller } from '@nestjs/common'; // Import the common service in the import { ConnectionService } from './connection.service'; // Import the common service in connection module import { MessagePattern } from '@nestjs/microservices'; // Import the nestjs microservices package import { + GetAllConnections, IConnection, ICreateConnection, IFetchConnectionById, @@ -54,6 +55,12 @@ export class ConnectionController { return this.connectionService.getConnections(user, orgId, connectionSearchCriteria); } + @MessagePattern({ cmd: 'get-all-agent-connection-list' }) + async getConnectionListFromAgent(payload: GetAllConnections): Promise { + const {orgId, connectionSearchCriteria } = payload; + return this.connectionService.getAllConnectionListFromAgent(orgId, connectionSearchCriteria); + } + /** * * @param connectionId diff --git a/apps/connection/src/connection.service.ts b/apps/connection/src/connection.service.ts index 7dbb306f7..dc030a186 100644 --- a/apps/connection/src/connection.service.ts +++ b/apps/connection/src/connection.service.ts @@ -5,8 +5,9 @@ import { HttpException, Inject, Injectable, Logger, NotFoundException } from '@n import { ClientProxy, RpcException } from '@nestjs/microservices'; import { map } from 'rxjs'; import { + ConnectionResponseDetail, + AgentConnectionSearchCriteria, IConnection, - IConnectionInvitation, IConnectionSearchCriteria, ICreateConnection, IReceiveInvitation, @@ -22,6 +23,7 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { IConnectionList, ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface'; import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; import { IQuestionPayload } from './interfaces/question-answer.interfaces'; +import { InvitationMessage } from '@credebl/common/interfaces/agent-service.interface'; @Injectable() export class ConnectionService { @@ -31,7 +33,7 @@ export class ConnectionService { private readonly connectionRepository: ConnectionRepository, private readonly logger: Logger, @Inject(CACHE_MANAGER) private cacheService: Cache - ) { } + ) {} /** * Create connection legacy invitation URL @@ -40,10 +42,18 @@ export class ConnectionService { * @returns Connection legacy invitation URL */ async createLegacyConnectionInvitation(payload: IConnection): Promise { - - const { orgId, multiUseInvitation, autoAcceptConnection, alias, imageUrl, goal, goalCode, handshake, handshakeProtocols } = payload; + const { + orgId, + multiUseInvitation, + autoAcceptConnection, + alias, + imageUrl, + goal, + goalCode, + handshake, + handshakeProtocols + } = payload; try { - const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); const { agentEndPoint, id, organisation } = agentDetails; @@ -54,8 +64,8 @@ export class ConnectionService { this.logger.log(`logoUrl:::, ${organisation.logoUrl}`); const connectionPayload = { - multiUseInvitation: multiUseInvitation || true, - autoAcceptConnection: autoAcceptConnection || true, + multiUseInvitation: multiUseInvitation ?? true, + autoAcceptConnection: autoAcceptConnection ?? true, alias: alias || undefined, imageUrl: organisation.logoUrl || imageUrl || undefined, label: organisation.name, @@ -73,20 +83,31 @@ export class ConnectionService { apiKey = await this._getOrgAgentApiKey(orgId); } const createConnectionInvitation = await this._createConnectionInvitation(connectionPayload, url, apiKey); - const invitationObject = createConnectionInvitation?.message?.invitation['@id']; - let shortenedUrl; - if (agentDetails?.tenantId) { - shortenedUrl = `${agentEndPoint}/multi-tenancy/url/${agentDetails?.tenantId}/${invitationObject}`; - } else { - shortenedUrl = `${agentEndPoint}/url/${invitationObject}`; - } + const connectionInvitationUrl: string = createConnectionInvitation?.message?.invitationUrl; + const shortenedUrl: string = await this.storeConnectionObjectAndReturnUrl( + connectionInvitationUrl, + connectionPayload.multiUseInvitation + ); const saveConnectionDetails = await this.connectionRepository.saveAgentConnectionInvitations( shortenedUrl, agentId, orgId ); - return saveConnectionDetails; + + const connectionDetailRecords: ConnectionResponseDetail = { + id: saveConnectionDetails.id, + orgId: saveConnectionDetails.orgId, + agentId: saveConnectionDetails.agentId, + connectionInvitation: saveConnectionDetails.connectionInvitation, + multiUse: saveConnectionDetails.multiUse, + createDateTime: saveConnectionDetails.createDateTime, + createdBy: saveConnectionDetails.createdBy, + lastChangedDateTime: saveConnectionDetails.lastChangedDateTime, + lastChangedBy: saveConnectionDetails.lastChangedBy, + recordId: createConnectionInvitation.message.outOfBandRecord.id + }; + return connectionDetailRecords; } catch (error) { this.logger.error(`[createLegacyConnectionInvitation] - error in connection invitation: ${error}`); if (error && error?.status && error?.status?.message && error?.status?.message?.error) { @@ -126,8 +147,7 @@ export class ConnectionService { connectionPayload: object, url: string, apiKey: string - ): Promise { - + ): Promise { //nats call in agent-service to create an invitation url const pattern = { cmd: 'agent-create-connection-legacy-invitation' }; const payload = { connectionPayload, url, apiKey }; @@ -222,10 +242,59 @@ export class ConnectionService { }; return connectionResponse; } catch (error) { + this.logger.error(`[getConnections] [NATS call]- error in fetch connections details : ${JSON.stringify(error)}`); - this.logger.error( - `[getConnections] [NATS call]- error in fetch connections details : ${JSON.stringify(error)}` - ); + throw new RpcException(error.response ? error.response : error); + } + } + + async getAllConnectionListFromAgent( + orgId: string, + connectionSearchCriteria: AgentConnectionSearchCriteria + ): Promise { + try { + const { alias, myDid, outOfBandId, state, theirDid, theirLabel } = connectionSearchCriteria; + const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); + const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + const { agentEndPoint } = agentDetails; + if (!agentDetails) { + throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); + } + + let url: string; + if (orgAgentType === OrgAgentType.DEDICATED) { + url = `${agentEndPoint}${CommonConstants.URL_CONN_GET_CONNECTIONS}`; + } else if (orgAgentType === OrgAgentType.SHARED) { + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CREATEED_INVITATIONS}`.replace( + '#', + agentDetails.tenantId + ); + } else { + throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); + } + + //Create the dynamic URL for Search Criteria + const criteriaParams = []; + if (alias) { criteriaParams.push(`alias=${alias}`); } + if (myDid) { criteriaParams.push(`myDid=${myDid}`); } + if (outOfBandId) { criteriaParams.push(`outOfBandId=${outOfBandId}`); } + if (state) { criteriaParams.push(`state=${state}`); } + if (theirDid) { criteriaParams.push(`theirDid=${theirDid}`); } + if (theirLabel) { criteriaParams.push(`theirLabel=${theirLabel}`); } + + if (0 < criteriaParams.length) { + url += `?${criteriaParams.join('&')}`; + } + + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } + + const connectionResponse = await this._getAllConnections(url, apiKey); + return connectionResponse.response; + } catch (error) { + this.logger.error(`[getConnectionsFromAgent] [NATS call]- error in fetch connections details : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } @@ -288,7 +357,6 @@ export class ConnectionService { throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); } - // const apiKey = await this._getOrgAgentApiKey(orgId); let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); if (!apiKey || null === apiKey || undefined === apiKey) { @@ -296,8 +364,6 @@ export class ConnectionService { } const createConnectionInvitation = await this._getConnectionsByConnectionId(url, apiKey); return createConnectionInvitation; - - } catch (error) { this.logger.error(`[getConnectionsById] - error in get connections : ${JSON.stringify(error)}`); @@ -324,15 +390,13 @@ export class ConnectionService { const label = 'get-question-answer-record'; const url = await this.getQuestionAnswerAgentUrl(label, orgAgentType, agentEndPoint, agentDetails?.tenantId); - + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); if (!apiKey || null === apiKey || undefined === apiKey) { apiKey = await this._getOrgAgentApiKey(orgId); } const record = await this._getQuestionAnswersRecord(url, apiKey); return record; - - } catch (error) { this.logger.error(`[sendQuestion] - error in get question answer record: ${error}`); if (error && error?.status && error?.status?.message && error?.status?.message?.error) { @@ -348,18 +412,14 @@ export class ConnectionService { } } - async _getConnectionsByConnectionId( - url: string, - apiKey: string - ): Promise { - + async _getConnectionsByConnectionId(url: string, apiKey: string): Promise { //nats call in agent service for fetch connection details const pattern = { cmd: 'agent-get-connection-details-by-connectionId' }; const payload = { url, apiKey }; return this.connectionServiceProxy .send(pattern, payload) .toPromise() - .catch(error => { + .catch((error) => { this.logger.error( `[_getConnectionsByConnectionId] [NATS call]- error in fetch connections : ${JSON.stringify(error)}` ); @@ -368,21 +428,19 @@ export class ConnectionService { status: error.statusCode, error: error.error?.message?.error ? error.error?.message?.error : error.error, message: error.message - }, error.error); + }, + error.error + ); }); } - async _getQuestionAnswersRecord( - url: string, - apiKey: string - ): Promise { - + async _getQuestionAnswersRecord(url: string, apiKey: string): Promise { const pattern = { cmd: 'agent-get-question-answer-record' }; const payload = { url, apiKey }; return this.connectionServiceProxy .send(pattern, payload) .toPromise() - .catch(error => { + .catch((error) => { this.logger.error( `[_getQuestionAnswersRecord] [NATS call]- error in fetch connections : ${JSON.stringify(error)}` ); @@ -391,10 +449,13 @@ export class ConnectionService { status: error.statusCode, error: error.error?.message?.error ? error.error?.message?.error : error.error, message: error.message - }, error.error); + }, + error.error + ); }); } + /** * Description: Fetch agent url * @param referenceId @@ -425,20 +486,23 @@ export class ConnectionService { connectionId?: string ): Promise { try { - let url; switch (label) { case 'send-question': { - url = orgAgentType === OrgAgentType.DEDICATED + url = + orgAgentType === OrgAgentType.DEDICATED ? `${agentEndPoint}${CommonConstants.URL_SEND_QUESTION}`.replace('#', connectionId) : orgAgentType === OrgAgentType.SHARED - ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_SEND_QUESTION}`.replace('#', connectionId).replace('@', tenantId) + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_SEND_QUESTION}` + .replace('#', connectionId) + .replace('@', tenantId) : null; break; } case 'get-question-answer-record': { - url = orgAgentType === OrgAgentType.DEDICATED + url = + orgAgentType === OrgAgentType.DEDICATED ? `${agentEndPoint}${CommonConstants.URL_QUESTION_ANSWER_RECORD}` : orgAgentType === OrgAgentType.SHARED ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_QUESTION_ANSWER_RECORD}`.replace('#', tenantId) @@ -472,14 +536,21 @@ export class ConnectionService { return message; } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException({ - status: error.status, - error: error.message - }, error.status); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); } } - async receiveInvitationUrl(user: IUserRequest, receiveInvitationUrl: IReceiveInvitationUrl, orgId: string): Promise { + async receiveInvitationUrl( + user: IUserRequest, + receiveInvitationUrl: IReceiveInvitationUrl, + orgId: string + ): Promise { try { const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); @@ -489,13 +560,14 @@ export class ConnectionService { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); } - let url; if (orgAgentType === OrgAgentType.DEDICATED) { url = `${agentEndPoint}${CommonConstants.URL_RECEIVE_INVITATION_URL}`; } else if (orgAgentType === OrgAgentType.SHARED) { - url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_RECEIVE_INVITATION_URL}` - .replace('#', agentDetails.tenantId); + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_RECEIVE_INVITATION_URL}`.replace( + '#', + agentDetails.tenantId + ); } else { throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); } @@ -506,8 +578,6 @@ export class ConnectionService { } const createConnectionInvitation = await this._receiveInvitationUrl(url, apiKey, receiveInvitationUrl); return createConnectionInvitation; - - } catch (error) { this.logger.error(`[receiveInvitationUrl] - error in receive invitation url : ${JSON.stringify(error)}`); @@ -528,13 +598,12 @@ export class ConnectionService { apiKey: string, receiveInvitationUrl: IReceiveInvitationUrl ): Promise { - const pattern = { cmd: 'agent-receive-invitation-url' }; const payload = { url, apiKey, receiveInvitationUrl }; return this.connectionServiceProxy .send(pattern, payload) .toPromise() - .catch(error => { + .catch((error) => { this.logger.error( `[_receiveInvitationUrl] [NATS call]- error in receive invitation url : ${JSON.stringify(error)}` ); @@ -543,11 +612,17 @@ export class ConnectionService { status: error.statusCode, error: error.error?.message?.error ? error.error?.message?.error : error.error, message: error.message - }, error.error); + }, + error.error + ); }); } - async receiveInvitation(user: IUserRequest, receiveInvitation: IReceiveInvitation, orgId: string): Promise { + async receiveInvitation( + user: IUserRequest, + receiveInvitation: IReceiveInvitation, + orgId: string + ): Promise { try { const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); @@ -561,8 +636,7 @@ export class ConnectionService { if (orgAgentType === OrgAgentType.DEDICATED) { url = `${agentEndPoint}${CommonConstants.URL_RECEIVE_INVITATION}`; } else if (orgAgentType === OrgAgentType.SHARED) { - url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_RECEIVE_INVITATION}` - .replace('#', agentDetails.tenantId); + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_RECEIVE_INVITATION}`.replace('#', agentDetails.tenantId); } else { throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); } @@ -573,8 +647,6 @@ export class ConnectionService { } const createConnectionInvitation = await this._receiveInvitation(url, apiKey, receiveInvitation); return createConnectionInvitation; - - } catch (error) { this.logger.error(`[receiveInvitation] - error in receive invitation : ${JSON.stringify(error)}`); @@ -595,61 +667,51 @@ export class ConnectionService { apiKey: string, receiveInvitation: IReceiveInvitation ): Promise { - const pattern = { cmd: 'agent-receive-invitation' }; const payload = { url, apiKey, receiveInvitation }; return this.connectionServiceProxy .send(pattern, payload) .toPromise() - .catch(error => { - this.logger.error( - `[_receiveInvitation] [NATS call]- error in receive invitation : ${JSON.stringify(error)}` - ); + .catch((error) => { + this.logger.error(`[_receiveInvitation] [NATS call]- error in receive invitation : ${JSON.stringify(error)}`); throw new HttpException( { status: error.statusCode, error: error.error?.message?.error ? error.error?.message?.error : error.error, message: error.message - }, error.error); + }, + error.error + ); }); } - async _sendQuestion( - questionPayload: IQuestionPayload, - url: string, - apiKey: string - ): Promise { - + async _sendQuestion(questionPayload: IQuestionPayload, url: string, apiKey: string): Promise { const pattern = { cmd: 'agent-send-question' }; const payload = { questionPayload, url, apiKey }; return this.connectionServiceProxy .send(pattern, payload) .toPromise() - .catch(error => { - this.logger.error( - `[_sendQuestion] [NATS call]- error in send question : ${JSON.stringify(error)}` - ); + .catch((error) => { + this.logger.error(`[_sendQuestion] [NATS call]- error in send question : ${JSON.stringify(error)}`); throw new HttpException( { status: error.statusCode, error: error.error?.message?.error ? error.error?.message?.error : error.error, message: error.message - }, error.error); + }, + error.error + ); }); - - } async sendQuestion(payload: IQuestionPayload): Promise { - - const { detail, validResponses, question, orgId, connectionId} = payload; + const { detail, validResponses, question, orgId, connectionId } = payload; try { - const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); - const { agentEndPoint} = agentDetails; - + const { agentEndPoint } = agentDetails; + if (!agentDetails) { throw new NotFoundException(ResponseMessages.connection.error.agentEndPointNotFound); } @@ -662,14 +724,19 @@ export class ConnectionService { const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); const label = 'send-question'; - const url = await this.getQuestionAnswerAgentUrl(label, orgAgentType, agentEndPoint, agentDetails?.tenantId, connectionId); + const url = await this.getQuestionAnswerAgentUrl( + label, + orgAgentType, + agentEndPoint, + agentDetails?.tenantId, + connectionId + ); let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); if (!apiKey || null === apiKey || undefined === apiKey) { apiKey = await this._getOrgAgentApiKey(orgId); } const createQuestion = await this._sendQuestion(questionPayload, url, apiKey); return createQuestion; - } catch (error) { this.logger.error(`[sendQuestion] - error in sending question: ${error}`); if (error && error?.status && error?.status?.message && error?.status?.message?.error) { @@ -684,5 +751,43 @@ export class ConnectionService { } } } -} + async storeConnectionObjectAndReturnUrl(connectionInvitationUrl: string, persistent: boolean): Promise { + const storeObj = connectionInvitationUrl; + //nats call in agent-service to create an invitation url + const pattern = { cmd: 'store-object-return-url' }; + const payload = { persistent, storeObj }; + + try { + const message = await this.connectionServiceProxy + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error( + `[storeConnectionObjectAndReturnUrl] [NATS call]- error in storing object and returning url : ${JSON.stringify( + error + )}` + ); + throw new HttpException( + { + status: error.statusCode, + error: error.error?.message?.error ? error.error?.message?.error : error.error, + message: error.message + }, + error.error + ); + }); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + } + } +} diff --git a/apps/connection/src/interfaces/connection.interfaces.ts b/apps/connection/src/interfaces/connection.interfaces.ts index f26a6fd2f..c47ede3a5 100644 --- a/apps/connection/src/interfaces/connection.interfaces.ts +++ b/apps/connection/src/interfaces/connection.interfaces.ts @@ -80,6 +80,12 @@ export interface IFetchConnections { orgId: string; } +export interface GetAllConnections { + connectionSearchCriteria: AgentConnectionSearchCriteria; + user: IUserRequest; + orgId: string; +} + export interface IFetchConnectionById { user: IUserRequest; connectionId: string; @@ -97,8 +103,9 @@ export interface IConnectionInvitation { } interface IInvitation { invitation: string; - + invitationUrl: string; } + export interface OrgAgent { organisation: organisation; id: string; @@ -124,6 +131,15 @@ export interface IConnectionSearchCriteria { user: IUserRequestInterface } +export interface AgentConnectionSearchCriteria { + outOfBandId: string; + alias: string; + state: string; + myDid: string; + theirDid: string; + theirLabel: string; +} + export interface IReceiveInvitationByUrlOrg { user: IUserRequestInterface, receiveInvitationUrl: IReceiveInvitationUrl, @@ -236,3 +252,16 @@ export interface IReceiveInvitationResponse { outOfBandRecord: OutOfBandRecord; connectionRecord: ConnectionRecord; } + +export interface ConnectionResponseDetail { + id: string; + orgId: string; + agentId: string; + connectionInvitation: string; + multiUse: boolean; + createDateTime: Date; + createdBy: number; + lastChangedDateTime: Date; + lastChangedBy: number; + recordId: string; +} diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index a42b0e373..01de77bf6 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -905,7 +905,7 @@ export class EcosystemRepository { // eslint-disable-next-line camelcase async getEndorsementTransactionById(endorsementId: string, status: endorsementTransactionStatus): Promise { try { - const ecosystemLeadDetails = await this.prisma.endorsement_transaction.findFirst({ + const ecosystemLeadDetails = await this.prisma.endorsement_transaction.findUnique({ where: { id: endorsementId, status diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index a758ac941..fa78b67c6 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -593,7 +593,7 @@ export class EcosystemService { const emailData = new EmailDto(); emailData.emailFrom = platformConfigData[0].emailFrom; emailData.emailTo = email; - emailData.emailSubject = `Invitation to join an Ecosystem “${ecosystemName}” on CREDEBL`; + emailData.emailSubject = `Invitation to join an Ecosystem “${ecosystemName}” on ${process.env.PLATFORM_NAME}`; emailData.emailHtml = await urlEmailTemplate.sendInviteEmailTemplate( email, @@ -1326,7 +1326,6 @@ export class EcosystemService { endorsementTransactionStatus.SIGNED ); - if (!endorsementTransactionPayload) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidTransaction); } @@ -1355,18 +1354,20 @@ export class EcosystemService { ecosystemLeadAgentDetails ); - const isSchemaExists = await this.ecosystemRepository.schemaExist( - payload.schema.name, - payload.schema.version - ); - - if (0 !== isSchemaExists.length) { - this.logger.error(ResponseMessages.ecosystem.error.schemaAlreadyExist); - throw new ConflictException( - ResponseMessages.ecosystem.error.schemaAlreadyExist, - { cause: new Error(), description: ResponseMessages.errorMessages.conflict } + if (endorsementTransactionPayload.type === endorsementTransactionType.SCHEMA) { + const isSchemaExists = await this.ecosystemRepository.schemaExist( + payload.schema.name, + payload.schema.version ); - } + + if (0 !== isSchemaExists.length) { + this.logger.error(ResponseMessages.ecosystem.error.schemaAlreadyExist); + throw new ConflictException( + ResponseMessages.ecosystem.error.schemaAlreadyExist, + { cause: new Error(), description: ResponseMessages.errorMessages.conflict } + ); + } + } let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); if (!apiKey || null === apiKey || undefined === apiKey) { diff --git a/apps/ecosystem/templates/EcosystemInviteTemplate.ts b/apps/ecosystem/templates/EcosystemInviteTemplate.ts index 4403e1785..2d1166ea0 100644 --- a/apps/ecosystem/templates/EcosystemInviteTemplate.ts +++ b/apps/ecosystem/templates/EcosystemInviteTemplate.ts @@ -12,13 +12,13 @@ export class EcosystemInviteTemplate { const message = isUserExist ? `Please accept the invitation using the following link:` - : `To get started, kindly register on CREDEBL platform using this link:`; + : `To get started, kindly register on ${process.env.PLATFORM_NAME} platform using this link:`; const secondMessage = isUserExist - ? `After successful login into CREDEBL and click on "Accept Ecosystem Invitation" link on your dashboard to start participating in the digital trust ecosystem.` - : `After successful registration, you can log into CREDEBL and click on "Accept Ecosystem Invitation" link on your dashboard to start participating in the digital trust ecosystem.`; + ? `After successful login into ${process.env.PLATFORM_NAME} and click on "Accept Ecosystem Invitation" link on your dashboard to start participating in the digital trust ecosystem.` + : `After successful registration, you can log into ${process.env.PLATFORM_NAME} and click on "Accept Ecosystem Invitation" link on your dashboard to start participating in the digital trust ecosystem.`; - const Button = isUserExist ? 'Accept Ecosystem Invitation' : 'Register on CREDEBL'; + const Button = isUserExist ? `Accept Ecosystem Invitation` : `Register on ${process.env.PLATFORM_NAME}`; return ` @@ -33,7 +33,7 @@ export class EcosystemInviteTemplate {
- CREDEBL logo + ${process.env.PLATFORM_NAME} logo
- For any assistance or questions while accessing your account, please do not hesitate to contact the support team at support@blockster.global. Our team will ensure a seamless onboarding experience for you. + For any assistance or questions while accessing your account, please do not hesitate to contact the support team at ${process.env.PUBLIC_PLATFORM_SUPPORT_EMAIL}. Our team will ensure a seamless onboarding experience for you.

- © Blockster Labs Pvt. Ltd. + © ${process.env.POWERED_BY}

diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index 9806aca34..ccf86df78 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -66,20 +66,20 @@ export class IssuanceService { attributesArray.forEach((attribute) => { if (attribute.attributeName && attribute.isRequired) { - - payload.attributes.map((attr) => { + + payload.attributes.map((attr) => { if (attr.name === attribute.attributeName && attribute.isRequired && !attr.value) { schemaResponseError.push( `Attribute ${attribute.attributeName} is required` ); } return true; - }); + }); } }); if (0 < schemaResponseError.length) { - throw new BadRequestException(schemaResponseError); - + throw new BadRequestException(schemaResponseError); + } } @@ -114,7 +114,7 @@ export class IssuanceService { // eslint-disable-next-line @typescript-eslint/no-unused-vars attributes: (attributes).map(({ isRequired, ...rest }) => rest), credentialDefinitionId - + } }, autoAcceptCredential: payload.autoAcceptCredential || 'always', @@ -149,34 +149,34 @@ export class IssuanceService { async sendCredentialOutOfBand(payload: OOBIssueCredentialDto): Promise<{ response: object }> { try { - - const { orgId, credentialDefinitionId, comment, attributes, protocolVersion, credential, options, credentialType} = payload; + + const { orgId, credentialDefinitionId, comment, attributes, protocolVersion, credential, options, credentialType, isShortenUrl } = payload; if (credentialType === IssueCredentialType.INDY) { const schemadetailsResponse: SchemaDetails = await this.issuanceRepository.getCredentialDefinitionDetails( credentialDefinitionId ); - + if (schemadetailsResponse?.attributes) { const schemadetailsResponseError = []; const attributesArray: IAttributes[] = JSON.parse(schemadetailsResponse.attributes); - + attributesArray.forEach((attribute) => { if (attribute.attributeName && attribute.isRequired) { - - payload.attributes.map((attr) => { + + payload.attributes.map((attr) => { if (attr.name === attribute.attributeName && attribute.isRequired && !attr.value) { schemadetailsResponseError.push( `Attribute '${attribute.attributeName}' is required but has an empty value.` ); } return true; - }); + }); } }); if (0 < schemadetailsResponseError.length) { throw new BadRequestException(schemadetailsResponseError); } - + } } @@ -199,53 +199,56 @@ export class IssuanceService { if (!apiKey || null === apiKey || undefined === apiKey) { apiKey = await this._getOrgAgentApiKey(orgId); } - let issueData; + let issueData; if (credentialType === IssueCredentialType.INDY) { - issueData = { - protocolVersion: protocolVersion || 'v1', - credentialFormats: { - indy: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - attributes: (attributes).map(({ isRequired, ...rest }) => rest), - credentialDefinitionId - } - }, - autoAcceptCredential: payload.autoAcceptCredential || 'always', - goalCode: payload.goalCode || undefined, - parentThreadId: payload.parentThreadId || undefined, - willConfirm: payload.willConfirm || undefined, - imageUrl: organisation?.logoUrl || payload?.imageUrl || undefined, - label: organisation?.name, - comment: comment || '' - }; - - } + issueData = { + protocolVersion: protocolVersion || 'v1', + credentialFormats: { + indy: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + attributes: (attributes).map(({ isRequired, ...rest }) => rest), + credentialDefinitionId + } + }, + autoAcceptCredential: payload.autoAcceptCredential || 'always', + goalCode: payload.goalCode || undefined, + parentThreadId: payload.parentThreadId || undefined, + willConfirm: payload.willConfirm || undefined, + imageUrl: organisation?.logoUrl || payload?.imageUrl || undefined, + label: organisation?.name, + comment: comment || '' + }; - if (credentialType === IssueCredentialType.JSONLD) { - issueData = { - protocolVersion: protocolVersion || 'v2', - credentialFormats: { - jsonld: { - credential, - options - } - }, - autoAcceptCredential: payload.autoAcceptCredential || 'always', - goalCode: payload.goalCode || undefined, - parentThreadId: payload.parentThreadId || undefined, - willConfirm: payload.willConfirm || undefined, - imageUrl: organisation?.logoUrl || payload?.imageUrl || undefined, - label: organisation?.name, - comment: comment || '' - }; - } + } + if (credentialType === IssueCredentialType.JSONLD) { + issueData = { + protocolVersion: protocolVersion || 'v2', + credentialFormats: { + jsonld: { + credential, + options + } + }, + autoAcceptCredential: payload.autoAcceptCredential || 'always', + goalCode: payload.goalCode || undefined, + parentThreadId: payload.parentThreadId || undefined, + willConfirm: payload.willConfirm || undefined, + imageUrl: organisation?.logoUrl || payload?.imageUrl || undefined, + label: organisation?.name, + comment: comment || '' + }; + } const credentialCreateOfferDetails = await this._outOfBandCredentialOffer(issueData, url, apiKey); - + if (isShortenUrl) { + const invitationUrl: string = credentialCreateOfferDetails.response?.invitationUrl; + const url: string = await this.storeIssuanceObjectReturnUrl(invitationUrl); + credentialCreateOfferDetails.response['invitationUrl'] = url; + } return credentialCreateOfferDetails; } catch (error) { - this.logger.error(`[sendCredentialCreateOffer] - error in create credentials : ${JSON.stringify(error)}`); + this.logger.error(`[storeIssuanceObjectReturnUrl] - error in create credentials : ${JSON.stringify(error)}`); const errorStack = error?.status?.message?.error; if (errorStack) { @@ -260,6 +263,21 @@ export class IssuanceService { } } + async storeIssuanceObjectReturnUrl(storeObj: string): Promise { + try { + // Set default to false, since currently our invitation are not multi-use + const persistent: boolean = false; + //nats call in agent-service to create an invitation url + const pattern = { cmd: 'store-object-return-url' }; + const payload = { persistent, storeObj }; + const message = await this.natsCall(pattern, payload); + return message.response; + } catch (error) { + this.logger.error(`[storeIssuanceObjectReturnUrl] [NATS call]- error in storing object and returning url : ${JSON.stringify(error)}`); + throw error; + } + } + // Created this function to avoid the impact of actual "natsCall" function for other operations // Once implement this for all component then we'll remove the duplicate function async natsCallAgent(pattern: IPattern, payload: ISendOfferNatsPayload): Promise { @@ -291,8 +309,8 @@ export class IssuanceService { .pipe( map((response) => ( { - response - })) + response + })) ).toPromise() .catch(error => { this.logger.error(`catch: ${JSON.stringify(error)}`); @@ -628,7 +646,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO goalCode: outOfBandCredential.goalCode || undefined, parentThreadId: outOfBandCredential.parentThreadId || undefined, willConfirm: outOfBandCredential.willConfirm || undefined, - label: outOfBandCredential.label || undefined, + label: organisation?.name, imageUrl: organisation?.logoUrl || outOfBandCredential?.imageUrl }; } @@ -647,15 +665,11 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO goalCode: outOfBandCredential.goalCode || undefined, parentThreadId: outOfBandCredential.parentThreadId || undefined, willConfirm: outOfBandCredential.willConfirm || undefined, - label: outOfBandCredential.label || undefined, + label: organisation?.name, imageUrl: organisation?.logoUrl || outOfBandCredential?.imageUrl }; } - const agentDetails = await this.issuanceRepository.getAgentEndPoint(organisation.id); - - this.logger.log(`outOfBandIssuancePayload ::: ${JSON.stringify(outOfBandIssuancePayload)}`); - const credentialCreateOfferDetails = await this._outOfBandCredentialOffer(outOfBandIssuancePayload, url, apiKey); if (!credentialCreateOfferDetails) { @@ -663,16 +677,15 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO return false; } - const invitationId = credentialCreateOfferDetails.response.invitation['@id']; - if (!invitationId) { - errors.push(new NotFoundException(ResponseMessages.issuance.error.invitationNotFound)); - return false; - } - const agentEndPoint = agentDetails.tenantId - ? `${agentDetails.agentEndPoint}/multi-tenancy/url/${agentDetails.tenantId}/${invitationId}` - : `${agentDetails.agentEndPoint}/url/${invitationId}`; + const invitationUrl: string = credentialCreateOfferDetails.response?.invitationUrl; + const shortenUrl: string = await this.storeIssuanceObjectReturnUrl(invitationUrl); + + if (!invitationUrl) { + errors.push(new NotFoundException(ResponseMessages.issuance.error.invitationNotFound)); + return false; + } const qrCodeOptions = { type: 'image/png' }; - const outOfBandIssuanceQrCode = await QRCode.toDataURL(agentEndPoint, qrCodeOptions); + const outOfBandIssuanceQrCode = await QRCode.toDataURL(shortenUrl, qrCodeOptions); const platformConfigData = await this.issuanceRepository.getPlatformConfigDetails(); if (!platformConfigData) { errors.push(new NotFoundException(ResponseMessages.issuance.error.platformConfigNotFound)); @@ -681,7 +694,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO this.emailData.emailFrom = platformConfigData.emailFrom; this.emailData.emailTo = emailId; this.emailData.emailSubject = `${process.env.PLATFORM_NAME} Platform: Issuance of Your Credential`; - this.emailData.emailHtml = this.outOfBandIssuance.outOfBandIssuance(emailId, organizationDetails.name, agentEndPoint); + this.emailData.emailHtml = this.outOfBandIssuance.outOfBandIssuance(emailId, organizationDetails.name, shortenUrl); this.emailData.emailAttachments = [ { filename: 'qrcode.png', @@ -748,8 +761,8 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO switch (issuanceMethodLabel) { case 'create-offer': { url = orgAgentType === OrgAgentType.DEDICATED - ? `${agentEndPoint}${CommonConstants.URL_ISSUE_CREATE_CRED_OFFER_AFJ}` - : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_ISSUE_CREATE_CRED_OFFER_AFJ}` + : orgAgentType === OrgAgentType.SHARED ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_OFFER}`.replace('#', tenantId) : null; break; @@ -757,8 +770,8 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO case 'create-offer-oob': { url = orgAgentType === OrgAgentType.DEDICATED - ? `${agentEndPoint}${CommonConstants.URL_OUT_OF_BAND_CREDENTIAL_OFFER}` - : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_OUT_OF_BAND_CREDENTIAL_OFFER}` + : orgAgentType === OrgAgentType.SHARED ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_OFFER_OUT_OF_BAND}`.replace('#', tenantId) : null; break; @@ -766,18 +779,18 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO case 'get-issue-credentials': { url = orgAgentType === OrgAgentType.DEDICATED - ? `${agentEndPoint}${CommonConstants.URL_ISSUE_GET_CREDS_AFJ}` - : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_ISSUE_GET_CREDS_AFJ}` + : orgAgentType === OrgAgentType.SHARED ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CREDENTIALS}`.replace('#', tenantId) : null; break; } case 'get-issue-credential-by-credential-id': { - + url = orgAgentType === OrgAgentType.DEDICATED - ? `${agentEndPoint}${CommonConstants.URL_ISSUE_GET_CREDS_AFJ_BY_CRED_REC_ID}/${credentialRecordId}` - : orgAgentType === OrgAgentType.SHARED + ? `${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; break; @@ -802,7 +815,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO async exportSchemaToCSV(credentialDefinitionId: string): Promise { try { const schemaResponse: SchemaDetails = await this.issuanceRepository.getCredentialDefinitionDetails(credentialDefinitionId); - + const jsonData = []; const attributesArray = JSON.parse(schemaResponse.attributes); @@ -845,7 +858,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO async importAndPreviewDataForIssuance(importFileDetails: ImportFileDetails, requestId?: string): Promise { try { - + const credDefResponse = await this.issuanceRepository.getCredentialDefinitionDetails(importFileDetails.credDefId); @@ -914,8 +927,8 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO } catch (error) { this.logger.error(`error in validating credentials : ${error.response}`); - throw new RpcException(error.response ? error.response : error); - } + throw new RpcException(error.response ? error.response : error); + } } async previewFileDataForIssuance( @@ -1186,7 +1199,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO fileUploadData.createDateTime = new Date(); fileUploadData.referenceId = jobDetails.data.email; fileUploadData.jobId = jobDetails.id; - const {orgId} = jobDetails; + const { orgId } = jobDetails; const agentDetails = await this.issuanceRepository.getAgentEndPoint(orgId); // eslint-disable-next-line camelcase @@ -1240,7 +1253,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO 0 === errorCount ? FileUploadStatus.completed : FileUploadStatus.partially_completed; if (!jobDetails.isRetry) { - socket.emit('bulk-issuance-process-completed', {clientId: jobDetails.clientId, fileUploadId: jobDetails.fileUploadId}); + socket.emit('bulk-issuance-process-completed', { clientId: jobDetails.clientId, fileUploadId: jobDetails.fileUploadId }); this.cacheManager.del(jobDetails.cacheId); } else { socket.emit('bulk-issuance-process-retry-completed', { clientId: jobDetails.clientId }); @@ -1292,36 +1305,36 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO } } - async validateFileData(fileData: string[][], attributesArray: { attributeName: string, schemaDataType: string, displayName: string, isRequired: boolean }[], fileHeader: string[]): Promise { - try { + async validateFileData(fileData: string[][], attributesArray: { attributeName: string, schemaDataType: string, displayName: string, isRequired: boolean }[], fileHeader: string[]): Promise { + try { const filedata = fileData.map((item: string[]) => { - const fileHeaderData = item?.map((element, j) => ({ - value: element, - header: fileHeader[j] - })); - return fileHeaderData; + const fileHeaderData = item?.map((element, j) => ({ + value: element, + header: fileHeader[j] + })); + return fileHeaderData; }); - - const errorFileData = []; - - filedata.forEach((attr, i) => { + + const errorFileData = []; + + filedata.forEach((attr, i) => { attr.forEach((eachElement) => { - attributesArray.forEach((eachItem) => { - if (eachItem.attributeName === eachElement.header) { - if (eachItem.isRequired && !eachElement.value) { - errorFileData.push(`Attribute ${eachItem.attributeName} is required at row ${i + 1}`); - } - } - }); - return eachElement; - }); - return attr; - }); - - if (0 < errorFileData.length) { - throw new BadRequestException(errorFileData); - } + attributesArray.forEach((eachItem) => { + if (eachItem.attributeName === eachElement.header) { + if (eachItem.isRequired && !eachElement.value) { + errorFileData.push(`Attribute ${eachItem.attributeName} is required at row ${i + 1}`); + } + } + }); + return eachElement; + }); + return attr; + }); + + if (0 < errorFileData.length) { + throw new BadRequestException(errorFileData); + } } catch (error) { throw error; } @@ -1338,9 +1351,9 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException({ - status: error.status, - error: error.message - }, error.status); + status: error.status, + error: error.message + }, error.status); } } diff --git a/apps/issuance/templates/out-of-band-issuance.template.ts b/apps/issuance/templates/out-of-band-issuance.template.ts index 9ac9b4ade..e97c04918 100644 --- a/apps/issuance/templates/out-of-band-issuance.template.ts +++ b/apps/issuance/templates/out-of-band-issuance.template.ts @@ -13,7 +13,7 @@ export class OutOfBandIssuance {
- CREDEBL logo + ${process.env.PLATFORM_NAME} logo
@@ -52,11 +52,11 @@ export class OutOfBandIssuance {
- For any assistance or questions while accessing your account, please do not hesitate to contact the support team at support@blockster.global. Our team will ensure a seamless onboarding experience for you. + For any assistance or questions while accessing your account, please do not hesitate to contact the support team at ${process.env.PUBLIC_PLATFORM_SUPPORT_EMAIL}. Our team will ensure a seamless onboarding experience for you.

- © Blockster Labs Pvt. Ltd. + © ${process.env.POWERED_BY}

diff --git a/apps/ledger/src/credential-definition/credential-definition.service.ts b/apps/ledger/src/credential-definition/credential-definition.service.ts index 23d6f33f2..7f799e52c 100644 --- a/apps/ledger/src/credential-definition/credential-definition.service.ts +++ b/apps/ledger/src/credential-definition/credential-definition.service.ts @@ -41,7 +41,7 @@ export class CredentialDefinitionService extends BaseService { if (!apiKey || null === apiKey || undefined === apiKey) { apiKey = await this._getOrgAgentApiKey(credDef.orgId); } - const { userId } = user.selectedOrg; + const userId = user.id; credDef.tag = credDef.tag.trim(); const dbResult: credential_definition = await this.credentialDefinitionRepository.getByAttribute( credDef.schemaLedgerId, diff --git a/apps/ledger/src/credential-definition/interfaces/index.ts b/apps/ledger/src/credential-definition/interfaces/index.ts index b994e46a8..d4be794cd 100644 --- a/apps/ledger/src/credential-definition/interfaces/index.ts +++ b/apps/ledger/src/credential-definition/interfaces/index.ts @@ -1,6 +1,7 @@ import { UserRoleOrgPermsDto } from '../../schema/dtos/user-role-org-perms.dto'; export interface IUserRequestInterface { + id: string; userId: string; email: string; orgId: string; diff --git a/apps/ledger/src/schema/interfaces/schema.interface.ts b/apps/ledger/src/schema/interfaces/schema.interface.ts index a67d45a2b..a89044961 100644 --- a/apps/ledger/src/schema/interfaces/schema.interface.ts +++ b/apps/ledger/src/schema/interfaces/schema.interface.ts @@ -1,6 +1,7 @@ import { UserRoleOrgPermsDto } from '../dtos/user-role-org-perms.dto'; export interface IUserRequestInterface { + id: string; userId: string; email: string; orgId: string; diff --git a/apps/ledger/src/schema/schema.service.ts b/apps/ledger/src/schema/schema.service.ts index 34ffde1ca..7d253557b 100644 --- a/apps/ledger/src/schema/schema.service.ts +++ b/apps/ledger/src/schema/schema.service.ts @@ -42,7 +42,7 @@ export class SchemaService extends BaseService { if (!apiKey || null === apiKey || undefined === apiKey) { apiKey = await this._getOrgAgentApiKey(orgId); } - const { userId } = user.selectedOrg; + const userId = user.id; try { const schemaExists = await this.schemaRepository.schemaExists( diff --git a/apps/organization/dtos/update-invitation.dt.ts b/apps/organization/dtos/update-invitation.dt.ts index 1d80b08d4..21e345858 100644 --- a/apps/organization/dtos/update-invitation.dt.ts +++ b/apps/organization/dtos/update-invitation.dt.ts @@ -5,5 +5,6 @@ export class UpdateInvitationDto { orgId: string; status: Invitation; userId: string; + keycloakUserId: string; email: string; } \ No newline at end of file diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 741d8bd09..e2464aab6 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -492,6 +492,38 @@ export class OrganizationRepository { } } + async getUnregisteredClientOrgs(): Promise { + try { + const recordsWithNullIdpId = await this.prisma.organisation.findMany({ + where: { + idpId: null + }, + include: { + userOrgRoles: { + include: { + user: { + select: { + email: true, + username: true, + id: true, + keycloakUserId: true, + isEmailVerified: true + } + }, + orgRole: true + } + } + } + }); + + return recordsWithNullIdpId; + + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + /** * * @param queryObject diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index 87b1c45ea..245f4a8e8 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -8,9 +8,9 @@ import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { UpdateInvitationDto } from '../dtos/update-invitation.dt'; import { IGetOrgById, IGetOrganization, IUpdateOrganization, Payload } from '../interfaces/organization.interface'; import { organisation } from '@prisma/client'; -import { IOrgRoles } from 'libs/org-roles/interfaces/org-roles.interface'; import { IOrgCredentials, IOrganizationInvitations, IOrganization, IOrganizationDashboard } from '@credebl/common/interfaces/organization.interface'; import { IAccessTokenData } from '@credebl/common/interfaces/interface'; +import { IClientRoles } from '@credebl/client-registration/interfaces/client.interface'; @Controller() export class OrganizationController { @@ -24,8 +24,8 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'create-organization' }) - async createOrganization(@Body() payload: { createOrgDto: CreateOrganizationDto; userId: string }): Promise { - return this.organizationService.createOrganization(payload.createOrgDto, payload.userId); + async createOrganization(@Body() payload: { createOrgDto: CreateOrganizationDto; userId: string, keycloakUserId: string }): Promise { + return this.organizationService.createOrganization(payload.createOrgDto, payload.userId, payload.keycloakUserId); } /** @@ -34,8 +34,8 @@ export class OrganizationController { * @returns organization client credentials */ @MessagePattern({ cmd: 'create-org-credentials' }) - async createOrgCredentials(@Body() payload: { orgId: string; userId: string }): Promise { - return this.organizationService.createOrgCredentials(payload.orgId); + async createOrgCredentials(@Body() payload: { orgId: string; userId: string, keycloakUserId: string }): Promise { + return this.organizationService.createOrgCredentials(payload.orgId, payload.userId, payload.keycloakUserId); } /** @@ -113,8 +113,13 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'get-org-roles' }) - async getOrgRoles(): Promise { - return this.organizationService.getOrgRoles(); + async getOrgRoles(payload: {orgId: string}): Promise { + return this.organizationService.getOrgRoles(payload.orgId); + } + + @MessagePattern({ cmd: 'register-orgs-users-map' }) + async registerOrgsMapUsers(): Promise { + return this.organizationService.registerOrgsMapUsers(); } /** diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 0d3154c44..0976f33f6 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -1,6 +1,16 @@ /* eslint-disable prefer-destructuring */ -import { organisation, user } from '@prisma/client'; -import { Injectable, Logger, ConflictException, InternalServerErrorException, HttpException, BadRequestException, ForbiddenException, UnauthorizedException } from '@nestjs/common'; +// eslint-disable-next-line camelcase +import { org_invitations, organisation, user } from '@prisma/client'; +import { + Injectable, + Logger, + ConflictException, + InternalServerErrorException, + HttpException, + BadRequestException, + ForbiddenException, + UnauthorizedException +} from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; import { CommonService } from '@credebl/common'; import { OrganizationRepository } from '../repositories/organization.repository'; @@ -17,7 +27,7 @@ import { CreateOrganizationDto } from '../dtos/create-organization.dto'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { UpdateInvitationDto } from '../dtos/update-invitation.dt'; import { Invitation, OrgAgentType, transition } from '@credebl/enum/enum'; -import { IGetOrgById, IGetOrganization, IUpdateOrganization, IOrgAgent, IClientCredentials, ICreateConnectionUrl } from '../interfaces/organization.interface'; +import { IGetOrgById, IGetOrganization, IUpdateOrganization, IOrgAgent, IClientCredentials, ICreateConnectionUrl, IOrgRole } from '../interfaces/organization.interface'; import { UserActivityService } from '@credebl/user-activity'; import { CommonConstants } from '@credebl/common/common.constant'; import { ClientRegistrationService } from '@credebl/client-registration/client-registration.service'; @@ -25,11 +35,16 @@ import { map } from 'rxjs/operators'; import { Cache } from 'cache-manager'; import { AwsService } from '@credebl/aws'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; -import { IOrgRoles } from 'libs/org-roles/interfaces/org-roles.interface'; -import { IOrgCredentials, IOrganization, IOrganizationInvitations, IOrganizationDashboard } from '@credebl/common/interfaces/organization.interface'; +import { + IOrgCredentials, + IOrganization, + IOrganizationInvitations, + IOrganizationDashboard +} from '@credebl/common/interfaces/organization.interface'; import { ClientCredentialTokenPayloadDto } from '@credebl/client-registration/dtos/client-credential-token-payload.dto'; import { IAccessTokenData } from '@credebl/common/interfaces/interface'; +import { IClientRoles } from '@credebl/client-registration/interfaces/client.interface'; @Injectable() export class OrganizationService { constructor( @@ -44,7 +59,7 @@ export class OrganizationService { private readonly logger: Logger, @Inject(CACHE_MANAGER) private cacheService: Cache, private readonly clientRegistrationService: ClientRegistrationService - ) { } + ) {} /** * @@ -53,9 +68,12 @@ export class OrganizationService { */ // eslint-disable-next-line camelcase - async createOrganization(createOrgDto: CreateOrganizationDto, userId: string): Promise { + async createOrganization( + createOrgDto: CreateOrganizationDto, + userId: string, + keycloakUserId: string + ): Promise { try { - const organizationExist = await this.organizationRepository.checkOrganizationNameExist(createOrgDto.name); if (organizationExist) { @@ -80,7 +98,7 @@ export class OrganizationService { } else { createOrgDto.logo = ''; } - + const organizationDetails = await this.organizationRepository.createOrganization(createOrgDto); // To return selective object data @@ -89,18 +107,47 @@ export class OrganizationService { delete organizationDetails.orgSlug; delete organizationDetails.website; - const ownerRoleData = await this.orgRoleService.getRole(OrgRoles.OWNER); + try { + const orgCredentials = await this.registerToKeycloak( + organizationDetails.name, + organizationDetails.id, + keycloakUserId, + userId, + false + ); + + const { clientId, idpId } = orgCredentials; + + const updateOrgData = { + clientId, + idpId + }; + + const updatedOrg = await this.organizationRepository.updateOrganizationById( + updateOrgData, + organizationDetails.id + ); + + if (!updatedOrg) { + throw new InternalServerErrorException(ResponseMessages.organisation.error.credentialsNotUpdate); + } + } catch (error) { + this.logger.error(`Error In creating client : ${JSON.stringify(error)}`); + throw new InternalServerErrorException('Unable to create client'); + } if (createOrgDto.notificationWebhook) { await this.storeOrgWebhookEndpoint(organizationDetails.id, createOrgDto.notificationWebhook); } - await this.userOrgRoleService.createUserOrgRole(userId, ownerRoleData.id, organizationDetails.id); + await this.userActivityService.createActivity( + userId, + organizationDetails.id, + `${organizationDetails.name} organization created`, + 'Get started with inviting users to join organization' + ); - await this.userActivityService.createActivity(userId, organizationDetails.id, `${organizationDetails.name} organization created`, 'Get started with inviting users to join organization'); - return organizationDetails; - } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -108,28 +155,57 @@ export class OrganizationService { } /** - * - * @param orgId + * + * @param orgId * @returns organization client credentials */ - async createOrgCredentials(orgId: string): Promise { + async createOrgCredentials(orgId: string, userId: string, keycloakUserId: string): Promise { try { - const organizationDetails = await this.organizationRepository.getOrganizationDetails(orgId); if (!organizationDetails) { throw new ConflictException(ResponseMessages.organisation.error.orgNotFound); } - const orgCredentials = await this.registerToKeycloak(organizationDetails.name, organizationDetails.id); - - const {clientId, clientSecret, idpId} = orgCredentials; + let updateOrgData = {}; + let generatedClientSecret = ''; - const updateOrgData = { - clientId, - clientSecret: this.maskString(clientSecret), - idpId - }; + if (organizationDetails.idpId) { + const token = await this.clientRegistrationService.getManagementToken(); + + generatedClientSecret = await this.clientRegistrationService.generateClientSecret( + organizationDetails.idpId, + token + ); + + updateOrgData = { + clientSecret: this.maskString(generatedClientSecret) + }; + } else { + + try { + const orgCredentials = await this.registerToKeycloak( + organizationDetails.name, + organizationDetails.id, + keycloakUserId, + userId, + true + ); + + const { clientId, idpId, clientSecret } = orgCredentials; + + generatedClientSecret = clientSecret; + + updateOrgData = { + clientId, + clientSecret: this.maskString(clientSecret), + idpId + }; + } catch (error) { + this.logger.error(`Error In creating client : ${JSON.stringify(error)}`); + throw new InternalServerErrorException('Unable to create client'); + } + } const updatedOrg = await this.organizationRepository.updateOrganizationById(updateOrgData, orgId); @@ -137,8 +213,11 @@ export class OrganizationService { throw new InternalServerErrorException(ResponseMessages.organisation.error.credentialsNotUpdate); } - return orgCredentials; - + return { + idpId: updatedOrg.idpId, + clientId: updatedOrg.clientId, + clientSecret: generatedClientSecret + }; } catch (error) { this.logger.error(`In createOrgCredentials : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -147,46 +226,95 @@ export class OrganizationService { /** * Register the organization to keycloak - * @param orgName - * @param orgId + * @param orgName + * @param orgId * @returns client credentials */ - async registerToKeycloak(orgName: string, orgId: string): Promise { - const token = await this.clientRegistrationService.getManagementToken(); - return this.clientRegistrationService.createClient(orgName, orgId, token); - } + async registerToKeycloak( + orgName: string, + orgId: string, + keycloakUserId: string, + userId: string, + shouldUpdateRole: boolean + ): Promise { + const token = await this.clientRegistrationService.getManagementToken(); + const orgDetails = await this.clientRegistrationService.createClient(orgName, orgId, token); + + const orgRolesList = [OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER]; + + for (const role of orgRolesList) { + await this.clientRegistrationService.createClientRole(orgDetails.idpId, token, role, role); + } + + const ownerRoleClient = await this.clientRegistrationService.getClientSpecificRoles( + orgDetails.idpId, + token, + OrgRoles.OWNER + ); + + const payload = [ + { + id: ownerRoleClient.id, + name: ownerRoleClient.name + } + ]; + + const ownerRoleData = await this.orgRoleService.getRole(OrgRoles.OWNER); + if (!shouldUpdateRole) { + + await Promise.all([ + this.clientRegistrationService.createUserClientRole(orgDetails.idpId, token, keycloakUserId, payload), + this.userOrgRoleService.createUserOrgRole(userId, ownerRoleData.id, orgId, ownerRoleClient.id) + ]); + + } else { + const roleIdList = [ + { + roleId: ownerRoleData.id, + idpRoleId: ownerRoleClient.id + } + ]; + + await Promise.all([ + this.clientRegistrationService.createUserClientRole(orgDetails.idpId, token, keycloakUserId, payload), + this.userOrgRoleService.deleteOrgRoles(userId, orgId), + this.userOrgRoleService.updateUserOrgRole(userId, orgId, roleIdList) + ]); + } + + return orgDetails; + } async deleteClientCredentials(orgId: string): Promise { - const token = await this.clientRegistrationService.getManagementToken(); + const token = await this.clientRegistrationService.getManagementToken(); - const organizationDetails = await this.organizationRepository.getOrganizationDetails(orgId); + const organizationDetails = await this.organizationRepository.getOrganizationDetails(orgId); - if (!organizationDetails) { - throw new NotFoundException(ResponseMessages.organisation.error.orgNotFound); - } + if (!organizationDetails) { + throw new NotFoundException(ResponseMessages.organisation.error.orgNotFound); + } - try { - await this.clientRegistrationService.deleteClient(organizationDetails.idpId, token); - const updateOrgData = { - clientId: null, - clientSecret: null, - idpId: null - }; - - await this.organizationRepository.updateOrganizationById(updateOrgData, orgId); - - } catch (error) { - throw new InternalServerErrorException('Unable to delete client credentails'); - } + try { + await this.clientRegistrationService.deleteClient(organizationDetails.idpId, token); + const updateOrgData = { + clientId: null, + clientSecret: null, + idpId: null + }; + + await this.organizationRepository.updateOrganizationById(updateOrgData, orgId); + } catch (error) { + throw new InternalServerErrorException('Unable to delete client credentails'); + } - return ResponseMessages.organisation.success.deleteCredentials; + return ResponseMessages.organisation.success.deleteCredentials; } /** * Mask string and display last 5 characters - * @param inputString - * @returns + * @param inputString + * @returns */ maskString(inputString: string): string { if (5 <= inputString.length) { @@ -202,20 +330,20 @@ export class OrganizationService { return inputString; } } - - async isValidBase64 (value: string): Promise { + + async isValidBase64(value: string): Promise { try { if (!value || 'string' !== typeof value) { return false; } - + const base64Regex = /^data:image\/([a-zA-Z]*);base64,([^\"]*)$/; const matches = value.match(base64Regex); return Boolean(matches) && 3 === matches.length; } catch (error) { return false; } - }; + } async uploadFileToS3(orgLogo: string): Promise { try { @@ -234,8 +362,8 @@ export class OrganizationService { this.logger.error(`In getting imageUrl : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } - } - + } + /** * * @param orgName @@ -268,7 +396,7 @@ export class OrganizationService { const orgSlug = await this.createOrgSlug(updateOrgDto.name); updateOrgDto.orgSlug = orgSlug; updateOrgDto.userId = userId; - + if (await this.isValidBase64(updateOrgDto.logo)) { const imageUrl = await this.uploadFileToS3(updateOrgDto.logo); updateOrgDto.logo = imageUrl; @@ -328,9 +456,13 @@ export class OrganizationService { * @returns Get created organizations details */ - async getOrganizations(userId: string, pageNumber: number, pageSize: number, search: string): Promise { + async getOrganizations( + userId: string, + pageNumber: number, + pageSize: number, + search: string + ): Promise { try { - const query = { userOrgRoles: { some: { userId } @@ -345,14 +477,8 @@ export class OrganizationService { userId }; - const getOrgs = await this.organizationRepository.getOrganizations( - query, - filterOptions, - pageNumber, - pageSize - ); + const getOrgs = await this.organizationRepository.getOrganizations(query, filterOptions, pageNumber, pageSize); return getOrgs; - } catch (error) { this.logger.error(`In fetch getOrganizations : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -364,7 +490,6 @@ export class OrganizationService { return this.authenticateClientKeycloak(clientId, clientSecret); } - async authenticateClientKeycloak(clientId: string, clientSecret: string): Promise { try { @@ -396,7 +521,6 @@ export class OrganizationService { async getPublicOrganizations(pageNumber: number, pageSize: number, search: string): Promise { try { - const query = { publicProfile: true, OR: [ @@ -407,13 +531,7 @@ export class OrganizationService { const filterOptions = {}; - return this.organizationRepository.getOrganizations( - query, - filterOptions, - pageNumber, - pageSize - ); - + return this.organizationRepository.getOrganizations(query, filterOptions, pageNumber, pageSize); } catch (error) { this.logger.error(`In fetch getPublicOrganizations : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -423,7 +541,6 @@ export class OrganizationService { async getPublicProfile(payload: { orgSlug: string }): Promise { const { orgSlug } = payload; try { - const query = { orgSlug, publicProfile: true @@ -437,7 +554,6 @@ export class OrganizationService { const credDefs = await this.organizationRepository.getCredDefByOrg(organizationDetails.id); organizationDetails['credential_definitions'] = credDefs; return organizationDetails; - } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -452,7 +568,6 @@ export class OrganizationService { async getOrganization(orgId: string): Promise { try { - const query = { id: orgId }; @@ -471,13 +586,23 @@ export class OrganizationService { * @returns Get created invitation details */ - async getInvitationsByOrgId(orgId: string, pageNumber: number, pageSize: number, search: string): Promise { + async getInvitationsByOrgId( + orgId: string, + pageNumber: number, + pageSize: number, + search: string + ): Promise { try { - const getOrganization = await this.organizationRepository.getInvitationsByOrgId(orgId, pageNumber, pageSize, search); + const getOrganization = await this.organizationRepository.getInvitationsByOrgId( + orgId, + pageNumber, + pageSize, + search + ); for await (const item of getOrganization['invitations']) { const getOrgRoles = await this.orgRoleService.getOrgRolesByIds(item['orgRoles']); (item['orgRoles'] as object) = getOrgRoles; - }; + } return getOrganization; } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); @@ -490,10 +615,25 @@ export class OrganizationService { * @returns organization roles */ - - async getOrgRoles(): Promise { + async getOrgRoles(orgId: string): Promise { try { - return this.orgRoleService.getOrgRoles(); + if (!orgId) { + throw new BadRequestException(ResponseMessages.organisation.error.orgIdIsRequired); + } + + const organizationDetails = await this.organizationRepository.getOrganizationDetails(orgId); + + if (!organizationDetails) { + throw new NotFoundException(ResponseMessages.organisation.error.orgNotFound); + } + + if (!organizationDetails.idpId) { + return this.orgRoleService.getOrgRoles(); + } + + const token = await this.clientRegistrationService.getManagementToken(); + + return this.clientRegistrationService.getAllClientRoles(organizationDetails.idpId, token); } catch (error) { this.logger.error(`In getOrgRoles : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -505,12 +645,8 @@ export class OrganizationService { * @param email * @returns */ - async checkInvitationExist( - email: string, - orgId: string - ): Promise { + async checkInvitationExist(email: string, orgId: string): Promise { try { - const query = { email, orgId @@ -541,19 +677,14 @@ export class OrganizationService { } } - /** - * - * @Body sendInvitationDto - * @returns createInvitation - */ - - - async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: string, userEmail: string): Promise { + async createInvitationByOrgRoles( + bulkInvitationDto: BulkSendInvitationDto, + userEmail: string, + userId: string, + orgName: string + ): Promise { const { invitations, orgId } = bulkInvitationDto; - try { - const organizationDetails = await this.organizationRepository.getOrganizationDetails(orgId); - for (const invitation of invitations) { const { orgRoleId, email } = invitation; @@ -575,14 +706,111 @@ export class OrganizationService { await this.organizationRepository.createSendInvitation(email, String(orgId), String(userId), orgRoleId); try { - await this.sendInviteEmailTemplate(email, organizationDetails.name, orgRolesDetails, firstName, isUserExist); + await this.sendInviteEmailTemplate(email, orgName, orgRolesDetails, firstName, isUserExist); } catch (error) { throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); } } + } + } + + async createInvitationByClientRoles( + bulkInvitationDto: BulkSendInvitationDto, + userEmail: string, + userId: string, + orgName: string, + idpId: string + ): Promise { + const { invitations, orgId } = bulkInvitationDto; + + const token = await this.clientRegistrationService.getManagementToken(); + const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); + const orgRoles = await this.orgRoleService.getOrgRoles(); + + for (const invitation of invitations) { + const { orgRoleId, email } = invitation; + + const isUserExist = await this.checkUserExistInPlatform(email); + + const userData = await this.getUserFirstName(userEmail); + + const { firstName } = userData; + + const matchedRoles = clientRolesList + .filter((role) => orgRoleId.includes(role.id.trim())) + .map((role) => role.name); + + if (orgRoleId.length !== matchedRoles.length) { + throw new NotFoundException(ResponseMessages.organisation.error.orgRoleIdNotFound); + } + + const filteredOrgRoles = orgRoles.filter((role) => matchedRoles.includes(role.name.trim())); + + const isInvitationExist = await this.checkInvitationExist(email, orgId); + + if (!isInvitationExist && userEmail !== invitation.email) { + + await this.organizationRepository.createSendInvitation( + email, + String(orgId), + String(userId), + filteredOrgRoles.map((role) => role.id) + ); + + try { + await this.sendInviteEmailTemplate( + email, + orgName, + filteredOrgRoles, + firstName, + isUserExist + ); + } catch (error) { + throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); + } + } + } + } + + /** + * + * @Body sendInvitationDto + * @returns createInvitation + */ + + async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: string, userEmail: string): Promise { + const { orgId } = bulkInvitationDto; + try { + const organizationDetails = await this.organizationRepository.getOrganizationDetails(orgId); + + if (!organizationDetails) { + throw new NotFoundException(ResponseMessages.organisation.error.orgNotFound); + } + + if (!organizationDetails.idpId) { + await this.createInvitationByOrgRoles( + bulkInvitationDto, + userEmail, + userId, + organizationDetails.name + ); + } else { + await this.createInvitationByClientRoles( + bulkInvitationDto, + userEmail, + userId, + organizationDetails.name, + organizationDetails.idpId + ); } - await this.userActivityService.createActivity(userId, organizationDetails.id, `Invitations sent for ${organizationDetails.name}`, 'Get started with user role management once invitations accepted'); + + await this.userActivityService.createActivity( + userId, + organizationDetails.id, + `Invitations sent for ${organizationDetails.name}`, + 'Get started with user role management once invitations accepted' + ); return ResponseMessages.organisation.success.createInvitation; } catch (error) { this.logger.error(`In send Invitation : ${JSON.stringify(error)}`); @@ -602,7 +830,7 @@ export class OrganizationService { email: string, orgName: string, orgRolesDetails: object[], - firstName:string, + firstName: string, isUserExist: boolean ): Promise { const platformConfigData = await this.prisma.platform_config.findMany(); @@ -611,9 +839,15 @@ export class OrganizationService { const emailData = new EmailDto(); emailData.emailFrom = platformConfigData[0].emailFrom; emailData.emailTo = email; - emailData.emailSubject = `Invitation to join “${orgName}” on CREDEBL`; + emailData.emailSubject = `Invitation to join “${orgName}” on ${process.env.PLATFORM_NAME}`; - emailData.emailHtml = await urlEmailTemplate.sendInviteEmailTemplate(email, orgName, orgRolesDetails, firstName, isUserExist); + emailData.emailHtml = await urlEmailTemplate.sendInviteEmailTemplate( + email, + orgName, + orgRolesDetails, + firstName, + isUserExist + ); //Email is sent to user for the verification through emailData const isEmailSent = await sendEmail(emailData); @@ -648,7 +882,7 @@ export class OrganizationService { const pattern = { cmd: 'get-user-by-mail' }; const payload = { email: userEmail }; - const userData = await this.organizationServiceProxy + const userData = await this.organizationServiceProxy .send(pattern, payload) .toPromise() .catch((error) => { @@ -660,12 +894,38 @@ export class OrganizationService { }, error.status ); - }); - return userData; - } - + }); + return userData; + } + + async getUserUserId(userId: string): Promise { + const pattern = { cmd: 'get-user-by-user-id' }; + // const payload = { id: userId }; + + const userData = await this.organizationServiceProxy + .send(pattern, userId) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.error, + message: error.message + }, + error.status + ); + }); + return userData; + } - async fetchUserInvitation(email: string, status: string, pageNumber: number, pageSize: number, search = ''): Promise { + async fetchUserInvitation( + email: string, + status: string, + pageNumber: number, + pageSize: number, + search = '' + ): Promise { try { return this.organizationRepository.getAllOrgInvitations(email, status, pageNumber, pageSize, search); } catch (error) { @@ -674,6 +934,49 @@ export class OrganizationService { } } + async updateClientInvitation( + // eslint-disable-next-line camelcase + invitation: org_invitations, + idpId: string, + userId: string, + keycloakUserId: string, + orgId: string, + status: string + ): Promise { + const token = await this.clientRegistrationService.getManagementToken(); + const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); + + const orgRoles = await this.orgRoleService.getOrgRolesByIds(invitation.orgRoles); + + const rolesPayload: { roleId: string; name: string; idpRoleId: string }[] = orgRoles.map((orgRole: IOrgRole) => { + let roleObj: { roleId: string; name: string; idpRoleId: string} = null; + + for (let index = 0; index < clientRolesList.length; index++) { + if (clientRolesList[index].name === orgRole.name) { + roleObj = { + roleId: orgRole.id, + name: orgRole.name, + idpRoleId: clientRolesList[index].id + }; + break; + } + } + + return roleObj; + }); + + const data = { + status + }; + + await Promise.all([ + this.organizationRepository.updateOrgInvitation(invitation.id, data), + this.clientRegistrationService.createUserClientRole(idpId, token, keycloakUserId, rolesPayload.map(role => ({id: role.idpRoleId, name: role.name}))), + this.userOrgRoleService.updateUserOrgRole(userId, orgId, rolesPayload) + ]); + + } + /** * * @param payload @@ -681,10 +984,10 @@ export class OrganizationService { */ async updateOrgInvitation(payload: UpdateInvitationDto): Promise { try { - const { orgId, status, invitationId, userId } = payload; + const { orgId, status, invitationId, userId, keycloakUserId, email } = payload; const invitation = await this.organizationRepository.getInvitationById(String(invitationId)); - if (!invitation) { + if (!invitation || (invitation && invitation.email !== email)) { throw new NotFoundException(ResponseMessages.user.error.invitationNotFound); } @@ -692,32 +995,112 @@ export class OrganizationService { throw new NotFoundException(ResponseMessages.user.error.invalidOrgId); } + const organizationDetails = await this.organizationRepository.getOrganizationDetails(orgId); + + if (!organizationDetails) { + throw new ConflictException(ResponseMessages.organisation.error.orgNotFound); + } + const invitationStatus = invitation.status as Invitation; if (!transition(invitationStatus, payload.status)) { - throw new BadRequestException(`${ResponseMessages.user.error.invitationStatusUpdateInvalid} ${invitation.status}`); + throw new BadRequestException( + `${ResponseMessages.user.error.invitationStatusUpdateInvalid} ${invitation.status}` + ); } const data = { status }; - await this.organizationRepository.updateOrgInvitation(invitationId, data); - if (status === Invitation.REJECTED) { + await this.organizationRepository.updateOrgInvitation(invitationId, data); return ResponseMessages.user.success.invitationReject; } - for (const roleId of invitation.orgRoles) { - await this.userOrgRoleService.createUserOrgRole(userId, roleId, orgId); + + if (organizationDetails.idpId) { + await this.updateClientInvitation(invitation, organizationDetails.idpId, userId, keycloakUserId, orgId, status); + } else { + await this.organizationRepository.updateOrgInvitation(invitationId, data); + + for (const roleId of invitation.orgRoles) { + await this.userOrgRoleService.createUserOrgRole(userId, roleId, orgId); + } } return ResponseMessages.user.success.invitationAccept; - } catch (error) { this.logger.error(`In updateOrgInvitation : ${error}`); throw new RpcException(error.response ? error.response : error); } } + async updateUserClientRoles( + // eslint-disable-next-line camelcase + roleIds: string[], + idpId: string, + userId: string, + orgId: string + ): Promise { + const token = await this.clientRegistrationService.getManagementToken(); + const clientRolesList = await this.clientRegistrationService.getAllClientRoles( + idpId, + token + ); + const orgRoles = await this.orgRoleService.getOrgRoles(); + + const matchedClientRoles = clientRolesList.filter((role) => roleIds.includes(role.id.trim())); + + if (roleIds.length !== matchedClientRoles.length) { + throw new NotFoundException(ResponseMessages.organisation.error.orgRoleIdNotFound); + } + + const rolesPayload: { roleId: string; name: string; idpRoleId: string }[] = matchedClientRoles.map( + (clientRole: IClientRoles) => { + let roleObj: { roleId: string; name: string; idpRoleId: string } = null; + + for (let index = 0; index < orgRoles.length; index++) { + if (orgRoles[index].name === clientRole.name) { + roleObj = { + roleId: orgRoles[index].id, + name: orgRoles[index].name, + idpRoleId: clientRole.id + }; + break; + } + } + + return roleObj; + } + ); + + const userData = await this.getUserUserId(userId); + + const [, deletedUserRoleRecords] = await Promise.all([ + this.clientRegistrationService.deleteUserClientRoles( + idpId, + token, + userData.keycloakUserId + ), + this.userOrgRoleService.deleteOrgRoles(userId, orgId) + ]); + + if (0 === deletedUserRoleRecords['count']) { + throw new InternalServerErrorException(ResponseMessages.organisation.error.updateUserRoles); + } + + const [, isUserRoleUpdated] = await Promise.all([ + this.clientRegistrationService.createUserClientRole( + idpId, + token, + userData.keycloakUserId, + rolesPayload.map((role) => ({ id: role.idpRoleId, name: role.name })) + ), + this.userOrgRoleService.updateUserOrgRole(userId, orgId, rolesPayload) + ]); + + return isUserRoleUpdated; + } + /** * * @param orgId @@ -727,26 +1110,45 @@ export class OrganizationService { */ async updateUserRoles(orgId: string, roleIds: string[], userId: string): Promise { try { - const isUserExistForOrg = await this.userOrgRoleService.checkUserOrgExist(userId, orgId); if (!isUserExistForOrg) { throw new NotFoundException(ResponseMessages.organisation.error.userNotFound); } - const isRolesExist = await this.orgRoleService.getOrgRolesByIds(roleIds); + const organizationDetails = await this.organizationRepository.getOrganizationDetails(orgId); - if (isRolesExist && 0 === isRolesExist.length) { - throw new NotFoundException(ResponseMessages.organisation.error.rolesNotExist); + if (!organizationDetails) { + throw new NotFoundException(ResponseMessages.organisation.error.orgNotFound); } - const deleteUserRecords = await this.userOrgRoleService.deleteOrgRoles(userId, orgId); + if (!organizationDetails.idpId) { + const isRolesExist = await this.orgRoleService.getOrgRolesByIds(roleIds); - if (0 === deleteUserRecords['count']) { - throw new InternalServerErrorException(ResponseMessages.organisation.error.updateUserRoles); - } + if (isRolesExist && 0 === isRolesExist.length) { + throw new NotFoundException(ResponseMessages.organisation.error.rolesNotExist); + } + + const deleteUserRecords = await this.userOrgRoleService.deleteOrgRoles(userId, orgId); + + if (0 === deleteUserRecords['count']) { + throw new InternalServerErrorException(ResponseMessages.organisation.error.updateUserRoles); + } + + for (const role of roleIds) { + this.userOrgRoleService.createUserOrgRole(userId, role, orgId); + } + + return true; + } else { - return this.userOrgRoleService.updateUserOrgRole(userId, orgId, roleIds); + return this.updateUserClientRoles( + roleIds, + organizationDetails.idpId, + userId, + organizationDetails.id + ); + } } catch (error) { this.logger.error(`Error in updateUserRoles: ${JSON.stringify(error)}`); @@ -756,7 +1158,7 @@ export class OrganizationService { async getOrgDashboard(orgId: string): Promise { try { - return this.organizationRepository.getOrgDashboard(orgId); + return this.organizationRepository.getOrgDashboard(orgId); } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -792,7 +1194,6 @@ export class OrganizationService { } } - async getOrgOwner(orgId: string): Promise { try { const orgDetails = await this.organizationRepository.getOrganizationOwnerDetails(orgId, OrgRoles.OWNER); @@ -803,7 +1204,6 @@ export class OrganizationService { } } - async deleteOrganization(orgId: string): Promise { try { const getAgent = await this.organizationRepository.getAgentEndPoint(orgId); @@ -815,7 +1215,6 @@ export class OrganizationService { let url; if (getAgent.orgAgentTypeId === OrgAgentType.DEDICATED) { url = `${getAgent.agentEndPoint}${CommonConstants.URL_DELETE_WALLET}`; - } else if (getAgent.orgAgentTypeId === OrgAgentType.SHARED) { url = `${getAgent.agentEndPoint}${CommonConstants.URL_DELETE_SHARED_WALLET}`.replace('#', getAgent.tenantId); } @@ -827,14 +1226,12 @@ export class OrganizationService { const deleteWallet = await this._deleteWallet(payload); if (deleteWallet) { - const orgDelete = await this.organizationRepository.deleteOrg(orgId); if (false === orgDelete) { throw new NotFoundException(ResponseMessages.organisation.error.deleteOrg); } } - return true; } catch (error) { this.logger.error(`delete organization: ${JSON.stringify(error)}`); @@ -853,18 +1250,20 @@ export class OrganizationService { return this.organizationServiceProxy .send(pattern, payload) .pipe( - map((response) => ( - { + map((response) => ({ response })) - ).toPromise() - .catch(error => { + ) + .toPromise() + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( { status: error.statusCode, error: error.message - }, error.error); + }, + error.error + ); }); } catch (error) { this.logger.error(`[_deleteWallet] - error in delete wallet : ${JSON.stringify(error)}`); @@ -872,7 +1271,6 @@ export class OrganizationService { } } - async _getOrgAgentApiKey(orgId: string): Promise { const pattern = { cmd: 'get-org-agent-api-key' }; const payload = { orgId }; @@ -883,10 +1281,112 @@ export class OrganizationService { return message; } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException({ + throw new HttpException( + { status: error.status, error: error.message - }, error.status); + }, + error.status + ); + } + } + + async registerOrgsMapUsers(): Promise { + + try { + + const unregisteredOrgsList = await this.organizationRepository.getUnregisteredClientOrgs(); + + if (!unregisteredOrgsList || 0 === unregisteredOrgsList.length) { + throw new NotFoundException('Unregistered client organizations not found'); + } + + for (const org of unregisteredOrgsList) { + const userOrgRoles = 0 < org['userOrgRoles'].length && org['userOrgRoles']; + + const ownerUserList = 0 < org['userOrgRoles'].length + && userOrgRoles.filter(userOrgRole => userOrgRole.orgRole.name === OrgRoles.OWNER); + + const ownerUser = 0 < ownerUserList.length && ownerUserList[0].user; + + const orgObj = { + id: org.id, + idpId: org.idpId, + name: org.name, + ownerId: ownerUser.id, + ownerEmail: ownerUser.email, + ownerKeycloakId: ownerUser.keycloakUserId + }; + + if (orgObj.ownerKeycloakId) { + const orgCredentials = await this.registerToKeycloak( + orgObj.name, + orgObj.id, + orgObj.ownerKeycloakId, + orgObj.ownerId, + true + ); + + const { clientId, idpId, clientSecret } = orgCredentials; + + const updateOrgData = { + clientId, + clientSecret: this.maskString(clientSecret), + idpId + }; + + const updatedOrg = await this.organizationRepository.updateOrganizationById(updateOrgData, orgObj.id); + + this.logger.log(`updatedOrg::`, updatedOrg); + + const usersToRegisterList = userOrgRoles.filter(userOrgRole => null !== userOrgRole.user.keycloakUserId); + + const token = await this.clientRegistrationService.getManagementToken(); + const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); + + const deletedUserDetails: string[] = []; + for (const userRole of usersToRegisterList) { + const user = userRole.user; + + const matchedClientRoles = clientRolesList.filter((role) => userRole.orgRole.name === role.name) + .map(clientRole => ({roleId: userRole.orgRole.id, idpRoleId: clientRole.id, name: clientRole.name})); + + if (!deletedUserDetails.includes(user.id)) { + const [, deletedUserRoleRecords] = await Promise.all([ + this.clientRegistrationService.deleteUserClientRoles(idpId, token, user.keycloakUserId), + this.userOrgRoleService.deleteOrgRoles(user.id, orgObj.id) + ]); + + this.logger.log(`deletedUserRoleRecords::`, deletedUserRoleRecords); + + deletedUserDetails.push(user.id); + } + + + await Promise.all([ + this.clientRegistrationService.createUserClientRole( + idpId, + token, + user.keycloakUserId, + matchedClientRoles.map((role) => ({ id: role.idpRoleId, name: role.name })) + ), + this.userOrgRoleService.updateUserOrgRole( + user.id, + orgObj.id, + matchedClientRoles.map((role) => ({ roleId: role.roleId, idpRoleId: role.idpRoleId })) + ) + ]); + this.logger.log(`Organization client created and users mapped to roles`); + + } + } + } + + return ''; + } catch (error) { + this.logger.error(`Error in registerOrgsMapUsers: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } } @@ -931,10 +1431,13 @@ export class OrganizationService { return message; } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException({ - status: error.status, - error: error.message - }, error.status); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); } } -} \ No newline at end of file +} diff --git a/apps/organization/templates/organization-invitation.template.ts b/apps/organization/templates/organization-invitation.template.ts index 193886dc8..e3fb38f58 100644 --- a/apps/organization/templates/organization-invitation.template.ts +++ b/apps/organization/templates/organization-invitation.template.ts @@ -12,13 +12,13 @@ export class OrganizationInviteTemplate { const message = isUserExist ? `Please accept the invitation using the following link:` - : `To get started, kindly register on CREDEBL platform using this link:`; + : `To get started, kindly register on ${process.env.PLATFORM_NAME} platform using this link:`; const secondMessage = isUserExist - ? `After successful login into CREDEBL click on "Accept Organization Invitation" link on your dashboard.` + ? `After successful login into ${process.env.PLATFORM_NAME} click on "Accept Organization Invitation" link on your dashboard.` : `After successful registration, you can log in to the platform and click on “Accept Organization Invitation” on your dashboard.`; - const Button = isUserExist ? 'Accept Organization Invitation' : 'Register on CREDEBL'; + const Button = isUserExist ? `Accept Organization Invitation` : `Register on ${process.env.PLATFORM_NAME}`; return ` @@ -32,7 +32,7 @@ export class OrganizationInviteTemplate {
- CREDEBL logo + ${process.env.PLATFORM_NAME} logo
- For any assistance or questions while accessing your account, please do not hesitate to contact the support team at support@blockster.global. Our team will ensure a seamless onboarding experience for you. + For any assistance or questions while accessing your account, please do not hesitate to contact the support team at ${process.env.PUBLIC_PLATFORM_SUPPORT_EMAIL}. Our team will ensure a seamless onboarding experience for you.

- © Blockster Labs Pvt. Ltd. + © ${process.env.POWERED_BY}

diff --git a/apps/organization/templates/organization-onboard.template.ts b/apps/organization/templates/organization-onboard.template.ts index 0750a742e..7f4c8b3be 100644 --- a/apps/organization/templates/organization-onboard.template.ts +++ b/apps/organization/templates/organization-onboard.template.ts @@ -25,7 +25,7 @@ export class OnBoardVerificationRequest {
Credebl Logo
- +
diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index 7eaa65d52..503370357 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -5,6 +5,7 @@ export interface IUsersProfile { firstName?: string; lastName?: string; supabaseUserId?: string; + keycloakUserId?: string; userOrgRoles?: IUserOrgRole[]; } diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index f49d1cc79..95337baab 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -223,6 +223,7 @@ export class UserRepository { clientId: true, clientSecret: true, supabaseUserId: true, + keycloakUserId: true, userOrgRoles: { include: { orgRole: true, @@ -271,6 +272,7 @@ export class UserRepository { profileImg: true, publicProfile: true, supabaseUserId: true, + keycloakUserId: true, isEmailVerified: true, userOrgRoles: { select:{ diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 35574e26e..802b7b150 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -90,6 +90,12 @@ export class UserController { async findUserByEmail(payload: { email }): Promise { return this.userService.findUserByEmail(payload); } + + @MessagePattern({ cmd: 'get-user-by-user-id' }) + async findUserByUserId(id: string): Promise { + return this.userService.findUserByUserId(id); + } + /** * @param credentialId * @returns User credentials diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index ef9c036fe..dbbac2214 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -271,8 +271,21 @@ export class UserService { keycloakDetails.keycloakUserId.toString() ); - const holderRoleData = await this.orgRoleService.getRole(OrgRoles.HOLDER); - await this.userOrgRoleService.createUserOrgRole(userDetails.id, holderRoleData.id); + const realmRoles = await this.clientRegistrationService.getAllRealmRoles(token); + + const holderRole = realmRoles.filter(role => role.name === OrgRoles.HOLDER); + const holderRoleData = 0 < holderRole.length && holderRole[0]; + + const payload = [ + { + id: holderRoleData.id, + name: holderRoleData.name + } + ]; + + 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); return ResponseMessages.user.success.signUpUser; } catch (error) { @@ -503,6 +516,11 @@ export class UserService { } } + findUserByUserId(id: string): Promise { + return this.userRepository.getUserById(id); + + } + async resetPassword(resetPasswordDto: IUserResetPassword): Promise { const { email, oldPassword, newPassword } = resetPasswordDto; @@ -788,7 +806,7 @@ export class UserService { async acceptRejectInvitations(acceptRejectInvitation: AcceptRejectInvitationDto, userId: string): Promise { try { const userData = await this.userRepository.getUserById(userId); - return this.fetchInvitationsStatus(acceptRejectInvitation, userId, userData.email); + return this.fetchInvitationsStatus(acceptRejectInvitation, userData.keycloakUserId, userData.email, userId); } catch (error) { this.logger.error(`acceptRejectInvitations: ${error}`); throw new RpcException(error.response ? error.response : error); @@ -893,15 +911,16 @@ export class UserService { */ async fetchInvitationsStatus( acceptRejectInvitation: AcceptRejectInvitationDto, - userId: string, - email: string + keycloakUserId: string, + email: string, + userId: string ): Promise { try { const pattern = { cmd: 'update-invitation-status' }; const { orgId, invitationId, status } = acceptRejectInvitation; - const payload = { userId, orgId, invitationId, status, email }; + const payload = { userId, keycloakUserId, orgId, invitationId, status, email }; const invitationsData = await this.userServiceProxy .send(pattern, payload) diff --git a/apps/user/templates/arbiter-template.ts b/apps/user/templates/arbiter-template.ts index efeee0331..962c39710 100644 --- a/apps/user/templates/arbiter-template.ts +++ b/apps/user/templates/arbiter-template.ts @@ -86,7 +86,7 @@ export class ArbiterTemplate {

Date: 24, 25, 26 November 2023 | Place: Cidco Exhibition Centre, Navi Mumbai, India
-
Blockchain-based certificate issued using credebl.id, by Blockster Labs Pvt. Ltd.
+
Blockchain-based certificate issued using ${process.env.PLATFORM_WEB_URL}, by ${process.env.POWERED_BY}
diff --git a/apps/user/templates/participant-template.ts b/apps/user/templates/participant-template.ts index f2738fb23..c9250dd84 100644 --- a/apps/user/templates/participant-template.ts +++ b/apps/user/templates/participant-template.ts @@ -82,7 +82,7 @@ export class ParticipantTemplate {

exceptional memory skills demonstrated during the competition.

Date: 24, 25, 26 November 2023 | Place: Cidco Exhibition Centre, Navi Mumbai, India
-
Blockchain-based certificate issued using credebl.id, by Blockster Labs Pvt. Ltd.
+
Blockchain-based certificate issued using ${process.env.PLATFORM_WEB_URL}, by ${process.env.POWERED_BY}
diff --git a/apps/user/templates/reset-password-template.ts b/apps/user/templates/reset-password-template.ts index eb0019e68..ec05ecdde 100644 --- a/apps/user/templates/reset-password-template.ts +++ b/apps/user/templates/reset-password-template.ts @@ -23,7 +23,7 @@ export class URLUserResetPasswordTemplate {
- CREDEBL logo + ${process.env.PLATFORM_NAME} logo
@@ -46,11 +46,11 @@ export class URLUserResetPasswordTemplate {
- For any assistance or questions while accessing your account, please do not hesitate to contact the support team at support@blockster.global. Our team will ensure a seamless onboarding experience for you. + For any assistance or questions while accessing your account, please do not hesitate to contact the support team at ${process.env.PUBLIC_PLATFORM_SUPPORT_EMAIL}. Our team will ensure a seamless onboarding experience for you.

- © Blockster Labs Pvt. Ltd. + © ${process.env.POWERED_BY}

diff --git a/apps/user/templates/user-email-template.ts b/apps/user/templates/user-email-template.ts index 857a1bb7a..dd648c539 100644 --- a/apps/user/templates/user-email-template.ts +++ b/apps/user/templates/user-email-template.ts @@ -23,7 +23,7 @@ export class URLUserEmailTemplate {
- CREDEBL logo + ${process.env.PLATFORM_NAME} logo
@@ -48,11 +48,11 @@ export class URLUserEmailTemplate {
- For any assistance or questions while accessing your account, please do not hesitate to contact the support team at support@blockster.global. Our team will ensure a seamless onboarding experience for you. + For any assistance or questions while accessing your account, please do not hesitate to contact the support team at ${process.env.PUBLIC_PLATFORM_SUPPORT_EMAIL}. Our team will ensure a seamless onboarding experience for you.

- © Blockster Labs Pvt. Ltd. + © ${process.env.POWERED_BY}

diff --git a/apps/user/templates/user-onboard.template.ts b/apps/user/templates/user-onboard.template.ts index 66a60b702..99faeb07d 100644 --- a/apps/user/templates/user-onboard.template.ts +++ b/apps/user/templates/user-onboard.template.ts @@ -25,7 +25,7 @@ export class OnBoardVerificationRequest {
Credebl Logoexceptional memory skills demonstrated during the competition.

Date: 24, 25, 26 November 2023 | Place: Cidco Exhibition Centre, Navi Mumbai, India
-
Blockchain-based certificate issued using credebl.id, by Blockster Labs Pvt. Ltd.
+
Blockchain-based certificate issued using ${process.env.PLATFORM_WEB_URL}, by ${process.env.POWERED_BY}
diff --git a/apps/user/templates/world-record-template.ts b/apps/user/templates/world-record-template.ts index 3eb30cf8c..27c9fbfda 100644 --- a/apps/user/templates/world-record-template.ts +++ b/apps/user/templates/world-record-template.ts @@ -84,7 +84,7 @@ export class WorldRecordTemplate {

exceptional memory skills demonstrated during the competition.

Date: 24, 25, 26 November 2023 | Place: Cidco Exhibition Centre, Navi Mumbai, India
-
Blockchain-based certificate issued using credebl.id, by Blockster Labs Pvt. Ltd.
+
Blockchain-based certificate issued using ${process.env.PLATFORM_WEB_URL}, by ${process.env.POWERED_BY}
diff --git a/apps/utility/interfaces/shortening-url.interface.ts b/apps/utility/interfaces/shortening-url.interface.ts index 4a18a4078..7772e04ad 100644 --- a/apps/utility/interfaces/shortening-url.interface.ts +++ b/apps/utility/interfaces/shortening-url.interface.ts @@ -1,10 +1,10 @@ export interface IShorteningUrlData { - referenceId: string, - schemaId: string, - credDefId: string, - invitationUrl: string, - attributes: IAttributes[] + referenceId: string; + schemaId: string; + credDefId: string; + invitationUrl: string; + attributes: IAttributes[]; } export interface IAttributes { - [key: string]: string - } + [key: string]: string; +} diff --git a/apps/utility/src/utilities.controller.ts b/apps/utility/src/utilities.controller.ts index cf08cbe18..f9199625d 100644 --- a/apps/utility/src/utilities.controller.ts +++ b/apps/utility/src/utilities.controller.ts @@ -1,11 +1,14 @@ -import { Controller } from '@nestjs/common'; +import { Controller, Logger } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; import { UtilitiesService } from './utilities.service'; import { IShorteningUrlData } from '../interfaces/shortening-url.interface'; @Controller() export class UtilitiesController { - constructor(private readonly utilitiesService: UtilitiesService) {} + constructor( + private readonly utilitiesService: UtilitiesService, + private readonly logger: Logger + ) {} @MessagePattern({ cmd: 'create-shortening-url' }) async createAndStoreShorteningUrl(payload: IShorteningUrlData): Promise { @@ -16,4 +19,15 @@ export class UtilitiesController { async getShorteningUrl(referenceId: string): Promise { return this.utilitiesService.getShorteningUrl(referenceId); } -} \ No newline at end of file + + @MessagePattern({ cmd: 'store-object-return-url' }) + async storeObject(payload: { persistent: boolean; storeObj: unknown }): Promise { + try { + const url: string = await this.utilitiesService.storeObject(payload); + return url; + } catch (error) { + this.logger.error(error); + throw new Error('Error occured in Utility Microservices Controller'); + } + } +} diff --git a/apps/utility/src/utilities.module.ts b/apps/utility/src/utilities.module.ts index 5045ac127..5048add05 100644 --- a/apps/utility/src/utilities.module.ts +++ b/apps/utility/src/utilities.module.ts @@ -7,6 +7,7 @@ import { PrismaService } from '@credebl/prisma-service'; import { UtilitiesController } from './utilities.controller'; import { UtilitiesService } from './utilities.service'; import { UtilitiesRepository } from './utilities.repository'; +import { AwsService } from '@credebl/aws'; @Module({ imports: [ @@ -21,6 +22,6 @@ import { UtilitiesRepository } from './utilities.repository'; CacheModule.register() ], controllers: [UtilitiesController], - providers: [UtilitiesService, Logger, PrismaService, UtilitiesRepository] + providers: [UtilitiesService, Logger, PrismaService, UtilitiesRepository, AwsService] }) export class UtilitiesModule { } diff --git a/apps/utility/src/utilities.service.ts b/apps/utility/src/utilities.service.ts index fbd300063..a1c66bdd8 100644 --- a/apps/utility/src/utilities.service.ts +++ b/apps/utility/src/utilities.service.ts @@ -1,12 +1,16 @@ import { Injectable, Logger } from '@nestjs/common'; import { RpcException } from '@nestjs/microservices'; import { UtilitiesRepository } from './utilities.repository'; +import { AwsService } from '@credebl/aws'; +import { S3 } from 'aws-sdk'; +import { v4 as uuidv4 } from 'uuid'; @Injectable() export class UtilitiesService { constructor( private readonly logger: Logger, - private readonly utilitiesRepository: UtilitiesRepository + private readonly utilitiesRepository: UtilitiesRepository, + private readonly awsService: AwsService ) { } async createAndStoreShorteningUrl(payload): Promise { @@ -44,4 +48,16 @@ export class UtilitiesService { throw new RpcException(error); } } + + async storeObject(payload: {persistent: boolean, storeObj: unknown}): Promise { + try { + const uuid = uuidv4(); + const uploadResult:S3.ManagedUpload.SendData = await this.awsService.storeObject(payload.persistent, uuid, payload.storeObj); + const url: string = `https://${uploadResult.Bucket}.s3.${process.env.AWS_S3_STOREOBJECT_REGION}.amazonaws.com/${uploadResult.Key}`; + return url; + } catch (error) { + this.logger.error(error); + throw new Error('An error occurred while uploading data to S3. Error::::::'); + } + } } diff --git a/apps/verification/src/interfaces/verification.interface.ts b/apps/verification/src/interfaces/verification.interface.ts index 4f7cc448d..2a3397514 100644 --- a/apps/verification/src/interfaces/verification.interface.ts +++ b/apps/verification/src/interfaces/verification.interface.ts @@ -46,7 +46,7 @@ export interface IVerifiedProofData { export interface IProofPresentationData { proofId: string; - orgId: string; + orgId: string; user: IUserRequest; } @@ -144,6 +144,7 @@ export interface ISendProofRequestPayload { parentThreadId?: string; willConfirm?: boolean; imageUrl?: string; + isShortenUrl?: boolean; type?:string; presentationDefinition?:IProofRequestPresentationDefinition; } @@ -200,7 +201,7 @@ export interface IProofRequests { proofRequestsSearchCriteria: IProofRequestSearchCriteria; user: IUserRequest; orgId: string; - } +} export interface IProofRequestSearchCriteria { pageNumber: number; @@ -208,5 +209,5 @@ export interface IProofRequestSearchCriteria { sortField: string; sortBy: string; searchByText: string; - } - +} + diff --git a/apps/verification/src/verification.service.ts b/apps/verification/src/verification.service.ts index 024255043..b00c7e2f1 100644 --- a/apps/verification/src/verification.service.ts +++ b/apps/verification/src/verification.service.ts @@ -82,15 +82,15 @@ export class VerificationService { data: getProofRequestsList.proofRequestsList }; - return proofPresentationsResponse; - } catch (error) { + return proofPresentationsResponse; + } catch (error) { - this.logger.error( - `[getProofRequests] [NATS call]- error in fetch proof requests details : ${JSON.stringify(error)}` - ); - throw new RpcException(error.response ? error.response : error); - } -} + this.logger.error( + `[getProofRequests] [NATS call]- error in fetch proof requests details : ${JSON.stringify(error)}` + ); + throw new RpcException(error.response ? error.response : error); + } + } /** * Consume agent API for get all proof presentations @@ -140,9 +140,9 @@ export class VerificationService { statusCode: error?.response?.status, error: errorStack }); - } else { - throw new RpcException(error.response ? error.response : error); - } + } else { + throw new RpcException(error.response ? error.response : error); + } } } @@ -233,7 +233,7 @@ export class VerificationService { } catch (error) { this.logger.error(`[verifyPresentation] - error in verify presentation : ${JSON.stringify(error)}`); this.verificationErrorHandling(error); - + } } @@ -258,20 +258,20 @@ export class VerificationService { } } - /** - * Verify proof presentation - * @param proofId - * @param orgId - * @returns Verified proof presentation details - */ - async verifyPresentation(proofId: string, orgId: string): Promise { + /** + * Verify proof presentation + * @param proofId + * @param orgId + * @returns Verified proof presentation details + */ + async verifyPresentation(proofId: string, orgId: string): Promise { try { const getAgentData = await this.verificationRepository.getAgentEndPoint(orgId); const orgAgentTypeData = await this.verificationRepository.getOrgAgentType(getAgentData?.orgAgentTypeId); let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); const verificationMethod = 'accept-presentation'; - + const url = await this.getAgentUrl(verificationMethod, orgAgentTypeData, getAgentData?.agentEndPoint, getAgentData?.tenantId, '', proofId); if (!apiKey || null === apiKey || undefined === apiKey) { apiKey = await this._getOrgAgentApiKey(orgId); @@ -290,8 +290,8 @@ export class VerificationService { error: errorStack }); } else { - throw new RpcException(error.response ? error.response : error); - } + throw new RpcException(error.response ? error.response : error); + } } } @@ -333,12 +333,9 @@ export class VerificationService { * @param outOfBandRequestProof * @returns Get requested proof presentation details */ - async sendOutOfBandPresentationRequest(outOfBandRequestProof: ISendProofRequestPayload, user: IUserRequest): Promise { + async sendOutOfBandPresentationRequest(outOfBandRequestProof: ISendProofRequestPayload, user: IUserRequest): Promise { try { - outOfBandRequestProof.protocolVersion = outOfBandRequestProof.protocolVersion || 'v1'; - outOfBandRequestProof.autoAcceptProof = outOfBandRequestProof.autoAcceptProof || 'always'; - // const { requestedAttributes, requestedPredicates } = await this._proofRequestPayload(outOfBandRequestProof); const [getAgentDetails, getOrganization] = await Promise.all([ @@ -346,10 +343,12 @@ export class VerificationService { this.verificationRepository.getOrganization(user.orgId) ]); - const imageUrl = getOrganization?.logoUrl; const label = getOrganization?.name; - outOfBandRequestProof['imageUrl'] = imageUrl; + if (getOrganization?.logoUrl) { + outOfBandRequestProof['imageUrl'] = getOrganization?.logoUrl; + } + outOfBandRequestProof['label'] = label; const orgAgentType = await this.verificationRepository.getOrgAgentType(getAgentDetails?.orgAgentTypeId); let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); @@ -359,27 +358,27 @@ export class VerificationService { apiKey = await this._getOrgAgentApiKey(user.orgId); } + const { isShortenUrl, type, ...updateOutOfBandRequestProof } = outOfBandRequestProof; + outOfBandRequestProof.autoAcceptProof = outOfBandRequestProof.autoAcceptProof || 'always'; let payload: IProofRequestPayload | IPresentationExchangeProofRequestPayload; - if (ProofRequestType.INDY === outOfBandRequestProof.type) { - const outOfBandIndyRequestPayload = { ...outOfBandRequestProof }; - delete outOfBandIndyRequestPayload.type; - + if (ProofRequestType.INDY === type) { + updateOutOfBandRequestProof.protocolVersion = updateOutOfBandRequestProof.protocolVersion || 'v1'; payload = { apiKey, url, - proofRequestPayload: outOfBandIndyRequestPayload + proofRequestPayload: updateOutOfBandRequestProof }; } - if (ProofRequestType.PRESENTATIONEXCHANGE === outOfBandRequestProof.type) { + if (ProofRequestType.PRESENTATIONEXCHANGE === type) { payload = { apiKey, url, proofRequestPayload: { - protocolVersion:outOfBandRequestProof.protocolVersion || 'v1', + protocolVersion:outOfBandRequestProof.protocolVersion || 'v2', comment:outOfBandRequestProof.comment, label, proofFormats: { @@ -397,6 +396,15 @@ export class VerificationService { const getProofPresentation = await this._sendOutOfBandProofRequest(payload); + //apply presentation shorting URL + if (isShortenUrl) { + const proofRequestInvitationUrl: string = getProofPresentation?.response?.invitationUrl; + const shortenedUrl: string = await this.storeVerificationObjectAndReturnUrl(proofRequestInvitationUrl, false); + this.logger.log('shortenedUrl', shortenedUrl); + if (shortenedUrl) { + getProofPresentation.response.invitationUrl = shortenedUrl; + } + } if (!getProofPresentation) { throw new Error(ResponseMessages.verification.error.proofPresentationNotFound); } @@ -417,6 +425,14 @@ export class VerificationService { } } + async storeVerificationObjectAndReturnUrl(storeObj: string, persistent: boolean): Promise { + //nats call in agent-service to create an invitation url + const pattern = { cmd: 'store-object-return-url' }; + const payload = { persistent, storeObj }; + const message = await this.natsCall(pattern, payload); + return message.response; + } + private async generateOOBProofReq(payload: IProofRequestPayload, getAgentDetails: org_agents): Promise { let agentApiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); @@ -544,7 +560,7 @@ export class VerificationService { try { let requestedAttributes = {}; const requestedPredicates = {}; - const {attributes} = proofRequestpayload; + const { attributes } = proofRequestpayload; if (attributes) { requestedAttributes = Object.fromEntries(attributes.map((attribute, index) => { const attributeElement = attribute.attributeName || attribute.attributeNames; @@ -552,7 +568,7 @@ export class VerificationService { const attributeKey = attribute.attributeName ? 'name' : 'names'; if (!attribute.condition && !attribute.value) { - + return [ attributeReferent, { @@ -578,10 +594,10 @@ export class VerificationService { ] }; } - + return [attributeReferent]; })); - + return { requestedAttributes, requestedPredicates @@ -591,10 +607,10 @@ export class VerificationService { } } catch (error) { this.logger.error(`[proofRequestPayload] - error in proof request payload : ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); - - } - } + throw new RpcException(error.response ? error.response : error); + + } + } /** * Description: Fetch agent url @@ -705,13 +721,13 @@ export class VerificationService { const payload = { apiKey, url }; const getProofPresentationById = await this._getVerifiedProofDetails(payload); - + if (!getProofPresentationById?.response?.presentation) { throw new NotFoundException(ResponseMessages.verification.error.proofPresentationNotFound, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound + cause: new Error(), + description: ResponseMessages.errorMessages.notFound }); - } + } const requestedAttributes = getProofPresentationById?.response?.request?.indy?.requested_attributes; const requestedPredicates = getProofPresentationById?.response?.request?.indy?.requested_predicates; @@ -726,7 +742,7 @@ export class VerificationService { if (requestedAttributes.hasOwnProperty(key)) { const requestedAttributeKey = requestedAttributes[key]; const attributeName = requestedAttributeKey.name; - + if (requestedAttributeKey?.restrictions) { credDefId = requestedAttributeKey?.restrictions[0]?.cred_def_id; @@ -779,7 +795,7 @@ export class VerificationService { const attribute = requestedAttributes[key]; const attributeName = attribute.name; - + [credDefId, schemaId] = await this._schemaCredDefRestriction(attribute, getProofPresentationById); @@ -800,7 +816,7 @@ export class VerificationService { if (requestedPredicates.hasOwnProperty(key)) { const attribute = requestedPredicates[key]; const attributeName = attribute?.name; - + [credDefId, schemaId] = await this._schemaCredDefRestriction(attribute, getProofPresentationById); const extractedData: IProofPresentationDetails = { [attributeName]: `${attribute?.p_type}${attribute?.p_value}`, @@ -812,25 +828,25 @@ export class VerificationService { } } else { throw new InternalServerErrorException(ResponseMessages.errorMessages.serverError, { - cause: new Error(), - description: ResponseMessages.errorMessages.serverError + cause: new Error(), + description: ResponseMessages.errorMessages.serverError }); } return extractedDataArray; } catch (error) { - this.logger.error(`[getVerifiedProofDetails] - error in get verified proof details : ${JSON.stringify(error)}`); - const errorStack = error?.response?.error?.reason; - - if (errorStack) { - throw new RpcException({ - message: ResponseMessages.verification.error.verifiedProofNotFound, - statusCode: error?.response?.status, - error: errorStack - }); + this.logger.error(`[getVerifiedProofDetails] - error in get verified proof details : ${JSON.stringify(error)}`); + const errorStack = error?.response?.error?.reason; + + if (errorStack) { + throw new RpcException({ + message: ResponseMessages.verification.error.verifiedProofNotFound, + statusCode: error?.response?.status, + error: errorStack + }); } else { - throw new RpcException(error.response ? error.response : error); - } + throw new RpcException(error.response ? error.response : error); } + } } async _schemaCredDefRestriction(attribute, getProofPresentationById): Promise { @@ -838,7 +854,7 @@ export class VerificationService { let schemaId; if (attribute?.restrictions) { - + credDefId = attribute?.restrictions[0]?.cred_def_id; schemaId = attribute?.restrictions[0]?.schema_id; } else if (getProofPresentationById?.response?.presentation?.indy?.identifiers) { @@ -887,7 +903,7 @@ export class VerificationService { verificationErrorHandling(error): void { if (!error && !error?.status && !error?.status?.message && !error?.status?.message?.error) { - + throw new RpcException(error.response ? error.response : error); } else { throw new RpcException({ @@ -900,23 +916,23 @@ export class VerificationService { async natsCall(pattern: object, payload: object): Promise<{ response: string; }> { - return this.verificationServiceProxy - .send(pattern, payload) - .pipe( - map((response) => ( - { - response - })) - ) - .toPromise() - .catch(error => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException({ - status: error.statusCode, - error: error.error, - message: error.message - }, error.error); - }); - } - + return this.verificationServiceProxy + .send(pattern, payload) + .pipe( + map((response) => ( + { + response + })) + ) + .toPromise() + .catch(error => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException({ + status: error.statusCode, + error: error.error, + message: error.message + }, error.error); + }); + } + } \ No newline at end of file diff --git a/apps/verification/templates/out-of-band-verification.template.ts b/apps/verification/templates/out-of-band-verification.template.ts index 858635ad2..a7a385775 100644 --- a/apps/verification/templates/out-of-band-verification.template.ts +++ b/apps/verification/templates/out-of-band-verification.template.ts @@ -14,7 +14,7 @@ export class OutOfBandVerification {
- CREDEBL logo + ${process.env.PLATFORM_NAME} logo
@@ -50,11 +50,11 @@ export class OutOfBandVerification {
- For any assistance or questions while accessing your account, please do not hesitate to contact the support team at support@blockster.global. Our team will ensure a seamless onboarding experience for you. + For any assistance or questions while accessing your account, please do not hesitate to contact the support team at ${process.env.PUBLIC_PLATFORM_SUPPORT_EMAIL}. Our team will ensure a seamless onboarding experience for you.

- © Blockster Labs Pvt. Ltd. + © ${process.env.POWERED_BY}

diff --git a/libs/aws/src/aws.service.ts b/libs/aws/src/aws.service.ts index 29b067cdf..f9c7cb7e4 100644 --- a/libs/aws/src/aws.service.ts +++ b/libs/aws/src/aws.service.ts @@ -7,6 +7,7 @@ import { promisify } from 'util'; export class AwsService { private s3: S3; private s4: S3; + private s3StoreObject: S3; constructor() { this.s3 = new S3({ @@ -16,19 +17,24 @@ export class AwsService { }); this.s4 = new S3({ - accessKeyId: process.env.AWS_PUBLIC_ACCESS_KEY, secretAccessKey: process.env.AWS_PUBLIC_SECRET_KEY, region: process.env.AWS_PUBLIC_REGION }); + + this.s3StoreObject = new S3({ + accessKeyId: process.env.AWS_S3_STOREOBJECT_ACCESS_KEY, + secretAccessKey: process.env.AWS_S3_STOREOBJECT_SECRET_KEY, + region: process.env.AWS_S3_STOREOBJECT_REGION + }); } - + async uploadUserCertificate( fileBuffer: Buffer, ext: string, filename: string, bucketName: string, - encoding : string, + encoding: string, pathAWS: string = '' ): Promise { const timestamp = Date.now(); @@ -43,8 +49,8 @@ export class AwsService { ContentType: `image/png` }); - const imageUrl = `https://${process.env.AWS_ORG_LOGO_BUCKET_NAME}.s3.${process.env.AWS_PUBLIC_REGION}.amazonaws.com/${pathAWS}/${encodeURIComponent(filename)}-${timestamp}.${ext}`; - return imageUrl; + const imageUrl = `https://${process.env.AWS_ORG_LOGO_BUCKET_NAME}.s3.${process.env.AWS_PUBLIC_REGION}.amazonaws.com/${pathAWS}/${encodeURIComponent(filename)}-${timestamp}.${ext}`; + return imageUrl; } catch (error) { throw new HttpException(error, HttpStatus.SERVICE_UNAVAILABLE); } @@ -87,4 +93,23 @@ export class AwsService { throw new RpcException(error.response ? error.response : error); } } + + async storeObject(persistent: boolean, key: string, body: unknown): Promise { + const objKey: string = persistent.valueOf() ? `persist/${key}` : `default/${key}`; + const buf = Buffer.from(JSON.stringify(body)); + const params: AWS.S3.PutObjectRequest = { + Bucket: process.env.AWS_S3_STOREOBJECT_BUCKET, + Body: buf, + Key: objKey, + ContentEncoding: 'base64', + ContentType: 'application/json' + }; + + try { + const receivedData = await this.s3StoreObject.upload(params).promise(); + return receivedData; + } catch (error) { + throw new RpcException(error.response ? error.response : error); + } + } } diff --git a/libs/client-registration/src/client-registration.service.ts b/libs/client-registration/src/client-registration.service.ts index d5fed0c93..a43fef46a 100644 --- a/libs/client-registration/src/client-registration.service.ts +++ b/libs/client-registration/src/client-registration.service.ts @@ -18,6 +18,7 @@ import { userTokenPayloadDto } from './dtos/userTokenPayloadDto'; import { KeycloakUserRegistrationDto } from 'apps/user/dtos/keycloak-register.dto'; import { ResponseMessages } from '@credebl/common/response-messages'; import { ResponseService } from '@credebl/response'; +import { IClientRoles } from './interfaces/client.interface'; @Injectable() export class ClientRegistrationService { @@ -186,7 +187,6 @@ export class ClientRegistrationService { } } - async getManagementTokenForMobile() { try { const payload = new ClientCredentialTokenPayloadDto(); @@ -276,6 +276,205 @@ export class ClientRegistrationService { } + async createUserClientRole( + idpId: string, + token: string, + userId: string, + payload: object[] + ): Promise { + + const realmName = process.env.KEYCLOAK_REALM; + + const createClientRolesResponse = await this.commonService.httpPost( + await this.keycloakUrlService.GetClientUserRoleURL(realmName, userId, idpId), + payload, + this.getAuthHeader(token) + ); + + this.logger.debug( + `createUserClientRolesResponse ${JSON.stringify( + createClientRolesResponse + )}` + ); + + return 'User client role is assigned'; + } + + async deleteUserClientRoles( + idpId: string, + token: string, + userId: string + ): Promise { + + const realmName = process.env.KEYCLOAK_REALM; + + const createClientRolesResponse = await this.commonService.httpDelete( + await this.keycloakUrlService.GetClientUserRoleURL(realmName, userId, idpId), + this.getAuthHeader(token) + ); + + this.logger.debug( + `deleteUserClientRoles ${JSON.stringify( + createClientRolesResponse + )}` + ); + + return 'User client role is deleted'; + } + + async createUserHolderRole( + token: string, + userId: string, + payload: object[] + ): Promise { + + const realmName = process.env.KEYCLOAK_REALM; + + const createClientRolesResponse = await this.commonService.httpPost( + await this.keycloakUrlService.GetClientUserRoleURL(realmName, userId), + payload, + this.getAuthHeader(token) + ); + + this.logger.debug( + `createUserHolderRole ${JSON.stringify( + createClientRolesResponse + )}` + ); + + return 'User holder role is assigned'; + } + + async getAllClientRoles( + idpId: string, + token: string + ): Promise { + + const realmName = process.env.KEYCLOAK_REALM; + + const clientRolesResponse = await this.commonService.httpGet( + await this.keycloakUrlService.GetClientRoleURL(realmName, idpId), + this.getAuthHeader(token) + ); + + this.logger.debug( + `getAllClientRoles ${JSON.stringify( + clientRolesResponse + )}` + ); + + return clientRolesResponse; + } + + async getClientSpecificRoles( + idpId: string, + token: string, + roleName: string + ): Promise { + + const realmName = process.env.KEYCLOAK_REALM; + + const clientRolesResponse = await this.commonService.httpGet( + await this.keycloakUrlService.GetClientRoleURL(realmName, idpId, roleName), + this.getAuthHeader(token) + ); + + this.logger.debug( + `getClientSpecificRoles ${JSON.stringify( + clientRolesResponse + )}` + ); + + return clientRolesResponse; + } + + async getAllRealmRoles( + token: string + ): Promise { + + const realmName = process.env.KEYCLOAK_REALM; + + const realmRolesResponse = await this.commonService.httpGet( + await this.keycloakUrlService.GetRealmRoleURL(realmName), + this.getAuthHeader(token) + ); + + this.logger.debug( + `getAllRealmRoles ${JSON.stringify( + realmRolesResponse + )}` + ); + + return realmRolesResponse; + } + + + async createClientRole( + idpId: string, + token: string, + name: string, + description: string + ): Promise { + + const payload = { + clientRole: true, + name, + description + }; + + const realmName = process.env.KEYCLOAK_REALM; + + const createClientRolesResponse = await this.commonService.httpPost( + await this.keycloakUrlService.GetClientRoleURL(realmName, idpId), + payload, + this.getAuthHeader(token) + ); + + this.logger.debug( + `createClientRolesResponse ${JSON.stringify( + createClientRolesResponse + )}` + ); + + return 'Client role is created'; + + } + + async generateClientSecret( + idpId: string, + token: string + ): Promise { + + const realmName = process.env.KEYCLOAK_REALM; + + const createClientSercretResponse = await this.commonService.httpPost( + await this.keycloakUrlService.GetClientSecretURL(realmName, idpId), + {}, + this.getAuthHeader(token) + ); + + this.logger.debug( + `ClientRegistrationService create realm client secret ${JSON.stringify( + createClientSercretResponse + )}` + ); + + const getClientSercretResponse = await this.commonService.httpGet( + await this.keycloakUrlService.GetClientSecretURL(realmName, idpId), + this.getAuthHeader(token) + ); + this.logger.debug( + `ClientRegistrationService get client secret ${JSON.stringify( + getClientSercretResponse + )}` + ); + this.logger.log(`${getClientSercretResponse.value}`); + const clientSecret = getClientSercretResponse.value; + + return clientSecret; + + } + async createClient( orgName: string, orgId: string, @@ -323,20 +522,7 @@ export class ClientRegistrationService { publicClient: false, frontchannelLogout: false, fullScopeAllowed: false, - nodeReRegistrationTimeout: 0, - defaultClientScopes: [ - 'web-origins', - 'role_list', - 'profile', - 'roles', - 'email' - ], - optionalClientScopes: [ - 'address', - 'phone', - 'offline_access', - 'microprofile-jwt' - ] + nodeReRegistrationTimeout: 0 }; const createClientResponse = await this.commonService.httpPost( @@ -489,9 +675,6 @@ export class ClientRegistrationService { qs.stringify(payload) , config); - this.logger.debug( - `ClientRegistrationService token ${JSON.stringify(tokenResponse)}` - ); return tokenResponse; } catch (error) { throw error; @@ -631,9 +814,6 @@ export class ClientRegistrationService { qs.stringify(payload) , config); - this.logger.debug( - `ClientRegistrationService token ${JSON.stringify(tokenResponse)}` - ); return tokenResponse; } catch (error) { diff --git a/libs/client-registration/src/interfaces/client.interface.ts b/libs/client-registration/src/interfaces/client.interface.ts new file mode 100644 index 000000000..59e78a52c --- /dev/null +++ b/libs/client-registration/src/interfaces/client.interface.ts @@ -0,0 +1,8 @@ +export interface IClientRoles { + id: string + name: string + description?: string + composite?: boolean + clientRole?: boolean + containerId?: string +} \ No newline at end of file diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 2f03c0019..14f7ddc02 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -260,14 +260,6 @@ export enum CommonConstants { ACTIVE_NON_ADMIN_USER = 1, ALL_NON_ADMIN_USER = 3, - //passwordLess-login - PASSWORDLESS_LOGIN_SCHEMA_ORG = 1, - PASSWORDLESS_LOGIN_SCHEMA_NAME = 'CREDEBL-PLA', - PLATFORM_ADMIN_CRED_DEF_NAME = 'CREDEBL-PLA', - PLATFORM_ADMIN_SCHEMA_VERSION = '1.0', - - LOGIN_PASSWORDLESS = 'passwordless', - LOGIN_PASSWORD = 'password', // Platform admin Details PLATFORM_ADMIN_EMAIL='platform.admin@yopmail.com', diff --git a/libs/common/src/interfaces/agent-service.interface.ts b/libs/common/src/interfaces/agent-service.interface.ts new file mode 100644 index 000000000..ea63de484 --- /dev/null +++ b/libs/common/src/interfaces/agent-service.interface.ts @@ -0,0 +1,53 @@ +export interface InvitationMessage { + message: { + invitationUrl: string; + invitation: { + '@type': string; + '@id': string; + label: string; + recipientKeys: string[]; + serviceEndpoint: string; + routingKeys: string[]; + }; + outOfBandRecord: OutOfBandRecord; + }; + } + + interface OutOfBandRecord { + _tags: Tags; + metadata?: { [key: string]: string }; + id: string; + createdAt: string; + outOfBandInvitation: OutOfBandInvitation; + role: string; + state: string; + autoAcceptConnection: boolean; + reusable: boolean; + updatedAt: string; + } + + interface Tags { + invitationId: string; + recipientKeyFingerprints: string[]; + role: string; + state: string; + threadId: string; + } + + interface OutOfBandInvitation { + '@type': string; + '@id': string; + label: string; + accept: string[]; + handshake_protocols: string[]; + services: OutOfBandInvitationService[]; + } + + interface OutOfBandInvitationService { + id: string; + serviceEndpoint: string; + type: string; + recipientKeys: string[]; + routingKeys: string[]; + } + \ No newline at end of file diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index f574d0612..c75109bd6 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -40,7 +40,7 @@ export const ResponseMessages = { verifyMail: 'Please verify your email', invalidCredentials: 'Invalid Credentials', registerFido: 'Please complete your fido registration', - invitationNotFound: 'Invitation not found', + invitationNotFound: 'Invitation not found for this user', invitationAlreadyAccepted:'Organization invitation already accepted', invitationAlreadyRejected:'Organization invitation already rejected', invalidInvitationStatus: 'Invalid invitation status', @@ -91,7 +91,7 @@ export const ResponseMessages = { rolesNotExist: 'Provided roles not exists in the platform', orgProfile: 'Organization profile not found', userNotFound: 'User not found for the given organization', - orgRoleIdNotFound:'Provided roles not exists in the platform', + orgRoleIdNotFound:'Provided roles not exists for this organization', updateUserRoles: 'Unable to update user roles', deleteOrg: 'Organization not found', deleteOrgInvitation: 'Organization does not have access to delete this invitation', @@ -411,5 +411,10 @@ export const ResponseMessages = { notFound: 'Notification record not found.', invalidUrl: 'Invalid URL' } - } + }, + storeObject: { + success: { + storeObject: 'Data stored successfully' + } + } }; \ No newline at end of file diff --git a/libs/keycloak-url/src/keycloak-url.service.ts b/libs/keycloak-url/src/keycloak-url.service.ts index cb766e604..d10c9f5f8 100644 --- a/libs/keycloak-url/src/keycloak-url.service.ts +++ b/libs/keycloak-url/src/keycloak-url.service.ts @@ -71,6 +71,48 @@ export class KeycloakUrlService { return `${process.env.KEYCLOAK_DOMAIN}admin/realms/${realm}/clients/${clientid}/client-secret`; } + async GetClientRoleURL( + realm: string, + clientid: string, + roleName = '' + ):Promise { + + if ('' === roleName) { + return `${process.env.KEYCLOAK_DOMAIN}admin/realms/${realm}/clients/${clientid}/roles`; + } + + return `${process.env.KEYCLOAK_DOMAIN}admin/realms/${realm}/clients/${clientid}/roles/${roleName}`; + + } + + async GetRealmRoleURL( + realm: string, + roleName = '' + ):Promise { + + if ('' === roleName) { + return `${process.env.KEYCLOAK_DOMAIN}admin/realms/${realm}/roles`; + } + + return `${process.env.KEYCLOAK_DOMAIN}admin/realms/${realm}/roles/${roleName}`; + + } + + async GetClientUserRoleURL( + realm: string, + userId: string, + clientId?: string + ):Promise { + + if (clientId) { + return `${process.env.KEYCLOAK_DOMAIN}admin/realms/${realm}/users/${userId}/role-mappings/clients/${clientId}`; + } + + return `${process.env.KEYCLOAK_DOMAIN}admin/realms/${realm}/users/${userId}/role-mappings/realm`; + + } + + async GetClientIdpURL( realm: string, idp: string diff --git a/libs/org-roles/interfaces/org-roles.interface.ts b/libs/org-roles/interfaces/org-roles.interface.ts index b170d93af..ec63e63c1 100644 --- a/libs/org-roles/interfaces/org-roles.interface.ts +++ b/libs/org-roles/interfaces/org-roles.interface.ts @@ -2,8 +2,8 @@ export interface IOrgRoles { id: string; name: string; description: string; - createDateTime: Date; - createdBy: string; - lastChangedDateTime: Date; - lastChangedBy: string; + createDateTime?: Date; + createdBy?: string; + lastChangedDateTime?: Date; + lastChangedBy?: string; } \ No newline at end of file diff --git a/libs/prisma-service/prisma/migrations/20240220121359_user_org_idproleid/migration.sql b/libs/prisma-service/prisma/migrations/20240220121359_user_org_idproleid/migration.sql new file mode 100644 index 000000000..4d6fed26f --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20240220121359_user_org_idproleid/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "user_org_roles" ADD COLUMN "idpRoleId" UUID; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 714d724d6..28dcfbf44 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -84,6 +84,7 @@ model user_org_roles { userId String @db.Uuid orgRoleId String @db.Uuid orgId String? @db.Uuid + idpRoleId String? @db.Uuid organisation organisation? @relation(fields: [orgId], references: [id]) orgRole org_roles @relation(fields: [orgRoleId], references: [id]) user user @relation(fields: [userId], references: [id]) @@ -484,4 +485,4 @@ model notification { lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) lastChangedBy String @default("1") deletedAt DateTime? @db.Timestamp(6) -} \ No newline at end of file +} diff --git a/libs/user-org-roles/repositories/index.ts b/libs/user-org-roles/repositories/index.ts index 618c6d50d..5458796cd 100644 --- a/libs/user-org-roles/repositories/index.ts +++ b/libs/user-org-roles/repositories/index.ts @@ -18,13 +18,14 @@ export class UserOrgRolesRepository { * @returns user details */ // eslint-disable-next-line camelcase - async createUserOrgRole(userId: string, roleId: string, orgId?: string): Promise { + async createUserOrgRole(userId: string, roleId: string, orgId?: string, idpRoleId?: string): Promise { try { const data: { orgRole: { connect: { id: string } }; user: { connect: { id: string } }; organisation?: { connect: { id: string } }; + idpRoleId?: string } = { orgRole: { connect: { id: roleId } }, user: { connect: { id: userId } } @@ -34,6 +35,10 @@ export class UserOrgRolesRepository { data.organisation = { connect: { id: orgId } }; } + if (idpRoleId) { + data.idpRoleId = idpRoleId; + } + const saveResponse = await this.prisma.user_org_roles.create({ data }); diff --git a/libs/user-org-roles/src/user-org-roles.service.ts b/libs/user-org-roles/src/user-org-roles.service.ts index e5677dc19..0b87cd9cf 100644 --- a/libs/user-org-roles/src/user-org-roles.service.ts +++ b/libs/user-org-roles/src/user-org-roles.service.ts @@ -13,8 +13,8 @@ export class UserOrgRolesService { * @returns user details */ // eslint-disable-next-line camelcase - async createUserOrgRole(userId: string, roleId: string, orgId?: string): Promise { - return this.userOrgRoleRepository.createUserOrgRole(userId, roleId, orgId); + async createUserOrgRole(userId: string, roleId: string, orgId?: string, idpRoleId?: string): Promise { + return this.userOrgRoleRepository.createUserOrgRole(userId, roleId, orgId, idpRoleId); } @@ -46,10 +46,14 @@ export class UserOrgRolesService { * @param roleIds * @returns */ - async updateUserOrgRole(userId: string, orgId: string, roleIds: string[]): Promise { + async updateUserOrgRole( + userId: string, + orgId: string, + roleIdList: {roleId: string, idpRoleId: string}[] + ): Promise { - for (const role of roleIds) { - this.userOrgRoleRepository.createUserOrgRole(userId, role, orgId); + for (const roleData of roleIdList) { + this.userOrgRoleRepository.createUserOrgRole(userId, roleData.roleId, orgId, roleData.idpRoleId); } return true; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5fe5087c8..8edc999d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: '@nestjs/bull': specifier: ^10.0.1 version: 10.0.1(@nestjs/common@10.2.8)(@nestjs/core@10.1.3)(bull@4.11.4) + '@nestjs/cache-manager': + specifier: ^2.1.0 + version: 2.2.1(@nestjs/common@10.2.8)(@nestjs/core@10.1.3)(cache-manager@5.4.0)(rxjs@7.8.1) '@nestjs/common': specifier: ^10.2.7 version: 10.2.8(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) @@ -25,7 +28,7 @@ dependencies: version: 10.1.0(@nestjs/common@10.2.8) '@nestjs/microservices': specifier: ^10.1.3 - version: 10.1.3(@nestjs/common@10.2.8)(@nestjs/core@10.1.3)(@nestjs/websockets@10.1.3)(nats@2.15.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) + version: 10.1.3(@nestjs/common@10.2.8)(@nestjs/core@10.1.3)(@nestjs/websockets@10.1.3)(cache-manager@5.4.0)(nats@2.15.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/passport': specifier: ^10.0.0 version: 10.0.0(@nestjs/common@10.2.8)(passport@0.6.0) @@ -75,8 +78,8 @@ dependencies: specifier: ^9.22.1 version: 9.22.1 aws-sdk: - specifier: ^2.1492.0 - version: 2.1499.0 + specifier: ^2.1510.0 + version: 2.1560.0 bcrypt: specifier: ^5.1.0 version: 5.1.0 @@ -92,6 +95,12 @@ dependencies: bull: specifier: ^4.11.4 version: 4.11.4 + cache-manager: + specifier: ^5.2.4 + version: 5.4.0 + cache-manager-redis-store: + specifier: ^2.0.0 + version: 2.0.0 class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -1208,6 +1217,20 @@ packages: tslib: 2.6.0 dev: false + /@nestjs/cache-manager@2.2.1(@nestjs/common@10.2.8)(@nestjs/core@10.1.3)(cache-manager@5.4.0)(rxjs@7.8.1): + resolution: {integrity: sha512-mXj0zenuyMPJICokwVud4Kjh0+pzBNBAgfpx3I48LozNkd8Qfv/MAhZsb15GihGpbFRxafUo3p6XvtAqRm8GRw==} + peerDependencies: + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + cache-manager: <=5 + rxjs: ^7.0.0 + dependencies: + '@nestjs/common': 10.2.8(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/core': 10.1.3(@nestjs/common@10.2.8)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3)(@nestjs/websockets@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) + cache-manager: 5.4.0 + rxjs: 7.8.1 + dev: false + /@nestjs/cli@10.1.11: resolution: {integrity: sha512-ORkpVFQvcPYtvkLfa0I9dMSPIppkqTOyLqPvJV0wiZofp8iR1+VEVzJVi+PMj53gOkly8TV9+6iy/dBA5Ssrog==} engines: {node: '>= 16'} @@ -1303,7 +1326,7 @@ packages: optional: true dependencies: '@nestjs/common': 10.2.8(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/microservices': 10.1.3(@nestjs/common@10.2.8)(@nestjs/core@10.1.3)(@nestjs/websockets@10.1.3)(nats@2.15.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/microservices': 10.1.3(@nestjs/common@10.2.8)(@nestjs/core@10.1.3)(@nestjs/websockets@10.1.3)(cache-manager@5.4.0)(nats@2.15.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/platform-express': 10.1.3(@nestjs/common@10.2.8)(@nestjs/core@10.1.3) '@nestjs/websockets': 10.1.3(@nestjs/common@10.2.8)(@nestjs/core@10.1.3)(@nestjs/platform-socket.io@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nuxtjs/opencollective': 0.3.2 @@ -1346,7 +1369,7 @@ packages: reflect-metadata: 0.1.13 dev: false - /@nestjs/microservices@10.1.3(@nestjs/common@10.2.8)(@nestjs/core@10.1.3)(@nestjs/websockets@10.1.3)(nats@2.15.1)(reflect-metadata@0.1.13)(rxjs@7.8.1): + /@nestjs/microservices@10.1.3(@nestjs/common@10.2.8)(@nestjs/core@10.1.3)(@nestjs/websockets@10.1.3)(cache-manager@5.4.0)(nats@2.15.1)(reflect-metadata@0.1.13)(rxjs@7.8.1): resolution: {integrity: sha512-IBKefw+DR6v2SaXjPJ8tRT+gQTJUSGN83gxuaA32uCQNW2rK+CyVapgX3fDeM/zJsLfBkdveSMX+R74w5wuk+Q==} peerDependencies: '@grpc/grpc-js': '*' @@ -1385,6 +1408,7 @@ packages: '@nestjs/common': 10.2.8(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/core': 10.1.3(@nestjs/common@10.2.8)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3)(@nestjs/websockets@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/websockets': 10.1.3(@nestjs/common@10.2.8)(@nestjs/core@10.1.3)(@nestjs/platform-socket.io@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) + cache-manager: 5.4.0 iterare: 1.2.1 nats: 2.15.1 reflect-metadata: 0.1.13 @@ -1507,7 +1531,7 @@ packages: dependencies: '@nestjs/common': 10.2.8(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/core': 10.1.3(@nestjs/common@10.2.8)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3)(@nestjs/websockets@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/microservices': 10.1.3(@nestjs/common@10.2.8)(@nestjs/core@10.1.3)(@nestjs/websockets@10.1.3)(nats@2.15.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/microservices': 10.1.3(@nestjs/common@10.2.8)(@nestjs/core@10.1.3)(@nestjs/websockets@10.1.3)(cache-manager@5.4.0)(nats@2.15.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/platform-express': 10.1.3(@nestjs/common@10.2.8)(@nestjs/core@10.1.3) tslib: 2.6.1 dev: true @@ -2672,8 +2696,8 @@ packages: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} - /aws-sdk@2.1499.0: - resolution: {integrity: sha512-kh89lcXx7lP83uVjzRPkOueRoM8gQlep86W9+l3qCTHSLiVJuc0MiPmqCLMPlOAZil+35roFkwWIP2FJ1WcdXg==} + /aws-sdk@2.1560.0: + resolution: {integrity: sha512-nakTZHytnhKWZpwu9d1crqjoegBRG+j1/rflsVnckXxoIwlKM0D/v/NIe+BJmRnCA2aCdwuMx3dtkgLz/AB6VA==} engines: {node: '>= 10.0.0'} dependencies: buffer: 4.9.2 @@ -2685,7 +2709,7 @@ packages: url: 0.10.3 util: 0.12.5 uuid: 8.0.0 - xml2js: 0.5.0 + xml2js: 0.6.2 dev: false /aws-sign2@0.7.0: @@ -3051,6 +3075,20 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + /cache-manager-redis-store@2.0.0: + resolution: {integrity: sha512-bWLWlUg6nCYHiJLCCYxY2MgvwvKnvlWwrbuynrzpjEIhfArD2GC9LtutIHFEPeyGVQN6C+WEw+P3r+BFBwhswg==} + engines: {node: '>= 8.3'} + dependencies: + redis: 3.1.2 + dev: false + + /cache-manager@5.4.0: + resolution: {integrity: sha512-FS7o8vqJosnLpu9rh2gQTo8EOzCRJLF1BJ4XDEUDMqcfvs7SJZs5iuoFTXLauzQ3S5v8sBAST1pCwMaurpyi1A==} + dependencies: + lodash.clonedeep: 4.5.0 + lru-cache: 10.2.0 + promise-coalesce: 1.1.2 + /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: @@ -3643,6 +3681,11 @@ packages: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} dev: false + /denque@1.5.1: + resolution: {integrity: sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==} + engines: {node: '>=0.10'} + dev: false + /denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -6286,7 +6329,6 @@ packages: /lodash.clonedeep@4.5.0: resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} - dev: false /lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} @@ -6332,6 +6374,11 @@ packages: /lru-cache@10.0.1: resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} engines: {node: 14 || >=16.14} + dev: true + + /lru-cache@10.2.0: + resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} + engines: {node: 14 || >=16.14} /lru-cache@4.0.2: resolution: {integrity: sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==} @@ -7320,6 +7367,10 @@ packages: engines: {node: '>=0.4.0'} dev: false + /promise-coalesce@1.1.2: + resolution: {integrity: sha512-zLaJ9b8hnC564fnJH6NFSOGZYYdzrAJn2JUUIwzoQb32fG2QAakpDNM+CZo1km6keXkRXRM+hml1BFAPVnPkxg==} + engines: {node: '>=16'} + /prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -7584,6 +7635,10 @@ packages: resolve: 1.22.2 dev: true + /redis-commands@1.7.0: + resolution: {integrity: sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==} + dev: false + /redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} @@ -7596,6 +7651,16 @@ packages: redis-errors: 1.2.0 dev: false + /redis@3.1.2: + resolution: {integrity: sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==} + engines: {node: '>=10'} + dependencies: + denque: 1.5.1 + redis-commands: 1.7.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + dev: false + /reflect-metadata@0.1.13: resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} @@ -9311,8 +9376,8 @@ packages: xmlbuilder: 11.0.1 dev: false - /xml2js@0.5.0: - resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} + /xml2js@0.6.2: + resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} engines: {node: '>=4.0.0'} dependencies: sax: 1.2.4