@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
@@ -87,7 +87,7 @@ export declare function checkOtpLockout(input: DoctorInput): Finding | null;
87
87
  export declare function checkSudoMode(_input: DoctorInput): Finding | null;
88
88
  /** rateLimit ligado mas @adonisjs/limiter ausente → warn. */
89
89
  export declare function checkRateLimit(input: DoctorInput): Finding;
90
- /** admin.enabled mas sem roles → warn. Reporta o modo de UI ativo. */
90
+ /** admin.enabled mas sem roles, ou sem `admin.authorize` → warn. Reporta o modo de UI ativo. */
91
91
  export declare function checkAdmin(input: DoctorInput): Finding | null;
92
92
  /** requireVerifiedEmail ligado mas o store não sabe checar verificação → warn. */
93
93
  export declare function checkRequireVerifiedEmail(input: DoctorInput): Finding | null;
@@ -214,6 +214,12 @@ export declare function checkPasskeyAutofill(input: DoctorInput): Finding | null
214
214
  * de TERCEIROS precisa saber que existe uma lista para fechar.
215
215
  */
216
216
  export declare function checkFirstPartyClients(input: DoctorInput): Finding | null;
217
+ /**
218
+ * Como se entra no console de conta e no console admin. O default é o login do
219
+ * IdP (SSO, `accountSession.acceptIdpSession`); desligado, o console só tem o
220
+ * login próprio por senha.
221
+ */
222
+ export declare function checkConsoleSignIn(input: DoctorInput): Finding | null;
217
223
  /**
218
224
  * Finding da idade da chave de assinatura managed. `ageDays === null` (sem
219
225
  * keystore em arquivo/cofre) → no-op `ok`. Acima de `maxAgeDays` → `warn`.
@@ -311,7 +311,7 @@ export function checkRateLimit(input) {
311
311
  }
312
312
  return { level: 'ok', message: 'rate-limiting on and @adonisjs/limiter available.' };
313
313
  }
314
- /** admin.enabled mas sem roles → warn. Reporta o modo de UI ativo. */
314
+ /** admin.enabled mas sem roles, ou sem `admin.authorize` → warn. Reporta o modo de UI ativo. */
315
315
  export function checkAdmin(input) {
316
316
  const admin = input.authkitConfig?.admin;
317
317
  if (!admin || admin.enabled !== true)
@@ -323,6 +323,12 @@ export function checkAdmin(input) {
323
323
  message: 'admin console on, but no `admin.roles` — nobody will have access (the default ["ADMIN"] was not resolved here).',
324
324
  };
325
325
  }
326
+ if (!admin.authorize) {
327
+ return {
328
+ level: 'warn',
329
+ message: `admin console on for roles: ${roles.join(', ')}, but no \`admin.authorize\` — it is open outside production and denies every request in production. Set \`admin.authorize\` in config/authkit.ts.`,
330
+ };
331
+ }
326
332
  return {
327
333
  level: 'ok',
328
334
  message: `admin console on for roles: ${roles.join(', ')} (React SPA self-contained — JSON API under {prefix}/api/*).`,
@@ -908,6 +914,43 @@ export function checkFirstPartyClients(input) {
908
914
  message: `firstPartyClients: ${allowlist.length} client(s) receive the roles/organization claims (${allowlist.join(', ')}).`,
909
915
  };
910
916
  }
