authhero 9.9.1 → 9.11.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 (64) hide show
  1. package/dist/assets/u/widget/index.esm.js +1 -1
  2. package/dist/authhero.cjs +145 -145
  3. package/dist/authhero.d.ts +585 -190
  4. package/dist/authhero.mjs +15529 -14313
  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/passwordless.d.ts +6 -5
  9. package/dist/types/authentication-flows/refresh-token.d.ts +2 -1
  10. package/dist/types/authentication-flows/token-exchange.d.ts +2 -1
  11. package/dist/types/helpers/client-assertion-replay.d.ts +21 -0
  12. package/dist/types/helpers/client-assertion.d.ts +24 -2
  13. package/dist/types/helpers/dcr/metadata-mapping.d.ts +1 -1
  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/reserved-claims.d.ts +64 -0
  19. package/dist/types/helpers/run-outbox-relay.d.ts +8 -0
  20. package/dist/types/helpers/run-retention.d.ts +5 -0
  21. package/dist/types/helpers/users-import/map.d.ts +128 -0
  22. package/dist/types/helpers/users-import/process.d.ts +95 -0
  23. package/dist/types/helpers/users-import-cleanup.d.ts +26 -0
  24. package/dist/types/index.d.ts +321 -185
  25. package/dist/types/routes/auth-api/index.d.ts +44 -44
  26. package/dist/types/routes/auth-api/passwordless.d.ts +14 -14
  27. package/dist/types/routes/auth-api/register/index.d.ts +2 -2
  28. package/dist/types/routes/auth-api/token.d.ts +24 -24
  29. package/dist/types/routes/management-api/action-triggers.d.ts +0 -2
  30. package/dist/types/routes/management-api/actions.d.ts +0 -7
  31. package/dist/types/routes/management-api/authentication-methods.d.ts +1 -1
  32. package/dist/types/routes/management-api/branding.d.ts +1 -1
  33. package/dist/types/routes/management-api/client-grants.d.ts +9 -9
  34. package/dist/types/routes/management-api/clients.d.ts +8 -8
  35. package/dist/types/routes/management-api/connections.d.ts +1 -1
  36. package/dist/types/routes/management-api/email-templates.d.ts +18 -18
  37. package/dist/types/routes/management-api/failed-events.d.ts +22 -2
  38. package/dist/types/routes/management-api/guardian.d.ts +5 -5
  39. package/dist/types/routes/management-api/helpers.d.ts +1 -1
  40. package/dist/types/routes/management-api/index.d.ts +216 -86
  41. package/dist/types/routes/management-api/jobs.d.ts +133 -0
  42. package/dist/types/routes/management-api/keys.d.ts +16 -16
  43. package/dist/types/routes/management-api/logs.d.ts +4 -4
  44. package/dist/types/routes/management-api/migration-sources.d.ts +6 -6
  45. package/dist/types/routes/management-api/organizations.d.ts +2 -2
  46. package/dist/types/routes/management-api/prompts.d.ts +4 -4
  47. package/dist/types/routes/management-api/roles.d.ts +1 -1
  48. package/dist/types/routes/management-api/tenant-export-import.d.ts +5 -5
  49. package/dist/types/routes/management-api/tenant-operations.d.ts +33 -9
  50. package/dist/types/routes/management-api/tenants.d.ts +11 -11
  51. package/dist/types/routes/management-api/users.d.ts +24 -24
  52. package/dist/types/routes/universal-login/common.d.ts +2 -2
  53. package/dist/types/routes/universal-login/flow-api.d.ts +12 -12
  54. package/dist/types/routes/universal-login/identifier.d.ts +2 -2
  55. package/dist/types/routes/universal-login/index.d.ts +2 -2
  56. package/dist/types/routes/universal-login/u2-index.d.ts +5 -5
  57. package/dist/types/routes/universal-login/u2-routes.d.ts +5 -5
  58. package/dist/types/state-machines/login-session.d.ts +1 -1
  59. package/dist/types/types/AuthHeroConfig.d.ts +40 -0
  60. package/dist/types/types/Bindings.d.ts +14 -0
  61. package/dist/types/types/OutboxMetrics.d.ts +47 -0
  62. package/dist/types/types/auth0/UserImport.d.ts +317 -0
  63. package/dist/types/types/index.d.ts +1 -0
  64. package/package.json +6 -6
