@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
102 lines • 6.18 kB
TypeScript
/**
* The built-in "access denied" page a BROWSER sees when the dashboard guard refuses a page
* navigation — the SPA shell, its assets, or (Mode A only) a session-less visit. Before this page
* existed the guard answered every denial with the JSON it answers the API with
* (`{ "error": "forbidden" }`), which is the right thing for the SPA's `fetch` calls and the wrong
* thing for a human staring at a browser tab.
*
* Deliberately a small, dependency-free, hand-authored HTML page (like `login_page.ts`) — NOT part
* of the bundled SPA, so a denial never needs the SPA to load first. It carries NO `<script>` at all,
* so a host running a nonce'd `script-src` CSP (the `@adonisjs/shield` default) cannot break it; the
* one inline `<style>` takes the request's CSP nonce when the provider hands one over.
*
* Every string the host can supply ({@link AccessDeniedPageOptions}) is HTML-escaped on the way in.
* They are developer-controlled config values, not user input — but escaping them costs nothing and
* removes the one class of bug a "just interpolate it" template would invite.
*
* The visual language (Aviary neutrals, the console's own accent, mono eyebrow over a sans body)
* mirrors the agent SPA so the refusal reads as part of the same product, not a server default.
*
* Ported from `@adonis-agora/payments` (also in `@adonis-agora/durable`, `@adonis-agora/media`, `@adonis-agora/agent` and
* `@adonis-agora/telescope` with only {@link CONSOLE} changed, so the Agora consoles share ONE
* refusal story rather than each inventing its own.
*/
/** Why the guard refused. Drives the default title/message and which buttons make sense. */
export type AccessDeniedReason =
/** Nobody is signed in (a `401`): offer the login page when one exists. */
'unauthenticated'
/** Somebody is signed in but `authorize` said no (a `403`). */
| 'forbidden'
/** `dashboardAuth` runs in Mode A only and there is no session — the host app must mint one. */
| 'session-required';
/** What the provider knows about the refusal — handed to a custom renderer and to the default page. */
export interface AccessDeniedInfo {
/** The HTTP status the page is served with. */
status: 401 | 403;
reason: AccessDeniedReason;
/** The console's mount path (`/agent/dashboard`; `''` when mounted at the root). */
basePath: string;
/** The built-in login page, present only when `dashboardAuth.login` is configured. */
loginHref?: string;
/** A developer-facing detail (e.g. a thrown hook's message). Providers set it OUTSIDE production only. */
detail?: string;
/** The request's CSP nonce, applied to the inline `<style>` so a nonce'd `style-src` keeps the page styled. */
nonce?: string;
}
/**
* Knobs for the built-in page — the `accessDenied` config option in its OBJECT form. Every field is
* optional; omit the option entirely for the defaults.
*/
export interface AccessDeniedPageOptions {
/** Console name in the eyebrow and `<title>`. Default: the console's own name (`Agent`). */
brand?: string;
/** Heading. Default depends on {@link AccessDeniedInfo.reason}. */
title?: string;
/** The sentence under the heading. Default depends on the reason. */
message?: string;
/** Where "Back to app" points. Default `/`; `false` hides the button. */
homeHref?: string | false;
/** Label of the "Back to app" button. */
homeLabel?: string;
/** Where "Sign in" points. Default: the built-in login page when one exists; `false` hides it. */
loginHref?: string | false;
/** Label of the "Sign in" button. */
loginLabel?: string;
/** Accent colour (any CSS colour). Default: the console's own accent. */
accent?: string;
}
/**
* The `accessDenied` config option in its FUNCTION form: render the page yourself. Return an HTML
* string and the provider serves it (with the refusal's status, `text/html`, `no-store`). Return
* nothing after answering the request yourself — a redirect to your own login page, most commonly —
* and the provider stands down. Return nothing WITHOUT answering and the default page is served, so
* the hook is safe to use for side effects (logging) alone.
*
* `Ctx` is the host framework's HTTP context — the provider passes AdonisJS's `HttpContext` through
* untouched; it is generic here so this module stays framework-light and unit-testable.
*/
export type AccessDeniedRenderer<Ctx = unknown> = (info: AccessDeniedInfo, ctx: Ctx) => string | undefined | null | void | Promise<string | undefined | null | void>;
/** The `accessDenied` config option: tweak the built-in page, or replace it. */
export type AccessDeniedOption<Ctx = unknown> = AccessDeniedPageOptions | AccessDeniedRenderer<Ctx>;
/** This console's identity — the ONLY thing that differs between the Agora ports of this file. */
export declare const CONSOLE: {
readonly brand: 'Agent';
readonly accent: '#a78bfa';
readonly packageName: '@adonis-agora/agent';
};
/** Escape the five characters that matter inside HTML text and double-quoted attributes. */
export declare function escapeHtml(value: string): string;
/**
* Render the built-in page. Pure: same `info` + `options` in, same HTML out — so it is trivially
* snapshot/regex testable and safe to call from anywhere (the provider, a test, a host that wants
* to embed the default page inside its own layout).
*/
export declare function renderAccessDeniedPage(info: AccessDeniedInfo, options?: AccessDeniedPageOptions): string;
/**
* Resolve WHAT to serve for a refusal, honouring the host's `accessDenied` option in either form.
* Returns the HTML to send, or `null` when a custom renderer already answered the request itself
* (`answered()` reports whether it did — the provider wires that to "a `location` header or a body
* is already set"). Framework-light: knows nothing about the HTTP context beyond passing it through.
*/
export declare function resolveAccessDeniedPage<Ctx>(info: AccessDeniedInfo, option: AccessDeniedOption<Ctx> | null | undefined, ctx: Ctx, answered: () => boolean): Promise<string | null>;
//# sourceMappingURL=access_denied_page.d.ts.map