@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
@@ -0,0 +1,80 @@
1
+ import { getBootedApp } from '../../../services/booted_app.js';
2
+ import { supportsOrganizations } from '../../accounts/account_store.js';
3
+ import { readAgoraConsoles, } from '../../console_switcher.js';
4
+ /** The admin console's id in the `'agora.consoles'` registry. */
5
+ export const ADMIN_CONSOLE_ID = 'authkit';
6
+ /** The `'agora.consoles'` registry, or `null` (none bound, or no booted app). */
7
+ export async function consoleRegistry() {
8
+ try {
9
+ return await readAgoraConsoles(getBootedApp());
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ }
15
+ const ORG_CACHE_MS = 30_000;
16
+ const orgCache = new WeakMap();
17
+ /** The store's organizations, cached for 30 s per store so a search session does one query. */
18
+ function cachedOrgs(store, load) {
19
+ const hit = orgCache.get(store);
20
+ if (hit && Date.now() - hit.at < ORG_CACHE_MS)
21
+ return hit.rows;
22
+ const rows = load();
23
+ orgCache.set(store, { at: Date.now(), rows });
24
+ rows.catch(() => orgCache.delete(store));
25
+ return rows;
26
+ }
27
+ /**
28
+ * Global search of the admin console: users by email (as the account store's `listAccounts`
29
+ * search matches them), and organizations by name or slug.
30
+ * Each hit opens the matching drawer in the console.
31
+ */
32
+ export async function searchAdminConsole(ctx, adminPrefix, query, limit) {
33
+ const service = await ctx.containerResolver.make('authkit.server');
34
+ const store = service.config.accountStore;
35
+ const hits = [];
36
+ const accounts = await store.listAccounts({ search: query, page: 1, size: limit });
37
+ for (const account of accounts.data.slice(0, limit)) {
38
+ hits.push({
39
+ title: account.email,
40
+ subtitle: account.name ? `User · ${account.name}` : 'User',
41
+ href: `${adminPrefix}?view=users&user=${encodeURIComponent(account.id)}`,
42
+ });
43
+ }
44
+ // Organizations only from a store that lists them in one query, for queries of 2+ characters,
45
+ // from a list cached briefly per process: search runs on every (debounced) keystroke.
46
+ if (hits.length < limit && query.length >= 2 && supportsOrganizations(store)) {
47
+ const listAll = store
48
+ .listAllOrgsWithMemberCount;
49
+ if (typeof listAll === 'function') {
50
+ const needle = query.toLowerCase();
51
+ const orgs = await cachedOrgs(store, () => listAll.call(store));
52
+ for (const org of orgs) {
53
+ if (hits.length >= limit)
54
+ break;
55
+ if (!org.name.toLowerCase().includes(needle) && !org.slug.toLowerCase().includes(needle)) {
56
+ continue;
57
+ }
58
+ hits.push({
59
+ title: org.name,
60
+ subtitle: `Organization · ${org.slug}`,
61
+ href: `${adminPrefix}?view=orgs&org=${encodeURIComponent(org.id)}`,
62
+ });
63
+ }
64
+ }
65
+ }
66
+ return hits;
67
+ }
68
+ /** The admin console's registry entry, for the shell mounted at `adminPrefix`. */
69
+ export function adminConsoleEntry(adminPrefix, authorize) {
70
+ return {
71
+ id: ADMIN_CONSOLE_ID,
72
+ title: 'Identity',
73
+ package: '@adonis-agora/authkit-server',
74
+ icon: 'identity',
75
+ path: adminPrefix,
76
+ order: 90,
77
+ authorize,
78
+ search: (ctx, query, limit) => searchAdminConsole(ctx, adminPrefix, query, limit),
79
+ };
80
+ }
@@ -1,8 +1,10 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { extname } from 'node:path';
3
+ import { countOtherConsoles } from '../../console_switcher.js';
3
4
  import { accountPath } from '../account_paths.js';
4
5
  import { resolveAccountRoles, rolesManagedExternally } from '../account_roles.js';
5
6
  import { getAdminPrefix } from '../admin_prefix.js';
7
+ import { ADMIN_CONSOLE_ID, consoleRegistry } from './admin_console_entry.js';
6
8
  // ─── Shell HTML cache ─────────────────────────────────────────────────────────
7
9
  /**
8
10
  * Points to the Vite-built index.html inside build/host/ui-dist/.
@@ -141,6 +143,9 @@ export default class AdminShellController {
141
143
  }
142
144
  }
143
145
  const adminBase = getAdminPrefix();
146
+ // Console switcher: only announced when other `@adonis-agora` consoles are registered; the SPA
147
+ // then asks the endpoint which of them this viewer may open.
148
+ const others = countOtherConsoles(await consoleRegistry(), ADMIN_CONSOLE_ID);
144
149
  const authkitConfig = {
145
150
  adminBase,
146
151
  csrfToken: ctx.request.csrfToken ?? null,
@@ -152,6 +157,9 @@ export default class AdminShellController {
152
157
  // Confirmação de identidade (sudo) das ações destrutivas.
153
158
  confirm: accountPath('confirm'),
154
159
  },
160
+ ...(others > 0 ? { switcher: { endpoint: `${adminBase}/api/consoles`, others } } : {}),
161
+ // Global search (Ctrl/Cmd-K) across this console and every other one the viewer may open.
162
+ search: { endpoint: `${adminBase}/api/consoles/search` },
155
163
  features: {
156
164
  // A página de personal agents só aparece com `personalAgents` no config.
157
165
  personalAgents: !!cfg.personalAgents,
@@ -9,8 +9,11 @@ import type { HttpContext } from '@adonisjs/core/http';
9
9
  * PATCH {prefix}/api/clients/:id → atualizar metadata
10
10
  * DELETE {prefix}/api/clients/:id → remover client
11
11
  * POST {prefix}/api/clients/:id/regenerate-secret → gerar novo secret
12
+ * POST {prefix}/api/clients/:id/disable → stop issuing tokens to the client
13
+ * POST {prefix}/api/clients/:id/enable → issue tokens again
12
14
  */
13
15
  export default class ConsoleClientsController {
16
+ #private;
14
17
  /** GET {prefix}/api/clients */
15
18
  index(ctx: HttpContext): Promise<{
16
19
  data: {
@@ -25,6 +28,8 @@ export default class ConsoleClientsController {
25
28
  backchannelLogoutSessionRequired: boolean;
26
29
  clientName: string | null;
27
30
  logoUri: string | null;
31
+ disabled: boolean;
32
+ serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
28
33
  }[];
29
34
  canList: boolean;
30
35
  }>;
@@ -41,6 +46,8 @@ export default class ConsoleClientsController {
41
46
  backchannelLogoutSessionRequired: boolean;
42
47
  clientName: string | null;
43
48
  logoUri: string | null;
49
+ disabled: boolean;
50
+ serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
44
51
  }>;
45
52
  /** POST {prefix}/api/clients */
46
53
  store(ctx: HttpContext): Promise<void | {
@@ -60,12 +67,46 @@ export default class ConsoleClientsController {
60
67
  backchannelLogoutSessionRequired: boolean;
61
68
  clientName: string | null;
62
69
  logoUri: string | null;
70
+ disabled: boolean;
71
+ serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
63
72
  }>;
64
73
  /** DELETE {prefix}/api/clients/:id */
65
74
  destroy(ctx: HttpContext): Promise<void | {
66
75
  ok: boolean;
67
76
  deleted: string;
68
77
  }>;
78
+ /** POST {prefix}/api/clients/:id/disable */
79
+ disable(ctx: HttpContext): Promise<void | {
80
+ clientId: string;
81
+ confidential: boolean;
82
+ grantTypes: string[];
83
+ redirectUris: string[];
84
+ postLogoutRedirectUris: string[];
85
+ tokenEndpointAuthMethod: string;
86
+ applicationType: import("../client_metadata.js").ApplicationType;
87
+ backchannelLogoutUri: string | null;
88
+ backchannelLogoutSessionRequired: boolean;
89
+ clientName: string | null;
90
+ logoUri: string | null;
91
+ disabled: boolean;
92
+ serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
93
+ }>;
94
+ /** POST {prefix}/api/clients/:id/enable */
95
+ enable(ctx: HttpContext): Promise<void | {
96
+ clientId: string;
97
+ confidential: boolean;
98
+ grantTypes: string[];
99
+ redirectUris: string[];
100
+ postLogoutRedirectUris: string[];
101
+ tokenEndpointAuthMethod: string;
102
+ applicationType: import("../client_metadata.js").ApplicationType;
103
+ backchannelLogoutUri: string | null;
104
+ backchannelLogoutSessionRequired: boolean;
105
+ clientName: string | null;
106
+ logoUri: string | null;
107
+ disabled: boolean;
108
+ serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
109
+ }>;
69
110
  /** POST {prefix}/api/clients/:id/regenerate-secret */
70
111
  regenerateSecret(ctx: HttpContext): Promise<void | {
71
112
  clientId: string;
@@ -19,6 +19,8 @@ function invalidMetadata(ctx, err) {
19
19
  * PATCH {prefix}/api/clients/:id → atualizar metadata
20
20
  * DELETE {prefix}/api/clients/:id → remover client
21
21
  * POST {prefix}/api/clients/:id/regenerate-secret → gerar novo secret
22
+ * POST {prefix}/api/clients/:id/disable → stop issuing tokens to the client
23
+ * POST {prefix}/api/clients/:id/enable → issue tokens again
22
24
  */
23
25
  export default class ConsoleClientsController {
24
26
  /** GET {prefix}/api/clients */
@@ -107,6 +109,34 @@ export default class ConsoleClientsController {
107
109
  });
108
110
  return { ok: true, deleted: id };
109
111
  }
112
+ /** POST {prefix}/api/clients/:id/disable */
113
+ async disable(ctx) {
114
+ return this.#setDisabled(ctx, true);
115
+ }
116
+ /** POST {prefix}/api/clients/:id/enable */
117
+ async enable(ctx) {
118
+ return this.#setDisabled(ctx, false);
119
+ }
120
+ /** Disabled clients get `invalid_client` from the next request on. */
121
+ async #setDisabled(ctx, disabled) {
122
+ const service = await ctx.containerResolver.make('authkit.server');
123
+ const svc = new AdminClientsService(service);
124
+ const id = ctx.request.param('id');
125
+ if (!(await svc.setDisabled(id, disabled))) {
126
+ return ctx.response.notFound(apiError('not_found', 'Client não encontrado.'));
127
+ }
128
+ const client = await svc.find(id);
129
+ await service.config.audit?.record({
130
+ type: disabled ? 'client.disabled' : 'client.enabled',
131
+ clientId: id,
132
+ ip: ctx.request.ip?.() ?? null,
133
+ metadata: {
134
+ actor: 'admin-console',
135
+ ...(client?.serviceTenant ? { partition: client.serviceTenant.partition } : {}),
136
+ },
137
+ });
138
+ return clientDto(client);
139
+ }
110
140
  /** POST {prefix}/api/clients/:id/regenerate-secret */
111
141
  async regenerateSecret(ctx) {
112
142
  const service = await ctx.containerResolver.make('authkit.server');
@@ -129,7 +159,10 @@ export default class ConsoleClientsController {
129
159
  type: 'client.secret_regenerated',
130
160
  clientId: id,
131
161
  ip: ctx.request.ip?.() ?? null,
132
- metadata: { actor: 'admin-console' },
162
+ metadata: {
163
+ actor: 'admin-console',
164
+ ...(existing.serviceTenant ? { partition: existing.serviceTenant.partition } : {}),
165
+ },
133
166
  });
134
167
  return { clientId: id, clientSecret: newSecret };
135
168
  }
@@ -22,7 +22,7 @@ interface AgentEntry {
22
22
  connections: number;
23
23
  }
24
24
  /**
25
- * Endpoints JSON de personal agents (PACT e Poppy) do console admin React.
25
+ * Endpoints JSON de personal agents (PACT e `personalAgents.extensions`) do console admin React.
26
26
  *
27
27
  * GET {prefix}/api/personal-agents → agentes (config + console + conectados)
28
28
  * GET {prefix}/api/personal-agents/connections?clientId=&accountId=&protocol=&page=&size=
@@ -41,11 +41,6 @@ export default class ConsolePersonalAgentsController {
41
41
  /** A lista do config é uma função do host (o console não enxerga). */
42
42
  hostResolver: boolean;
43
43
  } | null;
44
- poppy: {
45
- requireRegistration: boolean;
46
- hostResolver: boolean;
47
- allowInsecureClientIds: boolean;
48
- } | null;
49
44
  };
50
45
  agents: AgentEntry[];
51
46
  }>;
