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
8 changes: 2 additions & 6 deletions .husky/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ while read local_ref local_sha remote_ref remote_sha; do
echo "${CYAN}ℹ️ Skipping merge commit $commit${RESET}"
continue
fi

sig_status=$(git log --format='%G?' -n 1 $commit)

sig_status=$(git log --format='%G?' -n 1 $commit)
# Currently only check for "N" - for no signature.
if [ "$sig_status" = "N" ]; then
unsigned_commits="$unsigned_commits $commit"
Expand Down Expand Up @@ -64,7 +62,6 @@ while read local_ref local_sha remote_ref remote_sha; do
echo "${YELLOW}You can manually sign commits using:${RESET}"
echo
# Latest commit (HEAD)
head_commit=$(git rev-parse HEAD)
echo " - For the latest commit (HEAD):"
echo " git commit --amend -sS --no-edit"
echo
Expand All @@ -75,7 +72,6 @@ while read local_ref local_sha remote_ref remote_sha; do
echo " - For the earliest unsigned commit $short_hash (skipping merge commits):"
echo " git rebase --onto $parent_commit $parent_commit \\"
echo " --exec 'if [ \$(git rev-list --parents -n 1 HEAD | awk \"{print NF-1}\") -eq 1 ]; then git commit --amend -sS --no-edit; else echo \"Skipping merge commit \$(git rev-parse --short HEAD)\"; fi'"

echo
echo "Then push normally with:"
echo " git push"
Expand All @@ -92,4 +88,4 @@ while read local_ref local_sha remote_ref remote_sha; do
fi
done

echo "${GREEN}✅ All commits are signed and verified${RESET}"
echo "${GREEN}✅ No unsigned commits found${RESET}"
24 changes: 24 additions & 0 deletions apps/api-gateway/src/organization/dtos/client-token.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator';

export class ClientTokenDto {
@ApiProperty()
@IsString({ message: 'orgId must be in string format.' })
orgId: string;

@ApiProperty()
@IsString({ message: 'clientAlias must be in string format.' })
clientAlias: string;

@ApiProperty()
@IsString({ message: 'clientId must be in string format.' })
clientId: string;

@ApiProperty()
@IsString({ message: 'clientSecret must be in string format.' })
clientSecret: string;
Comment on lines +9 to +19
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove secret-bearing fields from the gateway DTO.
This DTO exposes clientId and clientSecret to external callers, meaning admin credentials now traverse user payloads, logs, traces, and NATS messages. That is a critical secret-handling flaw—attackers can supply arbitrary secrets or exfiltrate the real ones. Fetch the admin client credentials server-side (e.g., by clientAlias from a secure store) and drop these fields from the public contract. Return a 400 if clients include them.

🤖 Prompt for AI Agents
In apps/api-gateway/src/organization/dtos/client-token.dto.ts around lines 9 to
19, the DTO currently exposes clientId and clientSecret which leaks admin
credentials; remove clientId and clientSecret properties from this public DTO
and leave only clientAlias, then update validation to reject requests that
include clientId or clientSecret (respond with 400) so callers cannot supply
secrets; ensure server-side logic fetches the admin client credentials from the
secure store using clientAlias instead of relying on values from the incoming
payload and update any downstream handlers to use the fetched credentials.


@ApiProperty()
@IsString({ message: 'grantType must be in string format.' })
grantType?: string = 'client_credentials';
}
13 changes: 13 additions & 0 deletions apps/api-gateway/src/organization/organization.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { UserAccessGuard } from '../authz/guards/user-access-guard';
import { GetAllOrganizationsDto } from './dtos/get-organizations.dto';
import { PrimaryDid } from './dtos/set-primary-did.dto';
import { TrimStringParamPipe } from '@credebl/common/cast.helper';
import { ClientTokenDto } from './dtos/client-token.dto';

@UseFilters(CustomExceptionFilter)
@Controller('orgs')
Expand Down Expand Up @@ -789,4 +790,16 @@ export class OrganizationController {
};
return res.status(HttpStatus.OK).json(finalResponse);
}

