Skip to content

feat: Refactor authorization system to use ABAC pattern with string-based roles#719

Merged
kingston merged 10 commits into
mainfrom
kingston/eng-955-update-authorization-framework-utilities-and-types
Dec 27, 2025
Merged

feat: Refactor authorization system to use ABAC pattern with string-based roles#719
kingston merged 10 commits into
mainfrom
kingston/eng-955-update-authorization-framework-utilities-and-types

Conversation

@kingston

@kingston kingston commented Dec 26, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Introduced ABAC-based authorization system with caching for improved performance and flexibility.
    • Added support for mixed global and instance-level authorization checks.
    • Implemented model authorizer framework for flexible, per-model authorization patterns.
  • Changes

    • Simplified authorization configuration by removing role-type dependencies.
    • Enhanced authorization caching with request-scoped decision tracking.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel

vercel Bot commented Dec 26, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
baseplate-project-builder-web Ready Ready Preview, Comment Dec 27, 2025 0:20am

@changeset-bot

changeset-bot Bot commented Dec 26, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a1ca70f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 18 packages
Name Type
@baseplate-dev/fastify-generators Patch
@baseplate-dev/project-builder-server Patch
@baseplate-dev/plugin-auth Patch
@baseplate-dev/plugin-queue Patch
@baseplate-dev/plugin-storage Patch
@baseplate-dev/project-builder-cli Patch
@baseplate-dev/project-builder-common Patch
@baseplate-dev/project-builder-test Patch
@baseplate-dev/project-builder-web Patch
@baseplate-dev/create-project Patch
@baseplate-dev/code-morph Patch
@baseplate-dev/core-generators Patch
@baseplate-dev/project-builder-lib Patch
@baseplate-dev/react-generators Patch
@baseplate-dev/sync Patch
@baseplate-dev/tools Patch
@baseplate-dev/ui-components Patch
@baseplate-dev/utils Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Dec 26, 2025

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Refactors authorization system from role-based to ABAC pattern. Replaces AuthRole type and extractRoles config with new authorizer utilities (checkGlobalAuthorization, checkInstanceAuthorization, createModelAuthorizer), adds caching fields to ServiceContext, simplifies authorization rule types to single root parameter, and updates GraphQL plugins and data operations accordingly.

Changes

