Skip to content
Merged
40 changes: 38 additions & 2 deletions apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ import { Reflector } from '@nestjs/core';
import { ResponseMessages } from '@credebl/common/response-messages';
import { validate as isValidUUID } from 'uuid';

interface EcosystemRoles {
lead?: string[];
member?: string[];
}

interface EcosystemRoleGroup {
ecosystem_role?: EcosystemRoles;
}

interface EcosystemAccess {
[ecosystemId: string]: EcosystemRoleGroup;
}

@Injectable()
export class EcosystemRolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {} // eslint-disable-next-line array-callback-return
Expand Down Expand Up @@ -43,12 +56,34 @@ export class EcosystemRolesGuard implements CanActivate {
orgId = '';
}

const isPlatformAdmin = user.email === process.env.PLATFORM_ADMIN_EMAIL;
const roles: string[] = [];

const ecosystemAccess: EcosystemAccess | undefined = user?.ecosystem_access;

const hasLead = Object.values(ecosystemAccess || {}).some(
(eco: EcosystemRoleGroup) => 0 < eco?.ecosystem_role?.lead?.length
);

if (user?.ecosystemRoles && requiredRolesNames.some((role: string) => user.ecosystemRoles.includes(role))) {
const hasMember = Object.values(ecosystemAccess || {}).some(
(eco: EcosystemRoleGroup) => 0 < eco?.ecosystem_role?.member?.length
);

if (hasLead && !roles.includes(OrgRoles.ECOSYSTEM_LEAD)) {
roles.push(OrgRoles.ECOSYSTEM_LEAD);
}

if (hasMember && !roles.includes(OrgRoles.ECOSYSTEM_MEMBER)) {
roles.push(OrgRoles.ECOSYSTEM_MEMBER);
}

const ecosystemRoleAccess = requiredRolesNames.some((role) => roles.includes(role));

if (ecosystemRoleAccess) {
return true;
}

const isPlatformAdmin = user.email === process.env.PLATFORM_ADMIN_EMAIL;

if (isPlatformAdmin && requiredRolesNames.includes(OrgRoles.PLATFORM_ADMIN)) {
// eslint-disable-next-line array-callback-return
const isPlatformAdminFlag = user.userOrgRoles.find((orgDetails) => {
Expand Down Expand Up @@ -77,6 +112,7 @@ export class EcosystemRolesGuard implements CanActivate {
description: ResponseMessages.errorMessages.forbidden
});
}

return roleAccess;
}

Expand Down
16 changes: 16 additions & 0 deletions apps/api-gateway/src/authz/jwt-payload.interface.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,4 +23,7 @@ export interface JwtPayload {
permissions: string[];
email?: string;
sid: string;

resource_access?: Record<string, ResourceAccess>;
ecosystem_access?: Record<string, EcosystemAccess>;
}
28 changes: 5 additions & 23 deletions apps/api-gateway/src/authz/jwt.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,12 +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';
import { OrganizationService } from '../organization/organization.service';
Expand All @@ -24,8 +24,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(),
Expand Down Expand Up @@ -74,20 +73,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']);
Expand Down Expand Up @@ -122,11 +107,8 @@ 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 (userDetails && payload?.ecosystem_access) {
userDetails.ecosystem_access = payload.ecosystem_access;
}

return {
Expand Down
4 changes: 2 additions & 2 deletions apps/api-gateway/src/ecosystem/ecosystem.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).'
Expand Down Expand Up @@ -210,7 +210,7 @@ export class EcosystemController {
required: true,
type: String
})
@Roles(OrgRoles.PLATFORM_ADMIN, OrgRoles.ECOSYSTEM_LEAD)
@Roles(OrgRoles.PLATFORM_ADMIN, OrgRoles.ECOSYSTEM_LEAD, OrgRoles.ECOSYSTEM_MEMBER)
async getEcosystems(
@User() reqUser: user,
@Res() res: Response,
Expand Down
11 changes: 8 additions & 3 deletions apps/api-gateway/src/ecosystem/ecosystem.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,15 @@ export class EcosystemService {
return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-intents', { ecosystemId, intentId, pageDetail });
}

async getVerificationTemplates(orgId: string, pageDetail: IPaginationSortingDto): Promise<PaginatedResponse<object>> {
async getVerificationTemplates(
ecosystemId: string,
pageDetail: IPaginationSortingDto,
orgId?: string
): Promise<PaginatedResponse<object>> {
return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-verification-templates-by-org-id', {
orgId,
pageDetail
ecosystemId,
pageDetail,
orgId
});
}