@@ -31,10 +31,21 @@ function enabledProtocols(runtime) {
31
31
  const out = [];
32
32
  if (runtime.config.pact)
33
33
  out.push('pact');
34
- if (runtime.config.poppy)
35
- out.push('poppy');
34
+ out.push(...runtime.extensions.keys());
36
35
  return out;
37
36
  }
37
+ /** O protocolo dono de um grant, pelo prefixo do id. */
38
+ function protocolOfGrant(runtime, grantId) {
39
+ for (const [id, { extension }] of runtime.extensions) {
40
+ if (grantId.startsWith(extension.grantIdPrefix))
41
+ return id;
42
+ }
43
+ return 'pact';
44
+ }
45
+ /** Prefixo de id dos grants de um protocolo (`agrant_` = PACT). */
46
+ function grantPrefixOf(runtime, protocol) {
47
+ return runtime.extensions.get(protocol)?.extension.grantIdPrefix ?? 'agrant_';
48
+ }
38
49
  function isUrl(value, allowHttp) {
39
50
  if (typeof value !== 'string' || value.length > 2048)
40
51
  return false;
@@ -52,14 +63,14 @@ function isUrl(value, allowHttp) {
52
63
  async function agentName(runtime, protocol, identifier, fallback) {
53
64
  if (fallback)
54
65
  return fallback;
55
- if (protocol === 'poppy' && runtime.poppy) {
56
- return runtime.poppy.registry.displayName(identifier);
57
- }
66
+ const extension = runtime.extensions.get(protocol);
67
+ if (extension)
68
+ return extension.runtime.displayName(identifier);
58
69
  const registration = await runtime.config.resolveAgent(identifier);
59
70
  return displayNameFor(registration ?? { issuer: identifier, jwksUri: '' });
60
71
  }
61
72
  /**
62
- * Endpoints JSON de personal agents (PACT e Poppy) do console admin React.
73
+ * Endpoints JSON de personal agents (PACT e `personalAgents.extensions`) do console admin React.
63
74
  *
64
75
  * GET {prefix}/api/personal-agents → agentes (config + console + conectados)
65
76
  * GET {prefix}/api/personal-agents/connections?clientId=&accountId=&protocol=&page=&size=
@@ -105,17 +116,20 @@ export default class ConsolePersonalAgentsController {
105
116
  e.jwksUri = agent.jwksUri;
106
117
  names.set(keyOf('pact', agent.issuer), agent.name ?? null);
107
118
  }
108
- for (const [clientId, count] of await runtime.grants.countLiveGrantsByClient(now, 'pact')) {
119
+ for (const [clientId, count] of await runtime.grants.countLiveGrantsByClient(now, 'agrant_')) {
109
120
  entry('pact', clientId).connections = count;
110
121
  }
111
122
  }
112
- if (cfg.poppy) {
113
- for (const id of cfg.poppy.clients.registeredList ?? [])
114
- entry('poppy', id).config = 'allowed';
115
- for (const id of cfg.poppy.clients.blockedList ?? [])
116
- entry('poppy', id).config = 'blocked';
117
- for (const [clientId, count] of await runtime.grants.countLiveGrantsByClient(now, 'poppy')) {
118
- entry('poppy', clientId).connections = count;
123
+ const summaries = {};
124
+ for (const [protocol, { extension, runtime: ext }] of runtime.extensions) {
125
+ const summary = ext.consoleSummary();
126
+ summaries[protocol] = { label: extension.label, ...summary };
127
+ for (const id of summary.configured.allowed)
128
+ entry(protocol, id).config = 'allowed';
129
+ for (const id of summary.configured.blocked)
130
+ entry(protocol, id).config = 'blocked';
131
+ for (const [clientId, count] of await runtime.grants.countLiveGrantsByClient(now, extension.grantIdPrefix)) {
132
+ entry(protocol, clientId).connections = count;
119
133
  }
120
134
  }
121
135
  for (const row of await runtime.registry.list()) {
@@ -153,13 +167,17 @@ export default class ConsolePersonalAgentsController {
153
167
  hostResolver: cfg.staticAgents === null,
154
168
  }
155
169
  : null,
156
- poppy: cfg.poppy
157
- ? {
158
- requireRegistration: cfg.poppy.clients.requireRegistration,
159
- hostResolver: cfg.poppy.clients.registeredList === null || cfg.poppy.clients.blockedList === null,
160
- allowInsecureClientIds: cfg.poppy.allowInsecureClientIds,
161
- }
162
- : null,
170
+ ...Object.fromEntries(Object.entries(summaries).map(([protocol, summary]) => [
171
+ protocol,
172
+ {
173
+ label: summary.label,
174
+ requireRegistration: summary.requireRegistration,
175
+ hostResolver: summary.hostResolver,
176
+ allowHttpIdentifiers: summary.allowHttpIdentifiers,
177
+ identifierLabel: summary.identifierLabel,
178
+ identifierPlaceholder: summary.identifierPlaceholder ?? null,
179
+ },
180
+ ])),
163
181
  },
164
182
  agents,
165
183
  };
@@ -172,11 +190,9 @@ export default class ConsolePersonalAgentsController {
172
190
  const qs = ctx.request.qs();
173
191
  const page = parseListPage(qs.page);
174
192
  const size = parseListSize(qs.size);
175
- const protocol = qs.protocol === 'pact' || qs.protocol === 'poppy'
176
- ? qs.protocol
177
- : undefined;
193
+ const protocol = qs.protocol && enabledProtocols(runtime).includes(qs.protocol) ? qs.protocol : undefined;
178
194
  const filter = {
179
- protocol,
195
+ idPrefix: protocol ? grantPrefixOf(runtime, protocol) : undefined,
180
196
  clientId: qs.clientId?.trim() || undefined,
181
197
  accountId: qs.accountId?.trim() || undefined,
182
198
  };
@@ -189,7 +205,7 @@ export default class ConsolePersonalAgentsController {
189
205
  const emails = new Map();
190
206
  const nameCache = new Map();
191
207
  const data = await Promise.all(rows.map(async (g) => {
192
- const rowProtocol = g.id.startsWith('pgrant_') ? 'poppy' : 'pact';
208
+ const rowProtocol = protocolOfGrant(runtime, g.id);
193
209
  if (!emails.has(g.accountId)) {
194
210
  const account = await store.findById(g.accountId).catch(() => null);
195
211
  emails.set(g.accountId, account?.email ?? null);
@@ -248,17 +264,21 @@ export default class ConsolePersonalAgentsController {
248
264
  const invalid = (message) => ctx.response.unprocessableEntity(apiError('invalid_request', message));
249
265
  const protocol = body.protocol;
250
266
  if (!enabledProtocols(runtime).includes(protocol)) {
251
- return invalid('`protocol` precisa ser um protocolo ligado (`pact` ou `poppy`).');
267
+ return invalid(`\`protocol\` precisa ser um protocolo ligado (${enabledProtocols(runtime)
268
+ .map((p) => `\`${p}\``)
269
+ .join(', ')}).`);
252
270
  }
253
271
  const status = body.status;
254
272
  if (status !== 'allowed' && status !== 'blocked') {
255
273
  return invalid('`status` precisa ser `allowed` ou `blocked`.');
256
274
  }
257
- const allowHttp = protocol === 'poppy' && !!runtime.config.poppy?.allowInsecureClientIds;
275
+ const extension = runtime.extensions.get(protocol);
276
+ const summary = extension?.runtime.consoleSummary();
277
+ const allowHttp = !!summary?.allowHttpIdentifiers;
258
278
  const identifier = typeof body.identifier === 'string' ? body.identifier.trim() : '';
259
279
  if (!isUrl(identifier, allowHttp)) {
260
- return invalid(protocol === 'poppy'
261
- ? '`identifier` precisa ser o `client_id` do agente (URL HTTPS do documento de metadata).'
280
+ return invalid(summary
281
+ ? `\`identifier\` precisa ser uma URL ${allowHttp ? 'http(s)' : 'HTTPS'}: ${summary.identifierLabel}.`
262
282
  : '`identifier` precisa ser o `iss` do agente (URL HTTPS).');
263
283
  }
264
284
  const name = typeof body.name === 'string' && body.name.trim() ? body.name.trim().slice(0, 200) : null;
@@ -0,0 +1,11 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ /**
3
+ * The console switcher endpoints of the admin console (behind the admin console's own guard).
4
+ *
5
+ * GET {prefix}/api/consoles → the other consoles this viewer may open
6
+ * GET {prefix}/api/consoles/search?q= → hits from every console this viewer may open
7
+ */
8
+ export default class ConsoleSwitcherController {
9
+ index(ctx: HttpContext): Promise<import("../../console_switcher.js").VisibleConsoles>;
10
+ search(ctx: HttpContext): Promise<import("../../console_switcher.js").ConsoleSearchResults>;
11
+ }
@@ -0,0 +1,23 @@
1
+ import { listVisibleConsoles, searchConsoles } from '../../console_switcher.js';
2
+ import { ADMIN_CONSOLE_ID, consoleRegistry } from './admin_console_entry.js';
3
+ /**
4
+ * The console switcher endpoints of the admin console (behind the admin console's own guard).
5
+ *
6
+ * GET {prefix}/api/consoles → the other consoles this viewer may open
7
+ * GET {prefix}/api/consoles/search?q= → hits from every console this viewer may open
8
+ */
9
+ export default class ConsoleSwitcherController {
10
+ async index(ctx) {
11
+ const body = await listVisibleConsoles(await consoleRegistry(), ADMIN_CONSOLE_ID, ctx, {
12
+ logger: ctx.logger,
13
+ });
14
+ ctx.response.header('Cache-Control', 'private, no-store');
15
+ return body;
16
+ }
17
+ async search(ctx) {
18
+ const q = ctx.request.input('q', '');
19
+ const body = await searchConsoles(await consoleRegistry(), ADMIN_CONSOLE_ID, ctx, typeof q === 'string' ? q : '', { logger: ctx.logger });
20
+ ctx.response.header('Cache-Control', 'private, no-store');
21
+ return body;
22
+ }
23
+ }
@@ -87,7 +87,7 @@ export class AdminSessionsService {
87
87
  /**
88
88
  * Revogação total também corta os personal agents: "sair de todas as sessões",
89
89
  * reset de senha, a ação do admin e a exclusão de conta não podem deixar um
90
- * agente agindo na conta (delegações do PACT e refresh tokens do Poppy). Sem
90
+ * agente agindo na conta (delegações do PACT e conexões das extensions). Sem
91
91
  * `personalAgents` é no-op — e aí nem resolve o banco. Diferente do
92
92
  * `recordSubRevocation`, uma falha AQUI propaga: não há outra camada que corte
93
93
  * esses tokens.
@@ -142,11 +142,14 @@ export class AdminSessionsService {
142
142
  return [];
143
143
  return (await adapter.list()).filter((r) => grantIds.has(r.payload.grantId));
144
144
  }
145
- /** Grants da conta, exceto os espelhos `pgrant_` de refresh tokens do Poppy. */
145
+ /**
146
+ * Grants da conta, exceto os espelhos dos grants das `personalAgents.extensions` (os access
147
+ * tokens de MCP que elas emitem pelo oidc-provider usam o id do grant delas) — aparecem e são
148
+ * revogados como personal agent, não como app OIDC.
149
+ */
146
150
  async #accountGrants(accountId) {
147
- // Os grants `pgrant_` são o espelho de um refresh token do Poppy (Bearer de MCP) — aparecem e
148
- // são revogados como personal agent, não como app OIDC.
149
- return (await this.#listModelOfAccount('Grant', accountId)).filter((r) => !r.id.startsWith('pgrant_'));
151
+ const prefixes = (this.#oidc.config.personalAgents?.extensions ?? []).map((e) => e.grantIdPrefix);
152
+ return (await this.#listModelOfAccount('Grant', accountId)).filter((r) => !prefixes.some((prefix) => r.id.startsWith(prefix)));
150
153
  }
151
154
  /**
152
155
  * Conta TODAS as sessões ativas do IdP (todas as contas). 0 quando o adapter não
@@ -17,6 +17,19 @@ export declare const clientInputValidator: import("@vinejs/vine").VineValidator<
17
17
  backchannelLogoutSessionRequired: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine").VineBoolean>;
18
18
  clientName: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine/schema/base/literal").NullableModifier<import("@vinejs/vine").VineString>>;
19
19
  logoUri: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine/schema/base/literal").NullableModifier<import("@vinejs/vine").VineString>>;
20
+ serviceTenant: import("@vinejs/vine").OptionalModifier<import("@vinejs/vine").NullableModifier<import("@vinejs/vine").VineObject<{
21
+ partition: import("@vinejs/vine").VineString;
22
+ capabilities: import("@vinejs/vine").OptionalModifier<import("@vinejs/vine").VineArray<import("@vinejs/vine").VineString>>;
23
+ }, {
24
+ capabilities?: string[] | null | undefined;
25
+ partition: string;
26
+ }, {
27
+ capabilities?: string[] | undefined;
28
+ partition: string;
29
+ }, {
30
+ capabilities?: string[] | undefined;
31
+ partition: string;
32
+ }>>>;
20
33
  }, {
21
34
  applicationType?: "native" | "web" | null | undefined;
22
35
  backchannelLogoutSessionRequired?: string | number | boolean | null | undefined;
@@ -27,6 +40,10 @@ export declare const clientInputValidator: import("@vinejs/vine").VineValidator<
27
40
  logoUri?: string | null | undefined;
28
41
  postLogoutRedirectUris?: string[] | null | undefined;
29
42
  redirectUris?: string[] | null | undefined;
43
+ serviceTenant?: {
44
+ capabilities?: string[] | null | undefined;
45
+ partition: string;
46
+ } | null | undefined;
30
47
  tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none" | null | undefined;
31
48
  }, {
32
49
  applicationType?: "native" | "web" | undefined;
@@ -38,6 +55,10 @@ export declare const clientInputValidator: import("@vinejs/vine").VineValidator<
38
55
  logoUri?: string | null | undefined;
39
56
  postLogoutRedirectUris?: string[] | undefined;
40
57
  redirectUris?: string[] | undefined;
58
+ serviceTenant?: {
59
+ capabilities?: string[] | undefined;
60
+ partition: string;
61
+ } | null | undefined;
41
62
  tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none" | undefined;
42
63
  }, {
43
64
  applicationType?: "native" | "web" | undefined;
@@ -49,6 +70,10 @@ export declare const clientInputValidator: import("@vinejs/vine").VineValidator<
49
70
  logoUri?: string | null | undefined;
50
71
  postLogoutRedirectUris?: string[] | undefined;
51
72
  redirectUris?: string[] | undefined;
73
+ serviceTenant?: {
74
+ capabilities?: string[] | undefined;
75
+ partition: string;
76
+ } | null | undefined;
52
77
  tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none" | undefined;
53
78
  }>, Record<string, any> | undefined>;
54
79
  /** Forma validada de um client (saída do {@link clientInputValidator}). */
@@ -63,6 +88,10 @@ export type ClientInputFields = {
63
88
  backchannelLogoutSessionRequired?: boolean;
64
89
  clientName?: string | null;
65
90
  logoUri?: string | null;
91
+ serviceTenant?: {
92
+ partition: string;
93
+ capabilities?: string[];
94
+ } | null;
66
95
  };
67
96
  /**
68
97
  * Mapeia o input validado para um {@link ClientInput} COMPLETO (create), com defaults.
@@ -1,4 +1,5 @@
1
1
  import vine from '@vinejs/vine';
2
+ import { CAPABILITY_PATTERN, MAX_CAPABILITIES, PARTITION_PATTERN, } from '../service_tenants/client_metadata.js';
2
3
  import { APPLICATION_TYPES, redirectUriProblem } from './client_metadata.js';
3
4
  import { normalizeEmailIdentifier } from './email_identifier.js';
4
5
  /**
@@ -103,6 +104,18 @@ export const clientInputValidator = vine.compile(vine.object({
103
104
  // `null` (update) removes them.
104
105
  clientName: vine.string().trim().maxLength(200).nullable().optional(),
105
106
  logoUri: vine.string().trim().url(URL_OPTS).nullable().optional(),
107
+ // Service tenant (`client_credentials` + partition claim). `null` (update) turns it
108
+ // back into a regular client.
109
+ serviceTenant: vine
110
+ .object({
111
+ partition: vine.string().trim().regex(PARTITION_PATTERN),
112
+ capabilities: vine
113
+ .array(vine.string().trim().regex(CAPABILITY_PATTERN))
114
+ .maxLength(MAX_CAPABILITIES)
115
+ .optional(),
116
+ })
117
+ .nullable()
118
+ .optional(),
106
119
  }));
107
120
  /**
108
121
  * Mapeia o input validado para um {@link ClientInput} COMPLETO (create), com defaults.
@@ -111,6 +124,21 @@ export const clientInputValidator = vine.compile(vine.object({
111
124
  */
112
125
  export function clientCreateInput(v) {
113
126
  const applicationType = v.applicationType ?? 'web';
127
+ // A service tenant only uses client_credentials: that is its default grant.
128
+ if (v.serviceTenant) {
129
+ return {
130
+ clientId: v.clientId?.trim() || undefined,
131
+ applicationType,
132
+ redirectUris: v.redirectUris ?? [],
133
+ postLogoutRedirectUris: v.postLogoutRedirectUris ?? [],
134
+ grantTypes: v.grantTypes ?? ['client_credentials'],
135
+ tokenEndpointAuthMethod: v.tokenEndpointAuthMethod ?? 'client_secret_basic',
136
+ backchannelLogoutUri: v.backchannelLogoutUri || undefined,
137
+ clientName: v.clientName || undefined,
138
+ logoUri: v.logoUri || undefined,
139
+ serviceTenant: v.serviceTenant,
140
+ };
141
+ }
114
142
  return {
115
143
  clientId: v.clientId?.trim() || undefined,
116
144
  applicationType,
@@ -148,6 +176,8 @@ export function clientPartialInput(v) {
148
176
  out.clientName = v.clientName?.trim() || null;
149
177
  if (v.logoUri !== undefined)
150
178
  out.logoUri = v.logoUri?.trim() || null;
179
+ if (v.serviceTenant !== undefined)
180
+ out.serviceTenant = v.serviceTenant;
151
181
  return out;
152
182
  }
153
183
  // ─── Usuários ───────────────────────────────────────────────────────────────
@@ -1,4 +1,6 @@
1
1
  import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { AccountConsoleConfig } from '../account/runtime.js';
3
+ import type { ResolvedPersonalAgentExtension } from '../agents/extension.js';
2
4
  import type { AuthSocialConfig, ResolvedRateLimitConfig } from '../define_config.js';
3
5
  import type { PolicyRouteOption } from './config_locks.js';
4
6
  import type { AuthHostOptions } from './register_auth_host.js';
@@ -35,16 +37,21 @@ export interface AuthHostRuntimeConfig {
35
37
  * Ver `deriveLockedRouteOptions`.
36
38
  */
37
39
  lockedRouteOptions?: PolicyRouteOption[];
38
- /** Personal agents ligados → prefixo das rotas deles (`/agents`) e do Poppy (`/poppy`). */
40
+ /**
41
+ * Personal agents ligados → prefixo das rotas do PACT (`/agents`) e as
42
+ * `personalAgents.extensions` resolvidas (rotas, isenções de CSRF, navegador do agente).
43
+ */
39
44
  personalAgents?: {
40
45
  prefix: string;
41
- poppyPrefix?: string;
46
+ extensions?: readonly ResolvedPersonalAgentExtension[];
42
47
  };
43
48
  /**
44
49
  * `accountSession.acceptIdpSession: false` com `authMethods: { password: false }`:
45
50
  * o console só entra por um `accountLoginUrl` do host (`registerAuthHost` recusa sem ele).
46
51
  */
47
52
  consoleNeedsLoginUrl?: boolean;
53
+ /** `accountConsole` do config: os overrides das páginas de extension. */
54
+ accountConsole?: AccountConsoleConfig;
48
55
  /**
49
56
  * API headless (Clerk-style). Presente quando o host a declarou — o provider o
50
57
  * stash no boot para `registerAuthHost` montar as rotas sem reler o config inteiro.