import type { UserModel as OrmUserModel } from '@stacksjs/orm'; /** * Define the application's gates, policy mappings and before/after callbacks. * * The `define*` helper for `app/Gates.ts`. Both halves of `policies` are * checked - the key names a model that exists, the value names a policy file * that exists - where the type used to be * `Record` on both sides, so a mapping to * a policy that is not there registered nothing and denied every check on that * model with no error anywhere. * * The `const` type parameter keeps the ability names, which is what * `storage/framework/types/gates.d.ts` reads back to fill `AppGates`. * * The second half of the constraint is what rejects a key that is not a model, * and it is not redundant with `PolicyMapping`. Excess-property checking is a * freshness rule on the object literal and stops applying as soon as inference * has a matching property to work with: `{ Psot: 'PostPolicy' }` alone was * caught, and the same typo beside one correct entry was not. Requiring every * key outside the model list to hold something no policy name can be makes the * check structural. * * @example * ```ts * // app/Gates.ts * import { defineGates } from '@stacksjs/auth' * * export default defineGates({ * gates: { * 'access-admin': user => user?.email?.endsWith('@stacksjs.com') ?? false, * }, * policies: { * Post: 'PostPolicy', * }, * }) * ``` */ export declare function defineGates(definition: T & { policies?: OnlyKnownModels }): T; /** * Define a new authorization gate * * @example * define('edit-settings', (user) => user?.isAdmin) * define('update-post', (user, post) => user?.id === post.userId) */ export declare function define(ability: Ability, callback: GateCallback): void; /** * Register a policy for a model * * @example * policy('Post', PostPolicy) * policy(Post, PostPolicy) */ export declare function policy(model: PolicyModelName | { name: PolicyModelName }, policyClass: new () => Policy): void; /** * Register a callback to run before all gate checks * * @example * before((user, _ability) => { * if (user?.isSuperAdmin) return true // Super admins can do anything * return null // Continue to normal checks * }) */ export declare function before(callback: GateBeforeCallback): void; /** * Register a callback to run after all gate checks */ export declare function after(callback: GateAfterCallback): void; /** * Check if the user is allowed to perform an ability * * @example * if (await allows('edit-settings', user)) { ... } * if (await allows('update', user, post)) { ... } */ export declare function allows(ability: A, user: UserModel | null, ...args: AbilityArgs): Promise; /** * Check if the user is denied from performing an ability * * @example * if (await denies('delete', user, post)) { ... } */ export declare function denies(ability: A, user: UserModel | null, ...args: AbilityArgs): Promise; /** * Check if the user can perform an ability (alias for allows) */ export declare function can(ability: A, user: UserModel | null, ...args: AbilityArgs): Promise; /** * Check if the user cannot perform an ability (alias for denies) */ export declare function cannot(ability: A, user: UserModel | null, ...args: AbilityArgs): Promise; /** * Check if the user can perform any of the given abilities * * @example * if (await any(['update', 'delete'], user, post)) { ... } */ /* * `any` / `all` / `none` keep `any[]`: one argument list is checked against * SEVERAL abilities, which may each declare different parameters, so there is * no single tuple that is correct for the call. The single-ability functions * above are where the declaration can be enforced. */ export declare function any(abilities: readonly Ability[], user: UserModel | null, ...args: any[]): Promise; /** * Check if the user can perform all of the given abilities * * @example * if (await all(['view', 'update'], user, post)) { ... } */ export declare function all(abilities: readonly Ability[], user: UserModel | null, ...args: any[]): Promise; /** * Check if the user can perform none of the given abilities */ export declare function none(abilities: readonly Ability[], user: UserModel | null, ...args: any[]): Promise; /** * Authorize an ability or throw an exception * * @example * await authorize('update', user, post) // Throws if not allowed */ export declare function authorize(ability: A, user: UserModel | null, ...args: AbilityArgs): Promise; /** * Get detailed inspection result for an ability check */ export declare function inspect(ability: A, user: UserModel | null, ...args: AbilityArgs): Promise; /** * Get a policy instance for a model */ export declare function getPolicyFor(model: T): Policy | null; /** * Check if a gate is defined */ export declare function has(ability: Ability): boolean; /** * Check if a policy is registered for a model */ export declare function hasPolicy(model: string | { name: string }): boolean; /** * Get all defined gate names */ export declare function abilities(): string[]; /** * Clear all gates and policies (useful for testing) */ export declare function flush(): void; /** * Gate facade for convenient access */ export declare const Gate: { define: typeof define; policy: typeof policy; before: typeof before; after: typeof after; allows: typeof allows; denies: typeof denies; can: typeof can; cannot: typeof cannot; any: typeof any; all: typeof all; none: typeof none; authorize: typeof authorize; inspect: typeof inspect; has: typeof has; hasPolicy: typeof hasPolicy; abilities: typeof abilities; getPolicyFor: typeof getPolicyFor; flush: typeof flush; AuthorizationResponse: typeof AuthorizationResponse; AuthorizationException: typeof AuthorizationException }; /** * Augmentation target: the ability names this application's own gates define. * * Derived from `app/Gates.ts` itself by * `storage/framework/types/gates.d.ts`, so it cannot drift from the file it * describes - the gates are the declaration. * * @example * ```ts * declare module '@stacksjs/auth' { * interface AppGates { * 'access-admin': true * } * } * ``` */ // eslint-disable-next-line ts/no-empty-object-type -- augmentation target; empty by design export declare interface AppGates {} /** * Augmentation target: the policy classes under `app/Policies/`, and the * framework defaults behind it, by filename. * * Filled by `storage/framework/types/registries.d.ts`, which reads the same * name map `findPolicyFile` resolves through. */ // eslint-disable-next-line ts/no-empty-object-type -- augmentation target; empty by design export declare interface PolicyClasses {} /** * Augmentation target: the models a policy may be registered for. * * Derived from the models barrel, so it is the models that exist rather than a * list somebody maintains alongside them. */ // eslint-disable-next-line ts/no-empty-object-type -- augmentation target; empty by design export declare interface PolicyModels {} /** The shape of `app/Gates.ts`. */ export declare interface GatesDefinition { gates: Readonly> policies?: PolicyMapping before?: readonly GateBeforeCallback[] after?: readonly GateAfterCallback[] } /** * Policy class interface */ export declare interface Policy { before?(user: UserModel | null, ability: string): boolean | null | Promise viewAny?(user: UserModel | null): boolean | Promise | AuthorizationResponse view?(user: UserModel | null, model: T): boolean | Promise | AuthorizationResponse create?(user: UserModel | null): boolean | Promise | AuthorizationResponse update?(user: UserModel | null, model: T): boolean | Promise | AuthorizationResponse delete?(user: UserModel | null, model: T): boolean | Promise | AuthorizationResponse restore?(user: UserModel | null, model: T): boolean | Promise | AuthorizationResponse forceDelete?(user: UserModel | null, model: T): boolean | Promise | AuthorizationResponse [key: string]: PolicyMethod | undefined } // Alias the ORM-derived UserModel under the name this module uses internally. // The gate API receives authenticated user objects (rows / instances), // not the User class constructor. declare type UserModel = OrmUserModel; /** * Gate callback function type */ export type GateCallback = (_user: UserModel | null, ..._args: T[]) => boolean | Promise | AuthorizationResponse; /** An ability name defined by one of the application's gates. */ export type GateName = keyof AppGates extends never ? string : keyof AppGates & string; /** * The abilities `BasePolicy` resolves. A policy may add its own methods, which * is why `Ability` below stays open. */ export type PolicyAbility = 'viewAny' | 'view' | 'create' | 'update' | 'delete' | 'restore' | 'forceDelete'; /** * Any ability that can be checked. * * Deliberately open. An ability is legitimately dynamic - a `/can/:ability` * route passes one straight through, which is the reason * `RESERVED_POLICY_MEMBERS` exists at all - so narrowing this to the declared * set would reject correct code and break the fail-closed tests that check * what an UNKNOWN ability does. The union is here for the editor: the gates * and policy abilities are offered, and anything else still compiles. */ // eslint-disable-next-line ts/ban-types -- `string & {}` keeps literal completions alive export type Ability = GateName | PolicyAbility | (string & {}); /** * The arguments an ability takes after the user. * * A gate the application declares contributes its own parameter list, so * `Gate.allows('update-post', user, post)` is checked against how the gate was * written. Anything else - a policy ability, or a name computed at runtime - * keeps `any[]`, which is what it was for every ability before. */ export type AbilityArgs = A extends keyof AppGates ? (AppGates[A] extends readonly unknown[] ? AppGates[A] : any[]) : any[]; /** A policy class name, as narrow as the application has made it. */ export type PolicyName = keyof PolicyClasses extends never ? string : keyof PolicyClasses & string; /** A model name a policy may be registered for. */ export type PolicyModelName = keyof PolicyModels extends never ? string : keyof PolicyModels & string; /** Runs before every check. `true` allows, `false` denies, `null` continues. */ export type GateBeforeCallback = (_user: UserModel | null, _ability: string, _args: unknown[]) => boolean | null | Promise; /** Runs after every check. A boolean overrides the result; anything else keeps it. */ export type GateAfterCallback = (_user: UserModel | null, _ability: string, _result: boolean, _args: unknown[]) => boolean | void | Promise; /** * How `app/Gates.ts` maps a model to the policy that authorizes it. * * Every key optional: a mapping is written only for the models whose policy * does not follow the `Policy` convention. */ export type PolicyMapping = { readonly [K in PolicyModelName]?: PolicyName | { policy: PolicyName, model?: PolicyModelName } } /** * Every key of a policy map that is not a model, required to hold something no * policy name can be. * * Applied to the PARAMETER rather than to the type parameter's constraint: a * constraint that reads `T['policies']` is a self-reference and TypeScript * refuses it, while the parameter may name `T` freely because inference has * already run against the `T &` half. */ declare type OnlyKnownModels = { [K in keyof TPolicies]: K extends PolicyModelName ? TPolicies[K] : { 'this is not a model in this application': never } } /** * Policy method type. The return type intentionally allows `null` so that * a policy's `before()` hook (which returns `null` to delegate to the * underlying ability check) is index-compatible with the catch-all * `[key: string]: PolicyMethod | undefined` signature on `Policy`. */ export type PolicyMethod = (_user: UserModel | null, _model?: T, ..._args: any[]) => boolean | null | Promise | AuthorizationResponse; /** * Authorization response for detailed allow/deny */ export declare class AuthorizationResponse { readonly isAllowed: boolean; readonly message?: string; readonly code?: string; constructor(allowed: boolean, message?: string, code?: string); static allow(message?: string): AuthorizationResponse; static deny(message?: string, code?: string): AuthorizationResponse; allowed(): boolean; denied(): boolean; authorize(): void; } /** * Authorization exception */ export declare class AuthorizationException extends Error { public readonly code?: string; public readonly status?: number; constructor(message?: string, code?: string, status?: number); } export default Gate;