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

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 (74) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +4 -5
  3. package/build/index.d.ts +5 -11
  4. package/build/index.js +4 -9
  5. package/build/providers/authkit_server_provider.js +6 -8
  6. package/build/src/agents/config.d.ts +9 -13
  7. package/build/src/agents/config.js +17 -11
  8. package/build/src/agents/delegation_service.js +3 -3
  9. package/build/src/agents/delegation_store.d.ts +3 -2
  10. package/build/src/agents/delegation_store.js +6 -5
  11. package/build/src/agents/extension.d.ts +261 -0
  12. package/build/src/agents/extension.js +40 -0
  13. package/build/src/agents/middleware.js +1 -1
  14. package/build/src/agents/protocol.d.ts +4 -4
  15. package/build/src/agents/registry_store.d.ts +3 -2
  16. package/build/src/agents/registry_store.js +1 -1
  17. package/build/src/agents/runtime.d.ts +26 -7
  18. package/build/src/agents/runtime.js +110 -75
  19. package/build/src/audit/audit_sink.js +1 -1
  20. package/build/src/define_config.d.ts +0 -12
  21. package/build/src/define_config.js +9 -7
  22. package/build/src/host/account_api/account_api_controller.js +8 -6
  23. package/build/src/host/account_session_sign_in.d.ts +12 -0
  24. package/build/src/host/account_session_sign_in.js +19 -0
  25. package/build/src/host/admin_console/console_personal_agents_controller.d.ts +1 -6
  26. package/build/src/host/admin_console/console_personal_agents_controller.js +50 -30
  27. package/build/src/host/admin_sessions_service.js +8 -5
  28. package/build/src/host/auth_host_config.d.ts +6 -2
  29. package/build/src/host/controllers/account_apps_controller.js +15 -11
  30. package/build/src/host/csrf.d.ts +3 -3
  31. package/build/src/host/csrf.js +3 -13
  32. package/build/src/host/default_mailer.d.ts +11 -6
  33. package/build/src/host/default_mailer.js +16 -12
  34. package/build/src/host/i18n.d.ts +3 -14
  35. package/build/src/host/i18n.js +15 -13
  36. package/build/src/host/register_auth_host.js +26 -61
  37. package/build/src/host/ui-dist/assets/index-BqyJYRVX.js +67 -0
  38. package/build/src/host/ui-dist/index.html +1 -1
  39. package/build/src/provider/oidc_service.d.ts +1 -1
  40. package/build/src/provider/oidc_service.js +1 -1
  41. package/build/src/schema/ensure.d.ts +14 -0
  42. package/build/src/schema/ensure.js +43 -150
  43. package/package.json +2 -2
  44. package/build/src/agents/poppy/client_registry.d.ts +0 -50
  45. package/build/src/agents/poppy/client_registry.js +0 -260
  46. package/build/src/agents/poppy/config.d.ts +0 -396
  47. package/build/src/agents/poppy/config.js +0 -228
  48. package/build/src/agents/poppy/dpop.d.ts +0 -61
  49. package/build/src/agents/poppy/dpop.js +0 -158
  50. package/build/src/agents/poppy/email_code_sign_in.d.ts +0 -27
  51. package/build/src/agents/poppy/email_code_sign_in.js +0 -140
  52. package/build/src/agents/poppy/errors.d.ts +0 -25
  53. package/build/src/agents/poppy/errors.js +0 -25
  54. package/build/src/agents/poppy/replay.d.ts +0 -25
  55. package/build/src/agents/poppy/replay.js +0 -106
  56. package/build/src/agents/poppy/resource.d.ts +0 -100
  57. package/build/src/agents/poppy/resource.js +0 -129
  58. package/build/src/agents/poppy/safe_fetch.d.ts +0 -48
  59. package/build/src/agents/poppy/safe_fetch.js +0 -262
  60. package/build/src/agents/poppy/service.d.ts +0 -293
  61. package/build/src/agents/poppy/service.js +0 -1458
  62. package/build/src/agents/poppy/session_jwt.d.ts +0 -30
  63. package/build/src/agents/poppy/session_jwt.js +0 -133
  64. package/build/src/agents/poppy/store.d.ts +0 -106
  65. package/build/src/agents/poppy/store.js +0 -261
  66. package/build/src/agents/poppy/web_session.d.ts +0 -39
  67. package/build/src/agents/poppy/web_session.js +0 -122
  68. package/build/src/host/controllers/poppy_consent_controller.d.ts +0 -19
  69. package/build/src/host/controllers/poppy_consent_controller.js +0 -199
  70. package/build/src/host/controllers/poppy_controller.d.ts +0 -33
  71. package/build/src/host/controllers/poppy_controller.js +0 -219
  72. package/build/src/host/poppy_session_key.d.ts +0 -9
  73. package/build/src/host/poppy_session_key.js +0 -9
  74. package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +0 -67
