@adonis-agora/authkit-server 1.0.0-rc.1 → 1.0.0-rc.11

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 (181) hide show
  1. package/CHANGELOG.md +107 -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 +12 -12
  22. package/build/index.js +7 -9
  23. package/build/providers/authkit_server_provider.js +29 -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 +13 -7
  37. package/build/src/accounts/lucid_account_store.d.ts +3 -3
  38. package/build/src/accounts/lucid_account_store.js +42 -17
  39. package/build/src/accounts/lucid_store/mfa.js +3 -3
  40. package/build/src/accounts/lucid_store/organization_models.d.ts +14 -3
  41. package/build/src/accounts/lucid_store/organization_models.js +31 -3
  42. package/build/src/accounts/lucid_store/organizations.d.ts +2 -2
  43. package/build/src/accounts/lucid_store/organizations.js +40 -31
  44. package/build/src/accounts/lucid_store/shared.d.ts +11 -3
  45. package/build/src/accounts/lucid_store/shared.js +7 -5
  46. package/build/src/accounts/lucid_store/webauthn.js +1 -1
  47. package/build/src/adapters/factory.d.ts +13 -2
  48. package/build/src/adapters/factory.js +2 -2
  49. package/build/src/agents/config.d.ts +9 -13
  50. package/build/src/agents/config.js +17 -11
  51. package/build/src/agents/delegation_service.js +3 -3
  52. package/build/src/agents/delegation_store.d.ts +3 -2
  53. package/build/src/agents/delegation_store.js +6 -5
  54. package/build/src/agents/extension.d.ts +261 -0
  55. package/build/src/agents/extension.js +40 -0
  56. package/build/src/agents/middleware.js +1 -1
  57. package/build/src/agents/protocol.d.ts +4 -4
  58. package/build/src/agents/registry_store.d.ts +3 -2
  59. package/build/src/agents/registry_store.js +1 -1
  60. package/build/src/agents/runtime.d.ts +26 -7
  61. package/build/src/agents/runtime.js +110 -75
  62. package/build/src/audit/audit_sink.d.ts +1 -1
  63. package/build/src/audit/audit_sink.js +10 -1
  64. package/build/src/commands/expire_scan_command.js +1 -1
  65. package/build/src/commands/settings_commands.js +2 -2
  66. package/build/src/console_switcher.d.ts +132 -0
  67. package/build/src/console_switcher.js +206 -0
  68. package/build/src/define_config.d.ts +38 -13
  69. package/build/src/define_config.js +32 -10
  70. package/build/src/dev_tools/contract.d.ts +72 -0
  71. package/build/src/dev_tools/contract.js +10 -0
  72. package/build/src/dev_tools/gate.d.ts +17 -0
  73. package/build/src/dev_tools/gate.js +69 -0
  74. package/build/src/dev_tools/register.d.ts +11 -0
  75. package/build/src/dev_tools/register.js +25 -0
  76. package/build/src/dev_tools/tools.d.ts +24 -0
  77. package/build/src/dev_tools/tools.js +384 -0
  78. package/build/src/doctor/agora_checks.d.ts +15 -0
  79. package/build/src/doctor/agora_checks.js +150 -0
  80. package/build/src/doctor/agora_doctor.d.ts +115 -0
  81. package/build/src/doctor/agora_doctor.js +692 -0
  82. package/build/src/doctor/checks.js +2 -2
  83. package/build/src/doctor/doctor_input.d.ts +27 -0
  84. package/build/src/doctor/doctor_input.js +86 -0
  85. package/build/src/doctor/index.d.ts +5 -0
  86. package/build/src/doctor/index.js +4 -0
  87. package/build/src/host/access_token_verifier.js +7 -0
  88. package/build/src/host/account_api/account_api_controller.js +8 -6
  89. package/build/src/host/account_session_sign_in.d.ts +12 -0
  90. package/build/src/host/account_session_sign_in.js +19 -0
  91. package/build/src/host/admin_api/admin_users_service.d.ts +5 -2
  92. package/build/src/host/admin_api/api_clients_controller.d.ts +37 -0
  93. package/build/src/host/admin_api/api_clients_controller.js +31 -1
  94. package/build/src/host/admin_api/dto.d.ts +3 -1
  95. package/build/src/host/admin_api/dto.js +2 -0
  96. package/build/src/host/admin_authorize.d.ts +8 -0
  97. package/build/src/host/admin_authorize.js +25 -0
  98. package/build/src/host/admin_clients_service.d.ts +20 -0
  99. package/build/src/host/admin_clients_service.js +55 -0
  100. package/build/src/host/admin_console/admin_console_entry.d.ts +14 -0
  101. package/build/src/host/admin_console/admin_console_entry.js +80 -0
  102. package/build/src/host/admin_console/admin_shell_controller.js +8 -0
  103. package/build/src/host/admin_console/console_clients_controller.d.ts +41 -0
  104. package/build/src/host/admin_console/console_clients_controller.js +34 -1
  105. package/build/src/host/admin_console/console_personal_agents_controller.d.ts +1 -6
  106. package/build/src/host/admin_console/console_personal_agents_controller.js +50 -30
  107. package/build/src/host/admin_console/console_switcher_controller.d.ts +11 -0
  108. package/build/src/host/admin_console/console_switcher_controller.js +23 -0
  109. package/build/src/host/admin_sessions_service.js +8 -5
  110. package/build/src/host/admin_validators.d.ts +29 -0
  111. package/build/src/host/admin_validators.js +30 -0
  112. package/build/src/host/auth_host_config.d.ts +9 -2
  113. package/build/src/host/controllers/account_apps_controller.js +15 -11
  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 +14 -0
  123. package/build/src/host/register_auth_host.js +167 -106
  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/runtime_settings.d.ts +6 -6
  127. package/build/src/host/runtime_settings.js +4 -3
  128. package/build/src/host/screen_props.d.ts +39 -8
  129. package/build/src/host/ui-dist/assets/index-Dq3dJVBv.js +67 -0
  130. package/build/src/host/ui-dist/assets/{index-DTSmD4RU.css → index-WNIyCEPA.css} +1 -1
  131. package/build/src/host/ui-dist/index.html +2 -2
  132. package/build/src/provider/build_provider.js +37 -6
  133. package/build/src/provider/oidc_service.d.ts +1 -1
  134. package/build/src/provider/oidc_service.js +1 -1
  135. package/build/src/schema/ensure.d.ts +24 -1
  136. package/build/src/schema/ensure.js +44 -154
  137. package/build/src/schema/tables_location.d.ts +27 -0
  138. package/build/src/schema/tables_location.js +22 -0
  139. package/build/src/service_tenants/client_metadata.d.ts +39 -0
  140. package/build/src/service_tenants/client_metadata.js +80 -0
  141. package/build/src/service_tenants/config.d.ts +44 -0
  142. package/build/src/service_tenants/config.js +44 -0
  143. package/build/src/service_tenants/provider_hooks.d.ts +52 -0
  144. package/build/src/service_tenants/provider_hooks.js +124 -0
  145. package/build/src/service_tenants/tenant_verifier.d.ts +73 -0
  146. package/build/src/service_tenants/tenant_verifier.js +104 -0
  147. package/build/src/tenancy/tenant_labels.d.ts +30 -0
  148. package/build/src/tenancy/tenant_labels.js +64 -0
  149. package/build/types.d.ts +3 -1
  150. package/package.json +9 -6
  151. package/build/src/agents/poppy/client_registry.d.ts +0 -50
  152. package/build/src/agents/poppy/client_registry.js +0 -260
  153. package/build/src/agents/poppy/config.d.ts +0 -396
  154. package/build/src/agents/poppy/config.js +0 -228
  155. package/build/src/agents/poppy/dpop.d.ts +0 -61
  156. package/build/src/agents/poppy/dpop.js +0 -158
  157. package/build/src/agents/poppy/email_code_sign_in.d.ts +0 -27
  158. package/build/src/agents/poppy/email_code_sign_in.js +0 -140
  159. package/build/src/agents/poppy/errors.d.ts +0 -25
  160. package/build/src/agents/poppy/errors.js +0 -25
  161. package/build/src/agents/poppy/replay.d.ts +0 -25
  162. package/build/src/agents/poppy/replay.js +0 -106
  163. package/build/src/agents/poppy/resource.d.ts +0 -100
  164. package/build/src/agents/poppy/resource.js +0 -129
  165. package/build/src/agents/poppy/safe_fetch.d.ts +0 -48
  166. package/build/src/agents/poppy/safe_fetch.js +0 -262
  167. package/build/src/agents/poppy/service.d.ts +0 -293
  168. package/build/src/agents/poppy/service.js +0 -1458
  169. package/build/src/agents/poppy/session_jwt.d.ts +0 -30
  170. package/build/src/agents/poppy/session_jwt.js +0 -133
  171. package/build/src/agents/poppy/store.d.ts +0 -106
  172. package/build/src/agents/poppy/store.js +0 -261
  173. package/build/src/agents/poppy/web_session.d.ts +0 -39
  174. package/build/src/agents/poppy/web_session.js +0 -122
  175. package/build/src/host/controllers/poppy_consent_controller.d.ts +0 -19
  176. package/build/src/host/controllers/poppy_consent_controller.js +0 -199
  177. package/build/src/host/controllers/poppy_controller.d.ts +0 -33
  178. package/build/src/host/controllers/poppy_controller.js +0 -219
  179. package/build/src/host/poppy_session_key.d.ts +0 -9
  180. package/build/src/host/poppy_session_key.js +0 -9
  181. package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +0 -67