Expand Down
33 changes: 22 additions & 11 deletions apps/api-gateway/src/ecosystem/intent/intent.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export class IntentController {
@Body() createIntentDto: CreateIntentDto,
@Param(
'ecosystemId',
TrimStringParamPipe,
new ParseUUIDPipe({
exceptionFactory: (): Error => {
throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfEcosystemId);
Expand Down Expand Up @@ -274,38 +275,48 @@ export class IntentController {
/**
* Get template details by org ID
*/
@Get('/org/:orgId/verification-templates')
@Get('/ecosystem/:ecosystemId/verification-templates')
@Roles(OrgRoles.ECOSYSTEM_LEAD, OrgRoles.OWNER)
@UseGuards(AuthGuard('jwt'), EcosystemRolesGuard)
@ApiBearerAuth()
@ApiOperation({
summary: 'Get template details by orgId',
description: 'Retrieve verification template details by orgId'
summary: 'Get verification templates by ecosystemId',
description: 'Retrieve verification templates of all member orgs in ecosystem'
})
@ApiParam({
@ApiQuery({
name: 'orgId',
required: true,
required: false,
description: 'Organization ID'
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Template details fetched successfully'
})
async getTemplateByIntentId(
async getTemplatesByEcosystemId(
@Param(
'orgId',
'ecosystemId',
TrimStringParamPipe,
new ParseUUIDPipe({
exceptionFactory: (): Error => {
throw new BadRequestException(ResponseMessages.ecosystem.error.invalidOrgId);
throw new BadRequestException(ResponseMessages.ecosystem.error.invalidEcosystemId);
}
})
)
orgId: string,
ecosystemId: string,
@Query() pageDto: PaginationDto,
@Res() res: Response,
@Query() pageDto: PaginationDto
@Query(
'orgId',
new ParseUUIDPipe({
optional: true,
exceptionFactory: (): Error => {
throw new BadRequestException(ResponseMessages.ecosystem.error.invalidOrgId);
}
})
)
orgId?: string
): Promise<Response> {
const templates = await this.ecosystemService.getVerificationTemplates(orgId, pageDto);
const templates = await this.ecosystemService.getVerificationTemplates(ecosystemId, pageDto, orgId);

return res.status(HttpStatus.OK).json({
statusCode: HttpStatus.OK,
Expand Down
3 changes: 1 addition & 2 deletions apps/api-gateway/src/platform/platform.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ export class PlatformController {
description: 'Invitations fetched successfully'
})
@Roles(OrgRoles.PLATFORM_ADMIN)
@UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemFeatureGuard)
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
@ApiBearerAuth()
async getInvitations(
@User() reqUser: user,
Expand Down Expand Up @@ -324,7 +324,6 @@ export class PlatformController {
status: HttpStatus.OK,
description: 'Ecosystem status fetched successfully'
})
@Roles(OrgRoles.PLATFORM_ADMIN)
@UseGuards(AuthGuard('jwt'), EcosystemRolesGuard)
@ApiBearerAuth()
async getEcosystemEnableStatus(@Res() res: Response): Promise<Response> {
Expand Down
17 changes: 16 additions & 1 deletion apps/ecosystem/interfaces/ecosystem.interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EcosystemOrgStatus, InvitationViewRole } from '@credebl/enum/enum';
import { Prisma, PrismaClient } from '@prisma/client';
import { Prisma, PrismaClient, SignerOption } from '@prisma/client';

import { CommonTableColumns } from '@credebl/common/interfaces/interface';
import { OrgRoles } from 'libs/org-roles/enums';
Expand Down Expand Up @@ -244,3 +244,18 @@ export interface IGetEcosystemOrgsResponse {
name: string | null;
} | null;
}

export interface IVerificationTemplateList {
id: string;
name: string;
orgId: string;
createDateTime: Date;
createdBy: string;
lastChangedDateTime: Date;
lastChangedBy: string;
signerOption: SignerOption;
organisation: {
id: string;
name: string;
};
}
Loading