Cohort / File(s) Summary
Authorization Utilities Framework
examples/blog-with-auth/apps/backend/src/utils/authorizers.ts, examples/todo-with-auth0/apps/backend/src/utils/authorizers.ts, packages/fastify-generators/src/generators/prisma/prisma-authorizer-utils/templates/src/utils/authorizers.ts, packages/fastify-generators/src/generators/auth/authorizer-utils-stub/templates/src/utils/authorizers.ts
Introduces ABAC framework with GlobalRoleCheck/InstanceRoleCheck types, checkGlobalAuthorization(), checkInstanceAuthorization() functions, and createModelAuthorizer() factory with per-request caching, lazy instance loading, and model id resolution.
Service Context Caching
examples/blog-with-auth/apps/backend/src/utils/service-context.ts, examples/todo-with-auth0/apps/backend/src/utils/service-context.ts
Adds authorizerCache (Map\<string, boolean\>) and authorizerModelCache (Map\<string, unknown\>) fields to ServiceContext interface and initializes them in factory.
GraphQL Plugin Type Simplification
examples/.../src/plugins/graphql/FieldAuthorizePlugin/types.ts, examples/.../src/plugins/graphql/FieldAuthorizePlugin/global-types.ts, packages/fastify-generators/src/generators/pothos/pothos-auth/templates/src/plugins/graphql/FieldAuthorizePlugin/*
Replaces multi-parameter generic AuthorizeRoleRuleFunction<RootType, ArgsType, Types> with discriminated union AuthorizeRoleRule<RootType> (GlobalRoleCheck | InstanceRoleCheck). Removes UserSchemaTypes, ExtendDefaultTypes, and extractRoles from AuthorizeRolePluginOptions. Simplifies FieldOptions.authorize signature from three to one type parameter.
FieldAuthorizePlugin Logic
examples/blog-with-auth/apps/backend/src/plugins/graphql/FieldAuthorizePlugin/index.ts, examples/todo-with-auth0/apps/backend/src/plugins/graphql/FieldAuthorizePlugin/index.ts, packages/fastify-generators/src/generators/pothos/pothos-auth/templates/src/plugins/graphql/FieldAuthorizePlugin/index.ts
Simplifies authorizeAccess() method by removing args and info parameters; replaces inline rule evaluation with single checkInstanceAuthorization() call. Removes ForbiddenError handling; delegates to centralized authorization function.
SchemaBuilder Configuration
examples/blog-with-auth/apps/backend/src/plugins/graphql/builder.ts, examples/todo-with-auth0/apps/backend/src/plugins/graphql/builder.ts
Removes AuthRole type from SchemaBuilder generics; simplifies authorizeByRoles from { extractRoles: ..., requireOnRootFields: true } to { requireOnRootFields: true }.
Data Operations Authorization
examples/.../src/utils/data-operations/define-operations.ts, packages/fastify-generators/src/generators/prisma/data-utils/templates/src/utils/data-operations/define-operations.ts
Changes CreateOperationConfig.authorize from function to GlobalRoleCheck[]; updates UpdateOperationConfig.authorize and DeleteOperationConfig.authorize to (GlobalRoleCheck | InstanceRoleCheck<...>)[]. Replaces direct authorize() invocation with checkGlobalAuthorization() (create) and checkInstanceAuthorization() with lazy loading (update/delete).
Generator Providers & Infrastructure
packages/fastify-generators/src/generators/auth/_providers/authorizer-utils-imports.ts, packages/fastify-generators/src/generators/auth/_providers/{index.ts,providers.json}, packages/fastify-generators/src/generators/pothos/pothos-auth/{extractor.json,pothos-auth.generator.ts}, packages/fastify-generators/src/generators/prisma/_providers/authorizer-utils-imports.ts, packages/fastify-generators/src/generators/prisma/data-utils/extractor.json
Introduces authorizerUtilsImportsProvider and schema for auth utils; removes authRolesImportsProvider and extractRoles from pothos-auth generator; wires authorizerUtilsImportsProvider and serviceContextImportsProvider into field-authorize-plugin extractors.
Authorizer Stub Generator
packages/fastify-generators/src/generators/auth/authorizer-utils-stub/*
Adds new stub generator with templates and configuration for projects without authentication, exporting stub types/functions conforming to authorizer-utils API.
Prisma Authorizer Utils Generator
packages/fastify-generators/src/generators/prisma/prisma-authorizer-utils/{extractor.json,prisma-authorizer-utils.generator.ts,index.ts,templates/...}
Introduces new Prisma generator wiring authorizerCache and authorizerModelCache into ServiceContext configuration; defines extractor exposing checkGlobalAuthorization, checkInstanceAuthorization, createModelAuthorizer and related types.
Barrel Exports
packages/fastify-generators/src/generators/auth/index.ts, packages/fastify-generators/src/generators/prisma/index.ts
Adds re-exports for authorizer-utils-stub and prisma-authorizer-utils generators.
Application Integration
packages/project-builder-server/src/compiler/backend/fastify.ts, plugins/plugin-auth/src/auth/core/node.ts
Conditionally injects authorizerUtilsStubGenerator when authorizer utils not present; wires prismaAuthorizerUtilsGenerator into backend app compiler root children.
Metadata & Configuration
examples/.../baseplate/file-id-map.json, examples/.../src/utils/.templates-info.json
Adds file-id-map entries and template-info entries for utils-authorizers generator mapping.

Sequence Diagrams

sequenceDiagram
    actor Client
    participant GraphQL as GraphQL Resolver
    participant FAP as FieldAuthorizePlugin
    participant AuthUtils as Authorizer Utils
    participant DB as Database
    participant Context as ServiceContext

    Client->>GraphQL: Field Access
    GraphQL->>FAP: wrapResolve(authorize, source, context)
    FAP->>FAP: authorizeAccess(authorize, source, context)
    alt Single rule or array
        FAP->>AuthUtils: checkInstanceAuthorization(ctx, instance, rules)
        AuthUtils->>Context: Check authorizerCache
        alt Cache hit
            AuthUtils-->>FAP: Return (cached result)
        else Cache miss
            alt Global rules (string)
                AuthUtils->>AuthUtils: Match global roles
            else Instance rules (function)
                AuthUtils->>Context: Lazily resolve instance
                alt Instance provided
                    AuthUtils->>AuthUtils: Use provided instance
                else Instance loader
                    AuthUtils->>DB: Load instance via loader
                    DB-->>AuthUtils: Instance or null
                end
                AuthUtils->>AuthUtils: Execute instance check(ctx, instance)
            end
            AuthUtils->>Context: Cache result in authorizerCache
            AuthUtils-->>FAP: Return or throw ForbiddenError
        end
    end
    alt Authorization passed
        FAP->>GraphQL: Proceed to resolver
        GraphQL-->>Client: Result
    else Authorization failed
        FAP-->>Client: ForbiddenError
    end
Loading
sequenceDiagram
    actor User
    participant DataOp as Data Operation (Update/Delete)
    participant AuthUtils as Authorizer Utils
    participant DB as Database
    participant Context as ServiceContext

    User->>DataOp: Invoke operation with config.authorize
    DataOp->>DataOp: Check if authorize array non-empty
    alt authorize present and non-empty
        rect rgb(200, 220, 255)
            note over DataOp,Context: Lazy Instance Loading
            DataOp->>DataOp: Prepare loadExisting callback
        end
        DataOp->>AuthUtils: checkInstanceAuthorization(ctx, loadExisting, authorize)
        AuthUtils->>Context: Check authorizerCache for this rule+id
        alt Cache hit
            AuthUtils-->>DataOp: Return (cached)
        else Cache miss
            AuthUtils->>AuthUtils: Filter: global checks vs instance checks
            alt Global role checks
                AuthUtils->>AuthUtils: Check user roles
            else Instance checks
                AuthUtils->>DataOp: Call loadExisting (lazy)
                DataOp->>DB: Fetch existing record
                DB-->>DataOp: Record
                DataOp-->>AuthUtils: Instance data
                AuthUtils->>AuthUtils: Execute check(ctx, instance)
            end
            AuthUtils->>Context: Cache authorizerCache[key] = result
            AuthUtils-->>DataOp: Return or throw ForbiddenError
        end
    end
    alt Authorization passed or skipped
        DataOp->>DB: Execute operation
        DB-->>DataOp: Result
        DataOp-->>User: Success
    else Authorization failed
        DataOp-->>User: ForbiddenError
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #645: Directly modifies the same authorization-related files (FieldAuthorizePlugin types/global-types/index, service-context, utils/authorizers) that this PR refactors, introducing the ABAC foundation.
  • PR #700: Both modify data-operations create/update/delete operation signatures and integrate ServiceContext threading through the same templates.
  • PR #692: Both update the Prisma data-operations stack and integrate authorization via shared service-context and authorizer utilities.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.10% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Refactor authorization system to use ABAC pattern with string-based roles' clearly and specifically summarizes the main change: transitioning from role-based to attribute-based access control with string-based roles.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kingston/eng-955-update-authorization-framework-utilities-and-types

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
examples/todo-with-auth0/apps/backend/src/utils/data-operations/define-operations.ts (3)

387-415: JSDoc example is outdated.

The example in the defineCreateOperation JSDoc still shows the old function-based authorize pattern:

authorize: async (data, ctx) => {
  // Check if user has permission to create
},

This should be updated to match the new array-based pattern:

authorize: ['admin', 'system'],
🔎 Suggested fix
  * @example
  * ```typescript
  * const createUser = defineCreateOperation({
  *   model: 'user',
  *   fields: {
  *     name: scalarField(z.string()),
  *     email: scalarField(z.email()),
  *   },
- *   authorize: async (data, ctx) => {
- *     // Check if user has permission to create
- *   },
+ *   authorize: ['admin', 'system'],
  *   create: ({ tx, data, query, serviceContext }) =>

673-703: JSDoc example for update is outdated.

Similar to the create operation, the defineUpdateOperation JSDoc example still shows the old function-based authorization pattern. As per coding guidelines for examples/todo-with-auth0/**/*.{ts,tsx}, JSDocs should be accurate.

