Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/api-gateway/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ConnectionModule } from './connection/connection.module';
import { ContextModule } from '@credebl/context/contextModule';
import { CredentialDefinitionModule } from './credential-definition/credential-definition.module';
import { EcosystemModule } from './ecosystem/ecosystem.module';
import { EcosystemSwaggerFilter } from './authz/guards/ecosystem-swagger.filter';
import { FidoModule } from './fido/fido.module';
import { GeoLocationModule } from './geo-location/geo-location.module';
import { GlobalConfigModule } from '@credebl/config/global-config.module';
Expand Down Expand Up @@ -77,6 +78,7 @@ import { shouldLoadOidcModules } from '@credebl/common/common.utils';
controllers: [AppController],
providers: [
AppService,
EcosystemSwaggerFilter,
{
provide: MICRO_SERVICE_NAME,
useValue: 'APIGATEWAY'
Expand Down
20 changes: 20 additions & 0 deletions apps/api-gateway/src/authz/guards/ecosystem-feature-guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { CanActivate, ForbiddenException, Injectable, Scope } from '@nestjs/common';

import { EcosystemRepository } from 'apps/ecosystem/repositories/ecosystem.repository';
import { ResponseMessages } from '@credebl/common/response-messages';

@Injectable({ scope: Scope.REQUEST })
export class EcosystemFeatureGuard implements CanActivate {
constructor(private readonly ecosystemRepository: EcosystemRepository) {}

async canActivate(): Promise<boolean> {
const config = await this.ecosystemRepository.getPlatformConfig();
const enabled = Boolean(config?.isEcosystemEnabled);

if (!enabled) {
throw new ForbiddenException(ResponseMessages.ecosystem.error.featureIsDisabled);
}

return true;
}
}
Comment thread
pranalidhanavade marked this conversation as resolved.
34 changes: 34 additions & 0 deletions apps/api-gateway/src/authz/guards/ecosystem-swagger.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { EcosystemRepository } from 'apps/ecosystem/repositories/ecosystem.repository';
import { Injectable } from '@nestjs/common';
import { OpenAPIObject } from '@nestjs/swagger';

@Injectable()
export class EcosystemSwaggerFilter {
constructor(private readonly ecosystemRepository: EcosystemRepository) {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async filterDocument(document: OpenAPIObject): Promise<OpenAPIObject> {
const config = await this.ecosystemRepository.getPlatformConfig();
const enabled = Boolean(config?.isEcosystemEnabled);

if (!enabled) {
if (!document.paths) {
return document;
}
Object.keys(document.paths).forEach((path) => {
Object.keys(document.paths[path]).forEach((method) => {
const operation = document.paths[path][method];

if (operation.tags?.includes('ecosystem')) {
delete document.paths[path][method];
}
});

if (0 === Object.keys(document.paths[path]).length) {
delete document.paths[path];
}
});
}
Comment thread
pranalidhanavade marked this conversation as resolved.

return document;
}
}
11 changes: 11 additions & 0 deletions apps/api-gateway/src/ecosystem/dtos/enable-ecosystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean } from 'class-validator';

export class EnableEcosystemDto {
@ApiProperty({
example: true,
description: 'Enable or disable ecosystem creation'
})
@IsBoolean()
isEcosystemEnabled: boolean;
}
49 changes: 31 additions & 18 deletions apps/api-gateway/src/ecosystem/ecosystem.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import { ResponseMessages } from '@credebl/common/response-messages';
import { Roles } from '../authz/decorators/roles.decorator';
import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto';
import { InviteMemberToEcosystemDto, UpdateEcosystemInvitationDto } from './dtos/send-ecosystem-invitation';
import { OrgRolesGuard } from '../authz/guards/org-roles.guard';
import { EcosystemRolesGuard } from '../authz/guards/ecosystem-roles.guard';
import { user } from '@prisma/client';
import { User } from '../authz/decorators/user.decorator';
Expand All @@ -52,10 +51,12 @@ import { GetAllIntentTemplatesResponseDto } from '../utilities/dtos/get-all-inte
import { GetAllIntentTemplatesDto } from '../utilities/dtos/get-all-intent-templates.dto';
import { GetIntentTemplateByIntentAndOrgDto } from '../utilities/dtos/get-intent-template-by-intent-and-org.dto';
import { CreateIntentTemplateDto, UpdateIntentTemplateDto } from '../utilities/dtos/intent-template.dto';
import { EcosystemFeatureGuard } from '../authz/guards/ecosystem-feature-guard';

@UseFilters(CustomExceptionFilter)
@Controller('ecosystem')
@ApiTags('ecosystem')
@UseGuards(EcosystemFeatureGuard)
@ApiUnauthorizedResponse({
description: 'Unauthorized',
type: UnauthorizedErrorDto
Expand Down Expand Up @@ -198,20 +199,16 @@ export class EcosystemController {
* Get all ecosystems (platform admin)
* @returns All ecosystems from platform
*/
@Get()
@UseGuards(AuthGuard('jwt'), EcosystemRolesGuard)
@ApiBearerAuth()
@Get('/all-ecosystem')
@ApiOperation({
summary: 'Get all ecosystems (platform admin)',
description: 'Fetch all ecosystems available on the platform'
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Ecosystems fetched successfully'
summary: 'Get ecosystems',
description: 'Fetch ecosystems for Platform Admin or Ecosystem Lead'
})
@Roles(OrgRoles.PLATFORM_ADMIN)
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
@ApiBearerAuth()
async getAllEcosystems(@User() reqUser: user, @Res() res: Response): Promise<Response> {
const ecosystems = await this.ecosystemService.getAllEcosystems();
@Roles(OrgRoles.PLATFORM_ADMIN, OrgRoles.ECOSYSTEM_LEAD)
async getEcosystems(@User() reqUser: user, @Res() res: Response): Promise<Response> {
const ecosystems = await this.ecosystemService.getEcosystems(reqUser.id);

return res.status(HttpStatus.OK).json({
statusCode: HttpStatus.OK,
Expand Down Expand Up @@ -800,7 +797,6 @@ export class EcosystemController {

return res.status(HttpStatus.OK).json(finalResponse);
}

/**
* Delete intent
* @param id Intent ID
Expand All @@ -820,12 +816,29 @@ export class EcosystemController {
type: ApiResponseDto
})
async deleteIntent(
@Param('ecosystemId') ecosystemId: string,
@Param('intentId') intentId: string,
@User() user: IUserRequest,
@Param(
'ecosystemId',
new ParseUUIDPipe({
exceptionFactory: (): Error => {
throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfEcosystemId);
}
})
)
ecosystemId: string,
@Param(
'intentId',
new ParseUUIDPipe({
exceptionFactory: (): Error => {
throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfIntentId);
}
})
)
intentId: string,

@User() user: user,
@Res() res: Response
): Promise<Response> {
const intent = await this.ecosystemService.deleteIntent(ecosystemId, intentId, user);
const intent = await this.ecosystemService.deleteIntent(ecosystemId, intentId, user.id);

return res.status(HttpStatus.OK).json({
statusCode: HttpStatus.OK,
Expand Down
2 changes: 1 addition & 1 deletion apps/api-gateway/src/ecosystem/ecosystem.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ import { getNatsOptions } from '@credebl/common/nats.config';
],
controllers: [EcosystemController],
providers: [EcosystemService, NATSClient],
exports: [EcosystemService]
exports: [EcosystemService, EcosystemServiceModule]
})
export class EcosystemModule {}
9 changes: 5 additions & 4 deletions apps/api-gateway/src/ecosystem/ecosystem.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ export class EcosystemService {
* @param userId
* @returns All ecosystems from platform
*/
async getAllEcosystems(): Promise<IEcosystem[]> {
return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-all-ecosystems', {});
async getEcosystems(userId: string): Promise<IEcosystem[]> {
return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-ecosystems', { userId });
}

/**
*
* @param ecosystemId
Expand Down Expand Up @@ -199,11 +200,11 @@ export class EcosystemService {
* @param id Intent ID
* @returns Deleted intent
*/
async deleteIntent(ecosystemId: string, intentId: string, user: IUserRequest): Promise<object> {
async deleteIntent(ecosystemId: string, intentId: string, userId: string): Promise<object> {
return this.natsClient.sendNatsMessage(this.serviceProxy, 'delete-intent', {
ecosystemId,
intentId,
user: { id: user.userId }
userId
});
}
}
16 changes: 12 additions & 4 deletions apps/api-gateway/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { CommonConstants } from '@credebl/common/common.constant';
import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter';
import { UpdatableValidationPipe } from '@credebl/common/custom-overrideable-validation-pipe';
import * as useragent from 'express-useragent';
import { EcosystemSwaggerFilter } from './authz/guards/ecosystem-swagger.filter';

dotenv.config();

Expand Down Expand Up @@ -81,7 +82,15 @@ async function bootstrap(): Promise<void> {
defaultVersion: ['1']
});

const document = SwaggerModule.createDocument(app, options);
// Create Swagger document
let document = SwaggerModule.createDocument(app, options);
try {
const ecosystemFilter = app.get(EcosystemSwaggerFilter);
document = await ecosystemFilter.filterDocument(document);
} catch (err) {
Logger.warn('Skipping EcosystemSwaggerFilter due to error', err as Error);
}

Comment thread
pranalidhanavade marked this conversation as resolved.
SwaggerModule.setup('api', app, document);
const httpAdapter: HttpAdapterHost = app.get(HttpAdapterHost) as HttpAdapterHost;
app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
Expand Down Expand Up @@ -122,17 +131,16 @@ async function bootstrap(): Promise<void> {
if ('true' === process.env.DB_ALERT_ENABLE?.trim()?.toLowerCase()) {
// in case it is enabled, log that
Logger.log(
'We have enabled DB alert for \'ledger_null\' instances. This would send email in case the \'ledger_id\' column in \'org_agents\' table is set to null',
"We have enabled DB alert for 'ledger_null' instances. This would send email in case the 'ledger_id' column in 'org_agents' table is set to null",
'DB alert enabled'
);
}

if ('true' === (process.env.HIDE_EXPERIMENTAL_OIDC_CONTROLLERS || 'true').trim().toLowerCase()) {
Logger.warn('Hiding experimental OIDC Controllers: OID4VC, OID4VP, x509 in OpenAPI docs');
Logger.verbose(
'To enable the use of experimental OIDC controllers. Set, \'HIDE_EXPERIMENTAL_OIDC_CONTROLLERS\' env variable to false'
"To enable the use of experimental OIDC controllers. Set, 'HIDE_EXPERIMENTAL_OIDC_CONTROLLERS' env variable to false"
);
}

}
bootstrap();
37 changes: 35 additions & 2 deletions apps/api-gateway/src/platform/platform.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Logger,
Param,
Post,
Put,
Query,
Res,
UseFilters,
Expand All @@ -33,6 +34,8 @@ import { OrgRoles } from 'libs/org-roles/enums';
import { Roles } from '../authz/decorators/roles.decorator';
import { OrgRolesGuard } from '../authz/guards/org-roles.guard';
import { CreateEcosystemInvitationDto } from '../ecosystem/dtos/send-ecosystem-invitation';
import { EnableEcosystemDto } from '../ecosystem/dtos/enable-ecosystem';
import { EcosystemFeatureGuard } from '../authz/guards/ecosystem-feature-guard';

@Controller('')
@UseFilters(CustomExceptionFilter)
Expand Down Expand Up @@ -234,7 +237,7 @@ export class PlatformController {
description: 'Success',
type: ApiResponseDto
})
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
@UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemFeatureGuard)
@ApiBearerAuth()
async createInvitation(
@Body() createEcosystemInvitationDto: CreateEcosystemInvitationDto,
Expand Down Expand Up @@ -264,7 +267,7 @@ export class PlatformController {
description: 'Invitations fetched successfully'
})
@Roles(OrgRoles.PLATFORM_ADMIN)
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
@UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemFeatureGuard)
@ApiBearerAuth()
async getInvitations(@User() reqUser: user, @Res() res: Response): Promise<Response> {
const invitations = await this.platformService.getInvitationsByUserId(reqUser.id);
Expand All @@ -275,4 +278,34 @@ export class PlatformController {
data: invitations
});
}

/**
* Update ecosystem enable/disable flag
*/
@Put('/config/ecosystem')
@Roles(OrgRoles.PLATFORM_ADMIN)
@ApiOperation({
summary: 'Enable or disable ecosystem feature',
description: 'Platform admin can enable or disable ecosystem feature on the platform'
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Ecosystem configuration updated successfully',
type: ApiResponseDto
})
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
@ApiBearerAuth()
async updateEcosystemConfig(
@Body() platformConfigDto: EnableEcosystemDto,
@User() reqUser: user,
@Res() res: Response
): Promise<Response> {
await this.platformService.updateEcosystemConfig(platformConfigDto.isEcosystemEnabled, reqUser.id);

const finalResponse: IResponse = {
statusCode: HttpStatus.OK,
message: ResponseMessages.ecosystem.success.updateEcosystemConfig
};
return res.status(HttpStatus.OK).json(finalResponse);
}
}
15 changes: 10 additions & 5 deletions apps/api-gateway/src/platform/platform.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { ClientsModule, Transport } from '@nestjs/microservices';

import { CommonConstants } from '@credebl/common/common.constant';
import { ConfigModule } from '@nestjs/config';
import { EcosystemModule as EcosystemServiceModule } from 'apps/ecosystem/src/ecosystem.module';
import { Module } from '@nestjs/common';
import { NATSClient } from '@credebl/common/NATSClient';
import { PlatformController } from './platform.controller';
import { PlatformService } from './platform.service';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { ConfigModule } from '@nestjs/config';
import { getNatsOptions } from '@credebl/common/nats.config';
import { CommonConstants } from '@credebl/common/common.constant';
import { NATSClient } from '@credebl/common/NATSClient';

@Module({
imports: [
EcosystemServiceModule,
ConfigModule.forRoot(),
ClientsModule.register([
{
Expand All @@ -18,6 +22,7 @@ import { NATSClient } from '@credebl/common/NATSClient';
])
],
controllers: [PlatformController],
providers: [PlatformService, NATSClient]
providers: [PlatformService, NATSClient],
exports: [EcosystemServiceModule]
})
export class PlatformModule {}
10 changes: 10 additions & 0 deletions apps/api-gateway/src/platform/platform.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,14 @@ export class PlatformService extends BaseService {
async getInvitationsByUserId(userId: string): Promise<IEcosystemInvitations[]> {
return this.natsClient.sendNatsMessage(this.platformServiceProxy, 'get-ecosystem-invitations-by-user', { userId });
}

/**
* Update ecosystem enable/disable flag
*/
async updateEcosystemConfig(isEcosystemEnabled: boolean, platformAdminId: string): Promise<{ message: string }> {
return this.natsClient.sendNatsMessage(this.platformServiceProxy, 'update-ecosystem-config', {
isEcosystemEnabled,
platformAdminId
});
}
}
Loading