@adonis-agora/authkit-server 1.0.0-rc.0 → 1.0.0-rc.10

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 (177) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/README.md +4 -5
  3. package/build/commands/agora_doctor.d.ts +22 -0
  4. package/build/commands/agora_doctor.js +109 -0
  5. package/build/commands/clients_create.d.ts +4 -0
  6. package/build/commands/clients_create.js +97 -0
  7. package/build/commands/commands.json +78 -0
  8. package/build/commands/doctor.js +8 -74
  9. package/build/host/views/account/apps.edge +1 -5
  10. package/build/host/views/account/extension.edge +77 -0
  11. package/build/host/views/account/mfa.edge +2 -0
  12. package/build/host/views/account/partials/extension_actions.edge +14 -0
  13. package/build/host/views/account/partials/extension_block.edge +197 -0
  14. package/build/host/views/account/partials/extension_value.edge +8 -0
  15. package/build/host/views/account/partials/nav.edge +14 -0
  16. package/build/host/views/account/security.edge +1 -5
  17. package/build/host/views/account/tokens.edge +2 -2
  18. package/build/host/views/agents/consent.edge +11 -2
  19. package/build/host/views/agents/done.edge +1 -1
  20. package/build/host/views/partials/styles.edge +1 -1
  21. package/build/index.d.ts +14 -12
  22. package/build/index.js +8 -9
  23. package/build/providers/authkit_server_provider.js +36 -8
  24. package/build/src/account/extension.d.ts +10 -0
  25. package/build/src/account/extension.js +9 -0
  26. package/build/src/account/http.d.ts +77 -0
  27. package/build/src/account/http.js +381 -0
  28. package/build/src/account/mount.d.ts +40 -0
  29. package/build/src/account/mount.js +171 -0
  30. package/build/src/account/registry.d.ts +41 -0
  31. package/build/src/account/registry.js +81 -0
  32. package/build/src/account/remote.d.ts +72 -0
  33. package/build/src/account/remote.js +406 -0
  34. package/build/src/account/runtime.d.ts +289 -0
  35. package/build/src/account/runtime.js +641 -0
  36. package/build/src/accounts/account_store.d.ts +6 -0
  37. package/build/src/accounts/lucid_store/organizations.js +6 -0
  38. package/build/src/agents/config.d.ts +9 -13
  39. package/build/src/agents/config.js +17 -11
  40. package/build/src/agents/delegation_service.js +3 -3
  41. package/build/src/agents/delegation_store.d.ts +3 -2
  42. package/build/src/agents/delegation_store.js +6 -5
  43. package/build/src/agents/extension.d.ts +261 -0
  44. package/build/src/agents/extension.js +40 -0
  45. package/build/src/agents/middleware.js +1 -1
  46. package/build/src/agents/protocol.d.ts +4 -4
  47. package/build/src/agents/registry_store.d.ts +3 -2
  48. package/build/src/agents/registry_store.js +1 -1
  49. package/build/src/agents/runtime.d.ts +26 -7
  50. package/build/src/agents/runtime.js +110 -75
  51. package/build/src/audit/audit_sink.d.ts +1 -1
  52. package/build/src/audit/audit_sink.js +10 -1
  53. package/build/src/console_authorize.d.ts +56 -0
  54. package/build/src/console_authorize.js +85 -0
  55. package/build/src/console_switcher.d.ts +132 -0
  56. package/build/src/console_switcher.js +206 -0
  57. package/build/src/define_config.d.ts +54 -14
  58. package/build/src/define_config.js +17 -8
  59. package/build/src/dev_tools/contract.d.ts +72 -0
  60. package/build/src/dev_tools/contract.js +10 -0
  61. package/build/src/dev_tools/gate.d.ts +17 -0
  62. package/build/src/dev_tools/gate.js +69 -0
  63. package/build/src/dev_tools/register.d.ts +11 -0
  64. package/build/src/dev_tools/register.js +25 -0
  65. package/build/src/dev_tools/tools.d.ts +24 -0
  66. package/build/src/dev_tools/tools.js +384 -0
  67. package/build/src/doctor/agora_checks.d.ts +15 -0
  68. package/build/src/doctor/agora_checks.js +150 -0
  69. package/build/src/doctor/agora_doctor.d.ts +115 -0
  70. package/build/src/doctor/agora_doctor.js +692 -0
  71. package/build/src/doctor/checks.d.ts +7 -1
  72. package/build/src/doctor/checks.js +47 -1
  73. package/build/src/doctor/doctor_input.d.ts +27 -0
  74. package/build/src/doctor/doctor_input.js +86 -0
  75. package/build/src/doctor/index.d.ts +5 -0
  76. package/build/src/doctor/index.js +4 -0
  77. package/build/src/host/access_token_verifier.js +7 -0
  78. package/build/src/host/account_api/account_api_controller.js +8 -6
  79. package/build/src/host/account_session_sign_in.d.ts +12 -0
  80. package/build/src/host/account_session_sign_in.js +19 -0
  81. package/build/src/host/admin_api/admin_users_service.d.ts +5 -2
  82. package/build/src/host/admin_api/api_clients_controller.d.ts +37 -0
  83. package/build/src/host/admin_api/api_clients_controller.js +31 -1
  84. package/build/src/host/admin_api/dto.d.ts +3 -1
  85. package/build/src/host/admin_api/dto.js +2 -0
  86. package/build/src/host/admin_authorize.d.ts +8 -0
  87. package/build/src/host/admin_authorize.js +25 -0
  88. package/build/src/host/admin_clients_service.d.ts +20 -0
  89. package/build/src/host/admin_clients_service.js +65 -1
  90. package/build/src/host/admin_console/admin_console_entry.d.ts +14 -0
  91. package/build/src/host/admin_console/admin_console_entry.js +80 -0
  92. package/build/src/host/admin_console/admin_shell_controller.js +8 -0
  93. package/build/src/host/admin_console/console_clients_controller.d.ts +41 -0
  94. package/build/src/host/admin_console/console_clients_controller.js +34 -1
  95. package/build/src/host/admin_console/console_personal_agents_controller.d.ts +1 -6
  96. package/build/src/host/admin_console/console_personal_agents_controller.js +50 -30
  97. package/build/src/host/admin_console/console_switcher_controller.d.ts +11 -0
  98. package/build/src/host/admin_console/console_switcher_controller.js +23 -0
  99. package/build/src/host/admin_console_warning.d.ts +8 -0
  100. package/build/src/host/admin_console_warning.js +18 -0
  101. package/build/src/host/admin_sessions_service.js +8 -5
  102. package/build/src/host/admin_validators.d.ts +29 -0
  103. package/build/src/host/admin_validators.js +30 -0
  104. package/build/src/host/auth_host_config.d.ts +14 -2
  105. package/build/src/host/console_denied_page.d.ts +6 -0
  106. package/build/src/host/console_denied_page.js +18 -0
  107. package/build/src/host/console_sso.d.ts +24 -0
  108. package/build/src/host/console_sso.js +70 -0
  109. package/build/src/host/controllers/account_apps_controller.js +15 -11
  110. package/build/src/host/controllers/account_session_controller.js +10 -1
  111. package/build/src/host/controllers/console_sso_controller.d.ts +12 -0
  112. package/build/src/host/controllers/console_sso_controller.js +61 -0
  113. package/build/src/host/controllers/interaction_controller.js +3 -1
  114. package/build/src/host/csrf.d.ts +3 -3
  115. package/build/src/host/csrf.js +3 -13
  116. package/build/src/host/default_mailer.d.ts +11 -6
  117. package/build/src/host/default_mailer.js +16 -12
  118. package/build/src/host/i18n.d.ts +41 -14
  119. package/build/src/host/i18n.js +55 -13
  120. package/build/src/host/rate_limit.d.ts +8 -0
  121. package/build/src/host/rate_limit.js +9 -0
  122. package/build/src/host/register_auth_host.d.ts +16 -0
  123. package/build/src/host/register_auth_host.js +230 -88
  124. package/build/src/host/renderers/edge_renderer.js +5 -0
  125. package/build/src/host/renderers/inertia_renderer.js +7 -1
  126. package/build/src/host/screen_props.d.ts +39 -8
  127. package/build/src/host/ui-dist/assets/index-Dq3dJVBv.js +67 -0
  128. package/build/src/host/ui-dist/assets/{index-DTSmD4RU.css → index-WNIyCEPA.css} +1 -1
  129. package/build/src/host/ui-dist/index.html +2 -2
  130. package/build/src/provider/build_provider.js +43 -6
  131. package/build/src/provider/oidc_service.d.ts +1 -1
  132. package/build/src/provider/oidc_service.js +1 -1
  133. package/build/src/schema/ensure.d.ts +14 -0
  134. package/build/src/schema/ensure.js +43 -150
  135. package/build/src/service_tenants/client_metadata.d.ts +39 -0
  136. package/build/src/service_tenants/client_metadata.js +80 -0
  137. package/build/src/service_tenants/config.d.ts +44 -0
  138. package/build/src/service_tenants/config.js +44 -0
  139. package/build/src/service_tenants/provider_hooks.d.ts +52 -0
  140. package/build/src/service_tenants/provider_hooks.js +124 -0
  141. package/build/src/service_tenants/tenant_verifier.d.ts +73 -0
  142. package/build/src/service_tenants/tenant_verifier.js +104 -0
  143. package/build/src/tenancy/tenant_labels.d.ts +30 -0
  144. package/build/src/tenancy/tenant_labels.js +64 -0
  145. package/build/types.d.ts +3 -1
  146. package/package.json +12 -9
  147. package/build/src/agents/poppy/client_registry.d.ts +0 -50
  148. package/build/src/agents/poppy/client_registry.js +0 -260
  149. package/build/src/agents/poppy/config.d.ts +0 -396
  150. package/build/src/agents/poppy/config.js +0 -228
  151. package/build/src/agents/poppy/dpop.d.ts +0 -61
  152. package/build/src/agents/poppy/dpop.js +0 -158
  153. package/build/src/agents/poppy/email_code_sign_in.d.ts +0 -27
  154. package/build/src/agents/poppy/email_code_sign_in.js +0 -140
  155. package/build/src/agents/poppy/errors.d.ts +0 -25
  156. package/build/src/agents/poppy/errors.js +0 -25
  157. package/build/src/agents/poppy/replay.d.ts +0 -25
  158. package/build/src/agents/poppy/replay.js +0 -106
  159. package/build/src/agents/poppy/resource.d.ts +0 -100
  160. package/build/src/agents/poppy/resource.js +0 -129
  161. package/build/src/agents/poppy/safe_fetch.d.ts +0 -48
  162. package/build/src/agents/poppy/safe_fetch.js +0 -262
  163. package/build/src/agents/poppy/service.d.ts +0 -293
  164. package/build/src/agents/poppy/service.js +0 -1458
  165. package/build/src/agents/poppy/session_jwt.d.ts +0 -30
  166. package/build/src/agents/poppy/session_jwt.js +0 -133
  167. package/build/src/agents/poppy/store.d.ts +0 -106
  168. package/build/src/agents/poppy/store.js +0 -261
  169. package/build/src/agents/poppy/web_session.d.ts +0 -39
  170. package/build/src/agents/poppy/web_session.js +0 -122
  171. package/build/src/host/controllers/poppy_consent_controller.d.ts +0 -19
  172. package/build/src/host/controllers/poppy_consent_controller.js +0 -199
  173. package/build/src/host/controllers/poppy_controller.d.ts +0 -33
  174. package/build/src/host/controllers/poppy_controller.js +0 -219
  175. package/build/src/host/poppy_session_key.d.ts +0 -9
  176. package/build/src/host/poppy_session_key.js +0 -9
  177. package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +0 -67
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Service tenants: confidential `client_credentials` clients that act as a partition
3
+ * (`partition` claim) of a resource server (e.g. a durable control plane). Their tokens
4
+ * are JWT access tokens (RFC 9068) whose `aud` is the resource (RFC 8707).
5
+ */
6
+ /** One resource that issues service-tenant tokens. */
7
+ export interface ServiceTenantResourceInput {
8
+ /** `aud` of the token. Default: the resource indicator itself. */
9
+ audience?: string;
10
+ /** Token lifetime in seconds. Default: 600 (10 minutes). */
11
+ expiresIn?: number;
12
+ /** Scopes a service tenant may request for this resource. Default: none. */
13
+ scopes?: string[];
14
+ }
15
+ export interface ServiceTenantsConfigInput {
16
+ /**
17
+ * Resource indicators (RFC 8707) that issue service-tenant tokens, e.g.
18
+ * `{ 'urn:agora:durable': {} }`. Only service-tenant clients can get tokens for them.
19
+ */
20
+ resources: Record<string, ServiceTenantResourceInput>;
21
+ }
22
+ export interface ResolvedServiceTenantResource {
23
+ audience: string;
24
+ expiresIn: number;
25
+ scopes: string[];
26
+ }
27
+ export interface ResolvedServiceTenantsConfig {
28
+ /** `true` when at least one resource is declared. */
29
+ enabled: boolean;
30
+ resources: Record<string, ResolvedServiceTenantResource>;
31
+ }
32
+ /** Default token lifetime: 10 minutes. */
33
+ export declare const DEFAULT_SERVICE_TENANT_TTL = 600;
34
+ /**
35
+ * Resolves `serviceTenants`. Throws on an invalid resource indicator, a non-positive
36
+ * `expiresIn`, or a resource also declared in `accessTokens.resources` (a resource is
37
+ * either for users or for service tenants, never both).
38
+ */
39
+ export declare function resolveServiceTenants(input: ServiceTenantsConfigInput | undefined, accessTokenResources?: Record<string, unknown>): ResolvedServiceTenantsConfig;
40
+ /** Finds a declared service-tenant resource, tolerating a trailing slash. */
41
+ export declare function findServiceTenantResource(config: ResolvedServiceTenantsConfig, indicator: string): {
42
+ key: string;
43
+ resource: ResolvedServiceTenantResource;
44
+ } | null;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Service tenants: confidential `client_credentials` clients that act as a partition
3
+ * (`partition` claim) of a resource server (e.g. a durable control plane). Their tokens
4
+ * are JWT access tokens (RFC 9068) whose `aud` is the resource (RFC 8707).
5
+ */
6
+ /** Default token lifetime: 10 minutes. */
7
+ export const DEFAULT_SERVICE_TENANT_TTL = 600;
8
+ const trimSlash = (s) => s.replace(/\/+$/, '');
9
+ /**
10
+ * Resolves `serviceTenants`. Throws on an invalid resource indicator, a non-positive
11
+ * `expiresIn`, or a resource also declared in `accessTokens.resources` (a resource is
12
+ * either for users or for service tenants, never both).
13
+ */
14
+ export function resolveServiceTenants(input, accessTokenResources = {}) {
15
+ const resources = {};
16
+ const userResources = new Set(Object.keys(accessTokenResources).map(trimSlash));
17
+ for (const [indicator, rc] of Object.entries(input?.resources ?? {})) {
18
+ const parsed = URL.parse(indicator);
19
+ if (!parsed || indicator.includes('#')) {
20
+ throw new Error(`authkit: serviceTenants.resources["${indicator}"] must be an absolute URI without a fragment (e.g. "urn:agora:durable").`);
21
+ }
22
+ if (userResources.has(trimSlash(indicator))) {
23
+ throw new Error(`authkit: "${indicator}" is declared in both accessTokens.resources and serviceTenants.resources. Pick one.`);
24
+ }
25
+ const expiresIn = rc?.expiresIn ?? DEFAULT_SERVICE_TENANT_TTL;
26
+ if (!Number.isInteger(expiresIn) || expiresIn <= 0) {
27
+ throw new Error(`authkit: serviceTenants.resources["${indicator}"].expiresIn must be a positive integer (seconds).`);
28
+ }
29
+ resources[indicator] = {
30
+ audience: rc?.audience ?? indicator,
31
+ expiresIn,
32
+ scopes: [...(rc?.scopes ?? [])],
33
+ };
34
+ }
35
+ return { enabled: Object.keys(resources).length > 0, resources };
36
+ }
37
+ /** Finds a declared service-tenant resource, tolerating a trailing slash. */
38
+ export function findServiceTenantResource(config, indicator) {
39
+ if (config.resources[indicator])
40
+ return { key: indicator, resource: config.resources[indicator] };
41
+ const trimmed = trimSlash(indicator);
42
+ const key = Object.keys(config.resources).find((k) => trimSlash(k) === trimmed);
43
+ return key ? { key, resource: config.resources[key] } : null;
44
+ }
@@ -0,0 +1,52 @@
1
+ import type { OidcAdapterClass } from '../adapters/factory.js';
2
+ import type { AuditSink } from '../audit/audit_sink.js';
3
+ import { type ResolvedServiceTenantsConfig } from './config.js';
4
+ export interface ServiceTenantHooksDeps {
5
+ serviceTenants: ResolvedServiceTenantsConfig;
6
+ /** Adapter class of the `Client` model (raw: sees authkit's own keys). */
7
+ AdapterClass: OidcAdapterClass;
8
+ audit?: AuditSink;
9
+ }
10
+ /** Resource server info for a service-tenant resource, in oidc-provider's shape. */
11
+ export interface ServiceTenantResourceServerInfo {
12
+ scope: string;
13
+ audience: string;
14
+ accessTokenFormat: 'jwt';
15
+ accessTokenTTL: number;
16
+ jwt: {
17
+ sign: {
18
+ alg: string;
19
+ };
20
+ };
21
+ }
22
+ /**
23
+ * The provider hooks behind service tenants:
24
+ *
25
+ * - a service-tenant resource is granted only to a service-tenant client, only on the
26
+ * `client_credentials` grant, always as a JWT access token (RFC 9068);
27
+ * - a service-tenant client can only get tokens for a service-tenant resource (when
28
+ * it sends no `resource` and exactly one is declared, that one is used);
29
+ * - its tokens carry the `partition` (and `capabilities`) claims read from the client
30
+ * at issuance time, so an edit applies to the next token.
31
+ */
32
+ export declare function createServiceTenantHooks(deps: ServiceTenantHooksDeps): {
33
+ enabled: boolean;
34
+ /**
35
+ * `defaultResource` for a service-tenant client: the only declared resource, or
36
+ * `invalid_target` when several are declared. `undefined` for any other client.
37
+ */
38
+ defaultResource(ctx: any, client: any): Promise<string | undefined>;
39
+ /**
40
+ * `getResourceServerInfo` for service tenants. Returns the info for a
41
+ * service-tenant resource, `undefined` when neither the resource nor the client is
42
+ * a service-tenant one (the caller handles it), and throws `invalid_target` for a
43
+ * mismatch.
44
+ */
45
+ resourceServerInfo(ctx: any, indicator: string, client: any, signAlg: string): Promise<ServiceTenantResourceServerInfo | undefined>;
46
+ /** `extraTokenClaims` for a service-tenant token; audits the issuance. */
47
+ extraClaims(ctx: any, token: any): Promise<{
48
+ partition: string;
49
+ capabilities?: string[];
50
+ } | undefined>;
51
+ };
52
+ export type ServiceTenantHooks = ReturnType<typeof createServiceTenantHooks>;
@@ -0,0 +1,124 @@
1
+ import { errors } from 'oidc-provider';
2
+ import { serviceTenantOf } from './client_metadata.js';
3
+ import { findServiceTenantResource } from './config.js';
4
+ /**
5
+ * The provider hooks behind service tenants:
6
+ *
7
+ * - a service-tenant resource is granted only to a service-tenant client, only on the
8
+ * `client_credentials` grant, always as a JWT access token (RFC 9068);
9
+ * - a service-tenant client can only get tokens for a service-tenant resource (when
10
+ * it sends no `resource` and exactly one is declared, that one is used);
11
+ * - its tokens carry the `partition` (and `capabilities`) claims read from the client
12
+ * at issuance time, so an edit applies to the next token.
13
+ */
14
+ export function createServiceTenantHooks(deps) {
15
+ const { serviceTenants } = deps;
16
+ const memo = new WeakMap();
17
+ let adapter;
18
+ const read = async (clientId) => {
19
+ adapter ??= new deps.AdapterClass('Client');
20
+ return serviceTenantOf((await adapter.find(clientId)));
21
+ };
22
+ /** Service-tenant info of the client, memoized per request. */
23
+ const tenantOf = (ctx, clientId) => {
24
+ if (!clientId)
25
+ return Promise.resolve(null);
26
+ const key = ctx && typeof ctx === 'object' ? ctx : null;
27
+ if (!key)
28
+ return read(clientId);
29
+ let byClient = memo.get(key);
30
+ if (!byClient) {
31
+ byClient = new Map();
32
+ memo.set(key, byClient);
33
+ }
34
+ let pending = byClient.get(clientId);
35
+ if (!pending) {
36
+ pending = read(clientId);
37
+ byClient.set(clientId, pending);
38
+ }
39
+ return pending;
40
+ };
41
+ return {
42
+ enabled: serviceTenants.enabled,
43
+ /**
44
+ * `defaultResource` for a service-tenant client: the only declared resource, or
45
+ * `invalid_target` when several are declared. `undefined` for any other client.
46
+ */
47
+ async defaultResource(ctx, client) {
48
+ if (!serviceTenants.enabled)
49
+ return undefined;
50
+ if (!(await tenantOf(ctx, client?.clientId)))
51
+ return undefined;
52
+ const keys = Object.keys(serviceTenants.resources);
53
+ if (keys.length === 1)
54
+ return keys[0];
55
+ throw new errors.InvalidTarget('a service tenant must send the resource parameter');
56
+ },
57
+ /**
58
+ * `getResourceServerInfo` for service tenants. Returns the info for a
59
+ * service-tenant resource, `undefined` when neither the resource nor the client is
60
+ * a service-tenant one (the caller handles it), and throws `invalid_target` for a
61
+ * mismatch.
62
+ */
63
+ async resourceServerInfo(ctx, indicator, client, signAlg) {
64
+ if (!serviceTenants.enabled)
65
+ return undefined;
66
+ const found = findServiceTenantResource(serviceTenants, indicator);
67
+ const tenant = await tenantOf(ctx, client?.clientId);
68
+ if (found) {
69
+ const grantType = ctx?.oidc?.params?.grant_type;
70
+ if (!tenant || grantType !== 'client_credentials') {
71
+ throw new errors.InvalidTarget(`resource indicator not allowed: ${indicator}`);
72
+ }
73
+ return {
74
+ scope: found.resource.scopes.join(' '),
75
+ audience: found.resource.audience,
76
+ accessTokenFormat: 'jwt',
77
+ accessTokenTTL: found.resource.expiresIn,
78
+ jwt: { sign: { alg: signAlg } },
79
+ };
80
+ }
81
+ if (tenant) {
82
+ throw new errors.InvalidTarget(`resource indicator not allowed for a service tenant: ${indicator}`);
83
+ }
84
+ return undefined;
85
+ },
86
+ /** `extraTokenClaims` for a service-tenant token; audits the issuance. */
87
+ async extraClaims(ctx, token) {
88
+ if (!serviceTenants.enabled || token?.kind !== 'ClientCredentials')
89
+ return undefined;
90
+ const rs = token.resourceServer;
91
+ const indicator = typeof rs?.identifier === 'function' ? rs.identifier() : undefined;
92
+ if (typeof indicator !== 'string')
93
+ return undefined;
94
+ const found = findServiceTenantResource(serviceTenants, indicator);
95
+ if (!found)
96
+ return undefined;
97
+ const tenant = await tenantOf(ctx, token.clientId);
98
+ if (!tenant)
99
+ return undefined;
100
+ try {
101
+ await deps.audit
102
+ ?.record({
103
+ type: 'service_tenant.token_issued',
104
+ clientId: token.clientId,
105
+ ip: typeof ctx?.ip === 'string' ? ctx.ip : null,
106
+ metadata: {
107
+ partition: tenant.partition,
108
+ resource: found.key,
109
+ jti: token.jti,
110
+ expiresIn: found.resource.expiresIn,
111
+ },
112
+ })
113
+ .catch(() => { });
114
+ }
115
+ catch {
116
+ // Auditing is best-effort: it never fails an issuance.
117
+ }
118
+ return {
119
+ partition: tenant.partition,
120
+ ...(tenant.capabilities.length ? { capabilities: tenant.capabilities } : {}),
121
+ };
122
+ },
123
+ };
124
+ }
@@ -0,0 +1,73 @@
1
+ import type { ResolvedServiceTenantsConfig } from './config.js';
2
+ /**
3
+ * What a verified tenant token authenticates as. Same shape as `VerifiedTenant` in
4
+ * `@adonis-agora/durable` (declared here: authkit never imports durable).
5
+ */
6
+ export interface VerifiedTenant {
7
+ tenant: string;
8
+ capabilities?: string[] | undefined;
9
+ }
10
+ /** The input a control plane hands to its tenant verifier. */
11
+ export interface TenantVerifierInput {
12
+ /** The token the tenant presented. */
13
+ token?: string | undefined;
14
+ /** The tenant the request claims to be. Advisory only: the token decides. */
15
+ tenant?: string | undefined;
16
+ }
17
+ /** The issuer the verifier checks against: the local authkit server. */
18
+ export interface TenantTokenIssuer {
19
+ readonly publicJwks: {
20
+ keys: Record<string, any>[];
21
+ };
22
+ readonly config: {
23
+ issuer: string;
24
+ serviceTenants?: ResolvedServiceTenantsConfig;
25
+ };
26
+ }
27
+ export interface AuthkitTenantVerifierOptions {
28
+ /**
29
+ * Expected `aud`. Default: the audience of the only resource in
30
+ * `serviceTenants.resources` (required when several are declared).
31
+ */
32
+ audience?: string;
33
+ /** Claim holding the partition. Default: `'partition'`. */
34
+ partitionClaim?: string;
35
+ /** Claim holding the capabilities. Default: `'capabilities'`. */
36
+ capabilitiesClaim?: string;
37
+ /** Allowed clock skew in seconds for `exp`/`nbf`/`iat`. Default: 30. */
38
+ clockToleranceSec?: number;
39
+ /**
40
+ * Where the issuer comes from. Default: the `authkit.server` binding of the booted
41
+ * app. Read on every call, so a signing-key rotation applies right away.
42
+ */
43
+ issuer?: () => TenantTokenIssuer | Promise<TenantTokenIssuer>;
44
+ }
45
+ /**
46
+ * Verifies service-tenant tokens against the local authkit issuer and its JWKS (no
47
+ * network call), and returns the tenant they authenticate as, or `null`.
48
+ *
49
+ * Satisfies durable's `TenantVerifierContract` (`verify(input)`); usable as a class in
50
+ * `verifyTenant` (built by the container with the default options) or through
51
+ * {@link authkitTenantVerifier}.
52
+ */
53
+ export declare class AuthkitTenantVerifier {
54
+ #private;
55
+ constructor(options?: AuthkitTenantVerifierOptions);
56
+ verify(input: TenantVerifierInput): Promise<VerifiedTenant | null>;
57
+ }
58
+ /** A tenant verifier usable both as a function and as an object with `verify`. */
59
+ export type AuthkitTenantVerifierFn = ((input: TenantVerifierInput) => Promise<VerifiedTenant | null>) & {
60
+ verify(input: TenantVerifierInput): Promise<VerifiedTenant | null>;
61
+ };
62
+ /**
63
+ * Tenant verifier for a durable control plane hosted by the authkit app:
64
+ *
65
+ * ```ts
66
+ * // config/durable.ts
67
+ * verifyTenant: authkitTenantVerifier()
68
+ * ```
69
+ *
70
+ * The result is a function (durable's `TenantVerifier`) that also has `verify`
71
+ * (durable's `TenantVerifierContract`).
72
+ */
73
+ export declare function authkitTenantVerifier(options?: AuthkitTenantVerifierOptions): AuthkitTenantVerifierFn;
@@ -0,0 +1,104 @@
1
+ import { createLocalJWKSet, errors as joseErrors, jwtVerify } from 'jose';
2
+ import { getBootedApp } from '../../services/booted_app.js';
3
+ /** Asymmetric JWS algorithms only: never `none`, never `HS*`. */
4
+ const ALGORITHMS = [
5
+ 'RS256',
6
+ 'RS384',
7
+ 'RS512',
8
+ 'PS256',
9
+ 'PS384',
10
+ 'PS512',
11
+ 'ES256',
12
+ 'ES384',
13
+ 'ES512',
14
+ 'EdDSA',
15
+ 'Ed25519',
16
+ ];
17
+ const defaultIssuer = async () => (await getBootedApp().container.make('authkit.server'));
18
+ function audienceFor(issuer, explicit) {
19
+ if (explicit)
20
+ return explicit;
21
+ const resources = Object.values(issuer.config.serviceTenants?.resources ?? {});
22
+ if (resources.length === 1)
23
+ return resources[0].audience;
24
+ throw new Error(resources.length === 0
25
+ ? 'authkitTenantVerifier: no serviceTenants.resources declared in config/authkit.ts'
26
+ : 'authkitTenantVerifier: several serviceTenants.resources are declared; pass { audience }');
27
+ }
28
+ /**
29
+ * Verifies service-tenant tokens against the local authkit issuer and its JWKS (no
30
+ * network call), and returns the tenant they authenticate as, or `null`.
31
+ *
32
+ * Satisfies durable's `TenantVerifierContract` (`verify(input)`); usable as a class in
33
+ * `verifyTenant` (built by the container with the default options) or through
34
+ * {@link authkitTenantVerifier}.
35
+ */
36
+ export class AuthkitTenantVerifier {
37
+ #options;
38
+ #jwks;
39
+ #keySet;
40
+ constructor(options = {}) {
41
+ this.#options = options;
42
+ }
43
+ #keySetFor(jwks) {
44
+ // `publicJwks` is a new object whenever the keys change (rotation/reload).
45
+ if (jwks !== this.#jwks || !this.#keySet) {
46
+ this.#jwks = jwks;
47
+ this.#keySet = createLocalJWKSet(jwks);
48
+ }
49
+ return this.#keySet;
50
+ }
51
+ async verify(input) {
52
+ const token = input?.token;
53
+ if (typeof token !== 'string' || !token)
54
+ return null;
55
+ const issuer = await (this.#options.issuer ?? defaultIssuer)();
56
+ const audience = audienceFor(issuer, this.#options.audience);
57
+ let payload;
58
+ try {
59
+ ({ payload } = await jwtVerify(token, this.#keySetFor(issuer.publicJwks), {
60
+ issuer: issuer.config.issuer,
61
+ audience,
62
+ algorithms: ALGORITHMS,
63
+ typ: 'at+jwt',
64
+ clockTolerance: this.#options.clockToleranceSec ?? 30,
65
+ requiredClaims: ['exp', 'sub'],
66
+ }));
67
+ }
68
+ catch (error) {
69
+ if (error instanceof joseErrors.JOSEError)
70
+ return null;
71
+ throw error;
72
+ }
73
+ // A client_credentials token: no user behind it, `sub` is the client.
74
+ const clientId = payload.client_id;
75
+ if (typeof clientId !== 'string' || payload.sub !== clientId)
76
+ return null;
77
+ const partition = payload[this.#options.partitionClaim ?? 'partition'];
78
+ if (typeof partition !== 'string' || !partition)
79
+ return null;
80
+ const rawCapabilities = payload[this.#options.capabilitiesClaim ?? 'capabilities'];
81
+ if (rawCapabilities === undefined)
82
+ return { tenant: partition };
83
+ if (!Array.isArray(rawCapabilities) || rawCapabilities.some((c) => typeof c !== 'string')) {
84
+ return null;
85
+ }
86
+ return { tenant: partition, capabilities: rawCapabilities };
87
+ }
88
+ }
89
+ /**
90
+ * Tenant verifier for a durable control plane hosted by the authkit app:
91
+ *
92
+ * ```ts
93
+ * // config/durable.ts
94
+ * verifyTenant: authkitTenantVerifier()
95
+ * ```
96
+ *
97
+ * The result is a function (durable's `TenantVerifier`) that also has `verify`
98
+ * (durable's `TenantVerifierContract`).
99
+ */
100
+ export function authkitTenantVerifier(options = {}) {
101
+ const verifier = new AuthkitTenantVerifier(options);
102
+ const verify = (input) => verifier.verify(input);
103
+ return Object.assign(verify, { verify });
104
+ }
@@ -0,0 +1,30 @@
1
+ import type { AccountStore, OrgSummary } from '../accounts/account_store.js';
2
+ /** What `config/context.ts` → `tenancy.label` expects: tenant id → display label. */
3
+ export type TenantLabelResolver = (ids: readonly string[]) => Promise<Record<string, string>>;
4
+ export interface AuthkitTenantLabelsOptions {
5
+ /** Which field labels an organization: `'name'` (default), `'slug'`, or your own function. */
6
+ label?: 'name' | 'slug' | ((org: OrgSummary) => string);
7
+ /**
8
+ * Where the account store comes from. Default: the `authkit.server` binding of the
9
+ * booted app (`config/authkit.ts` → `accountStore`).
10
+ */
11
+ store?: () => AccountStore | Promise<AccountStore>;
12
+ }
13
+ /**
14
+ * A tenant label resolver backed by authkit organizations, for
15
+ * `config/context.ts` → `tenancy.label`. Consoles call it with the tenant ids they
16
+ * are about to show; it returns `{ [orgId]: name }` for the ids that are authkit
17
+ * organizations. Ids it does not know (personal tenants, other kinds) are left
18
+ * out, so the console shows the id. It only reads labels: no membership, role or
19
+ * metadata leaves it.
20
+ *
21
+ * ```ts
22
+ * // config/context.ts
23
+ * import { authkitTenantLabels } from '@adonis-agora/authkit-server'
24
+ *
25
+ * export default defineConfig({
26
+ * tenancy: { enabled: true, kinds: ['organization'], label: authkitTenantLabels() },
27
+ * })
28
+ * ```
29
+ */
30
+ export declare function authkitTenantLabels(options?: AuthkitTenantLabelsOptions): TenantLabelResolver;
@@ -0,0 +1,64 @@
1
+ import { getBootedApp } from '../../services/booted_app.js';
2
+ import { supportsOrganizations } from '../accounts/account_store.js';
3
+ /** Fallback walk without `findOrgsByIds`: one lookup per id, at most this many ids. */
4
+ const FALLBACK_MAX = 200;
5
+ const defaultStore = async () => {
6
+ const service = (await getBootedApp().container.make('authkit.server'));
7
+ return service.config.accountStore;
8
+ };
9
+ /**
10
+ * A tenant label resolver backed by authkit organizations, for
11
+ * `config/context.ts` → `tenancy.label`. Consoles call it with the tenant ids they
12
+ * are about to show; it returns `{ [orgId]: name }` for the ids that are authkit
13
+ * organizations. Ids it does not know (personal tenants, other kinds) are left
14
+ * out, so the console shows the id. It only reads labels: no membership, role or
15
+ * metadata leaves it.
16
+ *
17
+ * ```ts
18
+ * // config/context.ts
19
+ * import { authkitTenantLabels } from '@adonis-agora/authkit-server'
20
+ *
21
+ * export default defineConfig({
22
+ * tenancy: { enabled: true, kinds: ['organization'], label: authkitTenantLabels() },
23
+ * })
24
+ * ```
25
+ */
26
+ export function authkitTenantLabels(options = {}) {
27
+ const pick = typeof options.label === 'function'
28
+ ? options.label
29
+ : options.label === 'slug'
30
+ ? (org) => org.slug
31
+ : (org) => org.name;
32
+ return async (ids) => {
33
+ const wanted = [
34
+ ...new Set((Array.isArray(ids) ? ids : []).filter((id) => typeof id === 'string' && id)),
35
+ ];
36
+ if (wanted.length === 0)
37
+ return {};
38
+ const store = await (options.store ?? defaultStore)();
39
+ if (!store || !supportsOrganizations(store))
40
+ return {};
41
+ let orgs;
42
+ if (typeof store.findOrgsByIds === 'function') {
43
+ orgs = await store.findOrgsByIds(wanted);
44
+ }
45
+ else {
46
+ orgs = [];
47
+ for (const id of wanted.slice(0, FALLBACK_MAX)) {
48
+ const org = await store.findOrgById(id);
49
+ if (org)
50
+ orgs.push(org);
51
+ }
52
+ }
53
+ const asked = new Set(wanted);
54
+ const labels = {};
55
+ for (const org of orgs) {
56
+ if (!asked.has(org.id))
57
+ continue;
58
+ const label = pick(org);
59
+ if (typeof label === 'string' && label !== '')
60
+ labels[org.id] = label;
61
+ }
62
+ return labels;
63
+ };
64
+ }
package/build/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { MetricsRecorder } from '@adonis-agora/authkit-core';
2
+ import type { AccountExtensionRegistry } from './src/account/extension.js';
2
3
  import type { AccountStore } from './src/accounts/account_store.js';
3
4
  import type { PatStore } from './src/pat/pat_store.js';
4
5
  import type { OidcService } from './src/provider/oidc_service.js';
@@ -22,6 +23,7 @@ declare module '@adonisjs/core/types' {
22
23
  'authkit.metrics': MetricsRecorder;
23
24
  'authkit.accountStore': AccountStore;
24
25
  'authkit.patStore': PatStore;
26
+ 'authkit.accountExtensions': AccountExtensionRegistry;
25
27
  }
26
28
  }
27
- export type { AccountStore, MetricsRecorder, OidcService, PatStore };
29
+ export type { AccountExtensionRegistry, AccountStore, MetricsRecorder, OidcService, PatStore };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-server",
3
- "version": "1.0.0-rc.0",
3
+ "version": "1.0.0-rc.10",
4
4
  "description": "AdonisJS OIDC/OAuth2 provider (Identity Provider) toolkit: ejectable auth server with sessions, rate-limiting, MFA/TOTP, audit log, federated logout and OpenTelemetry metrics.",
5
5
  "license": "MIT",
6
6
  "author": "dudousxd",
@@ -41,12 +41,14 @@
41
41
  "exports": {
42
42
  ".": "./build/index.js",
43
43
  "./types": "./build/types.js",
44
+ "./account-extension": "./build/src/account/extension.js",
44
45
  "./services/main": "./build/services/main.js",
45
46
  "./durable": "./build/src/host/durable/index.js",
46
47
  "./telescope": "./build/src/observability/telescope/index.js",
47
48
  "./authkit_server_provider": "./build/providers/authkit_server_provider.js",
48
49
  "./commands": "./build/commands/main.js",
49
- "./commands/configure": "./build/commands/configure.js"
50
+ "./commands/configure": "./build/commands/configure.js",
51
+ "./doctor": "./build/src/doctor/index.js"
50
52
  },
51
53
  "adonisjs": {
52
54
  "types": "@adonis-agora/authkit-server",
@@ -106,19 +108,19 @@
106
108
  }
107
109
  },
