authhero 9.9.0 → 9.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/assets/u/widget/index.esm.js +1 -1
  2. package/dist/authhero.cjs +537 -338
  3. package/dist/authhero.d.ts +297 -124
  4. package/dist/authhero.mjs +14879 -14098
  5. package/dist/tsconfig.types.tsbuildinfo +1 -1
  6. package/dist/types/authentication-flows/authorization-code.d.ts +2 -1
  7. package/dist/types/authentication-flows/client-credentials.d.ts +2 -1
  8. package/dist/types/authentication-flows/grant-tokens.d.ts +16 -0
  9. package/dist/types/authentication-flows/passwordless.d.ts +6 -5
  10. package/dist/types/authentication-flows/refresh-token.d.ts +2 -1
  11. package/dist/types/authentication-flows/token-exchange.d.ts +2 -1
  12. package/dist/types/helpers/client-assertion-replay.d.ts +21 -0
  13. package/dist/types/helpers/client-assertion.d.ts +24 -2
  14. package/dist/types/helpers/default-destinations.d.ts +7 -1
  15. package/dist/types/helpers/outbox-destinations/index.d.ts +1 -0
  16. package/dist/types/helpers/outbox-destinations/pipeline.d.ts +63 -0
  17. package/dist/types/helpers/outbox-relay.d.ts +3 -0
  18. package/dist/types/helpers/refresh-token-lifetime.d.ts +103 -0
  19. package/dist/types/helpers/reserved-claims.d.ts +64 -0
  20. package/dist/types/helpers/run-outbox-relay.d.ts +8 -0
  21. package/dist/types/hooks/addDataHooks.d.ts +4 -3
  22. package/dist/types/index.d.ts +134 -121
  23. package/dist/types/routes/auth-api/index.d.ts +16 -16
  24. package/dist/types/routes/auth-api/passwordless.d.ts +14 -14
  25. package/dist/types/routes/auth-api/register/index.d.ts +2 -2
  26. package/dist/types/routes/management-api/action-triggers.d.ts +0 -2
  27. package/dist/types/routes/management-api/actions.d.ts +0 -7
  28. package/dist/types/routes/management-api/authentication-methods.d.ts +1 -1
  29. package/dist/types/routes/management-api/branding.d.ts +9 -9
  30. package/dist/types/routes/management-api/clients.d.ts +8 -8
  31. package/dist/types/routes/management-api/connections.d.ts +1 -1
  32. package/dist/types/routes/management-api/custom-domains.d.ts +6 -6
  33. package/dist/types/routes/management-api/email-templates.d.ts +18 -18
  34. package/dist/types/routes/management-api/failed-events.d.ts +21 -1
  35. package/dist/types/routes/management-api/forms.d.ts +126 -126
  36. package/dist/types/routes/management-api/guardian.d.ts +5 -5
  37. package/dist/types/routes/management-api/helpers.d.ts +1 -1
  38. package/dist/types/routes/management-api/index.d.ts +107 -96
  39. package/dist/types/routes/management-api/keys.d.ts +12 -12
  40. package/dist/types/routes/management-api/logs.d.ts +4 -4
  41. package/dist/types/routes/management-api/migration-sources.d.ts +6 -6
  42. package/dist/types/routes/management-api/organizations.d.ts +5 -5
  43. package/dist/types/routes/management-api/prompts.d.ts +4 -4
  44. package/dist/types/routes/management-api/roles.d.ts +1 -1
  45. package/dist/types/routes/management-api/tenant-export-import.d.ts +5 -5
  46. package/dist/types/routes/management-api/tenants.d.ts +11 -11
  47. package/dist/types/routes/management-api/users.d.ts +15 -15
  48. package/dist/types/routes/universal-login/common.d.ts +2 -2
  49. package/dist/types/routes/universal-login/flow-api.d.ts +8 -8
  50. package/dist/types/routes/universal-login/info-code-exchange.d.ts +27 -0
  51. package/dist/types/routes/universal-login/token-info-page.d.ts +26 -0
  52. package/dist/types/routes/universal-login/u2-index.d.ts +5 -5
  53. package/dist/types/routes/universal-login/u2-routes.d.ts +5 -5
  54. package/dist/types/types/AuthHeroConfig.d.ts +27 -0
  55. package/dist/types/types/Bindings.d.ts +12 -0
  56. package/dist/types/types/OutboxMetrics.d.ts +47 -0
  57. package/dist/types/types/index.d.ts +1 -0
  58. package/dist/types/utils/email.d.ts +21 -9
  59. package/package.json +8 -8