@@ -1,5 +1,6 @@
1
1
  import { getBootedApp } from '../../services/booted_app.js';
2
- import { resolveConsoleAuthorize, } from '../console_authorize.js';
2
+ import { mountAccountExtensions } from '../account/mount.js';
3
+ import { registerAgoraConsole } from '../console_switcher.js';
3
4
  import { resolveRateLimit } from '../define_config.js';
4
5
  import { accountHome } from './account_home.js';
5
6
  import { getAccountLoginUrl, setAccountLoginUrl } from './account_login_url.js';
@@ -7,12 +8,13 @@ import { accountPath, accountPathsMap, accountPrefix, joinAccountPath, setAccoun
7
8
  import { resolveAccountRoles } from './account_roles.js';
8
9
  import { ACCOUNT_SESSION_KEY } from './account_session_key.js';
9
10
  import { adminApiGuard } from './admin_api/admin_api_guard.js';
11
+ import { adminAuthorizeChecker, appInProduction } from './admin_authorize.js';
12
+ import { adminConsoleEntry } from './admin_console/admin_console_entry.js';
10
13
  import { normalizeAdminApiPrefix, normalizeAdminPrefix, setAdminApiPrefix, setAdminPrefix, } from './admin_prefix.js';
11
14
  import { getAuthHostConfig, markAuthHostAutoMounted, wasAuthHostAutoMounted, } from './auth_host_config.js';
12
15
  import { consoleDeniedPage } from './console_denied_page.js';
13
16
  import { CONSOLE_SSO_CALLBACK_PATH, CONSOLE_SSO_START_PATH } from './console_sso.js';
14
17
  import { endBridgedIdpSession, ensureConsoleSession } from './idp_session_bridge.js';
15
- import { POPPY_WEB_SESSION_KEY } from './poppy_session_key.js';
16
18
  import { createAuthThrottles } from './rate_limit.js';
17
19
  import { redirectExact } from './redirect_exact.js';
18
20
  import { requestPathWithQuery } from './request_url.js';
@@ -88,6 +90,23 @@ async function checkAndRefreshIdle(ctx) {
88
90
  return false;
89
91
  }
90
92
  }