917
+ /**
918
+ * Como se entra no console de conta e no console admin. O default é o login do
919
+ * IdP (SSO, `accountSession.acceptIdpSession`); desligado, o console só tem o
920
+ * login próprio por senha.
921
+ */
922
+ export function checkConsoleSignIn(input) {
923
+ const cfg = input.authkitConfig;
924
+ if (!cfg)
925
+ return null;
926
+ const sso = cfg.accountSession?.acceptIdpSession !== false;
927
+ const passwordOff = cfg.authMethods?.password === false;
928
+ const routes = typeof cfg.routes === 'object' && cfg.routes ? cfg.routes : undefined;
929
+ if (!sso && passwordOff && !routes?.accountLoginUrl) {
930
+ return {
931
+ level: 'warn',
932
+ message: 'The account and admin consoles have no way to sign in unless `registerAuthHost` gets an ' +
933
+ '`accountLoginUrl`: `accountSession.acceptIdpSession` is false and password login is off ' +
934
+ '(`authMethods: { password: false }`), and route registration refuses to start without ' +
935
+ 'one. Remove `acceptIdpSession: false` so the consoles sign in through the IdP, or point ' +
936
+ '`accountLoginUrl` at a login route of your app.',
937
+ };
938
+ }
939
+ if (!sso) {
940
+ return {
941
+ level: 'warn',
942
+ message: 'The consoles use their own password login (`accountSession.acceptIdpSession: false`): ' +
943
+ 'users sign in twice, and passwordless accounts cannot open them. The default (SSO through ' +
944
+ 'the IdP login) is recommended; sensitive operations still ask for sudo.',
945
+ };
946
+ }
947
+ return {
948
+ level: 'ok',
949
+ message: 'The consoles sign in through the IdP login (SSO). Sensitive operations ask for sudo. ' +
950
+ 'Make sure admin accounts have MFA enrolled (or require it for everyone with ' +
951
+ '`mfa.requiredFactors`).',
952
+ };
953
+ }
911
954
  /**
912
955
  * Finding da idade da chave de assinatura managed. `ageDays === null` (sem
913
956
  * keystore em arquivo/cofre) → no-op `ok`. Acima de `maxAgeDays` → `warn`.
@@ -1007,6 +1050,9 @@ export function runAllChecks(input) {
1007
1050
  const firstPartyClients = checkFirstPartyClients(input);
1008
1051
  if (firstPartyClients)
1009
1052
  findings.push(firstPartyClients);
1053
+ const consoleSignIn = checkConsoleSignIn(input);
1054
+ if (consoleSignIn)
1055
+ findings.push(consoleSignIn);
1010
1056
  return findings;
1011
1057
  }
1012
1058
  /** Há algum finding de nível 'error'? (define o exit code). */
