Skip to content

Commit

Permalink
fix: hotfix for security issues
Browse files Browse the repository at this point in the history
Signed-off-by: KulkarniShashank <[email protected]>
  • Loading branch information
KulkarniShashank committed Apr 18, 2024
1 parent c295851 commit a815bd6
Show file tree
Hide file tree
Showing 16 changed files with 144 additions and 44 deletions.
3 changes: 3 additions & 0 deletions apps/api-gateway/src/connection/dtos/connection.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { ArrayNotEmpty, IsArray, IsBoolean, IsNotEmpty, IsNumber, IsOptional, Is
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { HandshakeProtocol } from '../enums/connections.enum';
import { IsNotSQLInjection } from '@credebl/common/cast.helper';

export class CreateOutOfBandConnectionInvitation {
@ApiPropertyOptional()
@IsOptional()
@IsNotSQLInjection({ message: 'label is required.' })
label?: string;

@ApiPropertyOptional()
@IsOptional()
@IsNotSQLInjection({ message: 'alias is required.' })
alias?: string;

@ApiPropertyOptional()
Expand Down
3 changes: 2 additions & 1 deletion apps/api-gateway/src/dtos/create-schema.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ArrayMinSize, IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString, Val

import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { trim } from '@credebl/common/cast.helper';
import { IsNotSQLInjection, trim } from '@credebl/common/cast.helper';

class AttributeValue {

Expand Down Expand Up @@ -41,6 +41,7 @@ export class CreateSchemaDto {
@IsString({ message: 'schemaName must be a string' })
@Transform(({ value }) => trim(value))
@IsNotEmpty({ message: 'schemaName is required' })
@IsNotSQLInjection({ message: 'SchemaName is required.' })
schemaName: string;

@ApiProperty({
Expand Down
3 changes: 2 additions & 1 deletion apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagge
import { IsBoolean, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator';

import { Transform, Type } from 'class-transformer';
import { trim } from '@credebl/common/cast.helper';
import { IsNotSQLInjection, trim } from '@credebl/common/cast.helper';

@ApiExtraModels()
export class CreateEcosystemDto {
Expand All @@ -13,6 +13,7 @@ export class CreateEcosystemDto {
@MinLength(2, { message: 'Ecosystem name must be at least 2 characters.' })
@MaxLength(50, { message: 'Ecosystem name must be at most 50 characters.' })
@IsString({ message: 'Ecosystem name must be in string format.' })
@IsNotSQLInjection({ message: 'Ecosystem name is required.' })
name: string;

@ApiProperty()
Expand Down
3 changes: 2 additions & 1 deletion apps/api-gateway/src/ecosystem/dtos/edit-ecosystem-dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ApiExtraModels, ApiPropertyOptional } from '@nestjs/swagger';
import { IsBoolean, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator';

import { Transform } from 'class-transformer';
import { trim } from '@credebl/common/cast.helper';
import { IsNotSQLInjection, trim } from '@credebl/common/cast.helper';

@ApiExtraModels()
export class EditEcosystemDto {
Expand All @@ -13,6 +13,7 @@ export class EditEcosystemDto {
@MinLength(2, { message: 'Ecosystem name must be at least 2 characters.' })
@MaxLength(50, { message: 'Ecosystem name must be at most 50 characters.' })
@IsString({ message: 'Ecosystem name must be in string format.' })
@IsNotSQLInjection({ message: 'Ecosystem name is required.' })
name?: string;

@ApiPropertyOptional()
Expand Down
12 changes: 12 additions & 0 deletions apps/api-gateway/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ async function bootstrap(): Promise<void> {
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ limit: '50mb' }));

app.use(function (req, res, next) {
let err = null;
try {
decodeURIComponent(req.path);
} catch (e) {
err = e;
}
if (err) {
return res.status(500).json({ message: 'Invalid URL' });
}
next();
});

const options = new DocumentBuilder()
.setTitle(`${process.env.PLATFORM_NAME}`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagge
import { IsNotEmpty, IsOptional, IsString, IsUrl, MaxLength, MinLength } from 'class-validator';

import { Transform } from 'class-transformer';
import { trim } from '@credebl/common/cast.helper';
import { IsNotSQLInjection, trim } from '@credebl/common/cast.helper';

@ApiExtraModels()
export class CreateOrganizationDto {
Expand All @@ -13,6 +13,7 @@ export class CreateOrganizationDto {
@MinLength(2, { message: 'Organization name must be at least 2 characters.' })
@MaxLength(50, { message: 'Organization name must be at most 50 characters.' })
@IsString({ message: 'Organization name must be in string format.' })
@IsNotSQLInjection({ message: 'Organization name is required.' })
name: string;

@ApiPropertyOptional()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ApiExtraModels, ApiPropertyOptional } from '@nestjs/swagger';
import { IsNotEmpty, IsOptional, IsString, IsBoolean, MaxLength, MinLength } from 'class-validator';
import { IsNotEmpty, IsOptional, IsString, IsBoolean, MaxLength, MinLength, Validate } from 'class-validator';

import { Transform } from 'class-transformer';
import { trim } from '@credebl/common/cast.helper';
import { ImageBase64Validator, IsNotUUID, trim } from '@credebl/common/cast.helper';

@ApiExtraModels()
export class UpdateOrganizationDto {
Expand All @@ -17,6 +17,7 @@ export class UpdateOrganizationDto {
@MinLength(2, { message: 'Organization name must be at least 2 characters.' })
@MaxLength(50, { message: 'Organization name must be at most 50 characters.' })
@IsString({ message: 'Organization name must be in string format.' })
@IsNotUUID({message: 'Invalid orgName'})
name: string;

@ApiPropertyOptional()
Expand All @@ -31,7 +32,7 @@ export class UpdateOrganizationDto {
@ApiPropertyOptional()
@IsOptional()
@Transform(({ value }) => trim(value))
@IsString({ message: 'logo must be in string format.' })
@Validate(ImageBase64Validator)
logo?: string = '';

@ApiPropertyOptional()
Expand Down
8 changes: 6 additions & 2 deletions apps/api-gateway/src/organization/organization.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ApiBearerAuth, ApiExcludeEndpoint, ApiForbiddenResponse, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { CommonService } from '@credebl/common';
import { Controller, Get, Put, Param, UseGuards, UseFilters, Post, Body, Res, HttpStatus, Query, Delete, ParseUUIDPipe, BadRequestException } from '@nestjs/common';
import { Controller, Get, Put, Param, UseGuards, UseFilters, Post, Body, Res, HttpStatus, Query, Delete, ParseUUIDPipe, BadRequestException, ValidationPipe, UsePipes } from '@nestjs/common';
import { OrganizationService } from './organization.service';
import { CreateOrganizationDto } from './dtos/create-organization-dto';
import IResponse from '@credebl/common/interfaces/response.interface';
Expand Down Expand Up @@ -472,8 +472,12 @@ export class OrganizationController {
@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto })
@ApiBearerAuth()
@Roles(OrgRoles.OWNER, OrgRoles.ADMIN)
@ApiParam({
name: 'orgId'
})
@UseGuards(AuthGuard('jwt'), OrgRolesGuard, UserAccessGuard)
async updateOrganization(@Body() updateOrgDto: UpdateOrganizationDto, @Param('orgId') orgId: string, @Res() res: Response, @User() reqUser: user): Promise<Response> {
@UsePipes(new ValidationPipe())
async updateOrganization(@Body() updateOrgDto: UpdateOrganizationDto, @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(`Invalid format for orgId`); }})) orgId: string, @Res() res: Response, @User() reqUser: user): Promise<Response> {

updateOrgDto.orgId = orgId;
await this.organizationService.updateOrganization(updateOrgDto, reqUser.id, orgId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { Transform } from 'class-transformer';
import { IsEnum, IsOptional } from 'class-validator';
import { SortValue } from '../../enum';
import { trim } from '@credebl/common/cast.helper';
import { SortFields } from '../enum/verification.enum';
import { PaginationDto } from '@credebl/common/dtos/pagination.dto';

export class GetAllProofRequestsDto {
@ApiProperty({ required: false, example: '1' })
@IsOptional()
pageNumber: number = 1;

@ApiProperty({ required: false })
@IsOptional()
@Transform(({ value }) => trim(value))
@Type(() => String)
searchByText: string = '';

@ApiProperty({ required: false, example: '10' })
@IsOptional()
pageSize: number = 10;

export class GetAllProofRequestsDto extends PaginationDto {
@ApiProperty({
enum: [SortValue.DESC, SortValue.ASC],
required: false
Expand Down
4 changes: 2 additions & 2 deletions apps/api-gateway/src/verification/dto/request-proof.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ArrayNotEmpty, IsArray, IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsNumberString, IsObject, IsOptional, IsString, ValidateIf, ValidateNested, IsUUID, ArrayUnique, ArrayMaxSize } from 'class-validator';
import { toLowerCase, trim } from '@credebl/common/cast.helper';
import { trim } from '@credebl/common/cast.helper';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { AutoAccept } from '@credebl/enum/enum';
Expand Down Expand Up @@ -72,7 +72,7 @@ export class RequestProofDto extends ProofPayload {
@ApiProperty()
@IsString()
@Transform(({ value }) => trim(value))
@Transform(({ value }) => toLowerCase(value))
@IsUUID()
@IsNotEmpty({ message: 'connectionId is required.' })
connectionId: string;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface IProofRequestSearchCriteria {
pageSize: number;
sortField: string;
sortBy: string;
searchByText: string;
search: string;
user?: IUserRequestInterface
}

Expand Down
4 changes: 2 additions & 2 deletions apps/api-gateway/src/verification/verification.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,10 @@ export class VerificationController {
@User() user: IUserRequest,
@Param('orgId') orgId: string
): Promise<Response> {
const { pageSize, searchByText, pageNumber, sortField, sortBy } = getAllProofRequests;
const { pageSize, search, pageNumber, sortField, sortBy } = getAllProofRequests;
const proofRequestsSearchCriteria: IProofRequestSearchCriteria = {
pageNumber,
searchByText,
search,
pageSize,
sortField,
sortBy
Expand Down
2 changes: 1 addition & 1 deletion apps/verification/src/interfaces/verification.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ export interface IProofRequestSearchCriteria {
pageSize: number;
sortField: string;
sortBy: string;
searchByText: string;
search: string;
}

export interface IInvitation{
Expand Down
12 changes: 6 additions & 6 deletions apps/verification/src/repositories/verification.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ export class VerificationRepository {
where: {
orgId,
OR: [
{ connectionId: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } },
{ state: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } },
{ presentationId: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } }
{ connectionId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } },
{ state: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } },
{ presentationId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }
]
},
select: {
Expand All @@ -90,9 +90,9 @@ export class VerificationRepository {
where: {
orgId,
OR: [
{ connectionId: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } },
{ state: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } },
{ presentationId: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } }
{ connectionId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } },
{ state: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } },
{ presentationId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }
]
}
});
Expand Down
89 changes: 88 additions & 1 deletion libs/common/src/cast.helper.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { BadRequestException } from '@nestjs/common';
import { ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, isBase64, isMimeType, isUUID, registerDecorator } from 'class-validator';

interface ToNumberOptions {
default?: number;
min?: number;
Expand Down Expand Up @@ -55,4 +58,88 @@ export function ledgerName(value: string): string {

return network;

}
}

export function isSafeString(value: string): boolean {
// Define a regular expression to allow alphanumeric characters, spaces, and some special characters
const safeRegex = /^[a-zA-Z0-9\s!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]*$/;

// Check if the value matches the safe regex
return safeRegex.test(value);
}

export const IsNotSQLInjection = (validationOptions?: ValidationOptions): PropertyDecorator => (object: object, propertyName: string) => {
registerDecorator({
name: 'isNotSQLInjection',
target: object.constructor,
propertyName,
options: validationOptions,
validator: {
validate(value) {
// Check if the value contains any common SQL injection keywords
const sqlKeywords = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'UNION', 'WHERE', 'AND', 'OR'];
for (const keyword of sqlKeywords) {
if (value.includes(keyword)) {
return false; // Value contains a SQL injection keyword
}
}
return true; // Value does not contain any SQL injection keywords
},
defaultMessage(args: ValidationArguments) {
return `${args.property} contains SQL injection keywords.`;
}
}
});
};

@ValidatorConstraint({ name: 'customText', async: false })
export class ImageBase64Validator implements ValidatorConstraintInterface {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/explicit-function-return-type, @typescript-eslint/no-unused-vars
validate(value: string, args: ValidationArguments) {
// Implement your custom validation logic here
// Validation to allow option param logo
if ('' == value) {
return true;
}
if (!value || 'string' !== typeof value) {
throw new BadRequestException('Invalid base64 string');
}
const parts = value.split(',');
if (2 !== parts.length) {
throw new BadRequestException('Invalid data URI');
}
// eslint-disable-next-line prefer-destructuring
const mimeType = parts[0].split(';')[0].split(':')[1];
// eslint-disable-next-line prefer-destructuring
const base64Data = parts[1];

// Validate MIME type
if (!isMimeType(mimeType)) {
throw new BadRequestException('Please provide valid MIME type');
}
// Validate base64 data
if (!isBase64(base64Data) || '' == base64Data || null == base64Data) {
throw new BadRequestException('Invalid base64 string');
}
return true;
}

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/explicit-function-return-type, @typescript-eslint/no-unused-vars
defaultMessage(_args: ValidationArguments) {
return 'Default message received from [ImageBase64Validator]';
}
}

export const IsNotUUID = (validationOptions?: ValidationOptions): PropertyDecorator => (object: object, propertyName: string) => {
registerDecorator({
name: 'isNotUUID',
target: object.constructor,
propertyName,
options: validationOptions,
validator: {
validate(value) {
return !isUUID(value);
}
}
});
};
14 changes: 8 additions & 6 deletions libs/common/src/dtos/pagination.dto.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Transform, Type } from 'class-transformer';
import { toNumber } from '@credebl/common/cast.helper';
import { Type } from 'class-transformer';
import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, Min } from 'class-validator';
import { IsNumber, IsOptional, Max, Min } from 'class-validator';

export class PaginationDto {
@Type(() => Number)
@ApiProperty({ required: false, default: 1 })
@IsOptional()
@Transform(({ value }) => toNumber(value))
@IsNumber()
@Min(1, { message: 'Page number must be greater than 0' })
pageNumber = 1;

Expand All @@ -15,10 +15,12 @@ export class PaginationDto {
@Type(() => String)
search = '';

@Type(() => Number)
@ApiProperty({ required: false, default: 10 })
@IsOptional()
@Transform(({ value }) => toNumber(value))
@IsNumber()
@Min(1, { message: 'Page size must be greater than 0' })
@Max(100, { message: 'Page size must be less than 100' })
pageSize = 10;

}
}

0 comments on commit a815bd6

Please sign in to comment.