@@ -0,0 +1,40 @@
1
+ const EXTENSION_ID = /^[a-z0-9-]{2,16}$/;
2
+ const GRANT_PREFIX = /^[a-z0-9]+_$/;
3
+ function normalizePrefix(prefix, id) {
4
+ const trimmed = (prefix ?? '').trim().replace(/\/+$/, '');
5
+ if (!trimmed || trimmed === '/') {
6
+ throw new Error(`authkit: personalAgents.extensions "${id}" — prefix is required.`);
7
+ }
8
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
9
+ }
10
+ /** Resolves and validates `personalAgents.extensions`. */
11
+ export function resolvePersonalAgentExtensions(input, context) {
12
+ const out = [];
13
+ for (const extension of input ?? []) {
14
+ const id = extension?.id;
15
+ if (typeof id !== 'string' || !EXTENSION_ID.test(id) || id === 'pact') {
16
+ throw new Error(`authkit: personalAgents.extensions — invalid id "${String(id)}" (2–16 of a-z, 0-9, -; not "pact").`);
17
+ }
18
+ if (out.some((e) => e.id === id)) {
19
+ throw new Error(`authkit: personalAgents.extensions — "${id}" is listed twice.`);
20
+ }
21
+ const definition = extension.resolve(context);
22
+ if (!GRANT_PREFIX.test(definition.grantIdPrefix) || definition.grantIdPrefix === 'agrant_') {
23
+ throw new Error(`authkit: personalAgents.extensions "${id}" — invalid grantIdPrefix "${definition.grantIdPrefix}".`);
24
+ }
25
+ if (out.some((e) => e.grantIdPrefix === definition.grantIdPrefix)) {
26
+ throw new Error(`authkit: personalAgents.extensions "${id}" — grantIdPrefix "${definition.grantIdPrefix}" is already taken.`);
27
+ }
28
+ const prefix = normalizePrefix(definition.prefix, id);
29
+ for (const other of out) {
30
+ if (other.prefix === prefix ||
31
+ other.prefix.startsWith(`${prefix}/`) ||
32
+ prefix.startsWith(`${other.prefix}/`)) {
33
+ throw new Error(`authkit: personalAgents.extensions "${id}" — prefix "${prefix}" overlaps "${other.id}" ("${other.prefix}").`);
34
+ }
35
+ }
36
+ // A plain object literal: its own members are the definition.
37
+ out.push({ ...definition, id, prefix });
38
+ }
39
+ return out;
40
+ }
@@ -40,7 +40,7 @@ export function personalAgentAuth(options = {}) {
40
40
  const mode = options.delegation ?? 'optional';
41
41
  return async (ctx, next) => {
42
42
  const runtime = await personalAgentsFor(ctx);
43
- // Sem PACT (app só-Poppy) não há JWT de agente para verificar.
43
+ // Sem PACT (app só com extensions) não há JWT de agente para verificar.
44
44
  if (!runtime?.config.pact)
45
45
  return ctx.response.notFound();
46
46
  const { protocol } = runtime.config;
@@ -10,10 +10,10 @@ import type { DelegationReceipt } from './delegation_service.js';
10
10
  * MESMA família (JWT do agente + token de delegação) entra como mais um objeto
11
11
  * desta interface, passado direto em `personalAgents.protocol`.
12
12
  *
13
- * O Personal Agent Protocol ("Poppy", Sierra/Meta) tem outro modelo — um
14
- * authorization server OAuth com Sessions e tokens DPoP — e por isso não é um
15
- * adapter daqui: liga-se em `personalAgents.poppy` (ver `poppy/`), ao lado do
16
- * PACT, reaproveitando o núcleo de grants.
13
+ * Um protocolo de outro modelo (ex.: um authorization server OAuth próprio, como o
14
+ * Personal Agent Protocol de `@adonis-agora/authkit-poppy`) não é um adapter daqui:
15
+ * liga-se em `personalAgents.extensions` (ver `extension.ts`), ao lado do PACT,
16
+ * reaproveitando o núcleo de grants.
17
17
  */
18
18
  export interface PersonalAgentProtocol {
19
19
  /** Identificador estável (`'pact'`). */
@@ -1,10 +1,11 @@
1
1
  export declare const REGISTRY_TABLE = "auth_agent_registry";
2
- export type AgentProtocolId = 'pact' | 'poppy';
2
+ /** `'pact'` ou o id de uma extension (`personalAgents.extensions`). */
3
+ export type AgentProtocolId = 'pact' | (string & {});
3
4
  export type AgentRegistryStatus = 'allowed' | 'blocked';
4
5
  export interface AgentRegistryRow {
5
6
  id: string;
6
7
  protocol: AgentProtocolId;
7
- /** PACT: o `iss` do agente. Poppy: o `client_id` (URL do documento de metadata). */
8
+ /** PACT: o `iss` do agente. Extensions: o identificador do agente no protocolo delas. */
8
9
  identifier: string;
9
10
  status: AgentRegistryStatus;
10
11
  name: string | null;
@@ -3,7 +3,7 @@
3
3
  * `auth_agent_registry` (ver `schema/ensure.ts`). SOMA ao `defineConfig`:
4
4
  *
5
5
  * - `allowed`: o agente entra mesmo fora da lista do config (PACT: issuer + JWKS;
6
- * Poppy: `client_id` registrado quando `clients.requireRegistration`).
6
+ * extensions: o identificador do agente no protocolo delas).
7
7
  * - `blocked`: o agente é recusado mesmo que o config o liste — bloquear é a
8
8
  * ação de emergência, e precisa valer sem deploy.
9
9
  *
@@ -4,8 +4,7 @@ import { PersonalAgentVerifier } from './agent_identity.js';
4
4
  import type { ResolvedPersonalAgentsConfig } from './config.js';
5
5
  import { PersonalAgentDelegation } from './delegation_service.js';
6
6
  import { DelegationStore, type GrantRow } from './delegation_store.js';
7
- import type { JsonFetcher } from './poppy/safe_fetch.js';
8
- import { PoppyService } from './poppy/service.js';
7
+ import type { PersonalAgentProtocolRuntime, ResolvedPersonalAgentExtension } from './extension.js';
9
8
  import { type AgentProtocolId, AgentRegistryStore } from './registry_store.js';
10
9
  /** URLs públicas do authorization server de delegação. */
11
10
  export interface PersonalAgentUrls {
@@ -31,9 +30,15 @@ export interface PersonalAgentsRuntime {
31
30
  verifier: PersonalAgentVerifier;
32
31
  /** `null` quando o app não configurou `personalAgents.delegation`. */
33
32
  delegation: PersonalAgentDelegation | null;
34
- /** `null` quando o app não configurou `personalAgents.poppy`. */
35
- poppy: PoppyService | null;
36
- /** Revoga um grant de personal agent DA CONTA (PACT ou Poppy). */
33
+ /**
34
+ * Runtimes de `personalAgents.extensions`, por id do protocolo. Vazio sem extensions ou sem
35
+ * banco.
36
+ */
37
+ extensions: Map<string, {
38
+ extension: ResolvedPersonalAgentExtension;
39
+ runtime: PersonalAgentProtocolRuntime;
40
+ }>;
41
+ /** Revoga um grant de personal agent DA CONTA (PACT ou de uma extension). */
37
42
  revokeAgentGrant(accountId: string, grantId: string): Promise<boolean>;
38
43
  /** Revoga todos os grants de personal agents da conta. Devolve quantos estavam vivos. */
39
44
  revokeAllAgentGrants(accountId: string): Promise<number>;
@@ -52,8 +57,6 @@ export interface PersonalAgentsRuntime {
52
57
  export interface BuildPersonalAgentsOptions {
53
58
  /** Resolve serviços do container (redis do replay store). */
54
59
  make?: (binding: string) => Promise<any>;
55
- /** Busca de JSON (metadata/JWKS dos agentes Poppy) — injetável nos testes. */
56
- fetchJson?: JsonFetcher;
57
60
  now?: () => Date;
58
61
  }
59
62
  /**
@@ -63,3 +66,19 @@ export interface BuildPersonalAgentsOptions {
63
66
  export declare function buildPersonalAgentsRuntime(service: OidcService, makeDb: () => Promise<any>, options?: BuildPersonalAgentsOptions): Promise<PersonalAgentsRuntime | null>;
64
67
  /** Runtime a partir de uma request. */
65
68
  export declare function personalAgentsFor(ctx: HttpContext): Promise<PersonalAgentsRuntime | null>;
69
+ /** Where {@link personalAgentExtensionRuntime} builds the runtime from, outside a request. */
70
+ export interface PersonalAgentRuntimeSource {
71
+ service: OidcService;
72
+ /** The Lucid `Database` (the `lucid.db` binding). */
73
+ db(): Promise<unknown>;
74
+ make?(binding: string): Promise<unknown>;
75
+ now?(): Date;
76
+ }
77
+ /**
78
+ * The runtime an extension's `setup` built (`personalAgents.extensions`), by protocol id. `null`
79
+ * when the extension is not configured (or there is no database). Takes the request, an explicit
80
+ * source (tests, scripts) or nothing (the booted app).
81
+ *
82
+ * Stable for 1.x — see `extension.ts`.
83
+ */
84
+ export declare function personalAgentExtensionRuntime<R extends PersonalAgentProtocolRuntime = PersonalAgentProtocolRuntime>(id: string, from?: HttpContext | PersonalAgentRuntimeSource): Promise<R | null>;
@@ -1,13 +1,9 @@
1
+ import { getBootedApp } from '../../services/booted_app.js';
1
2
  import { supportsAccountStatus } from '../accounts/account_store.js';
2
3
  import { findMcpResource } from '../mcp/mcp_oauth.js';
3
4
  import { PersonalAgentVerifier } from './agent_identity.js';
4
5
  import { PersonalAgentDelegation } from './delegation_service.js';
5
6
  import { DelegationStore } from './delegation_store.js';
6
- import { PoppyClientRegistry } from './poppy/client_registry.js';
7
- import { hmacNonceSource } from './poppy/dpop.js';
8
- import { databaseReplayStore, memoryLimiter, memoryReplayStore, redisReplayStore, } from './poppy/replay.js';
9
- import { PoppyService, poppyUrls } from './poppy/service.js';
10
- import { PoppyStore } from './poppy/store.js';
11
7
  import { AgentRegistryStore } from './registry_store.js';
12
8
  import { keystoreSigner } from './signer.js';
13
9
  /**
@@ -27,9 +23,10 @@ export function personalAgentUrls(oidcIssuer, prefix) {
27
23
  };
28
24
  }
29
25
  const runtimes = new WeakMap();
30
- /** Ponte Poppy → oidc-provider: Bearer de MCP de Sessions logadas viram access tokens dele. */
31
- function mcpBridge(service, poppyApis) {
26
+ /** Access tokens do oidc-provider para resources MCP (ver {@link PersonalAgentAccessTokens}). */
27
+ function accessTokens(service) {
32
28
  return {
29
+ mcpEnabled: !!service.config.mcp?.enabled,
33
30
  isMcpResource(url) {
34
31
  try {
35
32
  return findMcpResource(url, service.config.issuer, service.config.mcp) !== null;
@@ -42,11 +39,6 @@ function mcpBridge(service, poppyApis) {
42
39
  const provider = service.provider;
43
40
  if (!provider?.AccessToken || !provider?.Grant)
44
41
  return null;
45
- // Só faz sentido quando a integração MCP do authkit está ligada, ou o MCP é de `apis`.
46
- if (!service.config.mcp?.enabled &&
47
- !poppyApis.some((a) => a.type === 'mcp' && a.url.replace(/\/+$/, '') === input.resource.replace(/\/+$/, ''))) {
48
- return null;
49
- }
50
42
  const remaining = Math.max(60, Math.floor((input.grantExpiresAt.getTime() - Date.now()) / 1000));
51
43
  let grant = await provider.Grant.find(input.grantId);
52
44
  if (!grant) {
@@ -63,13 +55,13 @@ function mcpBridge(service, poppyApis) {
63
55
  clientId: input.clientId,
64
56
  grantId: input.grantId,
65
57
  scope: input.scope,
66
- gty: 'poppy',
58
+ gty: input.grantType,
67
59
  expiresIn: input.expiresIn,
68
60
  });
69
61
  at.aud = input.resource;
70
62
  return at.save();
71
63
  },
72
- async revoke(grantId) {
64
+ async revokeGrant(grantId) {
73
65
  const provider = service.provider;
74
66
  if (!provider?.Grant)
75
67
  return;
@@ -80,18 +72,32 @@ function mcpBridge(service, poppyApis) {
80
72
  },
81
73
  };
82
74
  }
83
- async function resolveReplay(replay, conn, make) {
84
- if (replay === 'memory')
85
- return memoryReplayStore();
86
- if (replay === 'database')
87
- return databaseReplayStore(conn);
88
- if (typeof replay.claim === 'function')
89
- return replay;
90
- const name = replay.redis;
91
- if (!make)
92
- throw new Error('authkit: personalAgents.poppy.replay { redis } exige o container.');
93
- const redis = await make('redis');
94
- return redisReplayStore(redis.connection(name));
75
+ /** O ledger de grants visto por uma extension: só os ids com o prefixo dela. */
76
+ function scopedLedger(store, prefix) {
77
+ const mine = (id) => typeof id === 'string' && id.startsWith(prefix);
78
+ return {
79
+ async insertGrant(grant) {
80
+ if (!mine(grant.id)) {
81
+ throw new Error(`authkit: grant "${grant.id}" fora do prefixo "${prefix}" da extension.`);
82
+ }
83
+ await store.insertGrant(grant);
84
+ },
85
+ async findGrant(id) {
86
+ return mine(id) ? store.findGrant(id) : null;
87
+ },
88
+ async listGrants(accountId) {
89
+ return (await store.listGrants(accountId)).filter((g) => mine(g.id));
90
+ },
91
+ async unrevokedGrantIdsOfClient(clientId) {
92
+ return (await store.unrevokedGrantIdsOfClient(clientId)).filter(mine);
93
+ },
94
+ async revokeGrantById(id, at) {
95
+ if (mine(id))
96
+ await store.revokeGrantById(id, at);
97
+ },
98
+ insertRefreshToken: (token) => store.insertRefreshToken(token),
99
+ findRefreshToken: (tokenHash) => store.findRefreshToken(tokenHash),
100
+ };
95
101
  }
96
102
  /**
97
103
  * Config com o registro do console por cima: um agente BLOQUEADO no console é
@@ -112,21 +118,7 @@ function withRegistry(config, registry) {
112
118
  }
113
119
  return null;
114
120
  };
115
- const poppy = config.poppy;
116
- return {
117
- ...config,
118
- resolveAgent,
119
- poppy: poppy && {
120
- ...poppy,
121
- clients: {
122
- ...poppy.clients,
123
- isBlocked: async (id) => (await poppy.clients.isBlocked(id)) ||
124
- (await registry.find('poppy', id))?.status === 'blocked',
125
- isRegistered: async (id) => (await poppy.clients.isRegistered(id)) ||
126
- (await registry.find('poppy', id))?.status === 'allowed',
127
- },
128
- },
129
- };
121
+ return { ...config, resolveAgent };
130
122
  }
131
123
  /**
132
124
  * Monta (uma vez por `OidcService`) o runtime de personal agents. `null` quando
@@ -149,7 +141,7 @@ export function buildPersonalAgentsRuntime(service, makeDb, options = {}) {
149
141
  };
150
142
  let conn = null;
151
143
  const connection = service.config.schema.connection;
152
- if (config.delegation || config.poppy) {
144
+ if (config.delegation || (config.extensions ?? []).length > 0) {
153
145
  const db = await makeDb();
154
146
  conn = () => (connection ? db.connection(connection) : db.connection());
155
147
  }
@@ -177,39 +169,53 @@ export function buildPersonalAgentsRuntime(service, makeDb, options = {}) {
177
169
  isAccountActive,
178
170
  });
179
171
  }
180
- let poppy = null;
181
- if (effective.poppy && store && conn) {
182
- const cfg = effective.poppy;
183
- const limiter = cfg.rateLimit === false
184
- ? null
185
- : typeof cfg.rateLimit === 'function'
186
- ? cfg.rateLimit
187
- : memoryLimiter(cfg.rateLimit);
188
- const secret = service.deriveSecret('poppy-dpop-nonce');
189
- poppy = new PoppyService({
190
- cfg,
191
- urls: poppyUrls(service.config.issuer, cfg.prefix),
192
- store: new PoppyStore(conn),
193
- grants: store,
194
- registry: new PoppyClientRegistry(cfg, { fetchJson: options.fetchJson }),
195
- replay: await resolveReplay(cfg.replay, conn, options.make),
196
- limiter,
197
- nonces: hmacNonceSource(secret),
198
- isAccountActive,
199
- mcp: mcpBridge(service, cfg.apis),
200
- now: options.now,
201
- });
172
+ const extensions = new Map();
173
+ const now = () => options.now?.() ?? new Date();
174
+ if (store && conn && registry) {
175
+ const db = conn;
176
+ const tokens = accessTokens(service);
177
+ for (const extension of config.extensions ?? []) {
178
+ const runtime = await extension.setup({
179
+ protocol: extension.id,
180
+ issuer: service.config.issuer,
181
+ db: () => db(),
182
+ grants: scopedLedger(store, extension.grantIdPrefix),
183
+ registryStatus: async (identifier) => (await registry.find(extension.id, identifier))?.status ?? null,
184
+ deriveSecret: (label) => service.deriveSecret(label),
185
+ isAccountActive,
186
+ make: async (binding) => {
187
+ if (!options.make) {
188
+ throw new Error(`authkit: o binding "${binding}" exige o container do app.`);
189
+ }
190
+ return options.make(binding);
191
+ },
192
+ accessTokens: tokens,
193
+ now,
194
+ });
195
+ extensions.set(extension.id, { extension, runtime });
196
+ }
202
197
  }
198
+ /** A extension dona de um grant, pelo prefixo do id. */
199
+ const ownerOf = (grantId) => {
200
+ for (const entry of extensions.values()) {
201
+ if (grantId.startsWith(entry.extension.grantIdPrefix))
202
+ return entry;
203
+ }
204
+ return null;
205
+ };
206
+ const isExtensionGrant = (grantId) => (config.extensions ?? []).some((e) => grantId.startsWith(e.grantIdPrefix));
203
207
  const revokeAgentGrant = async (accountId, grantId) => {
204
- if (grantId.startsWith('pgrant_')) {
205
- return poppy ? poppy.revokeAccountGrant(accountId, grantId) : false;
208
+ if (isExtensionGrant(grantId)) {
209
+ const owner = ownerOf(grantId);
210
+ return owner ? owner.runtime.revokeAccountGrant(accountId, grantId) : false;
206
211
  }
207
212
  return delegation ? delegation.revokeGrant(accountId, grantId) : false;
208
213
  };
209
214
  const revokeAllAgentGrants = async (accountId) => {
210
215
  let count = 0;
211
- if (poppy)
212
- count += await poppy.revokeAllAccountGrants(accountId);
216
+ for (const { runtime } of extensions.values()) {
217
+ count += await runtime.revokeAllAccountGrants(accountId);
218
+ }
213
219
  if (delegation)
214
220
  count += await delegation.revokeAllGrants(accountId);
215
221
  return count;
@@ -222,10 +228,11 @@ export function buildPersonalAgentsRuntime(service, makeDb, options = {}) {
222
228
  // Expirado já não vale: revogar não muda nada (e não vira evento de auditoria).
223
229
  if (!grant || grant.revokedAt || grant.expiresAt <= now)
224
230
  return null;
225
- if (grantId.startsWith('pgrant_')) {
226
- if (!poppy)
231
+ if (isExtensionGrant(grantId)) {
232
+ const owner = ownerOf(grantId);
233
+ if (!owner)
227
234
  return null;
228
- await poppy.revokeGrant(grantId);
235
+ await owner.runtime.revokeGrant(grantId);
229
236
  }
230
237
  else {
231
238
  await store.revokeGrantById(grantId, now);
@@ -235,9 +242,11 @@ export function buildPersonalAgentsRuntime(service, makeDb, options = {}) {
235
242
  const disconnectAgent = async (protocol, identifier) => {
236
243
  if (!store)
237
244
  return 0;
238
- if (protocol === 'poppy')
239
- return poppy ? poppy.disconnectClient(identifier) : 0;
240
- const ids = (await store.unrevokedGrantIdsOfClient(identifier)).filter((id) => !id.startsWith('pgrant_'));
245
+ if (protocol !== 'pact') {
246
+ const entry = extensions.get(protocol);
247
+ return entry ? entry.runtime.disconnectAgent(identifier) : 0;
248
+ }
249
+ const ids = (await store.unrevokedGrantIdsOfClient(identifier)).filter((id) => !isExtensionGrant(id));
241
250
  const at = options.now?.() ?? new Date();
242
251
  for (const id of ids)
243
252
  await store.revokeGrantById(id, at);
@@ -248,7 +257,7 @@ export function buildPersonalAgentsRuntime(service, makeDb, options = {}) {
248
257
  urls,
249
258
  verifier: new PersonalAgentVerifier(effective),
250
259
  delegation,
251
- poppy,
260
+ extensions,
252
261
  revokeAgentGrant,
253
262
  revokeAllAgentGrants,
254
263
  registry,
@@ -270,3 +279,29 @@ export async function personalAgentsFor(ctx) {
270
279
  make: (binding) => ctx.containerResolver.make(binding),
271
280
  });
272
281
  }
282
+ /**
283
+ * The runtime an extension's `setup` built (`personalAgents.extensions`), by protocol id. `null`
284
+ * when the extension is not configured (or there is no database). Takes the request, an explicit
285
+ * source (tests, scripts) or nothing (the booted app).
286
+ *
287
+ * Stable for 1.x — see `extension.ts`.
288
+ */
289
+ export async function personalAgentExtensionRuntime(id, from) {
290
+ let runtime;
291
+ if (from && 'containerResolver' in from) {
292
+ runtime = await personalAgentsFor(from);
293
+ }
294
+ else if (from) {
295
+ const source = from;
296
+ runtime = await buildPersonalAgentsRuntime(source.service, () => source.db(), {
297
+ make: source.make ? (binding) => source.make(binding) : undefined,
298
+ now: source.now ? () => source.now() : undefined,
299
+ });
300
+ }
301
+ else {
302
+ const app = getBootedApp();
303
+ const service = (await app.container.make('authkit.server'));
304
+ runtime = await buildPersonalAgentsRuntime(service, () => app.container.make('lucid.db'), { make: (binding) => app.container.make(binding) });
305
+ }
306
+ return runtime?.extensions.get(id)?.runtime ?? null;
307
+ }
@@ -114,7 +114,7 @@ export const AUDIT_EVENT_TYPES = [
114
114
  'agent.delegation_denied',
115
115
  'agent.grant_revoked',
116
116
  // Console admin: um admin revogou a conexão de um usuário, ou liberou/bloqueou um
117
- // agente (PACT pelo `iss`, Poppy pelo `client_id`).
117
+ // agente (PACT pelo `iss`; uma extension pelo identificador dela).
118
118
  'agent.grant_revoked_by_admin',
119
119
  'agent.allowed',
120
120
  'agent.blocked',
@@ -112,18 +112,6 @@ export interface MailHooks {
112
112
  email: string;
113
113
  sudoUrl: string;
114
114
  }) => Promise<void>;
115
- /**
116
- * Código do Mediated Sign-In pronto do Poppy (`poppyEmailCodeSignIn`): um personal
117
- * agent quer entrar na conta e a pessoa precisa dar o código a ele. Sem o hook, o
118
- * host-kit envia pelo mailer default, com branding e i18n.
119
- */
120
- onPoppySignInCode?: (data: {
121
- email: string;
122
- code: string;
123
- /** Nome do agente (metadata dele, ou o host do `client_id`). */
124
- agentName: string;
125
- expiresInMinutes: number;
126
- }) => Promise<void>;
127
115
  /**
128
116
  * Disparado num login bem-sucedido a partir de um IP NOVO para a conta (nenhum
129
117
  * `login.success` anterior desse IP no audit sink), quando `notifications.newLoginEmail`
@@ -6,7 +6,7 @@ import { composeAuditSink, resolveEvents } from './events/dispatcher.js';
6
6
  import { resolveBotProtection, } from './host/bot_protection.js';
7
7
  import { resolveBranding } from './host/branding.js';
8
8
  import { deriveLockedRouteOptions, deriveLockedSettingKeys, } from './host/config_locks.js';
9
- import { resolveMessages } from './host/i18n.js';
9
+ import { registerExtensionMessages, resolveMessages, } from './host/i18n.js';
10
10
  import { resolveOtpLoginConfig, } from './host/otp_login.js';
11
11
  import { edgeRenderer } from './host/renderers/edge_renderer.js';
12
12
  import { warnUnsatisfiableSudoConfig } from './host/sudo/satisfiability.js';
@@ -343,7 +343,7 @@ export function defineConfig(config) {
343
343
  }
344
344
  // Personal agents com delegação assinam tokens e recibos com este keystore,
345
345
  // e o PACT só admite ES256/RS256 — falha no boot, não na primeira troca de token.
346
- const personalAgents = resolvePersonalAgentsConfig(config.personalAgents);
346
+ const personalAgents = resolvePersonalAgentsConfig(config.personalAgents, config.issuer);
347
347
  if (personalAgents?.delegation)
348
348
  assertDelegationSigningKey(jwks);
349
349
  // Debaixo do mountPath, a rota curinga do oidc-provider (`${mount}/*`)
@@ -355,11 +355,13 @@ export function defineConfig(config) {
355
355
  (personalAgents.prefix === oidcMount || personalAgents.prefix.startsWith(`${oidcMount}/`))) {
356
356
  throw new Error(`authkit: personalAgents.prefix "${personalAgents.prefix}" não pode ficar debaixo do mountPath do OIDC ("${oidcMount}").`);
357
357
  }
358
- const poppyPrefix = personalAgents?.poppy?.prefix;
359
- if (poppyPrefix &&
360
- oidcMount &&
361
- (poppyPrefix === oidcMount || poppyPrefix.startsWith(`${oidcMount}/`))) {
362
- throw new Error(`authkit: personalAgents.poppy.prefix "${poppyPrefix}" não pode ficar debaixo do mountPath do OIDC ("${oidcMount}").`);
358
+ for (const extension of personalAgents?.extensions ?? []) {
359
+ if (extension.messages)
360
+ registerExtensionMessages(extension.messages);
361
+ if (oidcMount &&
362
+ (extension.prefix === oidcMount || extension.prefix.startsWith(`${oidcMount}/`))) {
363
+ throw new Error(`authkit: o prefixo "${extension.prefix}" de personalAgents.extensions "${extension.id}" não pode ficar debaixo do mountPath do OIDC ("${oidcMount}").`);
364
+ }
363
365
  }
364
366
  // BACKSTOP DE SUDO. Um host cujo `sudo.methods` não tem um único método
365
367
  // satisfazível por conta sem senha fica bricado para TODA operação sob
@@ -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
+ }
@@ -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
  }>;