108
110
  "dependencies": {
109
- "@adonis-agora/authkit-core": "1.0.0-rc.0",
111
+ "@adonis-agora/authkit-core": "1.0.0-rc.1",
110
112
  "@simplewebauthn/browser": "14.0.0",
111
113
  "@simplewebauthn/server": "14.1.0",
112
- "jose": "6.2.15",
113
- "koa": "3.2.1",
114
+ "jose": "6.2.16",
115
+ "koa": "3.3.0",
114
116
  "koa-mount": "4.2.0",
115
117
  "oidc-provider": "9.13.3",
116
- "otplib": "13.5.0",
118
+ "otplib": "13.6.0",
117
119
  "qrcode": "1.5.4"
118
120
  },
119
121
  "devDependencies": {
120
- "@adonis-agora/authkit-react": "1.0.0-rc.0",
121
- "@adonis-agora/durable": "0.59.0",
122
+ "@adonis-agora/authkit-react": "1.0.0-rc.4",
123
+ "@adonis-agora/durable": "0.60.0",
122
124
  "@adonis-agora/telescope": "0.27.1",
123
125
  "@adonisjs/ally": "6.3.2",
124
126
  "@adonisjs/auth": "10.1.0",
@@ -140,7 +142,7 @@
140
142
  "@types/koa": "3.0.3",
141
143
  "@types/koa-mount": "4.0.5",
142
144
  "@types/luxon": "3.7.6",
143
- "@types/node": "26.6.5",
145
+ "@types/node": "26.6.6",
144
146
  "@types/qrcode": "1.5.6",
145
147
  "@types/react": "19.3.0",
146
148
  "@types/react-dom": "19.3.0",
@@ -169,6 +171,7 @@
169
171
  "build": "node scripts/build_host_css.mjs && node scripts/build_webauthn.mjs && node scripts/build_ui.mjs && node -e \"const fs=require('node:fs');for(const d of ['build/stubs','build/host/views'])fs.rmSync(d,{recursive:true,force:true})\" && tsc && node -e \"require('node:fs').cpSync('src/host/assets','build/src/host/assets',{recursive:true})\" && node -e \"require('node:fs').cpSync('stubs','build/stubs',{recursive:true,filter:(s)=>!s.endsWith('.ts')})\" && node -e \"const fs=require('node:fs');if(fs.existsSync('assets'))fs.cpSync('assets','build/assets',{recursive:true})\" && node -e \"require('node:fs').cpSync('src/host/views','build/host/views',{recursive:true})\" && node -e \"const fs=require('node:fs');fs.mkdirSync('build/host/ui',{recursive:true});fs.readdirSync('src/host/ui').filter(f=>f.endsWith('.html')).forEach(f=>fs.copyFileSync('src/host/ui/'+f,'build/host/ui/'+f))\" && node -e \"require('node:fs').copyFileSync('commands/commands.json','build/commands/commands.json')\" && node -e \"const fs=require('node:fs');fs.mkdirSync('build/src/password',{recursive:true});fs.copyFileSync('src/password/common_passwords.txt','build/src/password/common_passwords.txt')\"",
170
172
  "build:ui": "node scripts/build_ui.mjs",
171
173
  "build:webauthn": "node scripts/build_webauthn.mjs",
174
+ "check:agora-doctor": "node ../../scripts/check-agora-doctor.mjs .",
172
175
  "check:host-css": "node scripts/check_host_css.mjs",
173
176
  "check:webauthn-bundle": "node scripts/check_webauthn_bundle.mjs",
174
177
  "typecheck": "tsc --noEmit && node scripts/typecheck_ui.mjs && node scripts/typecheck_tests.mjs",