93
+ /** Read-only twin of {@link checkAndRefreshIdle}: whether the idle timeout passed. Never writes. */
94
+ async function isIdleExpired(ctx) {
95
+ try {
96
+ const runtimeSettings = await resolveRuntimeSettings(ctx);
97
+ if (!runtimeSettings)
98
+ return false;
99
+ const policy = await resolveEffectiveSessionPolicy(runtimeSettings);
100
+ const idleMs = policy.idleTimeoutMinutes * 60 * 1000;
101
+ if (idleMs <= 0)
102
+ return false;
103
+ const lastSeen = ctx.session?.get(ACCOUNT_LAST_SEEN_KEY);
104
+ return lastSeen !== undefined && Date.now() - lastSeen > idleMs;
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ }
91
110
  /**
92
111
  * Constrói a URL de redirect para o login da conta, preservando o destino
93
112
  * original como `return_to` (URL-encoded). A URL de destino é o path + query
@@ -121,10 +140,21 @@ function buildLoginRedirect(ctx, extra) {
121
140
  * classe NÃO era aplicada em runtime num grupo, deixando /account/tokens e
122
141
  * /account/mfa acessíveis sem sessão.
123
142
  */
124
- /** A sessão é do navegador de um personal agent Poppy (§5)? */
125
- function isPoppyBrowser(ctx) {
126
- const marker = ctx.session?.get?.(POPPY_WEB_SESSION_KEY);
127
- return typeof marker === 'object' && marker !== null && typeof marker.sessionId === 'string';
143
+ /**
144
+ * A sessão é do navegador de um personal agent (`isAgentBrowser` de uma extension)? Um hook que
145
+ * lança conta como "é": na dúvida, o console fica fechado.
146
+ */
147
+ function isAgentBrowser(ctx) {
148
+ for (const extension of getAuthHostConfig()?.personalAgents?.extensions ?? []) {
149
+ try {
150
+ if (extension.isAgentBrowser?.(ctx))
151
+ return true;
152
+ }
153
+ catch {
154
+ return true;
155
+ }
156
+ }
157
+ return false;
128
158
  }
129
159
  /**
130
160
  * `true` when the caller wants JSON (an `Accept` that prefers `application/json`,
@@ -149,9 +179,9 @@ function unauthenticated(ctx, redirectTo) {
149
179
  return redirectExact(ctx.response, redirectTo);
150
180
  }
151
181
  const accountGuard = async (ctx, next) => {
152
- // O navegador de um personal agent Poppy (§5) não entra no console de conta nem
153
- // nas telas de consentimento: a sessão é do agente, não do usuário.
154
- if (isPoppyBrowser(ctx))
182
+ // O navegador de um personal agent não entra no console de conta nem nas telas de
183
+ // consentimento: a sessão é do agente, não do usuário.
184
+ if (isAgentBrowser(ctx))
155
185
  return ctx.response.status(403).send('');
156
186
  // Sessão do console — ou, com `accountSession.acceptIdpSession`, a do IdP (SSO).
157
187
  if (!(await ensureConsoleSession(ctx))) {
@@ -178,45 +208,65 @@ const accountGuard = async (ctx, next) => {
178
208
  * As roles permitidas são resolvidas em runtime do `authkit.server` (config lazy).
179
209
  */
180
210
  export const adminGuard = async (ctx, next) => {
211
+ const access = await evaluateAdminAccess(ctx);
212
+ switch (access.kind) {
213
+ case 'allowed':
214
+ return next();
215
+ case 'disabled':
216
+ // Defesa contra flag-drift: as rotas são montadas com `admin: true` em tempo de
217
+ // registro, ANTES de o config resolver. Se o config tiver `admin.enabled: false`,
218
+ // as rotas existem mas o console deve estar desligado — 404 (não vaza a existência).
219
+ return ctx.response.notFound();
220
+ case 'agent':
221
+ return ctx.response.status(403).send('');
222
+ case 'signed-out':
223
+ // `/account/login` é sempre o login da conta — NÃO muda com o prefixo admin.
224
+ return unauthenticated(ctx, buildLoginRedirect(ctx));
225
+ case 'idle':
226
+ return unauthenticated(ctx, buildLoginRedirect(ctx, 'reason=idle'));
227
+ case 'not-admin':
228
+ // Evita vazar a existência do console admin: redireciona para o accountHome
229
+ // (sem mostrar a URL do admin).
230
+ if (wantsJson(ctx)) {
231
+ return ctx.response
232
+ .status(403)
233
+ .send({ error: { code: 'forbidden', message: 'Admin role required.' } });
234
+ }
235
+ return redirectExact(ctx.response, accountHome(access.cfg));
236
+ case 'denied':
237
+ return consoleDenied(ctx, access.decision);
238
+ }
239
+ };
240
+ /**
241
+ * The admin console gate: the session, then `admin.roles`, then `admin.authorize`.
242
+ *
243
+ * `probe: true` is the console switcher asking from another console: it reads the idle timeout
244
+ * without refreshing or ending the session.
245
+ */
246
+ async function evaluateAdminAccess(ctx, options = {}) {
181
247
  const service = await ctx.containerResolver.make('authkit.server');
182
248
  const cfg = service.config;
183
- // Defesa contra flag-drift: as rotas são montadas com `admin: true` em tempo de
184
- // registro, ANTES de o config resolver. Se o config tiver `admin.enabled: false`,
185
- // as rotas existem mas o console deve estar desligado — 404 (não vaza a existência).
186
- if (!cfg.admin.enabled) {
187
- return ctx.response.notFound();
188
- }
189
- if (isPoppyBrowser(ctx))
190
- return ctx.response.status(403).send('');
249
+ if (!cfg.admin.enabled)
250
+ return { kind: 'disabled' };
251
+ if (isAgentBrowser(ctx))
252
+ return { kind: 'agent' };
191
253
  // Com `accountSession.acceptIdpSession`, a sessão do IdP também abre o console.
192
254
  await ensureConsoleSession(ctx);
193
255
  const accountId = ctx.session?.get(ACCOUNT_SESSION_KEY);
194
- if (!accountId) {
195
- // `/account/login` é sempre o login da conta — NÃO muda com o prefixo admin.
196
- return unauthenticated(ctx, buildLoginRedirect(ctx));
197
- }
256
+ if (!accountId)
257
+ return { kind: 'signed-out' };
198
258
  // Idle timeout: também protege o console admin.
199
- const idleExpired = await checkAndRefreshIdle(ctx);
200
- if (idleExpired) {
201
- return unauthenticated(ctx, buildLoginRedirect(ctx, 'reason=idle'));
202
- }
259
+ const idleExpired = options.probe ? await isIdleExpired(ctx) : await checkAndRefreshIdle(ctx);
260
+ if (idleExpired)
261
+ return { kind: 'idle' };
203
262
  const allowed = cfg.admin.roles;
204
263
  const account = await cfg.accountStore.findById(accountId);
205
264
  // Resolve roles through the host's role authority (`resolveTokenRoles`) when set — the same source
206
265
  // the token claim is minted from — so an app-role admin reaches the console. Falls back to the
207
266
  // account's stored `globalRoles` when no hook is configured.
208
267
  const roles = account ? await resolveAccountRoles(cfg, account) : [];
209
- const isAdmin = roles.some((r) => allowed.includes(r));
210
- if (!isAdmin) {
211
- // Evita vazar a existência do console admin: redireciona para o accountHome
212
- // (sem mostrar a URL do admin).
213
- if (wantsJson(ctx)) {
214
- return ctx.response
215
- .status(403)
216
- .send({ error: { code: 'forbidden', message: 'Admin role required.' } });
217
- }
218
- return redirectExact(ctx.response, accountHome(cfg));
219
- }
268
+ if (!roles.some((r) => allowed.includes(r)))
269
+ return { kind: 'not-admin', cfg };
220
270
  // `admin.authorize`, after the roles: both must pass. Omitted, the console is open
221
271
  // outside production and closed (403) in production.
222
272
  const decision = cfg.admin.authorize
@@ -225,21 +275,45 @@ export const adminGuard = async (ctx, next) => {
225
275
  ? { allowed: false, status: 403 }
226
276
  : { allowed: true };
227
277
  if (!decision.allowed)
228
- return consoleDenied(ctx, decision);
229
- return next();
230
- };
278
+ return { kind: 'denied', decision };
279
+ return { kind: 'allowed' };
280
+ }
231
281
  /**
232
- * Whether the app runs in production. Unknown (the provider did not register the
233
- * app) counts as production, so an omitted `admin.authorize` fails closed.
282
+ * The admin console gate as the console switcher reads it: `{ allowed }`, never a response, never a
283
+ * throw. Used as the `authorize` of the admin console's `'agora.consoles'` entry.
234
284
  */
235
- function appInProduction() {
285
+ export async function adminConsoleDecision(ctx) {
236
286
  try {
237
- return getBootedApp().inProduction !== false;
287
+ const access = await evaluateAdminAccess(ctx, { probe: true });
288
+ if (access.kind === 'allowed')
289
+ return { allowed: true };
290
+ const unauthenticated = access.kind === 'signed-out' ||
291
+ access.kind === 'idle' ||
292
+ (access.kind === 'denied' && access.decision.status === 401);
293
+ return { allowed: false, reason: unauthenticated ? 'unauthenticated' : 'forbidden' };
238
294
  }
239
295
  catch {
240
- return true;
296
+ return { allowed: false, reason: 'forbidden' };
241
297
  }
242
298
  }
299
+ /**
300
+ * Registers the admin console in the shared `'agora.consoles'` registry, so every other
301
+ * `@adonis-agora` console's switcher can link to it. A no-op when the provider did not register the
302
+ * app (tests that mount routes on a bare router).
303
+ */
304
+ function announceAdminConsole(adminPrefix) {
305
+ let app;
306
+ try {
307
+ app = getBootedApp();
308
+ }
309
+ catch {
310
+ return;
311
+ }
312
+ void (async () => {
313
+ const logger = (await app.container.make('logger').catch(() => undefined));
314
+ await registerAgoraConsole(app, adminConsoleEntry(adminPrefix, adminConsoleDecision), logger);
315
+ })().catch(() => { });
316
+ }
243
317
  /** The response for an `admin.authorize` denial: same rules in every `@adonis-agora` console. */
244
318
  function consoleDenied(ctx, decision) {
245
319
  const json = wantsJson(ctx);
@@ -262,16 +336,6 @@ function consoleDenied(ctx, decision) {
262
336
  ctx.response.header('Content-Type', 'text/html; charset=utf-8');
263
337
  return ctx.response.status(403).send(consoleDeniedPage(403));
264
338
  }
265
- /** One checker per configured `admin.authorize`, so a lazy import runs once. */
266
- const adminAuthorizeCheckers = new WeakMap();
267
- function adminAuthorizeChecker(authorize) {
268
- let checker = adminAuthorizeCheckers.get(authorize);
269
- if (!checker) {
270
- checker = resolveConsoleAuthorize(authorize);
271
- adminAuthorizeCheckers.set(authorize, checker);
272
- }
273
- return checker;
274
- }
275
339
  const C = {
276
340
  oidc: () => import('../controllers/oidc_callback_controller.js'),
277
341
  authorizationServerMetadata: () => import('../controllers/authorization_server_metadata_controller.js'),
@@ -289,8 +353,6 @@ const C = {
289
353
  accountConfirm: () => import('./controllers/account_confirm_controller.js'),
290
354
  agentOAuth: () => import('./controllers/agent_oauth_controller.js'),
291
355
  agentConsent: () => import('./controllers/agent_consent_controller.js'),
292
- poppy: () => import('./controllers/poppy_controller.js'),
293
- poppyConsent: () => import('./controllers/poppy_consent_controller.js'),
294
356
  webauthnAsset: () => import('./controllers/webauthn_asset_controller.js'),
295
357
  logoutAsset: () => import('./controllers/logout_asset_controller.js'),
296
358
  passkeyAutofillAsset: () => import('./controllers/passkey_autofill_asset_controller.js'),
@@ -301,6 +363,7 @@ const C = {
301
363
  // Console React JSON API (session-authed, under {prefix}/api/*).
302
364
  consoleShell: () => import('./admin_console/admin_shell_controller.js'),
303
365
  consoleOverview: () => import('./admin_console/console_overview_controller.js'),
366
+ consoleSwitcher: () => import('./admin_console/console_switcher_controller.js'),
304
367
  consoleUsers: () => import('./admin_console/console_users_controller.js'),
305
368
  consoleSessions: () => import('./admin_console/console_sessions_controller.js'),
306
369
  consoleClients: () => import('./admin_console/console_clients_controller.js'),
@@ -671,56 +734,13 @@ export function registerAuthHost(router, opts = {}) {
671
734
  })
672
735
  .use([accountGuard]);
673
736
  }
674
- // Personal Agent Protocol (Poppy): descoberta, authorization server (token,
675
- // revogação, device authorization), Mediated Sign-In e sessão de navegador —
676
- // server-to-server ou POST cross-site do navegador do agente (sem CSRF, ver
677
- // `authkitCsrfExceptions`). As telas do usuário (authorize/device) exigem a
678
- // sessão de conta e mantêm o CSRF.
679
- const poppyPrefix = hostCfg?.personalAgents?.poppyPrefix;
680
- if (poppyPrefix) {
681
- // Nomes explícitos: o Router nomeia pela dupla controller.método, e as duas URLs da metadata
682
- // (RFC 8414 na raiz e debaixo do issuer) usam o mesmo — "poppy.metadata already exists" no boot.
683
- router.get('/.well-known/poppy.json', [C.poppy, 'discovery']).as('authkit.poppy.discovery');
684
- router
685
- .get('/.well-known/oauth-protected-resource', [C.poppy, 'protectedResource'])
686
- .as('authkit.poppy.protected_resource');
687
- router
688
- .get(`/.well-known/oauth-authorization-server${poppyPrefix}`, [C.poppy, 'metadata'])
689
- .as('authkit.poppy.metadata');
690
- router
691
- .get(`${poppyPrefix}/.well-known/oauth-authorization-server`, [C.poppy, 'metadata'])
692
- .as('authkit.poppy.metadata_under_issuer');
693
- router.post(`${poppyPrefix}/oauth/token`, [C.poppy, 'token']).as('authkit.poppy.token');
694
- router.post(`${poppyPrefix}/oauth/revoke`, [C.poppy, 'revoke']).as('authkit.poppy.revoke');
695
- router
696
- .post(`${poppyPrefix}/oauth/device`, [C.poppy, 'deviceAuthorization'])
697
- .as('authkit.poppy.device_authorization');
698
- // A página grava um pedido sem autenticação: throttle por IP.
699
- withLogin(router
700
- .get(`${poppyPrefix}/browser-session`, [C.poppy, 'browserSessionPage'])
701
- .as('authkit.poppy.browser_session_page'));
702
- router
703
- .post(`${poppyPrefix}/browser-session`, [C.poppy, 'browserSession'])
704
- .as('authkit.poppy.browser_session');
705
- router
706
- .post(`${poppyPrefix}/browser-session/authorize`, [C.poppy, 'browserSessionAuthorize'])
707
- .as('authkit.poppy.browser_session_authorize');
708
- router
709
- .group(() => {
710
- router
711
- .get(`${poppyPrefix}/oauth/authorize`, [C.poppyConsent, 'authorize'])
712
- .as('authkit.poppy.authorize');
713
- router
714
- .post(`${poppyPrefix}/oauth/authorize`, [C.poppyConsent, 'decideAuthorization'])
715
- .as('authkit.poppy.authorize_decide');
716
- withOtpLogin(router
717
- .get(`${poppyPrefix}/device`, [C.poppyConsent, 'device'])
718
- .as('authkit.poppy.device'));
719
- withOtpLogin(router
720
- .post(`${poppyPrefix}/device`, [C.poppyConsent, 'decideDevice'])
721
- .as('authkit.poppy.device_decide'));
722
- })
723
- .use([accountGuard]);
737
+ // `personalAgents.extensions`: cada uma monta as rotas dela, com o guard de conta e os
738
+ // buckets de rate limit das telas embutidas.
739
+ for (const extension of hostCfg?.personalAgents?.extensions ?? []) {
740
+ extension.routes?.(router, {
741
+ accountGuard,
742
+ throttle: { login: (route) => withLogin(route), otp: (route) => withOtpLogin(route) },
743
+ });
724
744
  }
725
745
  // Paths do console de conta (configuráveis/localizáveis via `accountRoutes`).
726
746
  // As TELAS vêm de `accountPath(key)` (prefixo + segmento configurável); os
@@ -969,6 +989,36 @@ export function registerAuthHost(router, opts = {}) {
969
989
  router.post(`${apiBase}/mfa/passkeys/verify`, [C.accountMfaApi, 'passkeyRegisterVerify']);
970
990
  })
971
991
  .use([accountGuard]);
992
+ // Account console extensions (`@adonis-agora/authkit-server/account-extension`): pages other
993
+ // packages register from their provider's `boot()`. Same guard as the built-in screens; the
994
+ // page routes mount once every provider booted. `account: false` turns them off too.
995
+ const extensionEntries = [];
996
+ if (accountOpt !== false) {
997
+ const builtins = {};
998
+ if (mountSecurity)
999
+ builtins.security = securityPath;
1000
+ if (mountMfa)
1001
+ builtins.mfa = mfaPath;
1002
+ if (mountTokens)
1003
+ builtins.tokens = tokensPath;
1004
+ if (mountApps)
1005
+ builtins.apps = appsPath;
1006
+ if (mountOrgs)
1007
+ builtins.orgs = orgsPath;
1008
+ mountAccountExtensions(router, {
1009
+ guard: accountGuard,
1010
+ ...(throttles
1011
+ ? { throttle: { html: throttles.accountActions, json: throttles.accountActionsJson } }
1012
+ : {}),
1013
+ overrides: hostCfg?.accountConsole?.extensions,
1014
+ ...(hostCfg?.accountConsole?.remote ? { remote: hostCfg.accountConsole.remote } : {}),
1015
+ ...(hostCfg?.accountConsole?.actionTimeoutMs !== undefined
1016
+ ? { actionTimeoutMs: hostCfg.accountConsole.actionTimeoutMs }
1017
+ : {}),
1018
+ builtins,
1019
+ entries: extensionEntries,
1020
+ });
1021
+ }
972
1022
  // API headless (Clerk-style) — montada fora do `accountGuard` (não depende da
973
1023
  // sessão de CONTA), autenticada pelo resolver do host (`headless.resolveAccountId`).
974
1024
  // `PUT` usa CSRF igual à API do console (middleware de shield do host).
@@ -994,6 +1044,8 @@ export function registerAuthHost(router, opts = {}) {
994
1044
  resolvedAdminPrefix = ap;
995
1045
  // Persiste no singleton de processo para que controllers e views usem o mesmo prefixo.
996
1046
  setAdminPrefix(ap);
1047
+ // Announce the console to the other `@adonis-agora` consoles (the switcher).
1048
+ announceAdminConsole(ap);
997
1049
  router
998
1050
  .group(() => {
999
1051
  // Console admin (SPA React self-contained): todas as rotas GET servem o
@@ -1007,6 +1059,10 @@ export function registerAuthHost(router, opts = {}) {
1007
1059
  // ─── JSON API do console (session-authed via adminGuard upstream) ─────
1008
1060
  // GET {ap}/api/overview
1009
1061
  router.get(`${ap}/api/overview`, [C.consoleOverview, 'handle']);
1062
+ // Console switcher: the other `@adonis-agora` consoles this viewer may open, and the
1063
+ // global search across them (search BEFORE the list: fixed path first).
1064
+ router.get(`${ap}/api/consoles/search`, [C.consoleSwitcher, 'search']);
1065
+ router.get(`${ap}/api/consoles`, [C.consoleSwitcher, 'index']);
1010
1066
  // Usuários.
1011
1067
  router.get(`${ap}/api/users`, [C.consoleUsers, 'index']);
1012
1068
  router.get(`${ap}/api/users/:id`, [C.consoleUsers, 'show']);
@@ -1032,6 +1088,8 @@ export function registerAuthHost(router, opts = {}) {
1032
1088
  router.get(`${ap}/api/clients/:id`, [C.consoleClients, 'show']);
1033
1089
  router.patch(`${ap}/api/clients/:id`, [C.consoleClients, 'update']);
1034
1090
  router.delete(`${ap}/api/clients/:id`, [C.consoleClients, 'destroy']);
1091
+ router.post(`${ap}/api/clients/:id/disable`, [C.consoleClients, 'disable']);
1092
+ router.post(`${ap}/api/clients/:id/enable`, [C.consoleClients, 'enable']);
1035
1093
  router.post(`${ap}/api/clients/:id/regenerate-secret`, [
1036
1094
  C.consoleClients,
1037
1095
  'regenerateSecret',
@@ -1131,6 +1189,8 @@ export function registerAuthHost(router, opts = {}) {
1131
1189
  withApiThrottle(router.patch('/clients/:id', [C.apiClients, 'update']));
1132
1190
  withApiThrottle(router.post('/clients/:id/regenerate-secret', [C.apiClients, 'regenerateSecret']));
1133
1191
  withApiThrottle(router.delete('/clients/:id', [C.apiClients, 'destroy']));
1192
+ withApiThrottle(router.post('/clients/:id/disable', [C.apiClients, 'disable']));
1193
+ withApiThrottle(router.post('/clients/:id/enable', [C.apiClients, 'enable']));
1134
1194
  // Organizações.
1135
1195
  withApiThrottle(router.get('/organizations', [C.apiOrgs, 'index']));
1136
1196
  withApiThrottle(router.post('/organizations', [C.apiOrgs, 'store']));
@@ -1188,6 +1248,7 @@ export function registerAuthHost(router, opts = {}) {
1188
1248
  mfa: mountMfa,
1189
1249
  apps: mountApps,
1190
1250
  },
1251
+ extensions: extensionEntries,
1191
1252
  },
1192
1253
  admin: resolvedAdminPrefix ? { prefix: resolvedAdminPrefix } : null,
1193
1254
  adminApi: resolvedAdminApiPrefix ? { prefix: resolvedAdminApiPrefix } : null,
@@ -1,3 +1,4 @@
1
+ import { accountNavFor } from '../../account/runtime.js';
1
2
  import { getAccountLoginUrl } from '../account_login_url.js';
2
3
  import { accountPathsMap } from '../account_paths.js';
3
4
  import { DEFAULT_MESSAGES, translate } from '../i18n.js';
@@ -40,9 +41,13 @@ export async function renderEdgeView(ctx, view, props) {
40
41
  // `accountRoutes` — sem cada view conhecer o prefixo. Os action-subpaths
41
42
  // fixos (`/password`, `/passkeys/verify`, ...) são concatenados na própria view.
42
43
  const accountPaths = accountPathsMap();
44
+ // `accountNav` como prop global das telas do console de conta: telas embutidas montadas e
45
+ // páginas de extension visíveis para a conta (partial `authkit::account/partials/nav`).
46
+ const accountNav = props.accountNav ?? (await accountNavFor(ctx, view, props));
43
47
  return ctx.view.render(`authkit::${view}`, {
44
48
  loginUrl,
45
49
  accountPaths,
50
+ accountNav,
46
51
  ...props,
47
52
  t,
48
53
  messages,
@@ -1,3 +1,4 @@
1
+ import { accountNavFor } from '../../account/runtime.js';
1
2
  import { DEFAULT_MESSAGES } from '../i18n.js';
2
3
  import { renderEdgeView } from './edge_renderer.js';
3
4
  /**
@@ -68,6 +69,11 @@ export function inertiaRenderer(opts) {
68
69
  // Happy path: view vai ao Inertia.
69
70
  // `messages` vai como shared prop para as páginas React traduzirem.
70
71
  const messages = await resolveMessagesFromCtx(ctx);
71
- return ctx.inertia.render(`${opts.prefix}/${view}`, { ...props, messages });
72
+ const accountNav = props.accountNav ?? (await accountNavFor(ctx, view, props));
73
+ return ctx.inertia.render(`${opts.prefix}/${view}`, {
74
+ ...(accountNav ? { accountNav } : {}),
75
+ ...props,
76
+ messages,
77
+ });
72
78
  };
73
79
  }
@@ -92,10 +92,10 @@ export interface RuntimeSettingsOptions {
92
92
  /** TTL do cache em ms. Default: 15_000 (15s). */
93
93
  ttlMs?: number;
94
94
  /**
95
- * Nome da conexão Lucid a usar (ex.: 'auth'). Quando presente, todas as
96
- * queries usam `db.connection(name)` em vez da conexão default. Necessário
97
- * quando o schema de auth vive numa conexão nomeada com searchPath próprio.
98
- * Ausente (ou undefined) → conexão default (back-compat total).
95
+ * Nome da conexão Lucid a usar (ex.: 'auth') — a `schema.connection` do
96
+ * `defineConfig`, onde vivem as tabelas do authkit. Quando presente, todas as
97
+ * queries usam `db.connection(name)` em vez da conexão default.
98
+ * Ausente (ou undefined) → conexão default.
99
99
  */
100
100
  connection?: string;
101
101
  }
@@ -171,8 +171,8 @@ export declare class RuntimeSettings implements SettingsCapability {
171
171
  *
172
172
  * Esta fábrica SUBSTITUI ~16 cópias do mesmo bloco db/connection que viviam sob
173
173
  * nomes distintos (`getRuntimeSettings`, `getSettingsService`,
174
- * `getRuntimeSettingsForSudo`) e elimina o cast `as any` (via `connectionName`
175
- * tipado em {@link AccountStore}).
174
+ * `getRuntimeSettingsForSudo`). A conexão é `schema.connection`, a de todas as
175
+ * tabelas do authkit.
176
176
  *
177
177
  * @example
178
178
  * ```ts
@@ -312,8 +312,8 @@ export class RuntimeSettings {
312
312
  *
313
313
  * Esta fábrica SUBSTITUI ~16 cópias do mesmo bloco db/connection que viviam sob
314
314
  * nomes distintos (`getRuntimeSettings`, `getSettingsService`,
315
- * `getRuntimeSettingsForSudo`) e elimina o cast `as any` (via `connectionName`
316
- * tipado em {@link AccountStore}).
315
+ * `getRuntimeSettingsForSudo`). A conexão é `schema.connection`, a de todas as
316
+ * tabelas do authkit.
317
317
  *
318
318
  * @example
319
319
  * ```ts
@@ -325,7 +325,8 @@ export async function resolveRuntimeSettings(ctx) {
325
325
  try {
326
326
  const db = await ctx.containerResolver.make('lucid.db');
327
327
  const service = await ctx.containerResolver.make('authkit.server').catch(() => null);
328
- const connection = service?.config?.accountStore?.connectionName;
328
+ // `auth_settings` vive na conexão das tabelas do authkit, não na do model da conta.
329
+ const connection = service?.config?.schema?.connection;
329
330
  return new RuntimeSettings(db, connection ? { connection } : {});
330
331
  }
331
332
  catch {
@@ -8,6 +8,8 @@
8
8
  * typed per view, so a controller that drifts from these types fails the
9
9
  * package's `tsc`.
10
10
  */
11
+ import type { ResolvedActionStep } from '../account/http.js';
12
+ import type { AccountNavEntry, ResolvedAccountPage } from '../account/runtime.js';
11
13
  import type { OrgInvitation, OrgMember, OrgSummary } from '../accounts/account_store.js';
12
14
  import type { PatRecord } from '../pat/pat_store.js';
13
15
  import type { AccountConfirmProps, AccountEmailConfirmedProps, AccountLoginProps, AccountMfaProps, AccountSecurityProps } from './account_screen_props.js';
@@ -183,16 +185,22 @@ export interface ThrottledProps extends WithMessages {
183
185
  retryUrl: string;
184
186
  }
185
187
  /** The agent asking for access, on `agents/consent`. */
188
+ /** One scope on the personal-agent consent screen. */
189
+ export interface AgentConsentScope {
190
+ id: string;
191
+ description: string;
192
+ /** i18n key shown instead of `description` (an extension's message). */
193
+ descriptionKey?: string;
194
+ /** Not ticked for the person: granted only if they tick it themselves. */
195
+ optIn?: boolean;
196
+ }
186
197
  export interface AgentConsentRequest {
187
198
  /** Device-flow user code, when the agent uses it. */
188
199
  userCode?: string;
189
200
  agentName: string | null;
190
201
  agentOrigin: string | null;
191
202
  logoUri?: string | null;
192
- scopes: Array<{
193
- id: string;
194
- description: string;
195
- }>;
203
+ scopes: Array<AgentConsentScope>;
196
204
  /** Extra hidden form fields to send back. */
197
205
  hidden?: Array<{
198
206
  name: string;
@@ -216,10 +224,7 @@ export interface AgentsConsentProps extends WithMessages {
216
224
  export interface AgentsDoneProps extends WithMessages {
217
225
  status: 'approved' | 'denied' | 'expired' | 'invalid';
218
226
  agentName: string | null;
219
- scopes: Array<{
220
- id: string;
221
- description: string;
222
- }>;
227
+ scopes: Array<AgentConsentScope>;
223
228
  }
224
229
  /** `account/apps` — apps and personal agents with access to the account. */
225
230
  export interface AccountAppsProps extends WithMessages {
@@ -276,6 +281,31 @@ export interface AccountOrgsProps extends WithMessages {
276
281
  invitation?: OrgInvitation | null;
277
282
  token?: string;
278
283
  }
284
+ /**
285
+ * `account/extension` — a page an account console extension added. `page` is the resolved page
286
+ * (texts translated, values formatted); `step` is set on the confirmation step of an action.
287
+ * `accountNav` (the console navigation) is added by the renderer to every account screen.
288
+ */
289
+ export interface AccountExtensionProps extends WithMessages {
290
+ csrfToken: string;
291
+ page: ResolvedAccountPage;
292
+ step: ResolvedActionStep | null;
293
+ flash: {
294
+ tone: 'success' | 'danger';
295
+ text: string;
296
+ } | null;
297
+ /** The response's CSP nonce, for the scripts of `component` blocks. */
298
+ cspNonce: string | null;
299
+ accountNav?: AccountNavEntry[] | null;
300
+ /** `<html lang>` of the console locale. */
301
+ htmlLang?: string;
302
+ /** Edge only: the `<script>` tag of a `component` block's script (nonce included). */
303
+ scriptTag?: (script: {
304
+ src: string;
305
+ integrity: string | null;
306
+ module: boolean;
307
+ }) => string;
308
+ }
279
309
  /** Props of every screen, by view name. */
280
310
  export interface AuthkitScreenProps {
281
311
  login: LoginProps;
@@ -299,6 +329,7 @@ export interface AuthkitScreenProps {
299
329
  'account/orgs': AccountOrgsProps;
300
330
  'account/confirm': AccountConfirmProps;
301
331
  'account/email-confirmed': AccountEmailConfirmedProps;
332
+ 'account/extension': AccountExtensionProps;
302
333
  }
303
334
  /** Every screen authkit-server renders. */
304
335
  export type AuthkitScreen = keyof AuthkitScreenProps;