authhero 8.24.0 → 8.25.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 (46) hide show
  1. package/dist/authhero.cjs +138 -138
  2. package/dist/authhero.d.ts +802 -225
  3. package/dist/authhero.mjs +12109 -11443
  4. package/dist/tsconfig.types.tsbuildinfo +1 -1
  5. package/dist/types/authentication-flows/passwordless.d.ts +3 -3
  6. package/dist/types/index.d.ts +228 -222
  7. package/dist/types/routes/auth-api/index.d.ts +44 -44
  8. package/dist/types/routes/auth-api/passwordless.d.ts +12 -12
  9. package/dist/types/routes/auth-api/register/index.d.ts +2 -2
  10. package/dist/types/routes/auth-api/revoke.d.ts +8 -8
  11. package/dist/types/routes/auth-api/token.d.ts +22 -22
  12. package/dist/types/routes/management-api/authentication-methods.d.ts +1 -1
  13. package/dist/types/routes/management-api/branding.d.ts +1 -1
  14. package/dist/types/routes/management-api/clients.d.ts +8 -8
  15. package/dist/types/routes/management-api/connections.d.ts +1 -1
  16. package/dist/types/routes/management-api/custom-domains.d.ts +6 -6
  17. package/dist/types/routes/management-api/failed-events.d.ts +1 -1
  18. package/dist/types/routes/management-api/forms.d.ts +126 -126
  19. package/dist/types/routes/management-api/guardian.d.ts +5 -5
  20. package/dist/types/routes/management-api/index.d.ts +172 -172
  21. package/dist/types/routes/management-api/logs.d.ts +4 -4
  22. package/dist/types/routes/management-api/organizations.d.ts +7 -7
  23. package/dist/types/routes/management-api/prompts.d.ts +4 -4
  24. package/dist/types/routes/management-api/roles.d.ts +1 -1
  25. package/dist/types/routes/management-api/tenant-export-import.d.ts +5 -5
  26. package/dist/types/routes/management-api/tenant-members.d.ts +304 -0
  27. package/dist/types/routes/management-api/tenant-members.test.d.ts +1 -0
  28. package/dist/types/routes/management-api/tenants.d.ts +9 -9
  29. package/dist/types/routes/management-api/users.d.ts +2 -2
  30. package/dist/types/routes/proxy-control-plane/index.d.ts +23 -0
  31. package/dist/types/routes/proxy-control-plane/scopes.d.ts +7 -0
  32. package/dist/types/routes/proxy-control-plane/tenant-members.d.ts +37 -0
  33. package/dist/types/routes/proxy-control-plane/verify.d.ts +14 -0
  34. package/dist/types/routes/proxy-control-plane/verify.test.d.ts +1 -0
  35. package/dist/types/routes/universal-login/common.d.ts +2 -2
  36. package/dist/types/routes/universal-login/flow-api.d.ts +12 -12
  37. package/dist/types/routes/universal-login/u2-index.d.ts +5 -5
  38. package/dist/types/routes/universal-login/u2-routes.d.ts +5 -5
  39. package/dist/types/tenant-members/control-plane-roundtrip.test.d.ts +1 -0
  40. package/dist/types/tenant-members/local-backend.d.ts +43 -0
  41. package/dist/types/tenant-members/local-backend.test.d.ts +1 -0
  42. package/dist/types/tenant-members/remote-backend.d.ts +17 -0
  43. package/dist/types/tenant-members/types.d.ts +112 -0
  44. package/dist/types/tenant-members/wire.d.ts +28 -0
  45. package/dist/types/types/AuthHeroConfig.d.ts +32 -0
  46. package/package.json +3 -3
@@ -3,6 +3,7 @@ import type { ResolvedHost } from "@authhero/proxy";
3
3
  import { CustomDomain, CustomDomainsAdapter, ProxyRoute, ProxyRoutesAdapter } from "@authhero/adapter-interfaces";
4
4
  import { SyncEvent } from "../../helpers/control-plane-sync-events";
5
5
  import { Bindings } from "../../types";