@@ -0,0 +1,27 @@
1
+ import type { ApplicationService } from '@adonisjs/core/types';
2
+ import { type DoctorInput, type Finding } from './checks.js';
3
+ /** Logger mínimo para reportar peers instalados-mas-quebrados. */
4
+ export interface CanImportLogger {
5
+ warning(msg: string): void;
6
+ }
7
+ /**
8
+ * Tenta importar um peer; `true` se importável.
9
+ *
10
+ * Distingue "não instalado" de "instalado mas quebrado": só um erro de
11
+ * módulo-não-encontrado (`ERR_MODULE_NOT_FOUND`/`MODULE_NOT_FOUND`) é tratado como
12
+ * peer ausente (silencioso — é o caso esperado). Qualquer outro erro significa que
13
+ * o peer ESTÁ presente mas falhou ao carregar (import circular, erro de sintaxe,
14
+ * config inválida) — isso o doctor precisa gritar, senão um peer quebrado aparece
15
+ * como "ausente" e o diagnóstico mente.
16
+ */
17
+ export declare function canImport(specifier: string, logger?: CanImportLogger): Promise<boolean>;
18
+ /**
19
+ * Monta o `DoctorInput` a partir do app (config `authkit` resolvida, `session`, peers). Usado por
20
+ * `authkit:doctor` e pelos checks do `agora:doctor`. Lança se `config/authkit.ts` não resolver.
21
+ */
22
+ export declare function buildDoctorInput(app: ApplicationService, logger?: CanImportLogger): Promise<DoctorInput>;
23
+ /**
24
+ * O finding de idade da chave de assinatura managed (best-effort): `null` quando o jwks não é
25
+ * `managed` com store, ou quando o keystore não pôde ser lido.
26
+ */
27
+ export declare function managedSigningKeyAgeFinding(app: ApplicationService, authkitConfig: Record<string, any> | null): Promise<Finding | null>;
@@ -0,0 +1,86 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { DatabaseAdapter } from '../adapters/database_adapter.js';
10
+ import { resolveAuthkitConfig } from '../commands/resolve_config.js';
11
+ import { defaultEncryptForStore } from '../define_config.js';
12
+ import { signingKeyAgeDays } from '../keys/keystore.js';
13
+ import { KeystoreCodec } from '../keys/keystore_codec.js';
14
+ import { loadEncryptionService } from '../keys/keystore_crypto.js';
15
+ import { KeystoreManager, resolveKeystoreVault } from '../keys/keystore_manager.js';
16
+ import { signingKeyAgeFinding } from './checks.js';
17
+ /**
18
+ * Tenta importar um peer; `true` se importável.
19
+ *
20
+ * Distingue "não instalado" de "instalado mas quebrado": só um erro de
21
+ * módulo-não-encontrado (`ERR_MODULE_NOT_FOUND`/`MODULE_NOT_FOUND`) é tratado como
22
+ * peer ausente (silencioso — é o caso esperado). Qualquer outro erro significa que
23
+ * o peer ESTÁ presente mas falhou ao carregar (import circular, erro de sintaxe,
24
+ * config inválida) — isso o doctor precisa gritar, senão um peer quebrado aparece
25
+ * como "ausente" e o diagnóstico mente.
26
+ */
27
+ export async function canImport(specifier, logger) {
28
+ try {
29
+ await import(__rewriteRelativeImportExtension(specifier));
30
+ return true;
31
+ }
32
+ catch (err) {
33
+ const code = err?.code;
34
+ if (code !== 'ERR_MODULE_NOT_FOUND' && code !== 'MODULE_NOT_FOUND') {
35
+ logger?.warning(`⚠️ ${specifier} está instalado mas falhou ao carregar: ${err.message}`);
36
+ }
37
+ return false;
38
+ }
39
+ }
40
+ /**
41
+ * Monta o `DoctorInput` a partir do app (config `authkit` resolvida, `session`, peers). Usado por
42
+ * `authkit:doctor` e pelos checks do `agora:doctor`. Lança se `config/authkit.ts` não resolver.
43
+ */
44
+ export async function buildDoctorInput(app, logger) {
45
+ const config = await app.container.make('config');
46
+ // `defineConfig` exporta um config provider — resolve antes de inspecionar,
47
+ // senão todo campo aparece como "ausente" (provider cru não tem issuer etc.).
48
+ const authkitConfig = await resolveAuthkitConfig(app, config.get('authkit', null));
49
+ const sessionConfig = config.get('session', null) ?? null;
50
+ return {
51
+ authkitConfig,
52
+ sessionConfig,
53
+ peers: {
54
+ session: await canImport('@adonisjs/session', logger),
55
+ shield: await canImport('@adonisjs/shield', logger),
56
+ ally: await canImport('@adonisjs/ally', logger),
57
+ limiter: await canImport('@adonisjs/limiter', logger),
58
+ },
59
+ __adapterClasses: { DatabaseAdapter },
60
+ };
61
+ }
62
+ /**
63
+ * O finding de idade da chave de assinatura managed (best-effort): `null` quando o jwks não é
64
+ * `managed` com store, ou quando o keystore não pôde ser lido.
65
+ */
66
+ export async function managedSigningKeyAgeFinding(app, authkitConfig) {
67
+ const jwksInput = (authkitConfig?.jwksConfig ?? authkitConfig?.jwks);
68
+ if (jwksInput?.source !== 'managed' || !jwksInput?.store)
69
+ return null;
70
+ try {
71
+ const vault = resolveKeystoreVault(jwksInput.store, {
72
+ makePath: (p) => app.makePath(p),
73
+ container: app.container,
74
+ });
75
+ const encrypt = jwksInput.encrypt ?? defaultEncryptForStore(jwksInput.store);
76
+ const enc = encrypt ? await loadEncryptionService().catch(() => undefined) : undefined;
77
+ const mgr = new KeystoreManager(vault, new KeystoreCodec({ encrypt, enc }), jwksInput.algorithm ?? 'RS256');
78
+ const store = await mgr.read().catch(() => null);
79
+ const maxAge = jwksInput.rotationDays ?? 90;
80
+ return signingKeyAgeFinding(signingKeyAgeDays(store), maxAge);
81
+ }
82
+ catch {
83
+ /* idade é best-effort no doctor */
84
+ return null;
85
+ }
86
+ }
@@ -0,0 +1,5 @@
1
+ import type { AgoraDoctorCheck } from './agora_doctor.js';
2
+ export type { AgoraDoctorCheck, AgoraDoctorRegistry, AgoraDoctorReport, AgoraDoctorResult, AgoraDoctorStatus, } from './agora_doctor.js';
3
+ export { AGORA_DOCTOR_BINDING, registerDoctorChecks } from './agora_doctor.js';
4
+ /** authkit's checks, for `node ace agora:doctor --no-boot` (which imports this entry instead of booting). */
5
+ export declare const agoraDoctorChecks: AgoraDoctorCheck[];
@@ -0,0 +1,4 @@
1
+ import { authkitDoctorChecks } from './agora_checks.js';
2
+ export { AGORA_DOCTOR_BINDING, registerDoctorChecks } from './agora_doctor.js';
3
+ /** authkit's checks, for `node ace agora:doctor --no-boot` (which imports this entry instead of booting). */
4
+ export const agoraDoctorChecks = authkitDoctorChecks();
@@ -41,6 +41,10 @@ function fromJwtPayload(payload) {
41
41
  if (typeof payload.sub !== 'string' || !payload.sub)
42
42
  return null;
43
43
  const clientId = payload.client_id;
44
+ // No user behind it: a `client_credentials` token (a service tenant's, for one) has
45
+ // `sub` = the client id (RFC 9068 §2.2) and never authenticates an account.
46
+ if (payload.sub === clientId)
47
+ return null;
44
48
  return {
45
49
  format: 'jwt',
46
50
  sub: payload.sub,
@@ -209,6 +213,9 @@ export function remoteAccessTokenVerifier(options) {
209
213
  return null;
210
214
  if (typeof body.sub !== 'string' || !body.sub)
211
215
  return null;
216
+ // `client_credentials` token: no account behind it.
217
+ if (body.sub === body.client_id)
218
+ return null;
212
219
  if (typeof body.exp === 'number' && body.exp <= Math.floor(Date.now() / 1000))
213
220
  return null;
214
221
  return {
@@ -611,7 +611,7 @@ export default class AccountApiController {
611
611
  /** Personal agents com delegação ativa na conta (scopes aprovados). */
612
612
  async listAgents(ctx) {
613
613
  const runtime = await personalAgentsFor(ctx);
614
- if (!runtime?.delegation && !runtime?.poppy) {
614
+ if (!runtime || (!runtime.delegation && runtime.extensions.size === 0)) {
615
615
  return ctx.response
616
616
  .status(404)
617
617
  .send(apiErr('capability_unsupported', 'Personal agent delegation is not enabled.'));
@@ -620,16 +620,18 @@ export default class AccountApiController {
620
620
  const pact = runtime.delegation
621
621
  ? (await runtime.delegation.listGrants(userId)).map((g) => ({ ...g, protocol: 'pact' }))
622
622
  : [];
623
- const poppy = runtime.poppy
624
- ? (await runtime.poppy.listGrants(userId)).map((g) => ({ ...g, protocol: 'poppy' }))
625
- : [];
626
- return { agents: [...pact, ...poppy] };
623
+ const others = [];
624
+ for (const [protocol, { runtime: extension }] of runtime.extensions) {
625
+ for (const g of await extension.listAccountGrants(userId))
626
+ others.push({ ...g, protocol });
627
+ }
628
+ return { agents: [...pact, ...others] };
627
629
  }
628
630
  // ─── DELETE /account/api/agents/:id ──────────────────────────────────────
629
631
  /** Revoga a delegação de um personal agent (efeito imediato nos tokens). */
630
632
  async revokeAgent(ctx) {
631
633
  const runtime = await personalAgentsFor(ctx);
632
- if (!runtime?.delegation && !runtime?.poppy) {
634
+ if (!runtime || (!runtime.delegation && runtime.extensions.size === 0)) {
633
635
  return ctx.response
634
636
  .status(404)
635
637
  .send(apiErr('capability_unsupported', 'Personal agent delegation is not enabled.'));
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Abrir e fechar a sessão de CONTA do authkit (o cookie do console, `ACCOUNT_SESSION_KEY`) a partir
3
+ * de código de fora do host-kit — o navegador de um personal agent que entrou na conta, por
4
+ * exemplo. Sincroniza `ctx.auth` (`adonisAuth.guard`) como o login do próprio console.
5
+ *
6
+ * Estável no 1.x (usado pelas `personalAgents.extensions`).
7
+ */
8
+ import type { HttpContext } from '@adonisjs/core/http';
9
+ /** Liga a sessão de conta a `accountId` e sincroniza `ctx.auth`. Não regenera a sessão. */
10
+ export declare function signInAccountSession(ctx: HttpContext, accountId: string): Promise<void>;
11
+ /** Desliga a sessão de conta e sincroniza `ctx.auth`. Não regenera a sessão. */
12
+ export declare function signOutAccountSession(ctx: HttpContext): Promise<void>;
@@ -0,0 +1,19 @@
1
+ import { ACCOUNT_SESSION_KEY } from './account_session_key.js';
2
+ import { syncAdonisAuthLogin, syncAdonisAuthLogout } from './adonis_auth_sync.js';
3
+ /** Liga a sessão de conta a `accountId` e sincroniza `ctx.auth`. Não regenera a sessão. */
4
+ export async function signInAccountSession(ctx, accountId) {
5
+ // Sem augmentation no barrel (ver `augmentation_isolation.spec.ts`): a sessão é lida sem tipo.
6
+ const session = ctx.session;
7
+ const service = await ctx.containerResolver.make('authkit.server');
8
+ session.put(ACCOUNT_SESSION_KEY, accountId);
9
+ const account = await service.config.accountStore.findById(accountId);
10
+ if (account)
11
+ await syncAdonisAuthLogin(ctx, service.config, account);
12
+ }
13
+ /** Desliga a sessão de conta e sincroniza `ctx.auth`. Não regenera a sessão. */
14
+ export async function signOutAccountSession(ctx) {
15
+ const session = ctx.session;
16
+ const service = await ctx.containerResolver.make('authkit.server');
17
+ session.forget(ACCOUNT_SESSION_KEY);
18
+ await syncAdonisAuthLogout(ctx, service.config);
19
+ }
@@ -8,8 +8,11 @@ import type { SettingsCapability } from '../runtime_settings.js';
8
8
  export interface AdminActor {
9
9
  actorId: string | null;
10
10
  ip: string | null;
11
- /** Marca metadata da auditoria — 'admin-api' nas escritas via REST, 'admin' no console HTML. */
12
- source?: 'admin-api' | 'admin';
11
+ /**
12
+ * Marca metadata da auditoria — 'admin-api' nas escritas via REST, 'admin' no console HTML,
13
+ * 'mcp' nas ações dos dev tools (endpoint MCP do Agora).
14
+ */
15
+ source?: 'admin-api' | 'admin' | 'mcp';
13
16
  }
14
17
  /** Resultado da deleção via admin: false quando o store não suporta hard delete. */
15
18
  export type DeleteUserResult = {
@@ -6,6 +6,7 @@ import type { HttpContext } from '@adonisjs/core/http';
6
6
  * em create/regenerate. Audita create/update/delete.
7
7
  */
8
8
  export default class ApiClientsController {
9
+ #private;
9
10
  index(ctx: HttpContext): Promise<{
10
11
  data: {
11
12
  clientId: string;
@@ -19,6 +20,8 @@ export default class ApiClientsController {
19
20
  backchannelLogoutSessionRequired: boolean;
20
21
  clientName: string | null;
21
22
  logoUri: string | null;
23
+ disabled: boolean;
24
+ serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
22
25
  }[];
23
26
  canList: boolean;
24
27
  }>;
@@ -34,6 +37,8 @@ export default class ApiClientsController {
34
37
  backchannelLogoutSessionRequired: boolean;
35
38
  clientName: string | null;
36
39
  logoUri: string | null;
40
+ disabled: boolean;
41
+ serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
37
42
  }>;
38
43
  store(ctx: HttpContext): Promise<void | {
39
44
  clientId: string;
@@ -51,11 +56,43 @@ export default class ApiClientsController {
51
56
  backchannelLogoutSessionRequired: boolean;
52
57
  clientName: string | null;
53
58
  logoUri: string | null;
59
+ disabled: boolean;
60
+ serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
54
61
  }>;
55
62
  regenerateSecret(ctx: HttpContext): Promise<void | {
56
63
  clientId: any;
57
64
  clientSecret: string;
58
65
  }>;
66
+ disable(ctx: HttpContext): Promise<void | {
67
+ clientId: string;
68
+ confidential: boolean;
69
+ grantTypes: string[];
70
+ redirectUris: string[];
71
+ postLogoutRedirectUris: string[];
72
+ tokenEndpointAuthMethod: string;
73
+ applicationType: import("../client_metadata.js").ApplicationType;
74
+ backchannelLogoutUri: string | null;
75
+ backchannelLogoutSessionRequired: boolean;
76
+ clientName: string | null;
77
+ logoUri: string | null;
78
+ disabled: boolean;
79
+ serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
80
+ }>;
81
+ enable(ctx: HttpContext): Promise<void | {
82
+ clientId: string;
83
+ confidential: boolean;
84
+ grantTypes: string[];
85
+ redirectUris: string[];
86
+ postLogoutRedirectUris: string[];
87
+ tokenEndpointAuthMethod: string;
88
+ applicationType: import("../client_metadata.js").ApplicationType;
89
+ backchannelLogoutUri: string | null;
90
+ backchannelLogoutSessionRequired: boolean;
91
+ clientName: string | null;
92
+ logoUri: string | null;
93
+ disabled: boolean;
94
+ serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
95
+ }>;
59
96
  destroy(ctx: HttpContext): Promise<{
60
97
  clientId: any;
61
98
  deleted: boolean;
@@ -85,11 +85,16 @@ export default class ApiClientsController {
85
85
  const id = ctx.request.param('id');
86
86
  try {
87
87
  const secret = await svc.regenerateSecret(id);
88
+ const tenant = (await svc.find(id))?.serviceTenant;
88
89
  await service.config.audit?.record({
89
90
  type: 'client.updated',
90
91
  clientId: id,
91
92
  ip: ctx.request.ip?.() ?? null,
92
- metadata: { actor: 'admin-api', action: 'regenerate_secret' },
93
+ metadata: {
94
+ actor: 'admin-api',
95
+ action: 'regenerate_secret',
96
+ ...(tenant ? { partition: tenant.partition } : {}),
97
+ },
93
98
  });
94
99
  return { clientId: id, clientSecret: secret };
95
100
  }
@@ -97,6 +102,31 @@ export default class ApiClientsController {
97
102
  return ctx.response.conflict(apiError('cannot_regenerate', e.message));
98
103
  }
99
104
  }
105
+ async disable(ctx) {
106
+ return this.#setDisabled(ctx, true);
107
+ }
108
+ async enable(ctx) {
109
+ return this.#setDisabled(ctx, false);
110
+ }
111
+ /** Disabled clients get `invalid_client` from the next request on. */
112
+ async #setDisabled(ctx, disabled) {
113
+ const { service, svc } = await clientsService(ctx);
114
+ const id = ctx.request.param('id');
115
+ if (!(await svc.setDisabled(id, disabled))) {
116
+ return ctx.response.notFound(apiError('not_found', 'Client não encontrado.'));
117
+ }
118
+ const client = await svc.find(id);
119
+ await service.config.audit?.record({
120
+ type: disabled ? 'client.disabled' : 'client.enabled',
121
+ clientId: id,
122
+ ip: ctx.request.ip?.() ?? null,
123
+ metadata: {
124
+ actor: 'admin-api',
125
+ ...(client?.serviceTenant ? { partition: client.serviceTenant.partition } : {}),
126
+ },
127
+ });
128
+ return clientDto(client);
129
+ }
100
130
  async destroy(ctx) {
101
131
  const { service, svc } = await clientsService(ctx);
102
132
  const id = ctx.request.param('id');
@@ -40,6 +40,8 @@ export declare function clientDto(client: AdminClient): {
40
40
  backchannelLogoutSessionRequired: boolean;
41
41
  clientName: string | null;
42
42
  logoUri: string | null;
43
+ disabled: boolean;
44
+ serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
43
45
  };
44
46
  /** Inclui o secret (mostrado UMA vez) em create/regenerate. */
45
47
  export declare function createdClientDto(created: CreatedClient): {
@@ -67,7 +69,7 @@ export declare function grantDto(grant: AdminGrant): {
67
69
  };
68
70
  export declare function auditDto(event: StoredAuditEvent): {
69
71
  id: string;
70
- type: "account.deleted" | "account.expiration_warned" | "account.expired_login_blocked" | "account.exported" | "account.locked" | "account.signed_out_all" | "agent.allowed" | "agent.blocked" | "agent.delegation_approved" | "agent.delegation_denied" | "agent.grant_revoked" | "agent.grant_revoked_by_admin" | "agent.registry_removed" | "bot_protection.rejected" | "client.created" | "client.deleted" | "client.secret_regenerated" | "client.updated" | "email.change_requested" | "email.changed" | "email_change.cancelled" | "email_change.confirmed" | "email_change.requested" | "email_verification.consumed" | "email_verification.issued" | "grant.revoked_by_user" | "impersonation" | "impersonation.panel_viewed" | "impersonation.stopped" | "keys.rotated" | "login.failure" | "login.magic_link_sent" | "login.new_device" | "login.new_ip_notified" | "login.otp_failed" | "login.otp_invalidated" | "login.otp_sent" | "login.otp_verified" | "login.success" | "maintenance.disabled" | "maintenance.enabled" | "mfa.disabled" | "mfa.enabled" | "mfa.recovery_codes_regenerated" | "organization.created" | "organization.deactivated" | "organization.deleted" | "organization.invitation_accepted" | "organization.invitation_revoked" | "organization.invitation_sent" | "organization.member_added" | "organization.member_removed" | "organization.member_role_changed" | "organization.member_role_updated" | "organization.switched" | "organization.updated" | "otp.locked" | "otp.unlock_failed" | "otp.unlocked" | "passkey.registered" | "passkey.removed" | "password.changed" | "password.expired_change_forced" | "password.rehashed" | "password_reset.consumed" | "password_reset.issued" | "pat.issued" | "pat.revoked" | "pat.used" | "profile.updated" | "roles_catalog.updated" | "security_notice.sent" | "session.revoked" | "session.revoked_all" | "session.single_enforced" | "settings.updated" | "signup" | "sudo.confirmed" | "trusted_device.revoked" | "user.created" | "user.deleted" | "user.disabled" | "user.enabled" | "user.password_reset_sent";
72
+ type: "account.deleted" | "account.expiration_warned" | "account.expired_login_blocked" | "account.exported" | "account.extension.action" | "account.extension.redirect" | "account.locked" | "account.signed_out_all" | "agent.allowed" | "agent.blocked" | "agent.delegation_approved" | "agent.delegation_denied" | "agent.grant_revoked" | "agent.grant_revoked_by_admin" | "agent.registry_removed" | "bot_protection.rejected" | "client.created" | "client.deleted" | "client.disabled" | "client.enabled" | "client.secret_regenerated" | "client.updated" | "email.change_requested" | "email.changed" | "email_change.cancelled" | "email_change.confirmed" | "email_change.requested" | "email_verification.consumed" | "email_verification.issued" | "grant.revoked_by_user" | "impersonation" | "impersonation.panel_viewed" | "impersonation.stopped" | "keys.rotated" | "login.failure" | "login.magic_link_sent" | "login.new_device" | "login.new_ip_notified" | "login.otp_failed" | "login.otp_invalidated" | "login.otp_sent" | "login.otp_verified" | "login.success" | "maintenance.disabled" | "maintenance.enabled" | "mfa.disabled" | "mfa.enabled" | "mfa.recovery_codes_regenerated" | "organization.created" | "organization.deactivated" | "organization.deleted" | "organization.invitation_accepted" | "organization.invitation_revoked" | "organization.invitation_sent" | "organization.member_added" | "organization.member_removed" | "organization.member_role_changed" | "organization.member_role_updated" | "organization.switched" | "organization.updated" | "otp.locked" | "otp.unlock_failed" | "otp.unlocked" | "passkey.registered" | "passkey.removed" | "password.changed" | "password.expired_change_forced" | "password.rehashed" | "password_reset.consumed" | "password_reset.issued" | "pat.issued" | "pat.revoked" | "pat.used" | "profile.updated" | "roles_catalog.updated" | "security_notice.sent" | "service_tenant.token_issued" | "session.revoked" | "session.revoked_all" | "session.single_enforced" | "settings.updated" | "signup" | "sudo.confirmed" | "trusted_device.revoked" | "user.created" | "user.deleted" | "user.disabled" | "user.enabled" | "user.password_reset_sent";
71
73
  accountId: string | null;
72
74
  email: string | null;
73
75
  clientId: string | null;
@@ -32,6 +32,8 @@ export function clientDto(client) {
32
32
  backchannelLogoutSessionRequired: client.backchannelLogoutSessionRequired ?? false,
33
33
  clientName: client.clientName,
34
34
  logoUri: client.logoUri,
35
+ disabled: client.disabled,
36
+ serviceTenant: client.serviceTenant,
35
37
  };
36
38
  }
37
39
  /** Inclui o secret (mostrado UMA vez) em create/regenerate. */
@@ -0,0 +1,8 @@
1
+ import { type ConsoleAuthorize, type ConsoleAuthorizeDecision } from '../console_authorize.js';
2
+ /**
3
+ * Whether the app runs in production. Unknown (the provider did not register the
4
+ * app) counts as production, so an omitted `admin.authorize` fails closed.
5
+ */
6
+ export declare function appInProduction(): boolean;
7
+ /** The resolved `admin.authorize` checker (memoized per configured value). Never throws. */
8
+ export declare function adminAuthorizeChecker(authorize: ConsoleAuthorize): (ctx: any) => Promise<ConsoleAuthorizeDecision>;
@@ -0,0 +1,25 @@
1
+ import { getBootedApp } from '../../services/booted_app.js';
2
+ import { resolveConsoleAuthorize, } from '../console_authorize.js';
3
+ /**
4
+ * Whether the app runs in production. Unknown (the provider did not register the
5
+ * app) counts as production, so an omitted `admin.authorize` fails closed.
6
+ */
7
+ export function appInProduction() {
8
+ try {
9
+ return getBootedApp().inProduction !== false;
10
+ }
11
+ catch {
12
+ return true;
13
+ }
14
+ }
15
+ /** One checker per configured `admin.authorize`, so a lazy import runs once. */
16
+ const adminAuthorizeCheckers = new WeakMap();
17
+ /** The resolved `admin.authorize` checker (memoized per configured value). Never throws. */
18
+ export function adminAuthorizeChecker(authorize) {
19
+ let checker = adminAuthorizeCheckers.get(authorize);
20
+ if (!checker) {
21
+ checker = resolveConsoleAuthorize(authorize);
22
+ adminAuthorizeCheckers.set(authorize, checker);
23
+ }
24
+ return checker;
25
+ }
@@ -1,4 +1,5 @@
1
1
  import type { OidcService } from '../provider/oidc_service.js';
2
+ import { type ServiceTenantInfo } from '../service_tenants/client_metadata.js';
2
3
  import { type ApplicationType } from './client_metadata.js';
3
4
  /** Métodos de autenticação no token endpoint suportados pelo formulário admin. */
4
5
  export type TokenEndpointAuthMethod = 'client_secret_basic' | 'client_secret_post' | 'none';
@@ -30,6 +31,15 @@ export interface ClientInput {
30
31
  * `backchannelLogoutUri` não está definido.
31
32
  */
32
33
  backchannelLogoutSessionRequired?: boolean;
34
+ /**
35
+ * Makes the client a service tenant: confidential, `client_credentials` only, no
36
+ * redirect URIs; its tokens carry `partition` and `capabilities`. On update, `null`
37
+ * turns it back into a regular client; absent keeps the current value.
38
+ */
39
+ serviceTenant?: {
40
+ partition: string;
41
+ capabilities?: string[];
42
+ } | null;
33
43
  }
34
44
  /** Client persistido, apresentado ao console admin. */
35
45
  export interface AdminClient {
@@ -49,6 +59,10 @@ export interface AdminClient {
49
59
  clientName: string | null;
50
60
  /** `logo_uri` registrado, ou `null`. */
51
61
  logoUri: string | null;
62
+ /** Disabled clients get no tokens (`invalid_client`) until enabled again. */
63
+ disabled: boolean;
64
+ /** Service-tenant settings, or `null` for a regular client. */
65
+ serviceTenant: ServiceTenantInfo | null;
52
66
  }
53
67
  /** Resultado de uma criação: o client + o secret em claro (mostrado UMA vez). */
54
68
  export interface CreatedClient {
@@ -94,6 +108,12 @@ export declare class AdminClientsService {
94
108
  * PRESERVANDO o client_secret existente. Lança se o client não existe.
95
109
  */
96
110
  update(clientId: string, input: Partial<ClientInput>): Promise<void>;
111
+ /**
112
+ * Disables or re-enables a client. A disabled client gets `invalid_client` on every
113
+ * endpoint from the next request on (tokens already issued stay valid until they
114
+ * expire). Returns `false` when the client does not exist.
115
+ */
116
+ setDisabled(clientId: string, disabled: boolean): Promise<boolean>;
97
117
  /**
98
118
  * Regenera o client_secret de um client confidencial, preservando o resto da
99
119
  * metadata. Retorna o novo secret em claro (mostrado UMA vez). Lança se o