@@ -3,6 +3,7 @@ import { z } from "@hono/zod-openapi";
3
3
  import { Bindings, Variables } from "../types";
4
4
  import { TokenResponse } from "@authhero/adapter-interfaces";
5
5
  import { GrantFlowUserResult } from "src/types/GrantFlowResult";
6
+ import { EnrichedClient } from "../helpers/client";
6
7
  export declare const authorizationCodeGrantParamsSchema: z.ZodObject<{
7
8
  grant_type: z.ZodLiteral<"authorization_code">;
8
9
  client_id: z.ZodString;
@@ -16,7 +17,7 @@ export type AuthorizationCodeGrantTypeParams = z.infer<typeof authorizationCodeG
16
17
  export declare function authorizationCodeGrantUser(ctx: Context<{
17
18
  Bindings: Bindings;
18
19
  Variables: Variables;
19
- }>, params: AuthorizationCodeGrantTypeParams): Promise<GrantFlowUserResult>;
20
+ }>, params: AuthorizationCodeGrantTypeParams, preloadedClient?: EnrichedClient): Promise<GrantFlowUserResult>;
20
21
  export declare function authorizationCodeGrant(ctx: Context<{
21
22
  Bindings: Bindings;
22
23
  Variables: Variables;
@@ -2,6 +2,7 @@ import { Context } from "hono";
2
2
  import { z } from "@hono/zod-openapi";
3
3
  import { Bindings, Variables } from "../types";
4
4
  import { GrantFlowResult } from "../types/GrantFlowResult";
5
+ import { EnrichedClient } from "../helpers/client";
5
6
  export declare const clientCredentialGrantParamsSchema: z.ZodObject<{
6
7
  grant_type: z.ZodLiteral<"client_credentials">;
7
8
  scope: z.ZodOptional<z.ZodString>;
@@ -13,4 +14,4 @@ export declare const clientCredentialGrantParamsSchema: z.ZodObject<{
13
14
  export declare function clientCredentialsGrant(ctx: Context<{
14
15
  Bindings: Bindings;
15
16
  Variables: Variables;
16
- }>, params: z.infer<typeof clientCredentialGrantParamsSchema>): Promise<GrantFlowResult>;
17
+ }>, params: z.infer<typeof clientCredentialGrantParamsSchema>, preloadedClient?: EnrichedClient): Promise<GrantFlowResult>;
@@ -0,0 +1,16 @@
1
+ import { Context } from "hono";
2
+ import { GrantType, TokenResponse } from "@authhero/adapter-interfaces";
3
+ import { Bindings, Variables } from "../types";
4
+ import { GrantFlowResult } from "../types/GrantFlowResult";
5
+ /**
6
+ * Turn a resolved grant into wire tokens: narrow the requested scopes to what
7
+ * the user/client is actually granted for the audience, pick the token
8
+ * lifetime from the resource server, then mint the tokens.
9
+ *
10
+ * Shared by the /oauth/token endpoint and the server-side code exchange on the
11
+ * /u2/info test page so both mint identical tokens for the same grant.
12
+ */
13
+ export declare function issueTokensForGrant(ctx: Context<{
14
+ Bindings: Bindings;
15
+ Variables: Variables;
16
+ }>, grantResult: GrantFlowResult, grantType: GrantType): Promise<TokenResponse>;
@@ -1,6 +1,7 @@
1
1
  import { Context } from "hono";
2
2
  import { z } from "@hono/zod-openapi";
3
3
  import { Bindings, Variables } from "../types";
4
+ import { EnrichedClient } from "../helpers/client";
4
5
  import { GrantFlowUserResult } from "../types/GrantFlowResult";
5
6
  export declare const passwordlessGrantParamsSchema: z.ZodObject<{
6
7
  client_id: z.ZodString;
@@ -45,7 +46,7 @@ export declare const passwordlessGrantParamsSchema: z.ZodObject<{
45
46
  export declare function passwordlessGrantUser(ctx: Context<{
46
47
  Bindings: Bindings;
47
48
  Variables: Variables;
48
- }>, { client_id, username, otp, scope, audience, authParams, enforceIpCheck, }: z.input<typeof passwordlessGrantParamsSchema>): Promise<{
49
+ }>, { client_id, username, otp, scope, audience, authParams, enforceIpCheck, }: z.input<typeof passwordlessGrantParamsSchema>, preloadedClient?: EnrichedClient): Promise<{
49
50
  user: {
50
51
  connection: string;
51
52
  email_verified: boolean;
@@ -474,7 +475,7 @@ export declare function passwordlessGrantUser(ctx: Context<{
474
475
  custom_login_page_preview?: string | undefined;
475
476
  form_template?: string | undefined;
476
477
  addons?: Record<string, any> | undefined;
477
- token_endpoint_auth_method?: "none" | "client_secret_post" | "client_secret_basic" | "client_secret_jwt" | "private_key_jwt" | undefined;
478
+ token_endpoint_auth_method?: "client_secret_post" | "client_secret_basic" | "none" | "client_secret_jwt" | "private_key_jwt" | undefined;
478
479
  client_metadata?: Record<string, string> | undefined;
479
480
  hide_sign_up_disabled_error?: boolean | undefined;
480
481
  mobile?: Record<string, any> | undefined;
@@ -557,8 +558,8 @@ export declare function passwordlessGrantUser(ctx: Context<{
557
558
  } | undefined;
558
559
  authenticated_at?: string | undefined;
559
560
  };
560
- connectionType: "username" | "email" | "sms";
561
- authConnection: "username" | "email" | "sms";
561
+ connectionType: "email" | "username" | "sms";
562
+ authConnection: "email" | "username" | "sms";
562
563
  session_id: string | undefined;
563
564
  authParams: {
564
565
  audience?: string | undefined;
@@ -612,7 +613,7 @@ export declare function passwordlessGrantUser(ctx: Context<{
612
613
  export declare function passwordlessOtpGrant(ctx: Context<{
613
614
  Bindings: Bindings;
614
615
  Variables: Variables;
615
- }>, params: z.input<typeof passwordlessGrantParamsSchema>): Promise<GrantFlowUserResult>;
616
+ }>, params: z.input<typeof passwordlessGrantParamsSchema>, preloadedClient?: EnrichedClient): Promise<GrantFlowUserResult>;
616
617
  export declare function passwordlessGrant(ctx: Context<{
617
618
  Bindings: Bindings;
618
619
  Variables: Variables;
@@ -1,6 +1,7 @@
1
1
  import { Context } from "hono";
2
2
  import { Bindings, Variables, GrantFlowUserResult } from "../types";
3
3
  import { z } from "@hono/zod-openapi";
4
+ import { EnrichedClient } from "../helpers/client";
4
5
  export declare const refreshTokenParamsSchema: z.ZodObject<{
5
6
  grant_type: z.ZodLiteral<"refresh_token">;
6
7
  client_id: z.ZodString;
@@ -12,4 +13,4 @@ export declare const refreshTokenParamsSchema: z.ZodObject<{
12
13
  export declare function refreshTokenGrant(ctx: Context<{
13
14
  Bindings: Bindings;
14
15
  Variables: Variables;
15
- }>, params: z.infer<typeof refreshTokenParamsSchema>): Promise<GrantFlowUserResult>;
16
+ }>, params: z.infer<typeof refreshTokenParamsSchema>, preloadedClient?: EnrichedClient): Promise<GrantFlowUserResult>;
@@ -1,6 +1,7 @@
1
1
  import { Context } from "hono";
2
2
  import { z } from "@hono/zod-openapi";
3
3
  import { Bindings, Variables, GrantFlowUserResult } from "../types";
4
+ import { EnrichedClient } from "../helpers/client";
4
5
  export declare const TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange";
5
6
  export declare const tokenExchangeParamsSchema: z.ZodObject<{
6
7
  grant_type: z.ZodLiteral<"urn:ietf:params:oauth:grant-type:token-exchange">;
@@ -16,4 +17,4 @@ export type TokenExchangeParams = z.infer<typeof tokenExchangeParamsSchema>;
16
17
  export declare function tokenExchangeGrant(ctx: Context<{
17
18
  Bindings: Bindings;
18
19
  Variables: Variables;
19
- }>, params: TokenExchangeParams): Promise<GrantFlowUserResult>;
20
+ }>, params: TokenExchangeParams, preloadedClient?: EnrichedClient): Promise<GrantFlowUserResult>;
@@ -0,0 +1,21 @@
1
+ import { Context } from "hono";
2
+ import { Bindings, Variables } from "../types";
3
+ export interface ConsumeClientAssertionJtiParams {
4
+ clientId: string;
5
+ /** The assertion's `jti`. When absent there is nothing to spend. */
6
+ jti?: string;
7
+ /** The assertion's `exp`, in seconds — when the marker becomes collectable. */
8
+ exp: number;
9
+ }
10
+ /**
11
+ * Spend a client assertion's `jti`.
12
+ *
13
+ * @returns false when this assertion has already been presented (the caller
14
+ * must reject it as `invalid_client`), true otherwise. An assertion carrying
15
+ * no `jti` cannot be tracked, so it returns true — its replay window is
16
+ * bounded only by the assertion lifetime cap.
17
+ */
18
+ export declare function consumeClientAssertionJti(ctx: Context<{
19
+ Bindings: Bindings;
20
+ Variables: Variables;
21
+ }>, tenantId: string, params: ConsumeClientAssertionJtiParams): Promise<boolean>;
@@ -1,5 +1,12 @@
1
1
  import { LoadClientKeysOptions, ClientWithKeys } from "./client-keys";
2
2
  declare const ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
3
+ /**
4
+ * Default upper bound on a client assertion's lifetime. RFC 7523 gives no
5
+ * limit, so without one a client can mint an assertion valid for a year and a
6
+ * captured assertion stays usable for that whole window. 300s matches the
7
+ * usual guidance for a single-use token presented directly to the endpoint.
8
+ */
9
+ declare const DEFAULT_MAX_LIFETIME_SECONDS = 300;
3
10
  export type ClientAssertionMethod = "private_key_jwt" | "client_secret_jwt";
4
11
  export declare class ClientAssertionError extends Error {
5
12
  code: "invalid_client" | "invalid_request" | "unsupported_alg" | "missing_keys";
@@ -18,6 +25,14 @@ export interface VerifyClientAssertionOptions extends LoadClientKeysOptions {
18
25
  acceptedAudiences: string[];
19
26
  /** Clock-skew leeway in seconds. Defaults to 30. */
20
27
  leewaySeconds?: number;
28
+ /**
29
+ * Maximum accepted assertion lifetime in seconds. Rejects an assertion whose
30
+ * `exp - iat` exceeds this, and caps the absolute `exp` at `now + max` so an
31
+ * assertion that omits `iat` cannot sidestep the bound. Defaults to
32
+ * DEFAULT_MAX_LIFETIME_SECONDS (300) — the window a captured assertion stays
33
+ * replayable in is bounded by this, so keep it short.
34
+ */
35
+ maxLifetimeSeconds?: number;
21
36
  /** Override Date.now() for tests. */
22
37
  now?: () => number;
23
38
  }
@@ -26,8 +41,15 @@ export interface VerifiedClientAssertion {
26
41
  clientId: string;
27
42
  /** Which authentication method was actually used. */
28
43
  method: ClientAssertionMethod;
29
- /** Optional jti claim — useful if callers want to enforce replay protection. */
44
+ /**
45
+ * The `jti` claim, when present. `consumeClientAssertionJti`
46
+ * (helpers/client-assertion-replay.ts) spends it so an assertion cannot be
47
+ * presented twice; the token endpoint calls that after this verifier
48
+ * returns.
49
+ */
30
50
  jti?: string;
51
+ /** The `exp` claim, in seconds. Bounds how long the `jti` must be remembered. */
52
+ exp: number;
31
53
  /** The full verified payload, in case callers need other claims. */
32
54
  payload: Record<string, unknown>;
33
55
  }
@@ -46,4 +68,4 @@ export interface VerifiedClientAssertion {
46
68
  * any of the iss/sub/aud/exp checks.
47
69
  */
48
70
  export declare function verifyClientAssertion(assertion: string, client: ClientAssertionClient, opts: VerifyClientAssertionOptions): Promise<VerifiedClientAssertion>;
49
- export { ASSERTION_TYPE as CLIENT_ASSERTION_TYPE };
71
+ export { ASSERTION_TYPE as CLIENT_ASSERTION_TYPE, DEFAULT_MAX_LIFETIME_SECONDS as CLIENT_ASSERTION_DEFAULT_MAX_LIFETIME_SECONDS, };
@@ -1,7 +1,7 @@
1
1
  import { CodeExecutor, DataAdapters } from "@authhero/adapter-interfaces";
2
2
  import { EventDestination } from "./outbox-relay";
3
3
  import { type GetServiceToken } from "./outbox-destinations/webhooks";
4
- import type { WebhookInvoker } from "../types/AuthHeroConfig";
4
+ import type { OutboxPipelineConfig, WebhookInvoker } from "../types/AuthHeroConfig";
5
5
  export interface CreateDefaultDestinationsConfig {
6
6
  /**
7
7
  * Data adapter — the `logs`, `hooks`, `users`, and `logStreams` adapters are
@@ -47,6 +47,12 @@ export interface CreateDefaultDestinationsConfig {
47
47
  * failed per-request delivery would be silently skipped on retry.
48
48
  */
49
49
  codeExecutor?: CodeExecutor;
50
+ /**
51
+ * Same shape as `init({ outbox: { pipeline } })`. When set, cron-drained
52
+ * events are also archived to the Cloudflare Pipelines stream, matching the
53
+ * per-request destination list. Omit to leave the archive out entirely.
54
+ */
55
+ pipeline?: OutboxPipelineConfig;
50
56
  }
51
57
  /**
52
58
  * Build the same array of outbox destinations that authhero's per-request
@@ -1,2 +1,3 @@
1
1
  export { LogsDestination } from "./logs";
2
2
  export { LogStreamDestination } from "./log-streams";
3
+ export { PipelineDestination } from "./pipeline";
@@ -0,0 +1,63 @@
1
+ import { AuditEvent, AuditCategory } from "@authhero/adapter-interfaces";
2
+ import { EventDestination } from "../outbox-relay";
3
+ /**
4
+ * One row of the archive table. The promoted columns are the query and
5
+ * erasure keys (`actor_id` / `target_id`); `event` carries the untouched
6
+ * `AuditEvent` so nothing is lost and the promoted set can grow later
7
+ * without a backfill.
8
+ *
9
+ * `actor_id` is emitted as `null` rather than omitted when the actor is
10
+ * anonymous, so every record has the same key set — Pipelines stream
11
+ * schemas are fixed once created.
12
+ */
13
+ export interface PipelineRecord {
14
+ id: string;
15
+ timestamp: string;
16
+ tenant_id: string;
17
+ event_type: string;
18
+ log_type: string;
19
+ category: AuditCategory;
20
+ actor_id: string | null;
21
+ target_type: string;
22
+ target_id: string;
23
+ event: AuditEvent;
24
+ }
25
+ export interface PipelineDestinationOptions {
26
+ /** Stream HTTP ingest endpoint, e.g. `https://<stream-id>.ingest.cloudflare.com`. */
27
+ endpoint: string;
28
+ /** Stream ingest token, sent as `Authorization: Bearer`. */
29
+ token: string;
30
+ /** Per-request timeout (default: 10s). */
31
+ timeoutMs?: number;
32
+ /** Override for tests. */
33
+ fetchImpl?: typeof fetch;
34
+ }
35
+ /**
36
+ * Archives audit events to a Cloudflare Pipelines stream, which lands them in
37
+ * R2 as an Iceberg table. See `apps/docs/architecture/audit-archive.md`.
38
+ *
39
+ * HTTP ingest is used rather than the Worker binding so the destination works
40
+ * in every deployment (Node included) and stays symmetric with the
41
+ * log-streams destination.
42
+ *
43
+ * Duplicates are expected and by design: the relay retries per event, not per
44
+ * destination, so a failure in a later destination re-delivers this one, and
45
+ * the Iceberg sink is append-only. Consumers dedup on `id` at query time.
46
+ */
47
+ export declare class PipelineDestination implements EventDestination {
48
+ name: string;
49
+ private endpoint;
50
+ private token;
51
+ private timeoutMs;
52
+ private fetchImpl;
53
+ constructor(options: PipelineDestinationOptions);
54
+ /**
55
+ * Archives the audit trail, not the delivery plumbing: `hook.*` and
56
+ * `controlplane.sync.*` are instructions to other destinations rather than
57
+ * records of something a tenant did. Same filter as the log-streams
58
+ * destination.
59
+ */
60
+ accepts(event: AuditEvent): boolean;
61
+ transform(event: AuditEvent): PipelineRecord;
62
+ deliver(records: PipelineRecord[]): Promise<void>;
63
+ }
@@ -1,4 +1,5 @@
1
1
  import { OutboxAdapter, AuditEvent } from "@authhero/adapter-interfaces";
2
+ import type { OutboxMetricsSink } from "../types/OutboxMetrics";
2
3
  /**
3
4
  * Interface for outbox event destinations.
4
5
  * Each destination transforms audit events into its own format and delivers them.
@@ -21,6 +22,7 @@ export interface EventDestination {
21
22
  */
22
23
  export declare function processOutboxEvents(outbox: OutboxAdapter, ids: string[], destinations: EventDestination[], options?: {
23
24
  maxRetries?: number;
25
+ metrics?: OutboxMetricsSink;
24
26
  }): Promise<void>;
25
27
  /**
26
28
  * Drain unprocessed events from the outbox and deliver to all destinations.
@@ -31,4 +33,5 @@ export declare function drainOutbox(outbox: OutboxAdapter, destinations: EventDe
31
33
  batchSize?: number;
32
34
  maxRetries?: number;
33
35
  retentionDays?: number;
36
+ metrics?: OutboxMetricsSink;
34
37
  }): Promise<void>;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Refresh-token lifetimes (issue #1260).
3
+ *
4
+ * Auth0 keeps refresh-token expiry and session expiry as two separate
5
+ * concepts: the former is configured per *client* (`client.refresh_token.*`,
6
+ * in seconds), the latter per *tenant* (`session_lifetime` /
7
+ * `idle_session_lifetime`, in hours). AuthHero historically derived
8
+ * refresh-token expiry from the tenant session lifetimes only, which made
9
+ * `infinite_token_lifetime` — the setting native/mobile clients rely on —
10
+ * unrepresentable.
11
+ *
12
+ * These helpers resolve the effective lifetime: per-client config wins when
13
+ * set, otherwise we fall back to the tenant-derived behaviour so existing
14
+ * tenants see no change.
15
+ */
16
+ /**
17
+ * A resolved refresh-token lifetime.
18
+ *
19
+ * - `seconds` — expires this many seconds after the anchor point.
20
+ * - `infinite` — explicitly configured never to expire.
21
+ * - `unset` — nothing configured at either level. Distinct from `infinite`
22
+ * so callers can tell "the operator asked for no expiry" from "no opinion":
23
+ * at mint both mean no expiry, but on rotation only `infinite` clears an
24
+ * expiry the parent row already carried.
25
+ */
26
+ export type RefreshTokenLifetime = {
27
+ kind: "seconds";
28
+ seconds: number;
29
+ } | {
30
+ kind: "infinite";
31
+ } | {
32
+ kind: "unset";
33
+ };
34
+ /** The subset of a client this module reads. */
35
+ export interface RefreshTokenLifetimeClient {
36
+ refresh_token?: {
37
+ expiration_type?: "expiring" | "non-expiring";
38
+ token_lifetime?: number;
39
+ infinite_token_lifetime?: boolean;
40
+ idle_token_lifetime?: number;
41
+ infinite_idle_token_lifetime?: boolean;
42
+ };
43
+ tenant: {
44
+ session_lifetime?: number;
45
+ idle_session_lifetime?: number;
46
+ };
47
+ }
48
+ /**
49
+ * Absolute ("hard") refresh-token lifetime: how long the token is valid for,
50
+ * regardless of use.
51
+ */
52
+ export declare function resolveAbsoluteRefreshTokenLifetime(client: RefreshTokenLifetimeClient): RefreshTokenLifetime;
53
+ /**
54
+ * Idle ("sliding") refresh-token lifetime: how long the token survives without
55
+ * being exchanged. Refreshed on every successful exchange.
56
+ */
57
+ export declare function resolveIdleRefreshTokenLifetime(client: RefreshTokenLifetimeClient): RefreshTokenLifetime;
58
+ /**
59
+ * Turn a lifetime into an ISO expiry timestamp measured from `from`.
60
+ * `infinite` and `unset` both yield `undefined` — no expiry column is written.
61
+ */
62
+ export declare function lifetimeToExpiresAt(lifetime: RefreshTokenLifetime, from?: number): string | undefined;
63
+ /**
64
+ * Both expiry timestamps for a freshly minted refresh token.
65
+ */
66
+ export declare function resolveRefreshTokenExpiry(client: RefreshTokenLifetimeClient, from?: number): {
67
+ expires_at?: string;
68
+ idle_expires_at?: string;
69
+ };
70
+ /**
71
+ * The idle expiry a token should carry after a successful exchange.
72
+ *
73
+ * Sliding only applies to a token that already had an idle window: a row minted
74
+ * without one is not retro-fitted with an expiry mid-life. The exception is an
75
+ * explicit `infinite` config, which clears an existing window so a client
76
+ * switched to non-expiring stops being cut off by expiries stamped earlier.
77
+ */
78
+ export declare function slideIdleExpiry(client: RefreshTokenLifetimeClient, currentIdleExpiresAt: string | undefined, from?: number): string | undefined;
79
+ /**
80
+ * The expiry columns an in-place (non-rotating) exchange should write.
81
+ *
82
+ * Three-valued per column, matching the adapter's update contract:
83
+ * `undefined` leaves the stored value alone, a string overwrites it, `null`
84
+ * clears it.
85
+ *
86
+ * Rotation reconciles a changed client config for free — the child row is
87
+ * minted from the current lifetimes — but the non-rotating path keeps handing
88
+ * back the row it was given, so it has to say so explicitly:
89
+ *
90
+ * - Idle window: slides by the resolved lifetime, and is cleared when the
91
+ * client is configured never to idle-expire. A row minted without an idle
92
+ * window is not retro-fitted with one mid-life.
93
+ * - Absolute window: never extended — refreshing must not let a bounded token
94
+ * outlive the expiry stamped at mint — and only cleared when the client is
95
+ * configured never to expire.
96
+ */
97
+ export declare function resolveExchangeExpiryUpdate(client: RefreshTokenLifetimeClient, current: {
98
+ expires_at?: string;
99
+ idle_expires_at?: string;
100
+ }, from?: number): {
101
+ expires_at?: null;
102
+ idle_expires_at?: string | null;
103
+ };
@@ -0,0 +1,64 @@
1
+ import { Context } from "hono";
2
+ import { Bindings, Variables } from "../types";
3
+ /** RFC 9068 §2.2 + AuthHero-owned access-token claims. */
4
+ export declare const ACCESS_TOKEN_RESERVED_CLAIMS: readonly ["iss", "sub", "aud", "exp", "nbf", "iat", "jti", "client_id", "azp", "scope", "auth_time", "acr", "amr", "act", "sid", "permissions", "tenant_id", "org_id", "org_name", "requested_userinfo_claims", "gty"];
5
+ /** OIDC Core ID-token claims: the access-token set plus the ID-token-only ones. */
6
+ export declare const ID_TOKEN_RESERVED_CLAIMS: readonly ["iss", "sub", "aud", "exp", "nbf", "iat", "jti", "client_id", "azp", "scope", "auth_time", "acr", "amr", "act", "sid", "permissions", "tenant_id", "org_id", "org_name", "requested_userinfo_claims", "gty", "nonce", "at_hash", "c_hash", "s_hash"];
7
+ /**
8
+ * /userinfo response. The identity set only — the response body is otherwise
9
+ * made up of user profile claims, which hooks are expected to extend.
10
+ */
11
+ export declare const USERINFO_RESERVED_CLAIMS: readonly ["iss", "sub", "aud", "exp", "nbf", "iat", "jti"];
12
+ /**
13
+ * Internal `auth-service` mints. Same set as an access token except `azp`:
14
+ * trusted internal hook code overrides it to attribute the call to a
15
+ * vendor/tenant for downstream APIs while `sub` stays `auth-service`.
16
+ * Client-bound mints keep `azp` locked (see below).
17
+ */
18
+ export declare const SERVICE_TOKEN_RESERVED_CLAIMS: ("client_id" | "scope" | "sid" | "tenant_id" | "org_id" | "org_name" | "sub" | "permissions" | "iat" | "exp" | "iss" | "aud" | "auth_time" | "acr" | "amr" | "nbf" | "jti" | "act" | "requested_userinfo_claims" | "gty")[];
19
+ /** Client-bound mints: `azp` must stay the registered client id. */
20
+ export declare const CLIENT_SERVICE_TOKEN_RESERVED_CLAIMS: ("client_id" | "scope" | "sid" | "tenant_id" | "org_id" | "org_name" | "sub" | "permissions" | "iat" | "exp" | "iss" | "aud" | "auth_time" | "acr" | "amr" | "azp" | "nbf" | "jti" | "act" | "requested_userinfo_claims" | "gty")[];
21
+ export type AccessTokenReservedClaim = (typeof ACCESS_TOKEN_RESERVED_CLAIMS)[number];
22
+ export type IdTokenReservedClaim = (typeof ID_TOKEN_RESERVED_CLAIMS)[number];
23
+ /**
24
+ * The server-owned half of an access-token payload. Typing the payload literal
25
+ * as this makes TypeScript's excess-property check reject any claim name that
26
+ * isn't in `ACCESS_TOKEN_RESERVED_CLAIMS`, so a new server-owned claim cannot
27
+ * be added to the mint without also being reserved.
28
+ */
29
+ export type ServerOwnedAccessTokenClaims = Partial<Record<AccessTokenReservedClaim, unknown>>;
30
+ /** As `ServerOwnedAccessTokenClaims`, for the ID token. */
31
+ export type ServerOwnedIdTokenClaims = Partial<Record<IdTokenReservedClaim, unknown>>;
32
+ /** Which payload a custom claim is being written to. */
33
+ export type ClaimPayloadKind = "access_token" | "id_token" | "userinfo" | "service_token" | "client_service_token";
34
+ export declare function isReservedClaim(claim: string, kind: ClaimPayloadKind): boolean;
35
+ export interface ApplyCustomClaimOptions {
36
+ /** Which payload is being written to — selects the reserved set. */
37
+ kind: ClaimPayloadKind;
38
+ /**
39
+ * Who is writing. Used in the warning so an operator can tell which hook
40
+ * dropped a claim (e.g. `onExecuteCredentialsExchange`,
41
+ * `template-hook:add-roles`, `createServiceToken`).
42
+ */
43
+ source: string;
44
+ /** Request context, when there is one — the warning goes to the tenant log. */
45
+ ctx?: Context<{
46
+ Bindings: Bindings;
47
+ Variables: Variables;
48
+ }>;
49
+ /** Tenant to log against. Defaults to `ctx.var.tenant_id`. */
50
+ tenantId?: string;
51
+ }
52
+ /**
53
+ * Write a caller-supplied claim onto a payload unless the authorization server
54
+ * owns that claim name.
55
+ *
56
+ * @returns true when the claim was written, false when it was dropped.
57
+ */
58
+ export declare function applyCustomClaim(payload: Record<string, unknown>, claim: string, value: unknown, options: ApplyCustomClaimOptions): boolean;
59
+ /**
60
+ * Bulk variant of `applyCustomClaim`. Returns a new object holding only the
61
+ * claims that are safe to merge, so callers can keep spreading them into a
62
+ * payload literal.
63
+ */
64
+ export declare function applyCustomClaims(claims: Record<string, unknown> | undefined, options: ApplyCustomClaimOptions): Record<string, unknown> | undefined;
@@ -1,5 +1,6 @@
1
1
  import { CodeExecutor, DataAdapters } from "@authhero/adapter-interfaces";
2
2
  import type { WebhookInvoker } from "../types/AuthHeroConfig";
3
+ import type { OutboxMetricsSink } from "../types/OutboxMetrics";
3
4
  export interface RunOutboxRelayConfig {
4
5
  /** Same `DataAdapters` passed to `init()`. Must include `outbox` to drain. */
5
6
  dataAdapter: DataAdapters;
@@ -31,6 +32,13 @@ export interface RunOutboxRelayConfig {
31
32
  * silently skipped.
32
33
  */
33
34
  codeExecutor?: CodeExecutor;
35
+ /**
36
+ * Optional metrics sink — same shape as `init({ outbox: { metrics } })`.
37
+ * Receives `outbox_events_processed_total`,
38
+ * `outbox_events_dead_lettered_total` and `outbox_retry_delay_seconds` for
39
+ * the events this cron drain handles, tagged `source: "cron"`.
40
+ */
41
+ metrics?: OutboxMetricsSink;
34
42
  }
35
43
  /**
36
44
  * One-call outbox relay for cron / scheduled handlers.
@@ -10,9 +10,10 @@ import { Bindings, Variables } from "../types";
10
10
  * dispatch post-event outbox messages. `users.rawCreate` is NOT decorated —
11
11
  * commit paths call it directly to bypass the hook layer by design.
12
12
  *
13
- * `email` is lowercased on the way in so the pre-commit hooks and lookups
14
- * (`preUserSignupHook`, the email→primary linking query) see the same
15
- * normalized value that will be stored. Hooks can assign `email` themselves
13
+ * `email` is normalized (trimmed and lowercased) on the way in so the
14
+ * pre-commit hooks and lookups (`preUserSignupHook`, the email→primary linking
15
+ * query) see the same normalized value that will be stored. Hooks can assign
16
+ * `email` themselves
16
17
  * after this point, so the decorators normalize again just before their
17
18
  * commit — see `createUserHooks` / `createUserUpdateHooks`.
18
19
  */