6
+ import { type TenantMembersControlPlaneOptions } from "./tenant-members";
6
7
  export interface ProxyControlPlaneOptions {
7
8
  /**
8
9
  * Cross-tenant host resolver. Typically delegated to a database adapter's
@@ -16,6 +17,16 @@ export interface ProxyControlPlaneOptions {
16
17
  * service binding by inspecting the URL and dispatching accordingly.
17
18
  */
18
19
  jwksFetch?: (url: string) => Promise<Response>;
20
+ /**
21
+ * Optional predicate that widens the set of accepted token issuers beyond
22
+ * `env.ISSUER` and the inbound host — specifically to a deployment's own
23
+ * Workers-for-Platforms tenant subdomains, whose per-tenant control-plane
24
+ * credential `jwksFetch` resolves locally (see #1139). Consulted before any
25
+ * JWKS fetch, so it still constrains where keys are fetched from; return
26
+ * `true` only for issuer hosts you serve. Applies to every mounted resource
27
+ * (custom-domains, tenant-members, sync).
28
+ */
29
+ isTrustedIssuer?: (iss: string) => boolean;
19
30
  /**
20
31
  * Optional handler for `POST /sync` — receives `controlplane.sync.*` events
21
32
  * emitted by tenant shards via `ControlPlaneSyncDestination` and replicates
@@ -43,6 +54,18 @@ export interface ProxyControlPlaneOptions {
43
54
  * login.acme.com" needs a view across every tenant that only exists here.
44
55
  */
45
56
  customDomains?: CustomDomainsAdapter;
57
+ /**
58
+ * The authoritative tenant-team resource. When set, mounts
59
+ * `/api/v2/proxy/control-plane/tenant-members` — the resource tenant shards
60
+ * call through `createControlPlaneTenantMembersAdapter` to let their admins
61
+ * manage who administers the tenant (control-plane organization membership +
62
+ * org-scoped roles + invitations), rows the shard itself cannot write.
63
+ *
64
+ * Wire the backend to the control-plane database (see
65
+ * `createLocalTenantMembersBackend`); the org is pinned from the verified
66
+ * token, never the request.
67
+ */
68
+ tenantMembers?: TenantMembersControlPlaneOptions;
46
69
  }
47
70
  /**
48
71
  * Returns a Hono app exposing the privileged proxy control-plane endpoint
@@ -10,4 +10,11 @@ export declare const CONTROL_PLANE_SYNC_SCOPE = "controlplane:sync";
10
10
  * `createControlPlaneCustomDomainsAdapter`.
11
11
  */
12
12
  export declare const CONTROL_PLANE_CUSTOM_DOMAINS_SCOPE = "controlplane:custom_domains";
13
+ /**
14
+ * Scope required by the authoritative `/tenant-members` resource on the control
15
+ * plane — a tenant's team (organization membership + org-scoped roles +
16
+ * invitations) that only the control plane can write. Tenant shards mint a
17
+ * token with this scope through `createControlPlaneTenantMembersAdapter`.
18
+ */
19
+ export declare const CONTROL_PLANE_TENANT_MEMBERS_SCOPE = "controlplane:tenant_members";
13
20
  export { PROXY_RESOLVE_HOST_SCOPE };
@@ -0,0 +1,37 @@
1
+ import { Hono, type Context } from "hono";
2
+ import { Bindings } from "../../types";
3
+ import { TenantMembersBackend } from "../../tenant-members/types";
4
+ import type { AuthenticateControlPlane } from "./custom-domains";
5
+ type TenantVars = {
6
+ tenantId: string;
7
+ };
8
+ /** Context seen by the control-plane resource: env + the pinned tenant id. */
9
+ type CpCtx = Context<{
10
+ Bindings: Bindings;
11
+ Variables: TenantVars;
12
+ }>;
13
+ export interface TenantMembersControlPlaneOptions {
14
+ /**
15
+ * Build the backend for a request. Bound to the control-plane database
16
+ * (`c.env.data`) and, optionally, an email sender at wire-up time. The tenant
17
+ * is pinned separately from the verified token, so the factory does not need
18
+ * it.
19
+ */
20
+ getBackend: (c: CpCtx) => TenantMembersBackend | Promise<TenantMembersBackend>;
21
+ authenticate: AuthenticateControlPlane;
22
+ }
23
+ /**
24
+ * The authoritative `tenant-members` resource. Tenant shards reach it through
25
+ * `createControlPlaneTenantMembersAdapter`; it manages the control-plane
26
+ * organization that models a tenant's team — rows the shard cannot write.
27
+ *
28
+ * Every operation is bound to the `tenant_id` claim of the verified token. The
29
+ * scope alone is not authorization: each shard holds it, so a request-supplied
30
+ * tenant id would let any shard read or edit any other tenant's team. Mount
31
+ * under `/api/v2/proxy/control-plane/tenant-members`.
32
+ */
33
+ export declare function createTenantMembersControlPlaneApp(options: TenantMembersControlPlaneOptions): Hono<{
34
+ Bindings: Bindings;
35
+ Variables: TenantVars;
36
+ }>;
37
+ export {};
@@ -38,6 +38,20 @@ export interface VerifyControlPlaneTokenOptions {
38
38
  * `proxy:resolve_host`.
39
39
  */
40
40
  requiredScope?: string | string[];
41
+ /**
42
+ * Optional predicate consulted IN ADDITION to `expectedIssuers`, for issuers
43
+ * that can't be enumerated ahead of time — specifically a deployment's own
44
+ * Workers-for-Platforms tenant subdomains (`https://{tenant}.{host}/`), whose
45
+ * per-tenant control-plane credential the accompanying `jwksFetch` resolves
46
+ * locally (see #1139).
47
+ *
48
+ * Like `expectedIssuers`, it runs BEFORE the JWKS fetch, so it still gates
49
+ * where the verifier will fetch keys from: return `true` only for issuer
50
+ * hosts you actually serve. The signature must still verify against the
51
+ * resolved key, so this does not broaden trust beyond "a caller holding that
52
+ * tenant's registered private key."
53
+ */
54
+ isTrustedIssuer?: (iss: string) => boolean;
41
55
  }
