Skip to content

Commit 6419ea3

Browse files
guguclaude
andcommitted
Add Cedar policy editor to group creation and edit dialogs
Expose cedarPolicy through backend API (DTOs, responses, use cases) and add Monaco-based code editor to group-add-dialog and group-name-edit-dialog in the Angular frontend, allowing users to write Cedar authorization policies when creating or editing user groups. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9a19ae0 commit 6419ea3

25 files changed

Lines changed: 196 additions & 48 deletions

backend/src/entities/connection/application/data-structures/create-group-in-connection.ds.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export class CreateGroupInConnectionDs {
22
group_parameters: {
33
title: string;
44
connectionId: string;
5+
cedarPolicy?: string | null;
56
};
67
creation_info: {
78
cognitoUserName: string;
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { ApiProperty } from '@nestjs/swagger';
2-
import { IsNotEmpty, IsString } from 'class-validator';
2+
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
33

44
export class CreateGroupInConnectionDTO {
55
@IsNotEmpty()
66
@IsString()
77
@ApiProperty()
88
title: string;
9+
10+
@IsOptional()
11+
@IsString()
12+
@ApiProperty({ required: false, nullable: true })
13+
cedarPolicy?: string | null;
914
}

backend/src/entities/connection/application/dto/found-user-groups-in-connection.dto.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export class FoundGroupInConnectionDTO {
1212
@ApiProperty()
1313
isMain: boolean;
1414

15+
@ApiProperty({ required: false, nullable: true })
16+
cedarPolicy?: string | null;
17+
1518
@ApiProperty({ required: false, isArray: true, type: SimpleFoundUserInfoDs })
1619
users?: Array<SimpleFoundUserInfoDs>;
1720
}

backend/src/entities/connection/connection.controller.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,7 @@ import { AmplitudeEventTypeEnum, InTransactionEnum } from '../../enums/index.js'
2222
import { Messages } from '../../exceptions/text/messages.js';
2323
import { processExceptionMessage } from '../../exceptions/utils/process-exception-message.js';
2424
import { ConnectionEditGuard, ConnectionReadGuard } from '../../guards/index.js';
25-
import {
26-
isConnectionTypeAgent,
27-
slackPostMessage,
28-
toPrettyErrorsMsg,
29-
} from '../../helpers/index.js';
25+
import { isConnectionTypeAgent, slackPostMessage, toPrettyErrorsMsg } from '../../helpers/index.js';
3026
import { SentryInterceptor } from '../../interceptors/index.js';
3127
import { SuccessResponse } from '../../microservices/saas-microservice/data-structures/common-responce.ds.js';
3228
import { AmplitudeService } from '../amplitude/amplitude.service.js';
@@ -413,14 +409,15 @@ export class ConnectionController {
413409
@SlugUuid('connectionId') connectionId: string,
414410
@UserId() userId: string,
415411
): Promise<FoundGroupResponseDto> {
416-
const { title } = groupData;
412+
const { title, cedarPolicy } = groupData;
417413
if (!title) {
418414
throw new BadRequestException(Messages.GROUP_TITLE_MISSING);
419415
}
420416
const inputData: CreateGroupInConnectionDs = {
421417
group_parameters: {
422418
title: title,
423419
connectionId: connectionId,
420+
cedarPolicy: cedarPolicy,
424421
},
425422
creation_info: {
426423
cognitoUserName: userId,
@@ -689,5 +686,4 @@ export class ConnectionController {
689686
}
690687
return await this.unfreezeConnectionUseCase.execute({ connectionId, userId }, InTransactionEnum.ON);
691688
}
692-
693689
}

backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class CreateGroupInConnectionUseCase
2626

2727
protected async implementation(inputData: CreateGroupInConnectionDs): Promise<FoundGroupResponseDto> {
2828
const {
29-
group_parameters: { connectionId, title },
29+
group_parameters: { connectionId, title, cedarPolicy },
3030
creation_info: { cognitoUserName },
3131
} = inputData;
3232
const connectionToUpdate = await this._dbContext.connectionRepository.findConnectionWithGroups(connectionId);
@@ -36,15 +36,13 @@ export class CreateGroupInConnectionUseCase
3636
const foundUser = await this._dbContext.userRepository.findOneUserById(cognitoUserName);
3737
const newGroupEntity = buildNewGroupEntityForConnectionWithUser(connectionToUpdate, foundUser, title);
3838
const savedGroup = await this._dbContext.groupRepository.saveNewOrUpdatedGroup(newGroupEntity);
39-
savedGroup.cedarPolicy = generateCedarPolicyForGroup(
40-
connectionId,
41-
false,
42-
{
39+
savedGroup.cedarPolicy =
40+
cedarPolicy ??
41+
generateCedarPolicyForGroup(connectionId, false, {
4342
connection: { connectionId, accessLevel: AccessLevelEnum.none },
4443
group: { groupId: savedGroup.id, accessLevel: AccessLevelEnum.none },
4544
tables: [],
46-
},
47-
);
45+
});
4846
await this._dbContext.groupRepository.saveNewOrUpdatedGroup(savedGroup);
4947
Cacher.invalidateCedarPolicyCache(connectionId);
5048
return buildFoundGroupResponseDto(savedGroup);

backend/src/entities/connection/utils/build-found-user-group-in-connection-dto.util.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export function buildFoundUserGroupInConnectionDto(
1212
id: group.id,
1313
title: group.title,
1414
isMain: group.isMain,
15+
cedarPolicy: group.cedarPolicy,
1516
users: group.users?.length ? group.users.map((user) => buildSimpleUserInfoDs(user)) : undefined,
1617
},
1718
accessLevel,

backend/src/entities/group/application/data-sctructures/found-user-groups.ds.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export class FoundGroupDataInfoDs {
1111

1212
@ApiProperty()
1313
isMain: boolean;
14+
15+
@ApiProperty({ required: false, nullable: true })
16+
cedarPolicy?: string | null;
1417
}
1518

1619
export class FoundGroupDataWithUsersDs extends FoundGroupDataInfoDs {

backend/src/entities/group/dto/found-group-response.dto.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export class FoundGroupResponseDto {
1111
@ApiProperty()
1212
isMain: boolean;
1313

14+
@ApiProperty({ required: false, nullable: true })
15+
cedarPolicy?: string | null;
16+
1417
@ApiProperty({ required: false, isArray: true, type: SimpleFoundUserInfoDs })
1518
users?: Array<SimpleFoundUserInfoDs>;
1619
}

backend/src/entities/group/dto/update-group-title.dto.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ApiProperty } from '@nestjs/swagger';
2-
import { IsNotEmpty, IsString, IsUUID } from 'class-validator';
2+
import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
33

44
export class UpdateGroupTitleDto {
55
@ApiProperty()
@@ -12,4 +12,9 @@ export class UpdateGroupTitleDto {
1212
@IsNotEmpty()
1313
@IsUUID()
1414
groupId: string;
15+
16+
@IsOptional()
17+
@IsString()
18+
@ApiProperty({ required: false, nullable: true })
19+
cedarPolicy?: string | null;
1520
}

backend/src/entities/group/use-cases/update-group-title.use.case.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import AbstractUseCase from '../../../common/abstract-use.case.js';
33
import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
44
import { BaseType } from '../../../common/data-injection.tokens.js';
55
import { Messages } from '../../../exceptions/text/messages.js';
6+
import { Cacher } from '../../../helpers/cache/cacher.js';
67
import { FoundGroupDataInfoDs } from '../application/data-sctructures/found-user-groups.ds.js';
78
import { UpdateGroupTitleDto } from '../dto/update-group-title.dto.js';
89
import { IUpdateGroupTitle } from './use-cases.interfaces.js';
@@ -20,7 +21,7 @@ export class UpdateGroupTitleUseCase
2021
}
2122

2223
protected async implementation(groupData: UpdateGroupTitleDto): Promise<FoundGroupDataInfoDs> {
23-
const { groupId, title } = groupData;
24+
const { groupId, title, cedarPolicy } = groupData;
2425
const groupToUpdate = await this._dbContext.groupRepository.findGroupByIdWithConnectionAndUsers(groupId);
2526
if (!groupToUpdate) {
2627
throw new HttpException(
@@ -34,16 +35,21 @@ export class UpdateGroupTitleUseCase
3435
groupToUpdate.connection.id,
3536
);
3637

37-
if (connectionWithGroups.groups.find((group) => group.title === title)) {
38+
if (connectionWithGroups.groups.find((group) => group.title === title && group.id !== groupId)) {
3839
throw new BadRequestException(Messages.GROUP_NAME_UNIQUE);
3940
}
4041

4142
groupToUpdate.title = title;
43+
if (cedarPolicy !== undefined) {
44+
groupToUpdate.cedarPolicy = cedarPolicy;
45+
Cacher.invalidateCedarPolicyCache(groupToUpdate.connection.id);
46+
}
4247
const updatedGroup = await this._dbContext.groupRepository.saveNewOrUpdatedGroup(groupToUpdate);
4348
return {
4449
id: updatedGroup.id,
4550
title: updatedGroup.title,
4651
isMain: updatedGroup.isMain,
52+
cedarPolicy: updatedGroup.cedarPolicy,
4753
};
4854
}
4955
}

0 commit comments

Comments
 (0)