diff --git a/packages/query-graphql/__tests__/__fixtures__/test-resolver.authorizer.ts b/packages/query-graphql/__tests__/__fixtures__/test-resolver.authorizer.ts index cefb102ea..8f4758621 100644 --- a/packages/query-graphql/__tests__/__fixtures__/test-resolver.authorizer.ts +++ b/packages/query-graphql/__tests__/__fixtures__/test-resolver.authorizer.ts @@ -13,4 +13,8 @@ export class TestResolverAuthorizer implements Authorizer { authorizeRelation(): Promise> { return Promise.reject(new Error('authorizeRelation Not Implemented')) } + + computeAuthorizationFilter(): Promise> { + return Promise.reject(new Error('computeAuthorizationFilter Not Implemented')) + } } diff --git a/packages/query-graphql/__tests__/auth/default-crud-auth.service.spec.ts b/packages/query-graphql/__tests__/auth/default-crud-auth.service.spec.ts index 3580dbe56..d2e9081ee 100644 --- a/packages/query-graphql/__tests__/auth/default-crud-auth.service.spec.ts +++ b/packages/query-graphql/__tests__/auth/default-crud-auth.service.spec.ts @@ -126,6 +126,21 @@ describe('createDefaultAuthorizer', () => { expect(filter).toEqual({ ownerId: { eq: 2 } }) }) + it('should expose computeAuthorizationFilter as a public alias of authorize', async () => { + const authorizer = testingModule.get>(getAuthorizerToken(TestDTO)) + const context = { user: { id: 7 } } + const authzCtx: AuthorizationContext = { + operationName: 'queryMany', + operationGroup: OperationGroup.READ, + readonly: true, + many: true + } + const viaAuthorize = await authorizer.authorize(context, authzCtx) + const viaCompute = await authorizer.computeAuthorizationFilter(context, authzCtx) + expect(viaCompute).toEqual(viaAuthorize) + expect(viaCompute).toEqual({ ownerId: { eq: 7 } }) + }) + it('should create an auth filter that depends on the passed operation name', async () => { const authorizer = testingModule.get>(getAuthorizerToken(TestDTO)) const filter = await authorizer.authorize( diff --git a/packages/query-graphql/src/auth/authorizer.ts b/packages/query-graphql/src/auth/authorizer.ts index 06f57dcc7..d79b237d8 100644 --- a/packages/query-graphql/src/auth/authorizer.ts +++ b/packages/query-graphql/src/auth/authorizer.ts @@ -44,4 +44,16 @@ export interface Authorizer extends CustomAuthorizer { context: any, authorizerContext: AuthorizationContext ): Promise | undefined> + + /** + * Compute the authorization filter the `@Authorize` pipeline would apply for + * the given context and operation. Intended for consumers outside the + * resolver pipeline (custom resolvers, domain services) so they can reuse + * the same `Filter` the generated CRUD resolvers use. + */ + computeAuthorizationFilter( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + context: any, + authorizerContext: AuthorizationContext + ): Promise> } diff --git a/packages/query-graphql/src/auth/default-crud.authorizer.ts b/packages/query-graphql/src/auth/default-crud.authorizer.ts index 468f77d37..a4f55edf7 100644 --- a/packages/query-graphql/src/auth/default-crud.authorizer.ts +++ b/packages/query-graphql/src/auth/default-crud.authorizer.ts @@ -12,15 +12,22 @@ export interface AuthorizerOptions { authorize: (context: any, authorizationContext: AuthorizationContext) => Filter | Promise> } -const createRelationAuthorizer = (opts: AuthorizerOptions): Authorizer => ({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async authorize(context: any, authorizationContext: AuthorizationContext): Promise> { - return opts.authorize(context, authorizationContext) ?? {} - }, - authorizeRelation(): Promise> { - return Promise.reject(new Error('Not implemented')) +const createRelationAuthorizer = (opts: AuthorizerOptions): Authorizer => { + const relationAuthorizer: Authorizer = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async authorize(context: any, authorizationContext: AuthorizationContext): Promise> { + return opts.authorize(context, authorizationContext) ?? {} + }, + authorizeRelation(): Promise> { + return Promise.reject(new Error('Not implemented')) + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + computeAuthorizationFilter(context: any, authorizationContext: AuthorizationContext): Promise> { + return relationAuthorizer.authorize(context, authorizationContext) + } } -}) + return relationAuthorizer +} export function createDefaultAuthorizer( DTOClass: Class, @@ -51,6 +58,14 @@ export function createDefaultAuthorizer( ) } + computeAuthorizationFilter( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + context: any, + authorizationContext: AuthorizationContext + ): Promise> { + return this.authorize(context, authorizationContext) + } + async authorizeRelation( relationName: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any