42
56
  /**
43
57
  * Verify a bearer token for the proxy control plane. Returns `{ ok: true }`
@@ -456,7 +456,7 @@ export declare function initJSXRoute(ctx: Context<{
456
456
  custom_login_page_preview?: string | undefined;
457
457
  form_template?: string | undefined;
458
458
  addons?: Record<string, any> | undefined;
459
- token_endpoint_auth_method?: "none" | "private_key_jwt" | "client_secret_post" | "client_secret_basic" | "client_secret_jwt" | undefined;
459
+ token_endpoint_auth_method?: "client_secret_post" | "client_secret_basic" | "none" | "client_secret_jwt" | "private_key_jwt" | undefined;
460
460
  client_metadata?: Record<string, string> | undefined;
461
461
  hide_sign_up_disabled_error?: boolean | undefined;
462
462
  mobile?: Record<string, any> | undefined;
@@ -1185,7 +1185,7 @@ export declare function initJSXRouteWithSession(ctx: Context<{
1185
1185
  custom_login_page_preview?: string | undefined;
1186
1186
  form_template?: string | undefined;
1187
1187
  addons?: Record<string, any> | undefined;
1188
- token_endpoint_auth_method?: "none" | "private_key_jwt" | "client_secret_post" | "client_secret_basic" | "client_secret_jwt" | undefined;
1188
+ token_endpoint_auth_method?: "client_secret_post" | "client_secret_basic" | "none" | "client_secret_jwt" | "private_key_jwt" | undefined;
1189
1189
  client_metadata?: Record<string, string> | undefined;
1190
1190
  hide_sign_up_disabled_error?: boolean | undefined;
1191
1191
  mobile?: Record<string, any> | undefined;
@@ -27,7 +27,7 @@ export declare const flowApiRoutes: OpenAPIHono<{
27
27
  output: {
28
28
  screen: {
29
29
  action: string;
30
- method: "POST" | "GET";
30
+ method: "GET" | "POST";
31
31
  components: {
32
32
  id: string;
33
33
  type: string;
@@ -43,7 +43,7 @@ export declare const flowApiRoutes: OpenAPIHono<{
43
43
  hint?: string | undefined;
44
44
  messages?: {
45
45
  text: string;
46
- type: "error" | "success" | "info" | "warning";
46
+ type: "success" | "error" | "info" | "warning";
47
47
  id?: number | undefined;
48
48
  }[] | undefined;
49
49
  }[];
@@ -51,7 +51,7 @@ export declare const flowApiRoutes: OpenAPIHono<{
51
51
  description?: string | undefined;
52
52
  messages?: {
53
53
  text: string;
54
- type: "error" | "success" | "info" | "warning";
54
+ type: "success" | "error" | "info" | "warning";
55
55
  id?: number | undefined;
56
56
  }[] | undefined;
57
57
  links?: {
@@ -107,7 +107,7 @@ export declare const flowApiRoutes: OpenAPIHono<{
107
107
  output: {
108
108
  screen: {
109
109
  action: string;
110
- method: "POST" | "GET";
110
+ method: "GET" | "POST";
111
111
  components: {
112
112
  id: string;
113
113
  type: string;
@@ -123,7 +123,7 @@ export declare const flowApiRoutes: OpenAPIHono<{
123
123
  hint?: string | undefined;
124
124
  messages?: {
125
125
  text: string;
126
- type: "error" | "success" | "info" | "warning";
126
+ type: "success" | "error" | "info" | "warning";
127
127
  id?: number | undefined;
128
128
  }[] | undefined;
129
129
  }[];
@@ -131,7 +131,7 @@ export declare const flowApiRoutes: OpenAPIHono<{
131
131
  description?: string | undefined;
132
132
  messages?: {
133
133
  text: string;
134
- type: "error" | "success" | "info" | "warning";
134
+ type: "success" | "error" | "info" | "warning";
135
135
  id?: number | undefined;
136
136
  }[] | undefined;
137
137
  links?: {
@@ -204,7 +204,7 @@ export declare const flowApiRoutes: OpenAPIHono<{
204
204
  output: {
205
205
  screen: {
206
206
  action: string;
207
- method: "POST" | "GET";
207
+ method: "GET" | "POST";
208
208
  components: {
209
209
  id: string;
210
210
  type: string;
@@ -220,7 +220,7 @@ export declare const flowApiRoutes: OpenAPIHono<{
220
220
  hint?: string | undefined;
221
221
  messages?: {
222
222
  text: string;
223
- type: "error" | "success" | "info" | "warning";
223
+ type: "success" | "error" | "info" | "warning";
224
224
  id?: number | undefined;
225
225
  }[] | undefined;
226
226
  }[];
@@ -228,7 +228,7 @@ export declare const flowApiRoutes: OpenAPIHono<{
228
228
  description?: string | undefined;
229
229
  messages?: {
230
230
  text: string;
231
- type: "error" | "success" | "info" | "warning";
231
+ type: "success" | "error" | "info" | "warning";
232
232
  id?: number | undefined;
233
233
  }[] | undefined;
234
234
  links?: {
@@ -319,7 +319,7 @@ export declare const flowApiRoutes: OpenAPIHono<{
319
319
  output: {
320
320
  screen: {
321
321
  action: string;
322
- method: "POST" | "GET";
322
+ method: "GET" | "POST";
323
323
  components: {
324
324
  id: string;
325
325
  type: string;
@@ -335,7 +335,7 @@ export declare const flowApiRoutes: OpenAPIHono<{
335
335
  hint?: string | undefined;
336
336
  messages?: {
337
337
  text: string;
338
- type: "error" | "success" | "info" | "warning";
338
+ type: "success" | "error" | "info" | "warning";
339
339
  id?: number | undefined;
340
340
  }[] | undefined;
341
341
  }[];
@@ -343,7 +343,7 @@ export declare const flowApiRoutes: OpenAPIHono<{
343
343
  description?: string | undefined;
344
344
  messages?: {
345
345
  text: string;
346
- type: "error" | "success" | "info" | "warning";
346
+ type: "success" | "error" | "info" | "warning";
347
347
  id?: number | undefined;
348
348
  }[] | undefined;
349
349
  links?: {
@@ -165,7 +165,7 @@ export default function createU2App(config: AuthHeroConfig): OpenAPIHono<{
165
165
  $get: {
166
166
  input: {
167
167
  param: {
168
- screen: "signup" | "consent" | "login" | "reset-password" | "account" | "enter-password" | "impersonate" | "reset-password/request" | "reset-password/code" | "try-connection-result" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
168
+ screen: "signup" | "login" | "reset-password" | "consent" | "account" | "enter-password" | "impersonate" | "try-connection-result" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "reset-password/code" | "reset-password/request" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
169
169
  };
170
170
  } & {
171
171
  query: {
@@ -181,7 +181,7 @@ export default function createU2App(config: AuthHeroConfig): OpenAPIHono<{
181
181
  } | {
182
182
  input: {
183
183
  param: {
184
- screen: "signup" | "consent" | "login" | "reset-password" | "account" | "enter-password" | "impersonate" | "reset-password/request" | "reset-password/code" | "try-connection-result" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
184
+ screen: "signup" | "login" | "reset-password" | "consent" | "account" | "enter-password" | "impersonate" | "try-connection-result" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "reset-password/code" | "reset-password/request" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
185
185
  };
186
186
  } & {
187
187
  query: {
@@ -197,7 +197,7 @@ export default function createU2App(config: AuthHeroConfig): OpenAPIHono<{
197
197
  } | {
198
198
  input: {
199
199
  param: {
200
- screen: "signup" | "consent" | "login" | "reset-password" | "account" | "enter-password" | "impersonate" | "reset-password/request" | "reset-password/code" | "try-connection-result" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
200
+ screen: "signup" | "login" | "reset-password" | "consent" | "account" | "enter-password" | "impersonate" | "try-connection-result" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "reset-password/code" | "reset-password/request" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
201
201
  };
202
202
  } & {
203
203
  query: {
@@ -217,7 +217,7 @@ export default function createU2App(config: AuthHeroConfig): OpenAPIHono<{
217
217
  $post: {
218
218
  input: {
219
219
  param: {
220
- screen: "signup" | "consent" | "login" | "reset-password" | "enter-password" | "impersonate" | "reset-password/request" | "reset-password/code" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
220
+ screen: "signup" | "login" | "reset-password" | "consent" | "enter-password" | "impersonate" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "reset-password/code" | "reset-password/request" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
221
221
  };
222
222
  } & {
223
223
  query: {
@@ -235,7 +235,7 @@ export default function createU2App(config: AuthHeroConfig): OpenAPIHono<{
235
235
  } | {
236
236
  input: {
237
237
  param: {
238
- screen: "signup" | "consent" | "login" | "reset-password" | "enter-password" | "impersonate" | "reset-password/request" | "reset-password/code" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
238
+ screen: "signup" | "login" | "reset-password" | "consent" | "enter-password" | "impersonate" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "reset-password/code" | "reset-password/request" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
239
239
  };
240
240
  } & {
241
241
  query: {
@@ -170,7 +170,7 @@ export declare const u2Routes: OpenAPIHono<{
170
170
  $get: {
171
171
  input: {
172
172
  param: {
173
- screen: "signup" | "consent" | "login" | "reset-password" | "account" | "enter-password" | "impersonate" | "reset-password/request" | "reset-password/code" | "try-connection-result" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
173
+ screen: "signup" | "login" | "reset-password" | "consent" | "account" | "enter-password" | "impersonate" | "try-connection-result" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "reset-password/code" | "reset-password/request" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
174
174
  };
175
175
  } & {
176
176
  query: {
@@ -186,7 +186,7 @@ export declare const u2Routes: OpenAPIHono<{
186
186
  } | {
187
187
  input: {
188
188
  param: {
189
- screen: "signup" | "consent" | "login" | "reset-password" | "account" | "enter-password" | "impersonate" | "reset-password/request" | "reset-password/code" | "try-connection-result" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
189
+ screen: "signup" | "login" | "reset-password" | "consent" | "account" | "enter-password" | "impersonate" | "try-connection-result" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "reset-password/code" | "reset-password/request" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
190
190
  };
191
191
  } & {
192
192
  query: {
@@ -202,7 +202,7 @@ export declare const u2Routes: OpenAPIHono<{
202
202
  } | {
203
203
  input: {
204
204
  param: {
205
- screen: "signup" | "consent" | "login" | "reset-password" | "account" | "enter-password" | "impersonate" | "reset-password/request" | "reset-password/code" | "try-connection-result" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
205
+ screen: "signup" | "login" | "reset-password" | "consent" | "account" | "enter-password" | "impersonate" | "try-connection-result" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "reset-password/code" | "reset-password/request" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
206
206
  };
207
207
  } & {
208
208
  query: {
@@ -222,7 +222,7 @@ export declare const u2Routes: OpenAPIHono<{
222
222
  $post: {
223
223
  input: {
224
224
  param: {
225
- screen: "signup" | "consent" | "login" | "reset-password" | "enter-password" | "impersonate" | "reset-password/request" | "reset-password/code" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
225
+ screen: "signup" | "login" | "reset-password" | "consent" | "enter-password" | "impersonate" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "reset-password/code" | "reset-password/request" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
226
226
  };
227
227
  } & {
228
228
  query: {
@@ -240,7 +240,7 @@ export declare const u2Routes: OpenAPIHono<{
240
240
  } | {
241
241
  input: {
242
242
  param: {
243
- screen: "signup" | "consent" | "login" | "reset-password" | "enter-password" | "impersonate" | "reset-password/request" | "reset-password/code" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
243
+ screen: "signup" | "login" | "reset-password" | "consent" | "enter-password" | "impersonate" | "login/identifier" | "login/email-otp-challenge" | "login/sms-otp-challenge" | "login/login-passwordless-identifier" | "reset-password/code" | "reset-password/request" | "mfa/login-options" | "mfa/totp-challenge" | "mfa/totp-enrollment" | "mfa/phone-challenge" | "mfa/phone-enrollment" | "passkey/challenge" | "passkey/enrollment" | "passkey/enrollment-nudge" | "account/profile" | "account/security" | "account/security/totp-enrollment" | "account/security/phone-enrollment" | "account/linked" | "account/delete" | "account/passkeys" | "connect/start" | "connect/select-tenant";
244
244
  };
245
245
  } & {
246
246
  query: {
@@ -0,0 +1,43 @@
1
+ import { DataAdapters } from "@authhero/adapter-interfaces";
2
+ import { TenantMembersBackend } from "./types";
3
+ /** The control-plane adapters the team lives in. */
4
+ type TeamAdapters = Pick<DataAdapters, "organizations" | "userOrganizations" | "userRoles" | "users" | "roles" | "invites">;
5
+ export interface LocalTenantMembersBackendOptions {
6
+ /**
7
+ * Adapters bound to the CONTROL-PLANE database — the team lives there, not on
8
+ * the tenant shard. On the control-plane instance this is just `ctx.env.data`.
9
+ */
10
+ data: TeamAdapters;
11
+ /** Tenant id under which the org rows are stored (the control-plane tenant). */
12
+ controlPlaneTenantId: string;
13
+ /** Issuer used to build invitation acceptance links and default avatars. */
14
+ issuer: string;
15
+ /**
16
+ * Client id embedded in invitation links. Resolved server-side so the caller
17
+ * never supplies it (the admin-UI bug this fixes). Without it, invitations
18
+ * cannot be created and `createInvitation` throws.
19
+ */
20
+ invitationClientId?: string;
21
+ /**
22
+ * Best-effort invitation email delivery. Bound to a request context at the
23
+ * call site (it needs branding/email infra). Failures must not fail the
24
+ * create — Auth0 returns the invite even when delivery fails.
25
+ */
26
+ sendInvitationEmail?: (params: {
27
+ to: string;
28
+ invitationUrl: string;
29
+ inviterName?: string;
30
+ organizationName: string;
31
+ ttlSec: number;
32
+ }) => Promise<void>;
33
+ }
34
+ /**
35
+ * A `TenantMembersBackend` that resolves the tenant's control-plane
36
+ * organization and manages membership/roles/invitations directly against the
37
+ * control-plane adapters. Used where the code already runs with the
38
+ * control-plane database in reach: single-instance deployments (via the
39
+ * management route) and the control-plane proxy resource (which fronts remote
40
+ * shards).
41
+ */
42
+ export declare function createLocalTenantMembersBackend(options: LocalTenantMembersBackendOptions): TenantMembersBackend;
43
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { ControlPlaneClient } from "../helpers/control-plane-client";
2
+ import { TenantMembersBackend } from "./types";
3
+ export interface ControlPlaneTenantMembersOptions {
4
+ /** Authed transport to the control plane (shared with other adapters). */
5
+ client: ControlPlaneClient;
6
+ /** Override the control-plane resource path (tests, custom mounts). */
7
+ basePath?: string;
8
+ }
9
+ /**
10
+ * A `TenantMembersBackend` for a tenant shard whose own database does not hold
11
+ * the control-plane organization that models its team. Every call is delegated
12
+ * up to the authoritative `tenant-members` resource
13
+ * (`createTenantMembersControlPlaneApp`) over the shared control-plane client,
14
+ * which mints a service token bound to this tenant. The control plane pins the
15
+ * org from that token, so the shard never has to (and cannot) name it.
16
+ */
17
+ export declare function createControlPlaneTenantMembersAdapter(options: ControlPlaneTenantMembersOptions): TenantMembersBackend;
@@ -0,0 +1,112 @@
1
+ import { z } from "@hono/zod-openapi";
2
+ import { inviteSchema, roleSchema } from "@authhero/adapter-interfaces";
3
+ /**
4
+ * A tenant's "team" is not the tenant's own users. It is an organization on the
5
+ * control-plane tenant whose `name` equals the tenant id (see
6
+ * `@authhero/multi-tenancy` provisioning). Its members are control-plane users
7
+ * holding org-scoped roles, and the tenant shard cannot write those rows.
8
+ *
9
+ * `TenantMembersBackend` is the seam that hides "where the team actually lives":
10
+ * - on a single-instance / control-plane deployment it resolves the org and
11
+ * acts on the local control-plane adapters (`createLocalTenantMembersBackend`);
12
+ * - on a Workers-for-Platforms shard it delegates every call up to the control
13
+ * plane over the shared control-plane client
14
+ * (`createControlPlaneTenantMembersAdapter`).
15
+ *
16
+ * Every method takes the CHILD `tenantId` (the tenant whose team is managed),
17
+ * never an organization id — the org is resolved from the tenant id internally.
18
+ * The management route that fronts this has already pinned `tenantId` to the
19
+ * caller's verified org claim, so the backend never has to re-authorize.
20
+ */
21
+ export declare const tenantMemberSchema: z.ZodObject<{
22
+ user_id: z.ZodString;
23
+ email: z.ZodOptional<z.ZodString>;
24
+ name: z.ZodOptional<z.ZodString>;
25
+ picture: z.ZodOptional<z.ZodString>;
26
+ roles: z.ZodDefault<z.ZodArray<z.ZodObject<{
27
+ name: z.ZodString;
28
+ description: z.ZodOptional<z.ZodString>;
29
+ is_system: z.ZodOptional<z.ZodBoolean>;
30
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
31
+ id: z.ZodString;
32
+ created_at: z.ZodOptional<z.ZodString>;
33
+ updated_at: z.ZodOptional<z.ZodString>;
34
+ }, z.core.$strip>>>;
35
+ }, z.core.$strip>;
36
+ export type TenantMember = z.infer<typeof tenantMemberSchema>;
37
+ export declare const tenantMembersListSchema: z.ZodObject<{
38
+ members: z.ZodArray<z.ZodObject<{
39
+ user_id: z.ZodString;
40
+ email: z.ZodOptional<z.ZodString>;
41
+ name: z.ZodOptional<z.ZodString>;
42
+ picture: z.ZodOptional<z.ZodString>;
43
+ roles: z.ZodDefault<z.ZodArray<z.ZodObject<{
44
+ name: z.ZodString;
45
+ description: z.ZodOptional<z.ZodString>;
46
+ is_system: z.ZodOptional<z.ZodBoolean>;
47
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
48
+ id: z.ZodString;
49
+ created_at: z.ZodOptional<z.ZodString>;
50
+ updated_at: z.ZodOptional<z.ZodString>;
51
+ }, z.core.$strip>>>;
52
+ }, z.core.$strip>>;
53
+ start: z.ZodNumber;
54
+ limit: z.ZodNumber;
55
+ total: z.ZodNumber;
56
+ }, z.core.$strip>;
57
+ export type TenantMembersListResult = z.infer<typeof tenantMembersListSchema>;
58
+ /**
59
+ * The invitee-facing fields of an invitation create. `organization_id`,
60
+ * `invitation_url` and `id` are resolved server-side (the org from the tenant
61
+ * id, the URL from the control-plane issuer), never trusted from the caller —
62
+ * that is the fix for the admin UI's "invite silently disappears when the
63
+ * client id isn't in local storage" foot-gun.
64
+ */
65
+ export declare const tenantInvitationInputSchema: z.ZodObject<{
66
+ invitee: z.ZodObject<{
67
+ email: z.ZodString;
68
+ }, z.core.$strip>;
69
+ inviter: z.ZodDefault<z.ZodObject<{
70
+ name: z.ZodOptional<z.ZodString>;
71
+ }, z.core.$strip>>;
72
+ roles: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
73
+ send_invitation_email: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
74
+ ttl_sec: z.ZodOptional<z.ZodNumber>;
75
+ }, z.core.$strip>;
76
+ export type TenantInvitationInput = z.input<typeof tenantInvitationInputSchema>;
77
+ export interface TenantMembersPageParams {
78
+ page?: number;
79
+ per_page?: number;
80
+ q?: string;
81
+ }
82
+ export type TenantInvitation = z.infer<typeof inviteSchema>;
83
+ export type TenantRole = z.infer<typeof roleSchema>;
84
+ export interface TenantMembersBackend {
85
+ listMembers(tenantId: string, params?: TenantMembersPageParams): Promise<TenantMembersListResult>;
86
+ addMembers(tenantId: string, userIds: string[]): Promise<void>;
87
+ removeMembers(tenantId: string, userIds: string[]): Promise<void>;
88
+ listMemberRoles(tenantId: string, userId: string): Promise<TenantRole[]>;
89
+ assignMemberRoles(tenantId: string, userId: string, roleIds: string[]): Promise<void>;
90
+ removeMemberRoles(tenantId: string, userId: string, roleIds: string[]): Promise<void>;
91
+ /** Roles that can be granted to a member — the control-plane tenant's roles. */
92
+ listRoles(tenantId: string, params?: TenantMembersPageParams): Promise<TenantRole[]>;
93
+ listInvitations(tenantId: string, params?: TenantMembersPageParams): Promise<TenantInvitation[]>;
94
+ createInvitation(tenantId: string, input: TenantInvitationInput): Promise<TenantInvitation>;
95
+ revokeInvitation(tenantId: string, invitationId: string): Promise<void>;
96
+ }
97
+ /**
98
+ * Base for the "asked for something that isn't there" cases a backend can hit.
99
+ * The management route maps any of these to a 404. It is a distinct type (not a
100
+ * bare HTTPException) so the local and remote backends can agree on the
101
+ * semantics without depending on hono.
102
+ */
103
+ export declare class TenantMembersNotFoundError extends Error {
104
+ }
105
+ /** The tenant id names no control-plane organization — i.e. no team. */
106
+ export declare class TenantOrganizationNotFoundError extends TenantMembersNotFoundError {
107
+ constructor(tenantId: string);
108
+ }
109
+ /** The invitation does not exist, or belongs to a different tenant's team. */
110
+ export declare class TenantInvitationNotFoundError extends TenantMembersNotFoundError {
111
+ constructor(invitationId: string);
112
+ }
@@ -0,0 +1,28 @@
1
+ import { z } from "@hono/zod-openapi";
2
+ /**
3
+ * The HTTP contract shared by the control-plane `tenant-members` resource
4
+ * (`createTenantMembersControlPlaneApp`) and the remote client that calls it
5
+ * (`createControlPlaneTenantMembersAdapter`). Keeping the paths and body shapes
6
+ * in one place is what keeps the two ends from drifting.
7
+ */
8
+ export declare const CONTROL_PLANE_TENANT_MEMBERS_PATH = "/api/v2/proxy/control-plane/tenant-members";
9
+ export declare const membersMutationBodySchema: z.ZodObject<{
10
+ user_ids: z.ZodArray<z.ZodString>;
11
+ tenant_id: z.ZodOptional<z.ZodString>;
12
+ }, z.core.$strip>;
13
+ export declare const memberRolesBodySchema: z.ZodObject<{
14
+ roles: z.ZodArray<z.ZodString>;
15
+ tenant_id: z.ZodOptional<z.ZodString>;
16
+ }, z.core.$strip>;
17
+ export declare const createInvitationBodySchema: z.ZodObject<{
18
+ invitee: z.ZodObject<{
19
+ email: z.ZodString;
20
+ }, z.core.$strip>;
21
+ inviter: z.ZodDefault<z.ZodObject<{
22
+ name: z.ZodOptional<z.ZodString>;
23
+ }, z.core.$strip>>;
24
+ roles: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
25
+ send_invitation_email: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
26
+ ttl_sec: z.ZodOptional<z.ZodNumber>;
27
+ tenant_id: z.ZodOptional<z.ZodString>;
28
+ }, z.core.$strip>;
@@ -315,6 +315,14 @@ export interface AuthHeroConfig {
315
315
  * hosts through a service binding by inspecting the URL.
316
316
  */
317
317
  jwksFetch?: (url: string) => Promise<Response>;
318
+ /**
319
+ * Optional predicate widening the accepted token issuers beyond
320
+ * `env.ISSUER` / the inbound host to a deployment's own WFP tenant
321
+ * subdomains, whose per-tenant control-plane credential `jwksFetch`
322
+ * resolves locally (see #1139). Consulted before any JWKS fetch; return
323
+ * `true` only for issuer hosts you serve.
324
+ */
325
+ isTrustedIssuer?: (iss: string) => boolean;
318
326
  /**
319
327
  * Optional receiver for `POST /sync` events emitted by tenant shards via
320
328
  * the `ControlPlaneSyncDestination`. Mount on the control-plane authhero
@@ -335,6 +343,30 @@ export interface AuthHeroConfig {
335
343
  * needs a view across every tenant that only exists here.
336
344
  */
337
345
  customDomains?: import("@authhero/adapter-interfaces").CustomDomainsAdapter;
346
+ /**
347
+ * The authoritative tenant-team resource. When set, mounts
348
+ * `/api/v2/proxy/control-plane/tenant-members` — the resource tenant shards
349
+ * call through `createControlPlaneTenantMembersAdapter` so their admins can
350
+ * manage who administers the tenant (control-plane organization membership
351
+ * + org-scoped roles + invitations). Wire the backend to the control-plane
352
+ * database via `createLocalTenantMembersBackend`.
353
+ */
354
+ tenantMembers?: import("../routes/proxy-control-plane/tenant-members").TenantMembersControlPlaneOptions;
355
+ };
356
+ /**
357
+ * Enables the tenant-scoped `/api/v2/tenant-members` management resource,
358
+ * which lets a tenant admin manage their own team from the per-tenant admin
359
+ * UI. Every request is pinned to the caller's `org_name` claim, then
360
+ * delegated to the backend this returns.
361
+ *
362
+ * Return `createLocalTenantMembersBackend(...)` on a single-instance /
363
+ * control-plane deployment (the team lives in the same database), or
364
+ * `createControlPlaneTenantMembersAdapter(...)` on a Workers-for-Platforms
365
+ * shard (the team lives on the control plane and is reached over the shared
366
+ * control-plane client). Leave unset to not mount the resource.
367
+ */
368
+ tenantMembers?: {
369
+ getBackend: import("../routes/management-api/tenant-members").GetTenantMembersBackend;
338
370
  };
339
371
  /**
340
372
  * Optional outbox-driven replication of `proxy_routes` mutations to a global