From f28c44014f3071f53da7fea5f11380a3d33a99d1 Mon Sep 17 00:00:00 2001 From: Tipu_Singh Date: Fri, 13 Feb 2026 10:18:38 +0530 Subject: [PATCH 1/6] main sync wih ecosystem Signed-off-by: Tipu_Singh --- apps/ecosystem/src/ecosystem.service.ts | 4 ---- libs/client-registration/src/client-registration.service.ts | 2 +- libs/common/src/nats.config.ts | 6 +++--- libs/prisma-service/prisma/seed.ts | 3 +-- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index b5c143efa..f707bb418 100755 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -210,10 +210,6 @@ export class EcosystemService { throw new Error('Error fetching user'); } - // if (!invitation) { - // throw new ForbiddenException(ResponseMessages.ecosystem.error.invitationRequired); - // } - const ecosystem = await this.prisma.$transaction(async (tx) => { const newEcosystem = await this.ecosystemRepository.createNewEcosystem(createEcosystemDto, tx); diff --git a/libs/client-registration/src/client-registration.service.ts b/libs/client-registration/src/client-registration.service.ts index b49ab13b2..4e02f8ae3 100644 --- a/libs/client-registration/src/client-registration.service.ts +++ b/libs/client-registration/src/client-registration.service.ts @@ -13,10 +13,10 @@ import { ClientTokenDto } from './dtos/client-token.dto'; import { CommonConstants } from '@credebl/common/common.constant'; import { CommonService } from '@credebl/common'; import { CreateUserDto } from './dtos/create-user.dto'; +import { EcosystemServiceRole } from '@credebl/enum/enum'; import { IClientRoles } from './interfaces/client.interface'; import { IFormattedResponse } from '@credebl/common/interfaces/interface'; import { JwtService } from '@nestjs/jwt'; -import { EcosystemServiceRole } from '@credebl/enum/enum'; import { KeycloakUrlService } from '@credebl/keycloak-url'; import { KeycloakUserRegistrationDto } from 'apps/user/dtos/keycloak-register.dto'; import { ResponseMessages } from '@credebl/common/response-messages'; diff --git a/libs/common/src/nats.config.ts b/libs/common/src/nats.config.ts index 7fd1ff011..0f142a6f1 100644 --- a/libs/common/src/nats.config.ts +++ b/libs/common/src/nats.config.ts @@ -9,13 +9,13 @@ export const getNatsOptions = ( authenticator?: Authenticator; maxReconnectAttempts: NATSReconnects; reconnectTimeWait: NATSReconnects; - queue?: string; + // queue?: string; } => { const baseOptions = { servers: `${process.env.NATS_URL}`.split(','), maxReconnectAttempts: NATSReconnects.maxReconnectAttempts, - reconnectTimeWait: NATSReconnects.reconnectTimeWait, - queue: serviceName + reconnectTimeWait: NATSReconnects.reconnectTimeWait + // queue: serviceName }; if (nkeySeed) { diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index 4204389fa..f9eafdd1c 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -691,14 +691,13 @@ export async function createKeycloakUser(): Promise { 'Missing required environment variables for either PLATFORM_ADMIN_USER_PASSWORD or KEYCLOAK_DOMAIN or KEYCLOAK_REALM or PLATFORM_ADMIN_KEYCLOAK_ID or PLATFORM_ADMIN_KEYCLOAK_SECRET or CRYPTO_PRIVATE_KEY' ); } - const decryptedPassword = CryptoJS.AES.decrypt(platformAdminData.password, CRYPTO_PRIVATE_KEY); const token = await getKeycloakToken(); const user = { username: cachedConfig.platformEmail, email: cachedConfig.platformEmail, firstName: cachedConfig.platformName, lastName: cachedConfig.platformName, - password: decryptedPassword.toString(CryptoJS.enc.Utf8) + password: 'Password@1' }; const res = await fetch(`${KEYCLOAK_DOMAIN}admin/realms/${KEYCLOAK_REALM}/users`, { method: 'POST', From 13cb21da1db276fef292a232dda8aab3fe9290ea Mon Sep 17 00:00:00 2001 From: Tipu_Singh Date: Tue, 17 Feb 2026 14:02:16 +0530 Subject: [PATCH 2/6] feat: added script to run keyclock configuration on init Signed-off-by: Tipu_Singh --- libs/common/src/nats.config.ts | 6 +++--- libs/keycloak-config/src/keycloak-config.service.ts | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libs/common/src/nats.config.ts b/libs/common/src/nats.config.ts index 0f142a6f1..7fd1ff011 100644 --- a/libs/common/src/nats.config.ts +++ b/libs/common/src/nats.config.ts @@ -9,13 +9,13 @@ export const getNatsOptions = ( authenticator?: Authenticator; maxReconnectAttempts: NATSReconnects; reconnectTimeWait: NATSReconnects; - // queue?: string; + queue?: string; } => { const baseOptions = { servers: `${process.env.NATS_URL}`.split(','), maxReconnectAttempts: NATSReconnects.maxReconnectAttempts, - reconnectTimeWait: NATSReconnects.reconnectTimeWait - // queue: serviceName + reconnectTimeWait: NATSReconnects.reconnectTimeWait, + queue: serviceName }; if (nkeySeed) { diff --git a/libs/keycloak-config/src/keycloak-config.service.ts b/libs/keycloak-config/src/keycloak-config.service.ts index a0fc0baf0..e237bd922 100644 --- a/libs/keycloak-config/src/keycloak-config.service.ts +++ b/libs/keycloak-config/src/keycloak-config.service.ts @@ -4,9 +4,10 @@ import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { ProtocolMapperResult, UnmanagedAttributePolicy } from '@credebl/enum/enum'; + +import { ClientRegistrationService } from '@credebl/client-registration'; import { CommonService } from '@credebl/common'; import { KeycloakUrlService } from '@credebl/keycloak-url'; -import { ClientRegistrationService } from '@credebl/client-registration'; @Injectable() export class KeycloakConfigService implements OnModuleInit { From 8d98969d00482b3f6db5c7fcb77d7199532d218d Mon Sep 17 00:00:00 2001 From: pranalidhanavade Date: Mon, 2 Mar 2026 01:27:48 +0530 Subject: [PATCH 3/6] refactor:ecosystem exist validation in ecosystem roleGaurd Signed-off-by: pranalidhanavade --- .../src/authz/guards/ecosystem-roles.guard.ts | 55 ++++++++++++++++++- .../src/authz/jwt-payload.interface.ts | 16 ++++++ apps/api-gateway/src/authz/jwt.strategy.ts | 28 ++-------- .../src/ecosystem/ecosystem.controller.ts | 4 +- .../src/ecosystem/intent/intent.controller.ts | 1 + 5 files changed, 77 insertions(+), 27 deletions(-) diff --git a/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts b/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts index 60f1fc5b7..bb8b30bec 100644 --- a/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts +++ b/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts @@ -7,6 +7,13 @@ import { Reflector } from '@nestjs/core'; import { ResponseMessages } from '@credebl/common/response-messages'; import { validate as isValidUUID } from 'uuid'; +interface EcosystemAccessEntry { + ecosystem_role?: { + lead?: string[]; + member?: string[]; + }; +} + @Injectable() export class EcosystemRolesGuard implements CanActivate { constructor(private readonly reflector: Reflector) {} // eslint-disable-next-line array-callback-return @@ -44,9 +51,52 @@ export class EcosystemRolesGuard implements CanActivate { } const isPlatformAdmin = user.email === process.env.PLATFORM_ADMIN_EMAIL; + /** + * ===================================== + * Ecosystem validation (JWT based only) + * ===================================== + */ - if (user?.ecosystemRoles && requiredRolesNames.some((role: string) => user.ecosystemRoles.includes(role))) { - return true; + let ecosystemId = ''; + + switch (true) { + case 'string' === typeof reqData.params?.ecosystemId: + ecosystemId = reqData.params.ecosystemId.trim(); + break; + case 'string' === typeof reqData.query?.ecosystemId: + ecosystemId = reqData.query.ecosystemId.trim(); + break; + case 'string' === typeof reqData.body?.ecosystemId: + ecosystemId = reqData.body.ecosystemId.trim(); + break; + default: + ecosystemId = ''; + } + + if (ecosystemId) { + if (!isValidUUID(ecosystemId)) { + throw new BadRequestException(ResponseMessages.ecosystem?.error?.invalidEcosystemId || 'Invalid ecosystem id'); + } + + const ecosystemAccessValues = Object.values(user?.ecosystem_access || {}); + + if (!ecosystemAccessValues.length) { + throw new ForbiddenException(ResponseMessages.ecosystem?.error?.ecosystemNotFound || 'Ecosystem not found'); + } + + const [ecosystemEntry] = ecosystemAccessValues as EcosystemAccessEntry[]; + + const leadList = ecosystemEntry?.ecosystem_role?.lead ?? []; + const memberList = ecosystemEntry?.ecosystem_role?.member ?? []; + + const hasAccess = leadList.includes(ecosystemId) || memberList.includes(ecosystemId); + + if (!hasAccess) { + throw new ForbiddenException(ResponseMessages.ecosystem?.error?.ecosystemNotFound || 'Ecosystem not found'); + } + + // Optional: attach for downstream usage + user.selectedEcosystem = ecosystemId; } if (isPlatformAdmin && requiredRolesNames.includes(OrgRoles.PLATFORM_ADMIN)) { @@ -77,6 +127,7 @@ export class EcosystemRolesGuard implements CanActivate { description: ResponseMessages.errorMessages.forbidden }); } + return roleAccess; } diff --git a/apps/api-gateway/src/authz/jwt-payload.interface.ts b/apps/api-gateway/src/authz/jwt-payload.interface.ts index da946d04f..5655d2355 100644 --- a/apps/api-gateway/src/authz/jwt-payload.interface.ts +++ b/apps/api-gateway/src/authz/jwt-payload.interface.ts @@ -1,3 +1,16 @@ +export interface ResourceAccess { + roles: string[]; +} + +export interface EcosystemRole { + lead?: string[]; + member?: string[]; +} + +export interface EcosystemAccess { + ecosystem_role: EcosystemRole; +} + export interface JwtPayload { iss: string; sub: string; @@ -10,4 +23,7 @@ export interface JwtPayload { permissions: string[]; email?: string; sid: string; + + resource_access?: Record; + ecosystem_access?: Record; } diff --git a/apps/api-gateway/src/authz/jwt.strategy.ts b/apps/api-gateway/src/authz/jwt.strategy.ts index 98b786bdd..d1f317e1c 100644 --- a/apps/api-gateway/src/authz/jwt.strategy.ts +++ b/apps/api-gateway/src/authz/jwt.strategy.ts @@ -1,11 +1,12 @@ +/* eslint-disable camelcase */ import * as dotenv from 'dotenv'; import * as jwt from 'jsonwebtoken'; +import { CommonConstants, uuidRegex } from '@credebl/common/common.constant'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { Injectable, Logger, NotFoundException, UnauthorizedException } from '@nestjs/common'; import { AuthzService } from './authz.service'; -import { CommonConstants, uuidRegex } from '@credebl/common/common.constant'; import { EcosystemService } from '../ecosystem/ecosystem.service'; import { IOrganization } from '@credebl/common/interfaces/organization.interface'; import { JwtPayload } from './jwt-payload.interface'; @@ -24,8 +25,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { constructor( private readonly usersService: UserService, private readonly organizationService: OrganizationService, - private readonly authzService: AuthzService, - private readonly ecosystemService: EcosystemService + private readonly authzService: AuthzService ) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), @@ -74,20 +74,6 @@ export class JwtStrategy extends PassportStrategy(Strategy) { if (payload?.email) { userInfo = await this.usersService.getUserByUserIdInKeycloak(payload?.email); } - let ecosystemRole = null; - if (userInfo?.id) { - try { - const user = await this.ecosystemService.getUserByKeycloakId(userInfo.id); - if (user?.id) { - const ecosystem = await this.ecosystemService.getEcosystemDetailsByUserId(user.id); - if (ecosystem?.id) { - ecosystemRole = await this.ecosystemService.getEcosystemOrgDetailsByUserId(user.id, ecosystem.id); - } - } - } catch (error) { - this.logger.warn('Failed to fetch ecosystem roles', JSON.stringify(error)); - } - } if (payload.hasOwnProperty('client_id') && uuidRegex.test(payload['client_id'])) { const orgDetails: IOrganization = await this.organizationService.findOrganizationOwner(payload['client_id']); @@ -122,13 +108,9 @@ export class JwtStrategy extends PassportStrategy(Strategy) { userDetails['userRole'] = userInfo?.['attributes']?.userRole; } - if (Array.isArray(ecosystemRole) && 0 < ecosystemRole.length) { - const ecosystemRoleList = [ - ...new Set(ecosystemRole.map((record: { ecosystemRole: { name: string } }) => record.ecosystemRole.name)) - ]; - userDetails.ecosystemRoles = ecosystemRoleList; + if (payload?.ecosystem_access) { + userDetails.ecosystem_access = payload.ecosystem_access; } - return { ...userDetails, ...payload diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index acd6f5281..761fb6d48 100755 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -95,7 +95,7 @@ export class EcosystemController { return res.status(HttpStatus.CREATED).json(finalResponse); } - @Post('/invitation/status') + @Put('/invitation/status') @ApiOperation({ summary: 'Update invitation status', description: 'Updates the status of an existing ecosystem invitation (accept or reject).' @@ -108,7 +108,7 @@ export class EcosystemController { name: 'status', enum: [Invitation.REJECTED, Invitation.ACCEPTED] }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard) @ApiBearerAuth() async updateEcosystemInvitationStatus( @Body() updateInvitation: UpdateEcosystemInvitationDto, diff --git a/apps/api-gateway/src/ecosystem/intent/intent.controller.ts b/apps/api-gateway/src/ecosystem/intent/intent.controller.ts index 454ca6159..6b82494ce 100755 --- a/apps/api-gateway/src/ecosystem/intent/intent.controller.ts +++ b/apps/api-gateway/src/ecosystem/intent/intent.controller.ts @@ -85,6 +85,7 @@ export class IntentController { @Body() createIntentDto: CreateIntentDto, @Param( 'ecosystemId', + TrimStringParamPipe, new ParseUUIDPipe({ exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfEcosystemId); From fb99f722f2358a9a03e3b75152bd57ae20cb744b Mon Sep 17 00:00:00 2001 From: pranalidhanavade Date: Mon, 2 Mar 2026 13:50:11 +0530 Subject: [PATCH 4/6] fix: empty string validation for ecosystemId when only space is entered Signed-off-by: pranalidhanavade --- .../src/authz/guards/ecosystem-roles.guard.ts | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts b/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts index bb8b30bec..cf547a314 100644 --- a/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts +++ b/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts @@ -7,7 +7,7 @@ import { Reflector } from '@nestjs/core'; import { ResponseMessages } from '@credebl/common/response-messages'; import { validate as isValidUUID } from 'uuid'; -interface EcosystemAccessEntry { +interface EcosystemRoleGroup { ecosystem_role?: { lead?: string[]; member?: string[]; @@ -51,14 +51,14 @@ export class EcosystemRolesGuard implements CanActivate { } const isPlatformAdmin = user.email === process.env.PLATFORM_ADMIN_EMAIL; - /** - * ===================================== - * Ecosystem validation (JWT based only) - * ===================================== - */ let ecosystemId = ''; + const ecosystemIdExists = + 'undefined' !== typeof reqData.params?.ecosystemId || + 'undefined' !== typeof reqData.query?.ecosystemId || + 'undefined' !== typeof reqData.body?.ecosystemId; + switch (true) { case 'string' === typeof reqData.params?.ecosystemId: ecosystemId = reqData.params.ecosystemId.trim(); @@ -73,30 +73,36 @@ export class EcosystemRolesGuard implements CanActivate { ecosystemId = ''; } - if (ecosystemId) { + if (ecosystemIdExists) { + if (!ecosystemId) { + throw new BadRequestException(ResponseMessages.ecosystem.error.ecosystemIdIsRequired); + } if (!isValidUUID(ecosystemId)) { throw new BadRequestException(ResponseMessages.ecosystem?.error?.invalidEcosystemId || 'Invalid ecosystem id'); } - const ecosystemAccessValues = Object.values(user?.ecosystem_access || {}); + const ecosystemAccess = user?.ecosystem_access; - if (!ecosystemAccessValues.length) { - throw new ForbiddenException(ResponseMessages.ecosystem?.error?.ecosystemNotFound || 'Ecosystem not found'); + if (!ecosystemAccess) { + throw new ForbiddenException( + ResponseMessages.ecosystem?.error?.ecosystemNotFound || 'User does not have ecosystem access' + ); } - const [ecosystemEntry] = ecosystemAccessValues as EcosystemAccessEntry[]; - - const leadList = ecosystemEntry?.ecosystem_role?.lead ?? []; - const memberList = ecosystemEntry?.ecosystem_role?.member ?? []; - - const hasAccess = leadList.includes(ecosystemId) || memberList.includes(ecosystemId); + const hasAccess = Object.values(ecosystemAccess).some((entry: EcosystemRoleGroup) => { + const leadList = entry?.ecosystem_role?.lead ?? []; + const memberList = entry?.ecosystem_role?.member ?? []; + return leadList.includes(ecosystemId) || memberList.includes(ecosystemId); + }); if (!hasAccess) { - throw new ForbiddenException(ResponseMessages.ecosystem?.error?.ecosystemNotFound || 'Ecosystem not found'); + throw new ForbiddenException( + ResponseMessages.ecosystem?.error?.ecosystemNotFound || 'User does not have access to this ecosystem' + ); } - // Optional: attach for downstream usage user.selectedEcosystem = ecosystemId; + return true; } if (isPlatformAdmin && requiredRolesNames.includes(OrgRoles.PLATFORM_ADMIN)) { @@ -122,7 +128,7 @@ export class EcosystemRolesGuard implements CanActivate { const roleAccess = requiredRoles.some((role) => orgRoles.includes(role)); if (!roleAccess) { - throw new ForbiddenException(ResponseMessages.organisation.error.roleNotMatch, { + throw new ForbiddenException('1111111', { cause: new Error('error'), description: ResponseMessages.errorMessages.forbidden }); @@ -157,7 +163,7 @@ export class EcosystemRolesGuard implements CanActivate { // Sending user friendly message if a user attempts to access an API that is inaccessible to their role const roleAccess = requiredRoles.some((role) => user.selectedOrg?.orgRoles.includes(role)); if (!roleAccess) { - throw new ForbiddenException(ResponseMessages.organisation.error.roleNotMatch, { + throw new ForbiddenException('222222', { cause: new Error('error'), description: ResponseMessages.errorMessages.forbidden }); From 17dc305124ae1c9418529049acb0a155deaa7a18 Mon Sep 17 00:00:00 2001 From: pranalidhanavade Date: Wed, 4 Mar 2026 11:42:14 +0530 Subject: [PATCH 5/6] fix: changes in response messages Signed-off-by: pranalidhanavade --- apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts b/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts index cf547a314..16f4be1dd 100644 --- a/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts +++ b/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts @@ -128,7 +128,7 @@ export class EcosystemRolesGuard implements CanActivate { const roleAccess = requiredRoles.some((role) => orgRoles.includes(role)); if (!roleAccess) { - throw new ForbiddenException('1111111', { + throw new ForbiddenException(ResponseMessages.organisation.error.roleNotMatch, { cause: new Error('error'), description: ResponseMessages.errorMessages.forbidden }); @@ -163,7 +163,7 @@ export class EcosystemRolesGuard implements CanActivate { // Sending user friendly message if a user attempts to access an API that is inaccessible to their role const roleAccess = requiredRoles.some((role) => user.selectedOrg?.orgRoles.includes(role)); if (!roleAccess) { - throw new ForbiddenException('222222', { + throw new ForbiddenException(ResponseMessages.organisation.error.roleNotMatch, { cause: new Error('error'), description: ResponseMessages.errorMessages.forbidden }); From a27bc21a9db61a7d00c55f5bca09c07cd3acabd2 Mon Sep 17 00:00:00 2001 From: pranalidhanavade Date: Wed, 4 Mar 2026 11:48:02 +0530 Subject: [PATCH 6/6] fix: role access for getting ecosystem og loggedin user Signed-off-by: pranalidhanavade --- apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 761fb6d48..4a764e764 100755 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -207,10 +207,11 @@ export class EcosystemController { }) @ApiQuery({ name: 'orgId', - required: true, + //Need to check this once + required: false, type: String }) - @Roles(OrgRoles.PLATFORM_ADMIN, OrgRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ECOSYSTEM_LEAD) async getEcosystems( @User() reqUser: user, @Res() res: Response, @@ -218,6 +219,7 @@ export class EcosystemController { @Query( 'orgId', new ParseUUIDPipe({ + optional: true, exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidOrgId); }