Conversation
There was a problem hiding this comment.
Pull request overview
Introduces Cedar-based authorization as an optional (“CEDAR_AUTHORIZATION_ENABLED”) authorization path, with policy generation persisted on groups and cached per connection, plus extensive E2E/unit tests to validate behavior.
Changes:
- Add Cedar authorization service/module, schema, entity builder, and policy generator; integrate Cedar checks into existing guards with legacy fallback intent.
- Persist generated Cedar policy text on
GroupEntityand invalidate a new per-connection Cedar policy cache when groups/permissions change. - Add/adjust AVA E2E + unit tests for Cedar authorization behavior and update expected error codes/messages.
Reviewed changes
Copilot reviewed 42 out of 43 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Locks new @cedar-policy/cedar-wasm dependency. |
| backend/package.json | Adds @cedar-policy/cedar-wasm runtime dependency. |
| backend/.development.env | Adds CEDAR_AUTHORIZATION_ENABLED dev env flag. |
| backend/src/app.module.ts | Registers global CedarAuthorizationModule. |
| backend/src/helpers/constants/constants.ts | Adds Cedar policy cache defaults. |
| backend/src/helpers/cache/cacher.ts | Adds Cedar policy cache + invalidation helpers. |
| backend/src/migrations/1771545600000-AddCedarPolicyToGroup.ts | Adds cedarPolicy column to group table. |
| backend/src/entities/group/group.entity.ts | Adds cedarPolicy field to GroupEntity. |
| backend/src/entities/permission/use-cases/create-or-update-permissions.use.case.ts | Generates/saves Cedar policy on permission updates; invalidates cache. |
| backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts | Initializes new group Cedar policy; invalidates cache. |
| backend/src/entities/connection/use-cases/create-connection.use.case.ts | Writes admin group Cedar policy at connection creation. |
| backend/src/entities/cedar-authorization/cedar-action-map.ts | Defines Cedar action/resource identifiers. |
| backend/src/entities/cedar-authorization/cedar-authorization.module.ts | Provides Cedar auth service as a global module. |
| backend/src/entities/cedar-authorization/cedar-authorization.service.interface.ts | Defines Cedar auth service interface. |
| backend/src/entities/cedar-authorization/cedar-authorization.service.ts | Implements Cedar evaluation, policy loading, and caching. |
| backend/src/entities/cedar-authorization/cedar-entity-builder.ts | Builds Cedar entities (user/group/connection/table). |
| backend/src/entities/cedar-authorization/cedar-policy-generator.ts | Generates Cedar policy text from complex permissions. |
| backend/src/entities/cedar-authorization/cedar-schema.ts | Adds Cedar schema (TS). |
| backend/src/entities/cedar-authorization/cedar-schema.json | Adds Cedar schema (JSON). |
| backend/src/entities/cedar-authorization/scripts/migrate-permissions-to-cedar.ts | Adds backfill script to generate policies from existing permissions. |
| backend/src/guards/connection-read.guard.ts | Adds Cedar-first check with intended legacy fallback. |
| backend/src/guards/connection-edit.guard.ts | Adds Cedar-first check with intended legacy fallback. |
| backend/src/guards/group-read.guard.ts | Adds Cedar-first check with intended legacy fallback. |
| backend/src/guards/group-edit.guard.ts | Adds Cedar-first check with intended legacy fallback. |
| backend/src/guards/table-read.guard.ts | Adds Cedar-first check with intended legacy fallback. |
| backend/src/guards/table-add.guard.ts | Adds Cedar-first check with intended legacy fallback. |
| backend/src/guards/table-edit.guard.ts | Adds Cedar-first check with intended legacy fallback. |
| backend/src/guards/table-delete.guard.ts | Adds Cedar-first check with intended legacy fallback. |
| backend/test/utils/user-with-different-permissions-utils.ts | Adds test helper for “connection:edit only” user scenario. |
| backend/test/ava-tests/saas-tests/saas-cedar-permissions-e2e.test.ts | New SaaS Cedar E2E test suite. |
| backend/test/ava-tests/non-saas-tests/non-saas-cedar-permissions-e2e.test.ts | New non-SaaS Cedar E2E test suite. |
| backend/test/ava-tests/non-saas-tests/non-saas-cedar-policy-generator.test.ts | New unit tests for policy generator. |
| backend/test/ava-tests/non-saas-tests/non-saas-cedar-entity-builder.test.ts | New unit tests for entity builder. |
| backend/test/ava-tests/*/group-e2e.test.ts | Updates expected status/messages to 403/DONT_HAVE_PERMISSIONS for Cedar path. |
| backend/test/ava-tests//user--permissions-e2e.test.ts | Updates expected status/messages to 403/DONT_HAVE_PERMISSIONS for Cedar path. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const policies = await this.loadPoliciesForConnection(connectionId); | ||
| if (!policies) return false; | ||
|
|
||
| const entities = buildCedarEntities(userId, userGroups, connectionId, tableName); | ||
|
|
||
| const call = { | ||
| principal: { type: CEDAR_USER_TYPE, id: userId }, | ||
| action: { type: CEDAR_ACTION_TYPE, id: action }, | ||
| resource: { type: resourceType as string, id: resourceId }, | ||
| context: {}, | ||
| policies: { staticPolicies: policies }, | ||
| entities: entities, | ||
| schema: this.schema, | ||
| }; | ||
|
|
||
| const result = this.cedarModule.isAuthorized(call as Parameters<typeof this.cedarModule.isAuthorized>[0]); | ||
| if (result.type === 'success') { | ||
| return result.response.decision === 'allow'; | ||
| } | ||
|
|
||
| this.logger.warn(`Cedar authorization error: ${JSON.stringify(result.errors)}`); | ||
| return false; | ||
| } | ||
|
|
||
| private async loadPoliciesForConnection(connectionId: string): Promise<string | null> { | ||
| const cached = Cacher.getCedarPolicyCache(connectionId); | ||
| if (cached !== null) return cached; | ||
|
|
||
| const groups = await this.groupRepository.findAllGroupsInConnection(connectionId); | ||
| const policyTexts = groups.map((g) => g.cedarPolicy).filter(Boolean); | ||
|
|
||
| if (policyTexts.length === 0) return null; | ||
|
|
||
| const combined = policyTexts.join('\n\n'); | ||
| Cacher.setCedarPolicyCache(connectionId, combined); | ||
| return combined; | ||
| } |
There was a problem hiding this comment.
evaluate() returns false when loadPoliciesForConnection() returns null/empty or when Cedar returns a non-success result. In the guards, a false return becomes a hard 403 and does not trigger the intended legacy fallback (fallback only happens on thrown exceptions). This can lock out users if Cedar is enabled but policies haven’t been backfilled yet, or if Cedar returns structured errors instead of throwing. Consider throwing on Cedar evaluation errors / missing policy set (or returning a tri-state like {allowed, shouldFallback}) so the guards can correctly fall back to legacy auth when Cedar can’t make a decision.
| import { PermissionEntity } from '../../permission/permission.entity.js'; | ||
| import { IComplexPermission, ITablePermissionData } from '../../permission/permission.interface.js'; | ||
| import { generateCedarPolicyForGroup } from '../cedar-policy-generator.js'; | ||
|
|
||
| export async function migratePermissionsToCedar(dataSource: DataSource): Promise<void> { | ||
| const connectionRepository = dataSource.getRepository(ConnectionEntity); | ||
| const groupRepository = dataSource.getRepository(GroupEntity); | ||
| const permissionRepository = dataSource.getRepository(PermissionEntity); |
There was a problem hiding this comment.
permissionRepository is declared but never used, which will be flagged by the repo’s Biome noUnusedVariables rule. Remove it (or use it) to keep the script lint-clean.
| import { PermissionEntity } from '../../permission/permission.entity.js'; | |
| import { IComplexPermission, ITablePermissionData } from '../../permission/permission.interface.js'; | |
| import { generateCedarPolicyForGroup } from '../cedar-policy-generator.js'; | |
| export async function migratePermissionsToCedar(dataSource: DataSource): Promise<void> { | |
| const connectionRepository = dataSource.getRepository(ConnectionEntity); | |
| const groupRepository = dataSource.getRepository(GroupEntity); | |
| const permissionRepository = dataSource.getRepository(PermissionEntity); | |
| import { IComplexPermission, ITablePermissionData } from '../../permission/permission.interface.js'; | |
| import { generateCedarPolicyForGroup } from '../cedar-policy-generator.js'; | |
| export async function migratePermissionsToCedar(dataSource: DataSource): Promise<void> { | |
| const connectionRepository = dataSource.getRepository(ConnectionEntity); | |
| const groupRepository = dataSource.getRepository(GroupEntity); |
… table name references in user group permissions tests
No description provided.