diff --git a/.changeset/mighty-forks-exercise.md b/.changeset/mighty-forks-exercise.md new file mode 100644 index 0000000000..bea5288811 --- /dev/null +++ b/.changeset/mighty-forks-exercise.md @@ -0,0 +1,5 @@ +--- +"@comet/cms-api": minor +--- + +Allow returning multiple content scopes in `ScopedEntity`-decorator diff --git a/packages/api/cms-api/src/builds/changes-checker.interceptor.ts b/packages/api/cms-api/src/builds/changes-checker.interceptor.ts index 43f18ee550..90297a3665 100644 --- a/packages/api/cms-api/src/builds/changes-checker.interceptor.ts +++ b/packages/api/cms-api/src/builds/changes-checker.interceptor.ts @@ -29,17 +29,20 @@ export class ChangesCheckerInterceptor implements NestInterceptor { this.reflector.get(SKIP_BUILD_METADATA_KEY, context.getClass()); if (!skipBuild) { - const scope = await this.contentScopeService.inferScopeFromExecutionContext(context); - - if (process.env.NODE_ENV === "development" && this.changeAffectsAllScopes(scope)) { - if (operation.name) { - console.warn(`Mutation "${operation.name.value}" affects all scopes. Are you sure this is correct?`); - } else { - console.warn(`Unknown mutation affects all scopes. Are you sure this is correct?`); + const scopes = await this.contentScopeService.inferScopesFromExecutionContext(context); + if (scopes) { + for (const scope of scopes) { + if (process.env.NODE_ENV === "development" && this.changeAffectsAllScopes(scope)) { + if (operation.name) { + console.warn(`Mutation "${operation.name.value}" affects all scopes. Are you sure this is correct?`); + } else { + console.warn(`Unknown mutation affects all scopes. Are you sure this is correct?`); + } + } + + await this.buildsService.setChangesSinceLastBuild(scope); } } - - await this.buildsService.setChangesSinceLastBuild(scope); } } } diff --git a/packages/api/cms-api/src/user-permissions/auth/user-permissions.guard.ts b/packages/api/cms-api/src/user-permissions/auth/user-permissions.guard.ts index 95500beffe..6d9a1241f4 100644 --- a/packages/api/cms-api/src/user-permissions/auth/user-permissions.guard.ts +++ b/packages/api/cms-api/src/user-permissions/auth/user-permissions.guard.ts @@ -39,10 +39,10 @@ export class UserPermissionsGuard implements CanActivate { throw new Error(`RequiredPermission decorator is missing in ${context.getClass().name}::${context.getHandler().name}()`); } - let contentScope: ContentScope | undefined; + let requiredContentScopes: ContentScope[] | undefined; if (!this.isResolvingGraphQLField(context) && !requiredPermission.options?.skipScopeCheck) { - contentScope = await this.contentScopeService.inferScopeFromExecutionContext(context); - if (!contentScope) { + requiredContentScopes = await this.contentScopeService.inferScopesFromExecutionContext(context); + if (!requiredContentScopes) { throw new Error( `Could not get ContentScope. Either pass a scope-argument or add @AffectedEntity()-decorator or enable skipScopeCheck in @RequiredPermission() (${ context.getClass().name @@ -57,7 +57,11 @@ export class UserPermissionsGuard implements CanActivate { if (requiredPermissions.length === 0) { throw new Error(`RequiredPermission decorator has empty permissions in ${context.getClass().name}::${context.getHandler().name}()`); } - return requiredPermissions.some((permission) => this.accessControlService.isAllowed(user, permission, contentScope)); + return requiredPermissions.some((permission) => + requiredContentScopes + ? requiredContentScopes.some((contentScope) => this.accessControlService.isAllowed(user, permission, contentScope)) + : this.accessControlService.isAllowed(user, permission), + ); } // See https://docs.nestjs.com/graphql/other-features#execute-enhancers-at-the-field-resolver-level diff --git a/packages/api/cms-api/src/user-permissions/content-scope.service.ts b/packages/api/cms-api/src/user-permissions/content-scope.service.ts index a940d90e5f..689863142e 100644 --- a/packages/api/cms-api/src/user-permissions/content-scope.service.ts +++ b/packages/api/cms-api/src/user-permissions/content-scope.service.ts @@ -21,12 +21,12 @@ export class ContentScopeService { return isEqual({ ...scope1 }, { ...scope2 }); } - async inferScopeFromExecutionContext(context: ExecutionContext): Promise { + async inferScopeFromExecutionContext(context: ExecutionContext): Promise { const args = await this.getArgs(context); const affectedEntity = this.reflector.getAllAndOverride("affectedEntity", [context.getHandler(), context.getClass()]); if (affectedEntity) { - let contentScope: ContentScope | undefined; + let contentScope: ContentScope | ContentScope[] | undefined; // eslint-disable-next-line @typescript-eslint/no-explicit-any const repo = this.orm.em.getRepository(affectedEntity.entity); if (affectedEntity.options.idArg) { @@ -74,6 +74,12 @@ export class ContentScopeService { } } + async inferScopesFromExecutionContext(context: ExecutionContext): Promise { + const scope = await this.inferScopeFromExecutionContext(context); + if (scope === undefined) return scope; + return Array.isArray(scope) ? scope : [scope]; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any private async getArgs(context: ExecutionContext): Promise> { if (context.getType().toString() === "graphql") { diff --git a/packages/api/cms-api/src/user-permissions/decorators/scoped-entity.decorator.ts b/packages/api/cms-api/src/user-permissions/decorators/scoped-entity.decorator.ts index 70a084b0f7..7aead92047 100644 --- a/packages/api/cms-api/src/user-permissions/decorators/scoped-entity.decorator.ts +++ b/packages/api/cms-api/src/user-permissions/decorators/scoped-entity.decorator.ts @@ -4,10 +4,10 @@ import { ContentScope } from "../../user-permissions/interfaces/content-scope.in export interface ScopedEntityMeta { // eslint-disable-next-line @typescript-eslint/no-explicit-any - fn: (entity: any) => Promise; + fn: (entity: any) => Promise; } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const ScopedEntity = (fn: (entity: any) => Promise): CustomDecorator => { +export const ScopedEntity = (fn: (entity: any) => Promise): CustomDecorator => { return SetMetadata("scopedEntity", { fn }); };