@Post('/generateIssuerApiToken')
@ApiExcludeEndpoint()
@ApiOperation({
summary: 'Generate API Token for the issuer',
description: 'Generate API Token for the issuer'
})
@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto })
async generateApiToken(@Body() clientTokenDto: ClientTokenDto, @Res() res: Response): Promise<Response> {
const finalResponse = await this.organizationService.generateClientApiToken(clientTokenDto);
return res.status(HttpStatus.OK).header('Content-Type', 'application/json').send(finalResponse);
}
Comment thread
shitrerohit marked this conversation as resolved.
}
4 changes: 4 additions & 0 deletions apps/api-gateway/src/organization/organization.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { GetAllOrganizationsDto } from './dtos/get-organizations.dto';
import { PrimaryDid } from './dtos/set-primary-did.dto';
import { NATSClient } from '@credebl/common/NATSClient';
import { ClientProxy } from '@nestjs/microservices';
import { ClientTokenDto } from './dtos/client-token.dto';

@Injectable()
export class OrganizationService extends BaseService {
Expand Down Expand Up @@ -238,4 +239,7 @@ export class OrganizationService extends BaseService {
const imageBuffer = Buffer.from(base64Data, 'base64');
return imageBuffer;
}
async generateClientApiToken(clientTokenDto: ClientTokenDto): Promise<{ token: string }> {
return this.natsClient.sendNatsMessage(this.serviceProxy, 'generate-client-api-token', clientTokenDto);
}
}
7 changes: 7 additions & 0 deletions apps/organization/dtos/client-token.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class ClientTokenDto {
orgId: string;
clientAlias: string;
clientId: string;
clientSecret: string;
grantType?: string = 'client_credentials';
}
1 change: 1 addition & 0 deletions apps/organization/repositories/organization.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@ export class OrganizationRepository {
countryId: true,
stateId: true,
cityId: true,
appLaunchDetails: true,
Comment thread
shitrerohit marked this conversation as resolved.
userOrgRoles: {
where: {
orgRole: {
Expand Down
109 changes: 64 additions & 45 deletions apps/organization/src/organization.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,27 @@ import { OrganizationService } from './organization.service';
import { CreateOrganizationDto } from '../dtos/create-organization.dto';
import { BulkSendInvitationDto } from '../dtos/send-invitation.dto';
import { UpdateInvitationDto } from '../dtos/update-invitation.dt';
import { IDidList, IGetOrgById, IGetOrganization, IOrgDetails, IUpdateOrganization, Payload } from '../interfaces/organization.interface';
import { IOrgCredentials, IOrganizationInvitations, IOrganization, IOrganizationDashboard, IDeleteOrganization, IOrgActivityCount } from '@credebl/common/interfaces/organization.interface';
import {
IDidList,
IGetOrgById,
IGetOrganization,
IOrgDetails,
IUpdateOrganization,
Payload
} from '../interfaces/organization.interface';
import {
IOrgCredentials,
IOrganizationInvitations,
IOrganization,
IOrganizationDashboard,
IDeleteOrganization,
IOrgActivityCount
} from '@credebl/common/interfaces/organization.interface';
import { organisation, user } from '@prisma/client';
import { IAccessTokenData } from '@credebl/common/interfaces/interface';
import { IClientRoles } from '@credebl/client-registration/interfaces/client.interface';
import { IOrgRoles } from 'libs/org-roles/interfaces/org-roles.interface';
import { ClientTokenDto } from '../dtos/client-token.dto';

@Controller()
export class OrganizationController {
Expand All @@ -23,7 +38,9 @@ export class OrganizationController {
*/

@MessagePattern({ cmd: 'create-organization' })
async createOrganization(@Body() payload: { createOrgDto: CreateOrganizationDto; userId: string, keycloakUserId: string }): Promise<organisation> {
async createOrganization(
@Body() payload: { createOrgDto: CreateOrganizationDto; userId: string; keycloakUserId: string }
): Promise<organisation> {
Comment thread
shitrerohit marked this conversation as resolved.
return this.organizationService.createOrganization(payload.createOrgDto, payload.userId, payload.keycloakUserId);
}

Expand All @@ -34,17 +51,19 @@ export class OrganizationController {
*/

@MessagePattern({ cmd: 'set-primary-did' })
async setPrimaryDid(@Body() payload: { orgId:string, did:string, id:string}): Promise<string> {
async setPrimaryDid(@Body() payload: { orgId: string; did: string; id: string }): Promise<string> {
Comment thread
shitrerohit marked this conversation as resolved.
return this.organizationService.setPrimaryDid(payload.orgId, payload.did, payload.id);
}

/**
*
* @param payload
*
* @param payload
* @returns organization client credentials
*/
@MessagePattern({ cmd: 'create-org-credentials' })
async createOrgCredentials(@Body() payload: { orgId: string; userId: string, keycloakUserId: string }): Promise<IOrgCredentials> {
async createOrgCredentials(
@Body() payload: { orgId: string; userId: string; keycloakUserId: string }
): Promise<IOrgCredentials> {
Comment thread
shitrerohit marked this conversation as resolved.
return this.organizationService.createOrgCredentials(payload.orgId, payload.userId, payload.keycloakUserId);
}

Expand All @@ -55,7 +74,11 @@ export class OrganizationController {
*/

@MessagePattern({ cmd: 'update-organization' })
async updateOrganization(payload: { updateOrgDto: IUpdateOrganization; userId: string, orgId: string }): Promise<organisation> {
async updateOrganization(payload: {
updateOrgDto: IUpdateOrganization;
userId: string;
orgId: string;
}): Promise<organisation> {
return this.organizationService.updateOrganization(payload.updateOrgDto, payload.userId, payload.orgId);
}

Expand All @@ -65,7 +88,7 @@ export class OrganizationController {
* @returns organization's did list
*/
@MessagePattern({ cmd: 'fetch-organization-dids' })
async getOrgDidList(payload: {orgId:string}): Promise<IDidList[]> {
async getOrgDidList(payload: { orgId: string }): Promise<IDidList[]> {
return this.organizationService.getOrgDidList(payload.orgId);
}

Expand All @@ -75,9 +98,7 @@ export class OrganizationController {
* @returns Get created organization details
*/
@MessagePattern({ cmd: 'get-organizations' })
async getOrganizations(
@Body() payload: { userId: string} & Payload
): Promise<IGetOrganization> {
async getOrganizations(@Body() payload: { userId: string } & Payload): Promise<IGetOrganization> {
const { userId, pageNumber, pageSize, search, role } = payload;
return this.organizationService.getOrganizations(userId, pageNumber, pageSize, search, role);
}
Comment on lines +101 to 104
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove @Body() from @MessagePattern handler.

@Body is HTTP-only; with NATS it yields undefined. Use plain param or @payload() from microservices.

-async getOrganizations(@Body() payload: { userId: string } & Payload): Promise<IGetOrganization> {
+async getOrganizations(payload: { userId: string } & Payload): Promise<IGetOrganization> {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async getOrganizations(@Body() payload: { userId: string } & Payload): Promise<IGetOrganization> {
const { userId, pageNumber, pageSize, search, role } = payload;
return this.organizationService.getOrganizations(userId, pageNumber, pageSize, search, role);
}
async getOrganizations(payload: { userId: string } & Payload): Promise<IGetOrganization> {
const { userId, pageNumber, pageSize, search, role } = payload;
return this.organizationService.getOrganizations(userId, pageNumber, pageSize, search, role);
}
🤖 Prompt for AI Agents
In apps/organization/src/organization.controller.ts around lines 101 to 104, the
MessagePattern handler currently uses the HTTP-only @Body() decorator which
yields undefined for NATS; remove @Body() and accept the payload as a plain
parameter or annotate it with @Payload() from @nestjs/microservices, keep the
existing type ({ userId: string } & Payload | IGetOrganization as appropriate),
and update imports to include @Payload if used; ensure the method signature and
service call stay the same but without @Body().

Expand All @@ -87,22 +108,17 @@ export class OrganizationController {
* @returns Get created organization details
*/
@MessagePattern({ cmd: 'get-organizations-count' })
async countTotalOrgs(
@Body() payload: { userId: string}
): Promise<number> {

async countTotalOrgs(@Body() payload: { userId: string }): Promise<number> {
Comment thread
shitrerohit marked this conversation as resolved.
const { userId } = payload;

return this.organizationService.countTotalOrgs(userId);
}

/**
* @returns Get public organization details
*/
@MessagePattern({ cmd: 'get-public-organizations' })
async getPublicOrganizations(
@Body() payload: Payload
): Promise<IGetOrganization> {
async getPublicOrganizations(@Body() payload: Payload): Promise<IGetOrganization> {
Comment thread
shitrerohit marked this conversation as resolved.
const { pageNumber, pageSize, search } = payload;
return this.organizationService.getPublicOrganizations(pageNumber, pageSize, search);
}
Expand All @@ -113,13 +129,13 @@ export class OrganizationController {
* @returns Get created organization details
*/
@MessagePattern({ cmd: 'get-organization-by-id' })
async getOrganization(@Body() payload: { orgId: string; userId: string}): Promise<IGetOrgById> {
async getOrganization(@Body() payload: { orgId: string; userId: string }): Promise<IGetOrgById> {
return this.organizationService.getOrganization(payload.orgId);
Comment thread
shitrerohit marked this conversation as resolved.
}
/**
* @param orgSlug
* @returns organization details
*/
/**
* @param orgSlug
* @returns organization details
*/
@MessagePattern({ cmd: 'get-organization-public-profile' })
async getPublicProfile(payload: { orgSlug }): Promise<IGetOrgById> {
return this.organizationService.getPublicProfile(payload);
Expand All @@ -131,9 +147,7 @@ export class OrganizationController {
* @returns Get created invitation details
*/
@MessagePattern({ cmd: 'get-invitations-by-orgId' })
async getInvitationsByOrgId(
@Body() payload: { orgId: string } & Payload
): Promise<IOrganizationInvitations> {
async getInvitationsByOrgId(@Body() payload: { orgId: string } & Payload): Promise<IOrganizationInvitations> {
Comment thread
shitrerohit marked this conversation as resolved.
return this.organizationService.getInvitationsByOrgId(
payload.orgId,
payload.pageNumber,
Expand All @@ -143,11 +157,11 @@ export class OrganizationController {
}

/**
* @returns Get org-roles
* @returns Get org-roles
*/

@MessagePattern({ cmd: 'get-org-roles' })
async getOrgRoles(payload: {orgId: string, user: user}): Promise<IClientRoles[]> {
async getOrgRoles(payload: { orgId: string; user: user }): Promise<IClientRoles[]> {
return this.organizationService.getOrgRoles(payload.orgId, payload.user);
}

Expand All @@ -163,7 +177,7 @@ export class OrganizationController {
*/
@MessagePattern({ cmd: 'send-invitation' })
async createInvitation(
@Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string, userEmail: string }
@Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string; userEmail: string }
Comment thread
shitrerohit marked this conversation as resolved.
): Promise<string> {
return this.organizationService.createInvitation(payload.bulkInvitationDto, payload.userId, payload.userEmail);
}
Expand Down Expand Up @@ -198,7 +212,7 @@ export class OrganizationController {
*/

@MessagePattern({ cmd: 'update-user-roles' })
async updateUserRoles(payload: { orgId: string; roleIds: string[]; userId: string}): Promise<boolean> {
async updateUserRoles(payload: { orgId: string; roleIds: string[]; userId: string }): Promise<boolean> {
return this.organizationService.updateUserRoles(payload.orgId, payload.roleIds, payload.userId);
}

Expand All @@ -212,9 +226,9 @@ export class OrganizationController {
return this.organizationService.getOrganizationActivityCount(payload.orgId, payload.userId);
}

/**
* @returns organization profile details
*/
/**
* @returns organization profile details
*/
@MessagePattern({ cmd: 'fetch-organization-profile' })
async getOrgPofile(payload: { orgId: string }): Promise<organisation> {
return this.organizationService.getOrgPofile(payload.orgId);
Expand All @@ -231,27 +245,27 @@ export class OrganizationController {
}

@MessagePattern({ cmd: 'get-organization-details' })
async getOrgData(payload: { orgId: string; }): Promise<organisation> {
async getOrgData(payload: { orgId: string }): Promise<organisation> {
return this.organizationService.getOrgDetails(payload.orgId);
}

@MessagePattern({ cmd: 'delete-organization' })
async deleteOrganization(payload: { orgId: string, user: user }): Promise<IDeleteOrganization> {
async deleteOrganization(payload: { orgId: string; user: user }): Promise<IDeleteOrganization> {
return this.organizationService.deleteOrganization(payload.orgId, payload.user);
}

@MessagePattern({ cmd: 'delete-org-client-credentials' })
async deleteOrganizationCredentials(payload: { orgId: string, user: user }): Promise<string> {
async deleteOrganizationCredentials(payload: { orgId: string; user: user }): Promise<string> {
return this.organizationService.deleteClientCredentials(payload.orgId, payload.user);
}

@MessagePattern({ cmd: 'delete-organization-invitation' })
async deleteOrganizationInvitation(payload: { orgId: string; invitationId: string; }): Promise<boolean> {
async deleteOrganizationInvitation(payload: { orgId: string; invitationId: string }): Promise<boolean> {
return this.organizationService.deleteOrganizationInvitation(payload.orgId, payload.invitationId);
}

@MessagePattern({ cmd: 'authenticate-client-credentials' })
async clientLoginCredentails(payload: { clientId: string; clientSecret: string;}): Promise<IAccessTokenData> {
async clientLoginCredentails(payload: { clientId: string; clientSecret: string }): Promise<IAccessTokenData> {
return this.organizationService.clientLoginCredentails(payload);
}

Expand All @@ -261,12 +275,12 @@ export class OrganizationController {
}

@MessagePattern({ cmd: 'get-agent-type-by-org-agent-type-id' })
async getAgentTypeByAgentTypeId(payload: {orgAgentTypeId: string}): Promise<string> {
async getAgentTypeByAgentTypeId(payload: { orgAgentTypeId: string }): Promise<string> {
return this.organizationService.getAgentTypeByAgentTypeId(payload.orgAgentTypeId);
}

@MessagePattern({ cmd: 'get-org-roles-details' })
async getOrgRolesDetails(payload: {roleName: string}): Promise<object> {
async getOrgRolesDetails(payload: { roleName: string }): Promise<object> {
return this.organizationService.getOrgRolesDetails(payload.roleName);
}

Expand All @@ -286,7 +300,12 @@ export class OrganizationController {
}

@MessagePattern({ cmd: 'get-org-agents-and-user-roles' })
async getOrgAgentDetailsForEcosystem(payload: {orgIds: string[], search: string}): Promise<IOrgDetails> {
async getOrgAgentDetailsForEcosystem(payload: { orgIds: string[]; search: string }): Promise<IOrgDetails> {
return this.organizationService.getOrgAgentDetailsForEcosystem(payload);
}
}

@MessagePattern({ cmd: 'generate-client-api-token' })
async generateClientApiToken(payload: ClientTokenDto): Promise<{ token: string }> {
return this.organizationService.generateClientApiToken(payload);
}
Comment on lines +307 to +310
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Do not accept or forward admin secrets; use alias-only request and fetch creds server-side.

The DTO currently includes clientId/clientSecret (via import). Secrets should not traverse the message bus. Accept orgId + clientAlias only; resolve clientId/secret inside the organization service.

-@MessagePattern({ cmd: 'generate-client-api-token' })
-async generateClientApiToken(payload: ClientTokenDto): Promise<{ token: string }> {
-  return this.organizationService.generateClientApiToken(payload);
-}
+@MessagePattern({ cmd: 'generate-client-api-token' })
+async generateClientApiToken(payload: { orgId: string; clientAlias: string; grantType?: 'client_credentials' }): Promise<{ token: string }> {
+  return this.organizationService.generateClientApiToken(payload);
+}

🏁 Script executed:

#!/bin/bash
# Ensure no @Body() remains in @MessagePattern handlers
rg -n "@MessagePattern" -n -C1 apps/organization/src/organization.controller.ts | rg -n "@Body\\("

Length of output: 712


🏁 Script executed:

#!/bin/bash
# Display context around generateClientApiToken in controller
sed -n '250,330p' apps/organization/src/organization.controller.ts
# Locate ClientTokenDto definition and inspect fields
rg -n "export class ClientTokenDto" -g "apps/organization/src/**/*.dto.ts" -C3

Length of output: 2865


🏁 Script executed:

#!/bin/bash
# Locate ClientTokenDto definition in repository
rg -n "class ClientTokenDto" -C5 .
rg -n "interface ClientTokenDto" -C5 .

Length of output: 1733


Remove client secrets from the message bus and strip @Body() decorators

  • The ClientTokenDto (apps/organization/dtos/client-token.dto.ts) still includes clientId and clientSecret, which exposes admin credentials over the message bus. Update the handler signature to accept only { orgId: string; clientAlias: string; grantType?: 'client_credentials' } and resolve the actual client ID/secret inside organizationService.generateClientApiToken.
@MessagePattern({ cmd: 'generate-client-api-token' })
-async generateClientApiToken(payload: ClientTokenDto): Promise<{ token: string }> {
+async generateClientApiToken(payload: { orgId: string; clientAlias: string; grantType?: 'client_credentials' }): Promise<{ token: string }> {
   return this.organizationService.generateClientApiToken(payload);
}
  • The verification script shows many other @MessagePattern handlers still using @Body() (e.g., setPrimaryDid, getOrganizations, etc.). Message handlers should accept raw payload parameters; remove all @Body() decorators.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@MessagePattern({ cmd: 'generate-client-api-token' })
async generateClientApiToken(payload: ClientTokenDto): Promise<{ token: string }> {
return this.organizationService.generateClientApiToken(payload);
}
@MessagePattern({ cmd: 'generate-client-api-token' })
async generateClientApiToken(
payload: { orgId: string; clientAlias: string; grantType?: 'client_credentials' }
): Promise<{ token: string }> {
return this.organizationService.generateClientApiToken(payload);
}

}
Loading