import { MaybePromise } from "@entwico/dash"; import { StringPattern } from "@entwico/dash/match"; import { ActionAPIContext, ActionClient, ActionError, ActionErrorCode } from "astro:actions"; import { z } from "astro/zod"; import { APIContext, APIRoute, MiddlewareHandler } from "astro"; //#region src/guards/guard.d.ts /** what a guard proves about the request: merged into `locals` for the guarded code */ type Proof = Record; /** the request context every guard runs against — the part actions, endpoints and pages share */ type GuardContext = ActionAPIContext; /** the page context: an endpoint's `context` or a page's `Astro`, which can also redirect and rewrite */ type PageContext = GuardContext & Pick; type GuardedLocals = Omit & P; /** the context with the proofs of the guards made required on `locals` */ type GuardedContext = Omit & { locals: GuardedLocals; }; type GuardInput = Shape extends z.ZodRawShape ? z.output> : unknown; type GuardPageDenial = (ctx: PageContext, denial: ActionError) => MaybePromise; type GuardDefinition = { name: string; /** request fields the guard needs: merged into the action's input schema, stripped again before the handler */ input?: Shape | undefined; /** denies by throwing (see `deny`), proves by returning the values the guarded code may rely on */ check: (ctx: Ctx, input: GuardInput) => MaybePromise; /** * what a browser sees when this guard denies (a redirect, a rewrite); * default: the denial's status with its message */ page?: GuardPageDenial | undefined; }; type ProofOf = R extends Proof ? R : Record; type ShapeOf = Shape extends z.ZodRawShape ? Shape : Record; interface Guard

, Shape extends z.ZodRawShape = Record> { readonly name: string; /** the fields this guard and its parents need from the request */ readonly input: Shape | undefined; readonly page: GuardPageDenial | undefined; /** @internal runs the parents, then the check; the proofs land on `ctx.locals` and are returned merged */ run(ctx: GuardContext, input: unknown): Promise

; /** a guard that runs after this one, with this one's proofs already on `locals` */ extend(definition: GuardDefinition>): Guard

, Shape & ShapeOf>; } type ProofOfGuard = G extends Guard ? P : never; type ShapeOfGuard = G extends Guard ? S : never; type Intersected = UnionToIntersection extends (infer I) ? (I extends object ? I : Record) : never; /** proofs of a guard list, intersected */ type Proofs = Intersected>; /** request fields of a guard list, intersected */ type GuardShapes = Intersected>; type UnionToIntersection = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never; export declare function defineGuard(definition: GuardDefinition): Guard, ShapeOf>; /** denies the request: an `ActionError` for actions, the matching status for endpoints and pages */ export declare function deny(code: ActionErrorCode, message?: string): never; export declare function isDenial(error: unknown): error is ActionError; //#endregion //#region src/guards/actions.d.ts type ActionAccept = 'form' | 'json'; type GuardedActionHandler = TInputSchema extends z.core.$ZodType ? (input: z.output, context: GuardedContext>) => MaybePromise : (input: any, context: GuardedContext>) => MaybePromise; /** the schema the client calls with: the action's own, plus the fields its guards read */ type ClientInputSchema = keyof GuardShapes extends never ? TInputSchema : TInputSchema extends z.ZodObject ? z.ZodObject> : TInputSchema extends undefined ? z.ZodObject> : TInputSchema; type GuardedActionOptions = { accept?: TAccept; input?: TInputSchema; /** run in order after validation; a denial becomes the action's error, the proofs land on `context.locals` */ guards?: Guards; handler: GuardedActionHandler; }; /** * Astro's `defineAction` with guards: the listed guards run after input validation and before the * handler, which sees `locals` with their proofs required. Fields a guard declares in `input` are * merged into the action's schema and stripped again before the handler — the action never learns * about them. */ export declare function defineAction : undefined, const Guards extends readonly Guard[] = []>(options: GuardedActionOptions): ActionClient> & string; //#endregion //#region src/guards/routes.d.ts type GuardedRouteOptions = { /** * run in order before the handler; a denial answers per the request: * json for api callers, the page response for a browser */ guards: Guards; handler: (context: GuardedContext>) => Response | Promise; }; /** * An endpoint behind guards. Fields the guards declare in `input` are read from the request body * (json or form data); the body stays readable for the handler. */ export declare function defineRoute(options: GuardedRouteOptions): APIRoute; //#endregion //#region src/guards/denial.d.ts /** * One denial policy for endpoints, pages and the middleware, decided by the request rather than * by the surface: a browser navigation — `text/html` in `Accept`, the same signal astro uses to * tell a form submission from an rpc call — gets the denying guard's `page` response (a redirect * to the login, a 404 rewrite), or the status with the message; anything else gets a json error * it can act on. */ export declare function denialResponse(context: PageContext, denial: ActionError): Promise; export declare function acceptsHtml(request: Request): boolean; //#endregion //#region src/guards/pages.d.ts type Guarded

= ({ denied: Response; } & { [K in keyof P]?: undefined; }) | ({ denied?: undefined; } & P); /** * Guards a page from its frontmatter: `const { denied, user } = await guard(Astro, [authenticated]); * if (denied) return denied;` — after the check the proofs are required, before it they are not. * The denial is the one the request asked for: the denying guard's `page` response for a browser, * a json error for anything else (see `denialResponse`). */ export declare function guard(context: PageContext, guards: Guards): Promise>>; //#endregion //#region src/guards/middleware.d.ts type GuardRule = { /** request paths the guards apply to, in the exclude-pattern vocabulary */ match: readonly StringPattern[]; guards: readonly Guard[]; }; /** * Guards whole areas from the middleware: every rule whose patterns match the request path runs * its guards, and the first denial answers per the request: the denying guard's `page` response * for a browser, a json error for an api caller. Defence in depth, not a replacement for the * page-level `guard()`: a middleware cannot narrow `locals`, and * guards that already passed here are not run again by the page's call. */ export declare function createGuardMiddleware(rules: readonly GuardRule[]): MiddlewareHandler; //#endregion //#region src/guards/rate-limit.d.ts type RateLimitOptions = { /** requests allowed per key within the window */ max: number; /** window length in milliseconds */ window: number; /** * What the limit is counted against; defaults to the client address. Return `undefined` to * exempt a request. */ key?: ((ctx: GuardContext) => string | undefined) | undefined; /** the guard's name in denials and logs; default `rate-limit` */ name?: string | undefined; }; /** * A fixed window per key, in memory and per process: the `max`-th request within `window` ms * still passes, the next one is denied with `TOO_MANY_REQUESTS` (429) until the window ends. * Per process means per pod; the total across replicas is still bounded, just by that factor. * Put it first in a guard list so a flood is refused before any guard that costs something * (a captcha verification, a database lookup) runs. */ export declare function rateLimit(options: RateLimitOptions): Guard; //#endregion export type { ClientInputSchema, Guard, GuardContext, GuardDefinition, GuardInput, GuardPageDenial, GuardRule, GuardShapes, Guarded, GuardedActionHandler, GuardedActionOptions, GuardedContext, GuardedLocals, GuardedRouteOptions, PageContext, Proof, Proofs, RateLimitOptions }; //# sourceMappingURL=guards.d.ts.map