🔎 Suggested fix
  * @example
  * ```typescript
  * const updateUser = defineUpdateOperation({
  *   model: 'user',
  *   fields: {
  *     name: scalarField(z.string()),
  *     email: scalarField(z.email()),
  *   },
- *   authorize: async (data, ctx) => {
- *     const existing = await ctx.loadExisting();
- *     // Check if user owns this record
- *   },
+ *   authorize: ['admin', userAuthorizer.roles.owner],
  *   update: ({ tx, where, data, query, serviceContext }) =>

945-974: JSDoc example for delete is outdated.

The defineDeleteOperation JSDoc example also shows the old function-based pattern.

🔎 Suggested fix
  * @example
  * ```typescript
  * const deleteUser = defineDeleteOperation({
  *   model: 'user',
- *   authorize: async (ctx) => {
- *     const existing = await ctx.loadExisting();
- *     // Check if user has permission to delete
- *   },
+ *   authorize: ['admin', userAuthorizer.roles.owner],
  *   delete: ({ tx, where, query, serviceContext }) =>
examples/blog-with-auth/apps/backend/src/utils/data-operations/define-operations.ts (3)

387-415: JSDoc example is outdated.

Same issue as in the todo-with-auth0 file - the defineCreateOperation JSDoc example shows the old function-based authorize pattern instead of the new array-based pattern.


673-703: JSDoc example for update is outdated.

The defineUpdateOperation JSDoc example still shows the old function-based authorization pattern.


945-974: JSDoc example for delete is outdated.

The defineDeleteOperation JSDoc example also shows the old function-based pattern.

🧹 Nitpick comments (6)
examples/todo-with-auth0/apps/backend/src/utils/service-context.ts (3)

5-11: Add JSDocs to the ServiceContext interface.

The ServiceContext interface and its new cache fields should be documented with JSDocs explaining their purpose, particularly the authorization caching mechanism.

📝 Suggested JSDoc documentation
+/**
+ * Service context for a single request, containing authentication info and per-request caches.
+ */
 export interface ServiceContext {
   /* TPL_CONTEXT_INTERFACE:START */
   auth: AuthContext;
+  /** Cache for global/instance authorization check results to avoid redundant checks within a request */
   authorizerCache: Map<string, boolean>;
+  /** Cache for model instances used in authorization checks to avoid redundant database queries */
   authorizerModelCache: Map<string, unknown>;
   /* TPL_CONTEXT_INTERFACE:END */
 }

Based on coding guidelines for examples/todo-with-auth0/**/*.{ts,tsx}.


13-25: Add JSDocs to the createServiceContext function.

The createServiceContext function should include JSDoc documentation explaining its purpose, parameters, and return value.

📝 Suggested JSDoc documentation
+/**
+ * Creates a service context for a request.
+ * 
+ * @param params - The parameters for creating the service context
+ * @param params.auth - The authentication context for the request
+ * @returns A new service context with initialized caches
+ */
 export function createServiceContext(
   /* TPL_CREATE_CONTEXT_ARGS:START */ {
     auth,
   }: {
     auth: AuthContext;
   } /* TPL_CREATE_CONTEXT_ARGS:END */,
 ): ServiceContext {

Based on coding guidelines for examples/todo-with-auth0/**/*.{ts,tsx}.


8-9: Consider more specific typing for authorizerModelCache.

The authorizerModelCache uses Map<string, unknown> which provides minimal type safety. While this may be intentional for flexibility, consider whether a union type of expected model types or a generic constraint could improve type safety.

For example:

authorizerModelCache: Map<string, Record<string, unknown>>;

This ensures cached values are at least object-like, which aligns with the model instance caching use case.

examples/blog-with-auth/apps/backend/src/plugins/graphql/FieldAuthorizePlugin/index.ts (1)

37-47: Add explicit return type to authorizeAccess method.

Per coding guidelines for TypeScript files, all functions should have explicit return types. The method is missing the return type annotation.

🔎 Proposed fix
   async authorizeAccess(
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
     authorize: AuthorizeRoleRuleOption<any>,
     root: unknown,
     context: Types['Context'],
-  ): Promise<void> {
+  ): Promise<void> {

The return type Promise<void> is already present - disregard this comment.

Actually, looking more carefully at line 42, the return type Promise<void> is specified. The implementation correctly delegates to checkInstanceAuthorization which throws ForbiddenError on authorization failure.

LGTM!

The refactored authorizeAccess method correctly delegates to the centralized checkInstanceAuthorization utility, simplifying the authorization flow.

examples/todo-with-auth0/apps/backend/src/plugins/graphql/FieldAuthorizePlugin/types.ts (1)

31-33: Add JSDoc to AuthorizeRolePluginOptions interface.

Per coding guidelines for examples/todo-with-auth0/**/*.ts, all exported interfaces should have JSDocs documenting their purpose and fields.

🔎 Proposed fix
+/**
+ * Configuration options for the authorize-by-roles plugin.
+ */
 export interface AuthorizeRolePluginOptions {
+  /**
+   * When true, requires all root Query/Mutation/Subscription fields to have an authorize option.
+   */
   requireOnRootFields?: boolean;
 }
examples/todo-with-auth0/apps/backend/src/utils/authorizers.ts (1)

261-295: Well-implemented two-level caching strategy.

The function correctly implements lazy loading with separate caches for role check results and model instances. The caching of null models prevents redundant database queries for non-existent entities.

Minor: Consider more explicit null check

Line 287 could use an explicit null check for clarity:

-    if (!model) {
+    if (model === null) {
       // Cache negative result for missing models
       ctx.authorizerCache.set(cacheKey, false);
       return false;
     }

This makes it clearer that we're specifically checking for the "not found" case, though the current code is functionally correct since undefined is already handled above.

Comment on lines +1 to +8
import type { AuthRole } from '../modules/accounts/auth/constants/auth-roles.constants.js';
import type {
GetPayload,
ModelPropName,
} from './data-operations/prisma-types.js';
import type { ServiceContext } from './service-context.js';

import { ForbiddenError } from './http-errors.js';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use @src/ alias for import paths.

According to the coding guidelines, import statements should use absolute paths via tsconfig paths (@src/ is the alias for src/).

🔎 Proposed fix
-import type { AuthRole } from '../modules/accounts/auth/constants/auth-roles.constants.js';
+import type { AuthRole } from '@src/modules/accounts/auth/constants/auth-roles.constants.js';
 import type {
   GetPayload,
   ModelPropName,
-} from './data-operations/prisma-types.js';
-import type { ServiceContext } from './service-context.js';
+} from '@src/utils/data-operations/prisma-types.js';
+import type { ServiceContext } from '@src/utils/service-context.js';

-import { ForbiddenError } from './http-errors.js';
+import { ForbiddenError } from '@src/utils/http-errors.js';

Based on coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { AuthRole } from '../modules/accounts/auth/constants/auth-roles.constants.js';
import type {
GetPayload,
ModelPropName,
} from './data-operations/prisma-types.js';
import type { ServiceContext } from './service-context.js';
import { ForbiddenError } from './http-errors.js';
import type { AuthRole } from '@src/modules/accounts/auth/constants/auth-roles.constants.js';
import type {
GetPayload,
ModelPropName,
} from '@src/utils/data-operations/prisma-types.js';
import type { ServiceContext } from '@src/utils/service-context.js';
import { ForbiddenError } from '@src/utils/http-errors.js';
🤖 Prompt for AI Agents
In examples/todo-with-auth0/apps/backend/src/utils/authorizers.ts lines 1-8, the
imports use relative paths but must use the project alias @src/ per tsconfig
paths; update each import to use the absolute alias (for example change import
type { AuthRole } from
'../modules/accounts/auth/constants/auth-roles.constants.js' to import type {
AuthRole } from '@src/modules/accounts/auth/constants/auth-roles.constants.js')
and do the same for the GetPayload/ModelPropName and ServiceContext imports,
preserving the exported names and file extensions; no other logic changes
required.

Comment on lines +42 to +58
export async function checkInstanceAuthorization<T>(
ctx: ServiceContext,
instance: T | (() => Promise<T>),
authorize: (InstanceRoleCheck<T> | GlobalRoleCheck)[],
): Promise<void> {
const resolvedInstance =
typeof instance === 'function'
? await (instance as () => Promise<T>)()
: instance;

// Run instance checks
for (const check of authorize) {
if (await check(ctx, resolvedInstance)) return;
}

throw new ForbiddenError('Forbidden');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Type safety issue with mixed check types.

The function signature accepts both InstanceRoleCheck<T> and GlobalRoleCheck in the authorize array (line 45), but line 54 calls check(ctx, resolvedInstance) with 2 arguments. GlobalRoleCheck only accepts 1 argument (ctx: ServiceContext) => boolean, which creates a type mismatch.

While JavaScript will ignore the extra argument at runtime, this violates type safety. Consider one of these approaches:

  1. Remove GlobalRoleCheck from the union type on line 45
  2. Add conditional logic to call checks with the correct number of arguments based on type
  3. Use function overloads to handle both cases separately
🔎 Proposed fix: Remove GlobalRoleCheck from union type
 export async function checkInstanceAuthorization<T>(
   ctx: ServiceContext,
   instance: T | (() => Promise<T>),
-  authorize: (InstanceRoleCheck<T> | GlobalRoleCheck)[],
+  authorize: InstanceRoleCheck<T>[],
 ): Promise<void> {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function checkInstanceAuthorization<T>(
ctx: ServiceContext,
instance: T | (() => Promise<T>),
authorize: (InstanceRoleCheck<T> | GlobalRoleCheck)[],
): Promise<void> {
const resolvedInstance =
typeof instance === 'function'
? await (instance as () => Promise<T>)()
: instance;
// Run instance checks
for (const check of authorize) {
if (await check(ctx, resolvedInstance)) return;
}
throw new ForbiddenError('Forbidden');
}
export async function checkInstanceAuthorization<T>(
ctx: ServiceContext,
instance: T | (() => Promise<T>),
authorize: InstanceRoleCheck<T>[],
): Promise<void> {
const resolvedInstance =
typeof instance === 'function'
? await (instance as () => Promise<T>)()
: instance;
// Run instance checks
for (const check of authorize) {
if (await check(ctx, resolvedInstance)) return;
}
throw new ForbiddenError('Forbidden');
}

@kingston kingston merged commit f1bab33 into main Dec 27, 2025
15 checks passed
@kingston kingston deleted the kingston/eng-955-update-authorization-framework-utilities-and-types branch December 27, 2025 12:49
@github-actions github-actions Bot mentioned this pull request Dec 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant