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
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ describe('AuthorizationInterceptor - Array Payload Handling', () => {

it('should process single object payloads correctly', async () => {
const requestBody = { _id: 'room_1', name: 'Living Room' };
const mockUser = new User('mock_profile_id', 'mock_player_id', 'mock_clan_id');
const mockUser = new User(
'mock_profile_id',
'mock_player_id',
'mock_clan_id',
);

const mockExecutionContext = {
switchToHttp: () => ({
Expand All @@ -51,9 +55,14 @@ describe('AuthorizationInterceptor - Array Payload Handling', () => {
getHandler: () => ({}),
} as unknown as ExecutionContext;

const mockCallHandler = { handle: jest.fn().mockReturnValue(of({ data: 'ok' })) };
const mockCallHandler = {
handle: jest.fn().mockReturnValue(of({ data: 'ok' })),
};

const result$ = await interceptor.intercept(mockExecutionContext, mockCallHandler);
const result$ = await interceptor.intercept(
mockExecutionContext,
mockCallHandler,
);

result$.subscribe({
next: () => {
Expand All @@ -70,7 +79,11 @@ describe('AuthorizationInterceptor - Array Payload Handling', () => {
{ _id: 'room_2', name: 'Bedroom' },
];

const mockUser = new User('mock_profile_id', 'mock_player_id', 'mock_clan_id');
const mockUser = new User(
'mock_profile_id',
'mock_player_id',
'mock_clan_id',
);

const mockExecutionContext = {
switchToHttp: () => ({
Expand All @@ -79,7 +92,9 @@ describe('AuthorizationInterceptor - Array Payload Handling', () => {
getHandler: () => ({}),
} as unknown as ExecutionContext;

const mockCallHandler = { handle: jest.fn().mockReturnValue(of({ data: 'ok' })) };
const mockCallHandler = {
handle: jest.fn().mockReturnValue(of({ data: 'ok' })),
};

const result$ = await interceptor.intercept(
mockExecutionContext,
Expand All @@ -90,7 +105,7 @@ describe('AuthorizationInterceptor - Array Payload Handling', () => {
next: () => {
const request = mockExecutionContext.switchToHttp().getRequest();
expect(Array.isArray(request.body)).toBe(true);
expect(request.body.length).toBe(2);
expect(request.body).toHaveLength(2);
expect(request.body[0]._id).toBe('room_1');
expect(request.body[1]._id).toBe('room_2');
},
Expand All @@ -104,7 +119,11 @@ describe('AuthorizationInterceptor - Array Payload Handling', () => {
mockCaslFactory.createForUser.mockResolvedValueOnce(restrictAbility);

const requestBody = [{ _id: 'room_1', name: 'Living Room' }];
const mockUser = new User('mock_profile_id', 'mock_player_id', 'mock_clan_id');
const mockUser = new User(
'mock_profile_id',
'mock_player_id',
'mock_clan_id',
);

const mockExecutionContext = {
switchToHttp: () => ({
Expand All @@ -127,7 +146,11 @@ describe('AuthorizationInterceptor - Array Payload Handling', () => {
mockCaslFactory.createForUser.mockResolvedValueOnce(restrictAbility);

const requestBody = { _id: 'room_1', name: 'Living Room' };
const mockUser = new User('mock_profile_id', 'mock_player_id', 'mock_clan_id');
const mockUser = new User(
'mock_profile_id',
'mock_player_id',
'mock_clan_id',
);

const mockExecutionContext = {
switchToHttp: () => ({
Expand All @@ -149,7 +172,11 @@ describe('AuthorizationInterceptor - Array Payload Handling', () => {
subject: TestDto,
});

const mockUser = new User('mock_profile_id', 'mock_player_id', 'mock_clan_id');
const mockUser = new User(
'mock_profile_id',
'mock_player_id',
'mock_clan_id',
);

const mockExecutionContext = {
switchToHttp: () => ({
Expand All @@ -158,9 +185,14 @@ describe('AuthorizationInterceptor - Array Payload Handling', () => {
getHandler: () => ({}),
} as unknown as ExecutionContext;

const mockCallHandler = { handle: jest.fn().mockReturnValue(of({ _id: 'room_1', name: 'Test' })) };
const mockCallHandler = {
handle: jest.fn().mockReturnValue(of({ _id: 'room_1', name: 'Test' })),
};

const result$ = await interceptor.intercept(mockExecutionContext, mockCallHandler);
const result$ = await interceptor.intercept(
mockExecutionContext,
mockCallHandler,
);

result$.subscribe({
next: (res) => {
Expand All @@ -170,7 +202,11 @@ describe('AuthorizationInterceptor - Array Payload Handling', () => {
});

it('should process response mapping logic for array responses', async () => {
const mockUser = new User('mock_profile_id', 'mock_player_id', 'mock_clan_id');
const mockUser = new User(
'mock_profile_id',
'mock_player_id',
'mock_clan_id',
);

const mockExecutionContext = {
switchToHttp: () => ({
Expand All @@ -184,9 +220,14 @@ describe('AuthorizationInterceptor - Array Payload Handling', () => {
{ _id: 'room_2', name: 'Bedroom' },
];

const mockCallHandler = { handle: jest.fn().mockReturnValue(of(responseData)) };
const mockCallHandler = {
handle: jest.fn().mockReturnValue(of(responseData)),
};

const result$ = await interceptor.intercept(mockExecutionContext, mockCallHandler);
const result$ = await interceptor.intercept(
mockExecutionContext,
mockCallHandler,
);

result$.subscribe({
next: (data) => {
Expand All @@ -198,7 +239,11 @@ describe('AuthorizationInterceptor - Array Payload Handling', () => {
it('should throw InternalServerErrorException if no permission metadata is defined on route', async () => {
mockReflector.get.mockReturnValueOnce(undefined);

const mockUser = new User('mock_profile_id', 'mock_player_id', 'mock_clan_id');
const mockUser = new User(
'mock_profile_id',
'mock_player_id',
'mock_clan_id',
);

const mockExecutionContext = {
switchToHttp: () => ({
Expand All @@ -213,4 +258,4 @@ describe('AuthorizationInterceptor - Array Payload Handling', () => {
interceptor.intercept(mockExecutionContext, mockCallHandler),
).rejects.toThrow(InternalServerErrorException);
});
});
});
80 changes: 40 additions & 40 deletions src/authorization/authorization.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,53 +159,53 @@ export class AuthorizationInterceptor implements NestInterceptor {
//Filter out all fields that logged user can not update
//Basically create a new request body
if (action === Action.update) {
if (Array.isArray(request.body)) {
// 1. Process Array Payloads
const items = request.body.map((item) => {
const dataClass = plainToInstance(subject, item);

if (!userAbility.can(requestAction, dataClass)) {
throw requestForbiddenError;
}
if (Array.isArray(request.body)) {
// 1. Process Array Payloads
const items = request.body.map((item) => {
const dataClass = plainToInstance(subject, item);

const allowedFields = this.getAllowedFields(
userAbility,
requestAction,
dataClass,
subject,
);
allowedFields.push('_id');
if (!userAbility.can(requestAction, dataClass)) {
throw requestForbiddenError;
}

if (!allowedFields || allowedFields.length === 0) {
throw requestForbiddenError;
}
const allowedFields = this.getAllowedFields(
userAbility,
requestAction,
dataClass,
subject,
);
allowedFields.push('_id');

return pick(dataClass, allowedFields);
});
if (!allowedFields || allowedFields.length === 0) {
throw requestForbiddenError;
}

request.body = items;
} else {
// 2. Process Single Object Payloads (Original Logic)
const dataClass = plainToInstance(subject, request.body);
if (!userAbility.can(requestAction, dataClass)) {
throw requestForbiddenError;
}
return pick(dataClass, allowedFields);
});

const allowedFields = this.getAllowedFields(
userAbility,
requestAction,
dataClass,
subject,
);
allowedFields.push('_id');
request.body = items;
} else {
// 2. Process Single Object Payloads (Original Logic)
const dataClass = plainToInstance(subject, request.body);
if (!userAbility.can(requestAction, dataClass)) {
throw requestForbiddenError;
}

if (!allowedFields || allowedFields.length === 0) {
throw requestForbiddenError;
}
const allowedFields = this.getAllowedFields(
userAbility,
requestAction,
dataClass,
subject,
);
allowedFields.push('_id');

request.body = pick(dataClass, allowedFields);
}
}
if (!allowedFields || allowedFields.length === 0) {
throw requestForbiddenError;
}

request.body = pick(dataClass, allowedFields);
}
}

return next.handle().pipe(
map(async (data: any) => {
Expand Down
11 changes: 4 additions & 7 deletions src/box/auth/boxTestingSession.guard.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import {
CanActivate,
ForbiddenException,
Injectable,
} from '@nestjs/common';
import { CanActivate, ForbiddenException, Injectable } from '@nestjs/common';
import { envVars } from '../../common/service/envHandler/envVars';
import { APIErrorReason } from '../../common/controller/APIErrorReason';
import { APIError } from '../../common/controller/APIError';

/**
* Guard for testing sessions.
*
*
* Prohibits access to endpoints used in Box related Controllers outside testing.
*/
@Injectable()
Expand All @@ -20,7 +16,8 @@ export class BoxTestingSessionGuard implements CanActivate {
throw new ForbiddenException({
statusCode: 403,
error: 'Forbidden',
message: 'Controller is only available when ENVIRONMENT is TESTING_SESSION',
message:
'Controller is only available when ENVIRONMENT is TESTING_SESSION',
errors: [
new APIError({
reason: APIErrorReason.NOT_ALLOWED,
Expand Down
5 changes: 1 addition & 4 deletions src/box/box.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,7 @@ import { Action } from '../authorization/enum/action.enum';
import { BoxTestingSessionGuard } from './auth/boxTestingSession.guard';

@Controller('box')
@UseGuards(
BoxAuthGuard,
BoxTestingSessionGuard
)
@UseGuards(BoxAuthGuard, BoxTestingSessionGuard)
export class BoxController {
public constructor(
@InjectModel(GroupAdmin.name) public readonly groupModel: Model<GroupAdmin>,
Expand Down
Loading