@@ -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>;
@@ -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, };
@@ -23,9 +23,9 @@ export declare const dcrRequestSchema: z.ZodObject<{
23
23
  grant_types: z.ZodOptional<z.ZodArray<z.ZodString>>;
24
24
  response_types: z.ZodOptional<z.ZodArray<z.ZodString>>;
25
25
  token_endpoint_auth_method: z.ZodOptional<z.ZodEnum<{
26
- none: "none";
27
26
  client_secret_post: "client_secret_post";
28
27
  client_secret_basic: "client_secret_basic";
28
+ none: "none";
29
29
  client_secret_jwt: "client_secret_jwt";
30
30
  private_key_jwt: "private_key_jwt";
31
31
  }>>;
@@ -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,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" | "sub" | "org_name" | "permissions" | "iat" | "org_id" | "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" | "sub" | "org_name" | "permissions" | "iat" | "org_id" | "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.
@@ -8,6 +8,11 @@ export interface RunRetentionConfig {
8
8
  outboxRetentionDays?: number;
9
9
  /** Days of action execution history to keep. Default 30. */
10
10
  actionExecutionsRetentionDays?: number;
11
+ /**
12
+ * Hours to keep finished bulk user-import jobs and their staged rows.
13
+ * Default 24, matching Auth0's job-data retention.
14
+ */
15
+ usersImportRetentionHours?: number;
11
16
  /**
12
17
  * Scope the session sweep to a single tenant. Codes and outbox events are
13
18
  * always swept globally — an expired row is dead regardless of who owns it.
@@ -0,0 +1,128 @@
1
+ import type { PasswordInsert, UserInsert } from "@authhero/adapter-interfaces";
2
+ import { type UserImportEntry } from "../../types/auth0/UserImport";
3
+ /**
4
+ * Machine-readable per-row failure reasons, surfaced by the Auth0-compatible
5
+ * `GET /api/v2/jobs/{id}/errors` endpoint. Stable strings — clients branch on
6
+ * them, so treat these as API surface.
7
+ */
8
+ export declare const IMPORT_ERROR_CODES: {
9
+ /** The entry did not match the import-file schema. */
10
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
11
+ /** A well-formed hash in an algorithm AuthHero cannot verify. */
12
+ readonly UNSUPPORTED_HASH_ALGORITHM: "UNSUPPORTED_HASH_ALGORITHM";
13
+ /** bcrypt, but a variant or encoding that would never verify. */
14
+ readonly UNSUPPORTED_HASH_FORMAT: "UNSUPPORTED_HASH_FORMAT";
15
+ /** The same identity appears earlier in the same file. */
16
+ readonly DUPLICATE_ENTRY: "DUPLICATE_ENTRY";
17
+ /** The user already exists and `upsert` was not enabled. */
18
+ readonly USER_ALREADY_EXISTS: "USER_ALREADY_EXISTS";
19
+ /** The write itself failed. */
20
+ readonly INTERNAL_ERROR: "INTERNAL_ERROR";
21
+ };
22
+ export type ImportErrorCode = (typeof IMPORT_ERROR_CODES)[keyof typeof IMPORT_ERROR_CODES];
23
+ export interface ImportRowError {
24
+ code: ImportErrorCode;
25
+ message: string;
26
+ path?: string;
27
+ }
28
+ export type MappedPassword = Pick<PasswordInsert, "password" | "algorithm">;
29
+ export interface MappedEntry {
30
+ user: Omit<UserInsert, "connection"> & {
31
+ connection: string;
32
+ };
33
+ password?: MappedPassword;
34
+ }
35
+ export type MapResult = {
36
+ ok: true;
37
+ value: MappedEntry;
38
+ } | {
39
+ ok: false;
40
+ error: ImportRowError;
41
+ };
42
+ /**
43
+ * Resolve the entry's password into something AuthHero can actually verify.
44
+ *
45
+ * Returns `undefined` when the entry carries no credential at all — a valid
46
+ * case that produces a shell user who signs in via password reset or the
47
+ * upstream `import_mode` fallback.
48
+ *
49
+ * AuthHero verifies with `bcryptjs.compare`, so bcrypt is the only algorithm
50
+ * that can round-trip. Storing anything else would create a user who can
51
+ * never authenticate, so unsupported hashes fail the row instead.
52
+ */
53
+ export declare function mapPassword(entry: UserImportEntry): {
54
+ ok: true;
55
+ value?: MappedPassword;
56
+ } | {
57
+ ok: false;
58
+ error: ImportRowError;
59
+ };
60
+ /**
61
+ * Build the stored `provider|id` identifier for an imported entry.
62
+ *
63
+ * Auth0 prefixes bare import ids with the connection's provider; AuthHero
64
+ * uses the tenant's resolved username-password provider (`auth0`, or `auth2`
65
+ * for tenants still pinned to the legacy value) so the password row lands on
66
+ * the identity the login path actually reads. An entry that already carries
67
+ * the prefix is not double-prefixed, and one with no id at all gets a
68
+ * generated one — the users table requires the column.
69
+ *
70
+ * Mirrors `POST /api/v2/users`, which derives the id exactly this way.
71
+ */
72
+ export declare function buildUserId(userId: string | undefined, provider: string, fallbackId?: string): string;
73
+ /**
74
+ * Deterministic bare id for an import row that supplied no `user_id`.
75
+ *
76
+ * Retry safety depends on this. A driver can create a user and then die
77
+ * before committing that row's outcome, leaving the row `pending`; the next
78
+ * driver reprocesses it. With a random id the retry cannot tell its own
79
+ * half-finished write from a genuinely pre-existing user, and reports a
80
+ * successful import as `USER_ALREADY_EXISTS`. Deriving the id from
81
+ * `(operation_id, seq)` — both immutable for the life of the row — makes the
82
+ * retry regenerate the exact same id, so it can recognise its own work.
83
+ *
84
+ * Not a security boundary: the inputs are our own identifiers, so this only
85
+ * needs to be stable and collision-free, which a truncated SHA-256 is.
86
+ */
87
+ export declare function deriveImportUserId(operationId: string, seq: number): Promise<string>;
88
+ export interface MapEntryParams {
89
+ entry: UserImportEntry;
90
+ /** Connection NAME (the users table stores the name, not the id). */
91
+ connection: string;
92
+ /** Resolved username-password provider for this tenant. */
93
+ provider: string;
94
+ /**
95
+ * Id to use when the entry supplies none. Pass the value from
96
+ * {@link deriveImportUserId} so a reprocessed row rebuilds the same id;
97
+ * omitting it falls back to a random one.
98
+ */
99
+ fallbackUserId?: string;
100
+ }
101
+ /**
102
+ * Map one validated import entry onto the user and password rows to write.
103
+ * Pure — performs no I/O and makes no existence checks, so it is safe to call
104
+ * repeatedly when a chunk is retried.
105
+ */
106
+ export declare function mapEntry({ entry, connection, provider, fallbackUserId, }: MapEntryParams): MapResult;
107
+ /**
108
+ * Strip credential material from a staged entry before it is returned by
109
+ * `GET /jobs/{id}/errors`.
110
+ *
111
+ * Redaction happens on the way OUT, not on the way in: the staged row is the
112
+ * work item, so it must keep the hash the job exists to import. What must
113
+ * never happen is echoing that hash back over the API, which is what this
114
+ * guards. The marker is left in place so an operator reading an error can
115
+ * still tell a credential was supplied.
116
+ */
117
+ export declare function redactEntry(entry: unknown): Record<string, unknown>;
118
+ /**
119
+ * Normalize a raw file entry into the record shape a staged row stores,
120
+ * WITHOUT redacting: the row has to carry the credential it is going to
121
+ * import. {@link redactEntry} is applied when the row is read back out.
122
+ */
123
+ export declare function toStagedPayload(entry: unknown): Record<string, unknown>;
124
+ /**
125
+ * Identity keys Auth0 dedupes an import file on: a repeat of any of these
126
+ * within one file is an error rather than a silent overwrite.
127
+ */
128
+ export declare function entryIdentityKeys(entry: UserImportEntry): string[];
@@ -0,0 +1,95 @@
1
+ import type { DataAdapters, TenantOperation } from "@authhero/adapter-interfaces";
2
+ /**
3
+ * How many staged rows one chunk processes.
4
+ *
5
+ * Sized against Cloudflare D1's per-invocation query cap (order of a
6
+ * thousand): a row costs up to four existence probes plus a user and a
7
+ * password write, so 50 rows leaves comfortable headroom for the
8
+ * surrounding reads and the outcome commit. Chunks are cheap — a smaller
9
+ * one only means more of them, while an oversized one fails the whole
10
+ * invocation.
11
+ */
12
+ export declare const DEFAULT_CHUNK_SIZE = 50;
13
+ /** How long a driver's lease on an operation is valid. */
14
+ export declare const DEFAULT_LEASE_MS = 60000;
15
+ export interface UsersImportInput {
16
+ connection_id: string;
17
+ connection: string;
18
+ upsert: boolean;
19
+ external_id?: string;
20
+ send_completion_email?: boolean;
21
+ provider: string;
22
+ }
23
+ export interface AdvanceOptions {
24
+ /** Stop after this many rows, so a driver can bound its own runtime. */
25
+ maxRows?: number;
26
+ /** Rows per chunk; defaults to {@link DEFAULT_CHUNK_SIZE}. */
27
+ chunkSize?: number;
28
+ /** Lease duration for this driver's claim. */
29
+ leaseMs?: number;
30
+ /**
31
+ * Wall-clock deadline (epoch ms). The driver stops cleanly at the next
32
+ * chunk boundary once passed, leaving the remainder `pending` for the
33
+ * next driver — never mid-chunk, so no work is half-committed.
34
+ */
35
+ deadline?: number;
36
+ /** Identifies the lease holder; defaults to a random id. */
37
+ workerId?: string;
38
+ }
39
+ export interface AdvanceResult {
40
+ /** True when no `pending` rows remain and the operation was finalized. */
41
+ done: boolean;
42
+ /** Rows this call committed an outcome for. */
43
+ processed: number;
44
+ /** Rows still `pending` after this call. */
45
+ remaining: number;
46
+ /** False when another live driver holds the lease. */
47
+ claimed: boolean;
48
+ }
49
+ /**
50
+ * Read the operation's `input` back into a typed shape. The row is written
51
+ * by the accept route, so a malformed one means the operation is
52
+ * unrunnable rather than that the caller made a mistake.
53
+ */
54
+ export declare function parseImportInput(operation: TenantOperation): UsersImportInput | null;
55
+ /**
56
+ * Advance a `users_import` operation by processing staged rows until it is
57
+ * finished or the caller's budget runs out.
58
+ *
59
+ * This is the whole execution model. Every engine — an inline kick from the
60
+ * accepting request, a cron sweep, a Cloudflare Workflow step — calls this
61
+ * same function; they differ only in how much budget they pass and how often
62
+ * they call it. Durability comes from the database, not from the caller:
63
+ * outcomes are committed chunk by chunk, so a driver that dies loses at most
64
+ * the chunk in flight, and those rows stay `pending` for whoever runs next.
65
+ */
66
+ export declare function advanceUsersImport(data: DataAdapters, operationId: string, options?: AdvanceOptions): Promise<AdvanceResult>;
67
+ /** Auth0's job summary shape, derived from the staged-row counts. */
68
+ export declare function buildSummary(counts: {
69
+ total: number;
70
+ inserted: number;
71
+ updated: number;
72
+ failed: number;
73
+ }): Record<string, number>;
74
+ export interface ResumeUsersImportsOptions extends Omit<AdvanceOptions, "workerId"> {
75
+ /** Maximum operations to advance in one sweep. */
76
+ maxOperations?: number;
77
+ }
78
+ export interface ResumeUsersImportsResult {
79
+ scanned: number;
80
+ advanced: number;
81
+ completed: number;
82
+ errors: number;
83
+ }
84
+ /**
85
+ * Resume every unfinished bulk import that no live driver is working on.
86
+ *
87
+ * This is what makes the feature durable regardless of deployment. Wire it
88
+ * to a scheduled handler alongside `runRetention`: whatever started an
89
+ * import — a request that timed out, a worker that was evicted, a process
90
+ * that was redeployed mid-run — the sweep picks the job back up and carries
91
+ * it to completion from the last committed chunk.
92
+ *
93
+ * One operation's failure never aborts the sweep.
94
+ */
95
+ export declare function resumeUsersImports(data: DataAdapters, options?: ResumeUsersImportsOptions): Promise<ResumeUsersImportsResult>;
@@ -0,0 +1,26 @@
1
+ import { DataAdapters } from "@authhero/adapter-interfaces";
2
+ /**
3
+ * Auth0 deletes all job-related data 24 hours after the job is created.
4
+ */
5
+ export declare const DEFAULT_IMPORT_JOB_RETENTION_HOURS = 24;
6
+ export interface UsersImportCleanupParams {
7
+ /** Hours to keep finished import jobs. Defaults to Auth0's 24. */
8
+ retentionHours?: number;
9
+ /** Maximum jobs to delete per sweep. */
10
+ limit?: number;
11
+ }
12
+ /**
13
+ * Delete finished bulk-import jobs (and, by cascade, their staged rows)
14
+ * older than the retention window.
15
+ *
16
+ * Staged rows hold the submitted user entries, so this is a privacy control
17
+ * as much as a housekeeping one — though credential material is redacted
18
+ * before staging rather than relying on this sweep.
19
+ *
20
+ * Only terminal jobs are removed: an unfinished import may legitimately be
21
+ * older than the window (a very large file, or one that has been waiting on
22
+ * a resume sweep), and deleting it would strand the users it had not yet
23
+ * created. Returns the number of jobs deleted, or `null` when the
24
+ * deployment has no tenant-operations adapter.
25
+ */
26
+ export declare function cleanupUsersImports(data: DataAdapters, params?: UsersImportCleanupParams): Promise<number | null>;