@adonis-agora/authkit-server 0.77.0 → 0.79.0

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 (67) hide show
  1. package/README.md +4 -0
  2. package/build/host/views/agents/consent.edge +13 -2
  3. package/build/host/views/agents/done.edge +3 -0
  4. package/build/index.d.ts +10 -0
  5. package/build/index.js +8 -0
  6. package/build/providers/authkit_server_provider.js +15 -1
  7. package/build/src/agents/config.d.ts +22 -3
  8. package/build/src/agents/config.js +22 -4
  9. package/build/src/agents/delegation_service.js +18 -8
  10. package/build/src/agents/delegation_store.d.ts +16 -0
  11. package/build/src/agents/delegation_store.js +44 -0
  12. package/build/src/agents/middleware.js +2 -1
  13. package/build/src/agents/poppy/client_registry.d.ts +45 -0
  14. package/build/src/agents/poppy/client_registry.js +226 -0
  15. package/build/src/agents/poppy/config.d.ts +365 -0
  16. package/build/src/agents/poppy/config.js +208 -0
  17. package/build/src/agents/poppy/dpop.d.ts +61 -0
  18. package/build/src/agents/poppy/dpop.js +158 -0
  19. package/build/src/agents/poppy/errors.d.ts +20 -0
  20. package/build/src/agents/poppy/errors.js +22 -0
  21. package/build/src/agents/poppy/replay.d.ts +25 -0
  22. package/build/src/agents/poppy/replay.js +106 -0
  23. package/build/src/agents/poppy/resource.d.ts +95 -0
  24. package/build/src/agents/poppy/resource.js +125 -0
  25. package/build/src/agents/poppy/safe_fetch.d.ts +48 -0
  26. package/build/src/agents/poppy/safe_fetch.js +256 -0
  27. package/build/src/agents/poppy/service.d.ts +286 -0
  28. package/build/src/agents/poppy/service.js +1464 -0
  29. package/build/src/agents/poppy/store.d.ts +111 -0
  30. package/build/src/agents/poppy/store.js +269 -0
  31. package/build/src/agents/poppy/web_session.d.ts +32 -0
  32. package/build/src/agents/poppy/web_session.js +104 -0
  33. package/build/src/agents/protocol.d.ts +8 -4
  34. package/build/src/agents/registry_store.d.ts +41 -0
  35. package/build/src/agents/registry_store.js +138 -0
  36. package/build/src/agents/runtime.d.ts +30 -1
  37. package/build/src/agents/runtime.js +221 -14
  38. package/build/src/audit/audit_sink.d.ts +1 -1
  39. package/build/src/audit/audit_sink.js +6 -0
  40. package/build/src/define_config.js +6 -0
  41. package/build/src/host/account_api/account_api_controller.d.ts +12 -1
  42. package/build/src/host/account_api/account_api_controller.js +10 -5
  43. package/build/src/host/admin_api/dto.d.ts +1 -1
  44. package/build/src/host/admin_console/admin_shell_controller.js +7 -0
  45. package/build/src/host/admin_console/console_personal_agents_controller.d.ts +83 -0
  46. package/build/src/host/admin_console/console_personal_agents_controller.js +337 -0
  47. package/build/src/host/admin_sessions_service.js +8 -5
  48. package/build/src/host/auth_host_config.d.ts +2 -1
  49. package/build/src/host/controllers/account_apps_controller.js +13 -3
  50. package/build/src/host/controllers/poppy_consent_controller.d.ts +19 -0
  51. package/build/src/host/controllers/poppy_consent_controller.js +173 -0
  52. package/build/src/host/controllers/poppy_controller.d.ts +25 -0
  53. package/build/src/host/controllers/poppy_controller.js +194 -0
  54. package/build/src/host/csrf.d.ts +7 -0
  55. package/build/src/host/csrf.js +16 -1
  56. package/build/src/host/i18n.d.ts +4 -0
  57. package/build/src/host/i18n.js +4 -0
  58. package/build/src/host/poppy_session_key.d.ts +9 -0
  59. package/build/src/host/poppy_session_key.js +9 -0
  60. package/build/src/host/register_auth_host.js +76 -0
  61. package/build/src/host/ui-dist/assets/index-LrScaR9C.js +155 -0
  62. package/build/src/host/ui-dist/index.html +1 -1
  63. package/build/src/provider/oidc_service.d.ts +5 -0
  64. package/build/src/provider/oidc_service.js +9 -2
  65. package/build/src/schema/ensure.js +152 -0
  66. package/package.json +2 -2
  67. package/build/src/host/ui-dist/assets/index-CIqtoNcO.js +0 -155
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Persistência do Poppy — tabelas LIB-OWNED (ver `schema/ensure.ts`): Sessions,
3
+ * Session Tokens (só o hash), pedidos de sign-in (autorização Direct, códigos,
4
+ * device codes, Mediated) e `jti` vistos. Os Account Tokens reaproveitam
5
+ * `auth_agent_grants` + `auth_agent_refresh_tokens` (ver `DelegationStore`).
6
+ *
7
+ * Mesmas regras do `DelegationStore`: query builder puro, datas comparadas em JS
8
+ * depois de buscar a linha pela chave, transições de estado atômicas por
9
+ * `where status = from`.
10
+ */
11
+ export declare const SESSION_TABLE = "auth_poppy_sessions";
12
+ export declare const TOKEN_TABLE = "auth_poppy_tokens";
13
+ export declare const REQUEST_TABLE = "auth_poppy_requests";
14
+ export interface SessionRow {
15
+ id: string;
16
+ clientId: string;
17
+ userId: string;
18
+ /** Conta a que a Session está PRESA (uma vez logada, não troca — §4.4). */
19
+ accountId: string | null;
20
+ /** Grant (Account Token) com que está logada agora; `null` = deslogada. */
21
+ grantId: string | null;
22
+ scope: string;
23
+ expiresAt: Date;
24
+ endedAt: Date | null;
25
+ createdAt: Date;
26
+ updatedAt: Date;
27
+ }
28
+ export interface TokenRow {
29
+ tokenHash: string;
30
+ sessionId: string;
31
+ clientId: string;
32
+ userId: string;
33
+ accountId: string | null;
34
+ grantId: string | null;
35
+ scope: string;
36
+ resource: string | null;
37
+ /** Thumbprint da chave DPoP; `null` = Bearer (só MCP). */
38
+ jkt: string | null;
39
+ expiresAt: Date;
40
+ createdAt: Date;
41
+ }
42
+ export type RequestKind = 'authorize' | 'device' | 'mediated';
43
+ export type RequestStatus = 'pending' | 'approved' | 'denied' | 'consumed' | 'failed';
44
+ export interface RequestRow {
45
+ id: string;
46
+ kind: RequestKind;
47
+ /** Hash do código de autorização / device code. */
48
+ codeHash: string | null;
49
+ userCode: string | null;
50
+ clientId: string;
51
+ sessionId: string | null;
52
+ accountId: string | null;
53
+ requestedScope: string;
54
+ grantedScope: string | null;
55
+ redirectUri: string | null;
56
+ codeChallenge: string | null;
57
+ status: RequestStatus;
58
+ attempts: number;
59
+ intervalSeconds: number | null;
60
+ lastPolledAt: Date | null;
61
+ data: Record<string, unknown> | null;
62
+ expiresAt: Date;
63
+ createdAt: Date;
64
+ }
65
+ export declare class PoppyStore {
66
+ private conn;
67
+ constructor(conn: () => any);
68
+ insertSession(row: Omit<SessionRow, 'endedAt'>): Promise<void>;
69
+ findSession(id: string): Promise<SessionRow | null>;
70
+ /** Estende a vida da Session (uso). */
71
+ touchSession(id: string, expiresAt: Date, at: Date): Promise<void>;
72
+ /**
73
+ * Loga a Session numa conta. Atômico: só se ainda estiver deslogada ou presa
74
+ * à MESMA conta. `false` = presa a outra conta (ou terminou) nesse meio-tempo.
75
+ */
76
+ signInSession(id: string, patch: {
77
+ accountId: string;
78
+ grantId: string;
79
+ scope: string;
80
+ expiresAt: Date;
81
+ at: Date;
82
+ }): Promise<boolean>;
83
+ /** Sign-out (§4.9): as Sessions logadas com o grant continuam, deslogadas. */
84
+ signOutSessionsOfGrant(grantId: string, at: Date): Promise<number>;
85
+ /** Encerra todas as Sessions de um agente (bloqueio no console): os tokens param na hora. */
86
+ endSessionsOfClient(clientId: string, at: Date): Promise<number>;
87
+ insertToken(row: TokenRow): Promise<void>;
88
+ findToken(hash: string): Promise<TokenRow | null>;
89
+ deleteToken(hash: string): Promise<boolean>;
90
+ deleteTokensExpiredBefore(before: Date): Promise<void>;
91
+ insertRequest(row: Omit<RequestRow, 'attempts' | 'lastPolledAt' | 'grantedScope'> & {
92
+ grantedScope?: string | null;
93
+ }): Promise<void>;
94
+ findRequest(id: string): Promise<RequestRow | null>;
95
+ findRequestByCodeHash(hash: string): Promise<RequestRow | null>;
96
+ findRequestByUserCode(userCode: string): Promise<RequestRow | null>;
97
+ /** Transição atômica — só se o pedido ainda está em `from`. */
98
+ transitionRequest(id: string, from: RequestStatus, patch: {
99
+ status: RequestStatus;
100
+ accountId?: string;
101
+ grantedScope?: string;
102
+ codeHash?: string;
103
+ attempts?: number;
104
+ expiresAt?: Date;
105
+ }): Promise<boolean>;
106
+ /** Conta uma tentativa errada (atômico sobre o valor lido). */
107
+ bumpAttempts(id: string, from: number): Promise<boolean>;
108
+ setRequestData(id: string, data: Record<string, unknown>): Promise<void>;
109
+ markPolled(id: string, at: Date): Promise<void>;
110
+ deleteRequestsExpiredBefore(before: Date): Promise<void>;
111
+ }
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Persistência do Poppy — tabelas LIB-OWNED (ver `schema/ensure.ts`): Sessions,
3
+ * Session Tokens (só o hash), pedidos de sign-in (autorização Direct, códigos,
4
+ * device codes, Mediated) e `jti` vistos. Os Account Tokens reaproveitam
5
+ * `auth_agent_grants` + `auth_agent_refresh_tokens` (ver `DelegationStore`).
6
+ *
7
+ * Mesmas regras do `DelegationStore`: query builder puro, datas comparadas em JS
8
+ * depois de buscar a linha pela chave, transições de estado atômicas por
9
+ * `where status = from`.
10
+ */
11
+ export const SESSION_TABLE = 'auth_poppy_sessions';
12
+ export const TOKEN_TABLE = 'auth_poppy_tokens';
13
+ export const REQUEST_TABLE = 'auth_poppy_requests';
14
+ function toDate(value) {
15
+ if (value instanceof Date)
16
+ return value;
17
+ if (typeof value === 'number')
18
+ return new Date(value);
19
+ if (typeof value === 'string' && /^\d+$/.test(value))
20
+ return new Date(Number(value));
21
+ return new Date(String(value));
22
+ }
23
+ function toDateOrNull(value) {
24
+ return value === null || value === undefined ? null : toDate(value);
25
+ }
26
+ function affected(result) {
27
+ if (typeof result === 'number')
28
+ return result;
29
+ if (Array.isArray(result))
30
+ return result.length;
31
+ return 0;
32
+ }
33
+ function toSession(row) {
34
+ return {
35
+ id: row.id,
36
+ clientId: row.client_id,
37
+ userId: row.user_id,
38
+ accountId: row.account_id ?? null,
39
+ grantId: row.grant_id ?? null,
40
+ scope: row.scope ?? '',
41
+ expiresAt: toDate(row.expires_at),
42
+ endedAt: toDateOrNull(row.ended_at),
43
+ createdAt: toDate(row.created_at),
44
+ updatedAt: toDate(row.updated_at),
45
+ };
46
+ }
47
+ function toToken(row) {
48
+ return {
49
+ tokenHash: row.token_hash,
50
+ sessionId: row.session_id,
51
+ clientId: row.client_id,
52
+ userId: row.user_id,
53
+ accountId: row.account_id ?? null,
54
+ grantId: row.grant_id ?? null,
55
+ scope: row.scope ?? '',
56
+ resource: row.resource ?? null,
57
+ jkt: row.jkt ?? null,
58
+ expiresAt: toDate(row.expires_at),
59
+ createdAt: toDate(row.created_at),
60
+ };
61
+ }
62
+ function toRequest(row) {
63
+ let data = null;
64
+ if (row.data) {
65
+ try {
66
+ data = JSON.parse(row.data);
67
+ }
68
+ catch {
69
+ data = null;
70
+ }
71
+ }
72
+ return {
73
+ id: row.id,
74
+ kind: row.kind,
75
+ codeHash: row.code_hash ?? null,
76
+ userCode: row.user_code ?? null,
77
+ clientId: row.client_id,
78
+ sessionId: row.session_id ?? null,
79
+ accountId: row.account_id ?? null,
80
+ requestedScope: row.requested_scope ?? '',
81
+ grantedScope: row.granted_scope ?? null,
82
+ redirectUri: row.redirect_uri ?? null,
83
+ codeChallenge: row.code_challenge ?? null,
84
+ status: row.status,
85
+ attempts: Number(row.attempts ?? 0),
86
+ intervalSeconds: row.interval_seconds === null || row.interval_seconds === undefined
87
+ ? null
88
+ : Number(row.interval_seconds),
89
+ lastPolledAt: toDateOrNull(row.last_polled_at),
90
+ data,
91
+ expiresAt: toDate(row.expires_at),
92
+ createdAt: toDate(row.created_at),
93
+ };
94
+ }
95
+ export class PoppyStore {
96
+ conn;
97
+ constructor(conn) {
98
+ this.conn = conn;
99
+ }
100
+ // ─── sessions ─────────────────────────────────────────────────────────────
101
+ async insertSession(row) {
102
+ await this.conn().table(SESSION_TABLE).insert({
103
+ id: row.id,
104
+ client_id: row.clientId,
105
+ user_id: row.userId,
106
+ account_id: row.accountId,
107
+ grant_id: row.grantId,
108
+ scope: row.scope,
109
+ expires_at: row.expiresAt,
110
+ created_at: row.createdAt,
111
+ updated_at: row.updatedAt,
112
+ });
113
+ }
114
+ async findSession(id) {
115
+ if (typeof id !== 'string' || !id)
116
+ return null;
117
+ const row = await this.conn().from(SESSION_TABLE).where('id', id).first();
118
+ return row ? toSession(row) : null;
119
+ }
120
+ /** Estende a vida da Session (uso). */
121
+ async touchSession(id, expiresAt, at) {
122
+ await this.conn()
123
+ .from(SESSION_TABLE)
124
+ .where('id', id)
125
+ .whereNull('ended_at')
126
+ .update({ expires_at: expiresAt, updated_at: at });
127
+ }
128
+ /**
129
+ * Loga a Session numa conta. Atômico: só se ainda estiver deslogada ou presa
130
+ * à MESMA conta. `false` = presa a outra conta (ou terminou) nesse meio-tempo.
131
+ */
132
+ async signInSession(id, patch) {
133
+ const result = await this.conn()
134
+ .from(SESSION_TABLE)
135
+ .where('id', id)
136
+ .whereNull('ended_at')
137
+ .where((q) => q.whereNull('account_id').orWhere('account_id', patch.accountId))
138
+ .update({
139
+ account_id: patch.accountId,
140
+ grant_id: patch.grantId,
141
+ scope: patch.scope,
142
+ expires_at: patch.expiresAt,
143
+ updated_at: patch.at,
144
+ });
145
+ return affected(result) > 0;
146
+ }
147
+ /** Sign-out (§4.9): as Sessions logadas com o grant continuam, deslogadas. */
148
+ async signOutSessionsOfGrant(grantId, at) {
149
+ const result = await this.conn()
150
+ .from(SESSION_TABLE)
151
+ .where('grant_id', grantId)
152
+ .update({ grant_id: null, scope: '', updated_at: at });
153
+ return affected(result);
154
+ }
155
+ /** Encerra todas as Sessions de um agente (bloqueio no console): os tokens param na hora. */
156
+ async endSessionsOfClient(clientId, at) {
157
+ const result = await this.conn()
158
+ .from(SESSION_TABLE)
159
+ .where('client_id', clientId)
160
+ .whereNull('ended_at')
161
+ .update({ ended_at: at, grant_id: null, scope: '', updated_at: at });
162
+ return affected(result);
163
+ }
164
+ // ─── session tokens ───────────────────────────────────────────────────────
165
+ async insertToken(row) {
166
+ await this.conn().table(TOKEN_TABLE).insert({
167
+ token_hash: row.tokenHash,
168
+ session_id: row.sessionId,
169
+ client_id: row.clientId,
170
+ user_id: row.userId,
171
+ account_id: row.accountId,
172
+ grant_id: row.grantId,
173
+ scope: row.scope,
174
+ resource: row.resource,
175
+ jkt: row.jkt,
176
+ expires_at: row.expiresAt,
177
+ created_at: row.createdAt,
178
+ });
179
+ }
180
+ async findToken(hash) {
181
+ const row = await this.conn().from(TOKEN_TABLE).where('token_hash', hash).first();
182
+ return row ? toToken(row) : null;
183
+ }
184
+ async deleteToken(hash) {
185
+ return affected(await this.conn().from(TOKEN_TABLE).where('token_hash', hash).delete()) > 0;
186
+ }
187
+ async deleteTokensExpiredBefore(before) {
188
+ await this.conn().from(TOKEN_TABLE).where('expires_at', '<', before).delete();
189
+ }
190
+ // ─── sign-in requests ─────────────────────────────────────────────────────
191
+ async insertRequest(row) {
192
+ await this.conn()
193
+ .table(REQUEST_TABLE)
194
+ .insert({
195
+ id: row.id,
196
+ kind: row.kind,
197
+ code_hash: row.codeHash,
198
+ user_code: row.userCode,
199
+ client_id: row.clientId,
200
+ session_id: row.sessionId,
201
+ account_id: row.accountId,
202
+ requested_scope: row.requestedScope,
203
+ granted_scope: row.grantedScope ?? null,
204
+ redirect_uri: row.redirectUri,
205
+ code_challenge: row.codeChallenge,
206
+ status: row.status,
207
+ attempts: 0,
208
+ interval_seconds: row.intervalSeconds,
209
+ data: row.data ? JSON.stringify(row.data) : null,
210
+ expires_at: row.expiresAt,
211
+ created_at: row.createdAt,
212
+ });
213
+ }
214
+ async findRequest(id) {
215
+ if (typeof id !== 'string' || !id)
216
+ return null;
217
+ const row = await this.conn().from(REQUEST_TABLE).where('id', id).first();
218
+ return row ? toRequest(row) : null;
219
+ }
220
+ async findRequestByCodeHash(hash) {
221
+ const row = await this.conn().from(REQUEST_TABLE).where('code_hash', hash).first();
222
+ return row ? toRequest(row) : null;
223
+ }
224
+ async findRequestByUserCode(userCode) {
225
+ const row = await this.conn().from(REQUEST_TABLE).where('user_code', userCode).first();
226
+ return row ? toRequest(row) : null;
227
+ }
228
+ /** Transição atômica — só se o pedido ainda está em `from`. */
229
+ async transitionRequest(id, from, patch) {
230
+ const update = { status: patch.status };
231
+ if (patch.accountId !== undefined)
232
+ update.account_id = patch.accountId;
233
+ if (patch.grantedScope !== undefined)
234
+ update.granted_scope = patch.grantedScope;
235
+ if (patch.codeHash !== undefined)
236
+ update.code_hash = patch.codeHash;
237
+ if (patch.attempts !== undefined)
238
+ update.attempts = patch.attempts;
239
+ if (patch.expiresAt !== undefined)
240
+ update.expires_at = patch.expiresAt;
241
+ const result = await this.conn()
242
+ .from(REQUEST_TABLE)
243
+ .where('id', id)
244
+ .where('status', from)
245
+ .update(update);
246
+ return affected(result) > 0;
247
+ }
248
+ /** Conta uma tentativa errada (atômico sobre o valor lido). */
249
+ async bumpAttempts(id, from) {
250
+ const result = await this.conn()
251
+ .from(REQUEST_TABLE)
252
+ .where('id', id)
253
+ .where('attempts', from)
254
+ .update({ attempts: from + 1 });
255
+ return affected(result) > 0;
256
+ }
257
+ async setRequestData(id, data) {
258
+ await this.conn()
259
+ .from(REQUEST_TABLE)
260
+ .where('id', id)
261
+ .update({ data: JSON.stringify(data) });
262
+ }
263
+ async markPolled(id, at) {
264
+ await this.conn().from(REQUEST_TABLE).where('id', id).update({ last_polled_at: at });
265
+ }
266
+ async deleteRequestsExpiredBefore(before) {
267
+ await this.conn().from(REQUEST_TABLE).where('expires_at', '<', before).delete();
268
+ }
269
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * O cookie do site para o navegador de um personal agent (§5): login pela
3
+ * asserção de navegador, e o acompanhamento do estado da Session a cada request.
4
+ */
5
+ import type { HttpContext } from '@adonisjs/core/http';
6
+ import type { PoppyBrowserSessionResult, PoppySessionState } from './service.js';
7
+ export interface PoppyWebSessionMarker {
8
+ sessionId: string;
9
+ clientId: string;
10
+ userId: string;
11
+ }
12
+ /**
13
+ * Liga o navegador à Session: regenera a sessão do app (session fixation),
14
+ * marca-a como do agente e, se a Session está logada, faz o login com o
15
+ * mecanismo do app — o hook `web.login`, ou a sessão de conta do authkit +
16
+ * `adonisAuth.guard`.
17
+ */
18
+ export declare function loginPoppyBrowser(httpCtx: HttpContext, result: PoppyBrowserSessionResult): Promise<void>;
19
+ /** Estado da Session Poppy deste navegador (preenchido por {@link poppyWebSession}). *
20
+ /** Estado da Session Poppy deste navegador (preenchido por {@link poppyWebSession}). * @experimental Poppy (Personal Agent Protocol) Draft 0.1 — acompanha a spec em desenvolvimento e PODE MUDAR
21
+ /** Estado da Session Poppy deste navegador (preenchido por {@link poppyWebSession}). * de forma incompatível fora de majors enquanto ela for draft.
22
+ /** Estado da Session Poppy deste navegador (preenchido por {@link poppyWebSession}). */
23
+ export declare function poppyWebSessionOf(ctx: HttpContext): PoppySessionState | null;
24
+ /**
25
+ * Middleware do site: um navegador de agente segue o estado ATUAL da Session
26
+ * (§5 — logou/deslogou/mudou de scopes, o próximo request reflete; o cookie não
27
+ * sobrevive à Session). Coloque-o depois do middleware de sessão.
28
+ *
29
+ * @experimental Poppy (Personal Agent Protocol) Draft 0.1 — acompanha a spec em desenvolvimento e PODE MUDAR
30
+ * de forma incompatível fora de majors enquanto ela for draft.
31
+ */
32
+ export declare function poppyWebSession(): (httpCtx: HttpContext, next: () => Promise<void>) => Promise<void>;
@@ -0,0 +1,104 @@
1
+ import { ACCOUNT_SESSION_KEY } from '../../host/account_session_key.js';
2
+ import { syncAdonisAuthLogin, syncAdonisAuthLogout } from '../../host/adonis_auth_sync.js';
3
+ import { POPPY_WEB_SESSION_KEY } from '../../host/poppy_session_key.js';
4
+ import { personalAgentsFor } from '../runtime.js';
5
+ /**
6
+ * Liga o navegador à Session: regenera a sessão do app (session fixation),
7
+ * marca-a como do agente e, se a Session está logada, faz o login com o
8
+ * mecanismo do app — o hook `web.login`, ou a sessão de conta do authkit +
9
+ * `adonisAuth.guard`.
10
+ */
11
+ export async function loginPoppyBrowser(httpCtx, result) {
12
+ // Sem augmentation no barrel (ver `augmentation_isolation.spec.ts`): a sessão é lida sem tipo.
13
+ const ctx = httpCtx;
14
+ const service = await ctx.containerResolver.make('authkit.server');
15
+ const cfg = service.config;
16
+ const login = cfg.personalAgents?.poppy?.web?.login;
17
+ await ctx.session.regenerate();
18
+ ctx.session.forget(ACCOUNT_SESSION_KEY);
19
+ const marker = {
20
+ sessionId: result.sessionId,
21
+ clientId: result.clientId,
22
+ userId: result.userId,
23
+ };
24
+ ctx.session.put(POPPY_WEB_SESSION_KEY, marker);
25
+ if (login) {
26
+ await login(ctx, {
27
+ sessionId: result.sessionId,
28
+ clientId: result.clientId,
29
+ userId: result.userId,
30
+ accountId: result.accountId,
31
+ scopes: result.scopes,
32
+ });
33
+ return;
34
+ }
35
+ if (result.accountId) {
36
+ ctx.session.put(ACCOUNT_SESSION_KEY, result.accountId);
37
+ const account = await cfg.accountStore.findById(result.accountId);
38
+ if (account)
39
+ await syncAdonisAuthLogin(ctx, cfg, account);
40
+ }
41
+ else {
42
+ await syncAdonisAuthLogout(ctx, cfg);
43
+ }
44
+ }
45
+ const states = new WeakMap();
46
+ /** Estado da Session Poppy deste navegador (preenchido por {@link poppyWebSession}). *
47
+ /** Estado da Session Poppy deste navegador (preenchido por {@link poppyWebSession}). * @experimental Poppy (Personal Agent Protocol) Draft 0.1 — acompanha a spec em desenvolvimento e PODE MUDAR
48
+ /** Estado da Session Poppy deste navegador (preenchido por {@link poppyWebSession}). * de forma incompatível fora de majors enquanto ela for draft.
49
+ /** Estado da Session Poppy deste navegador (preenchido por {@link poppyWebSession}). */
50
+ export function poppyWebSessionOf(ctx) {
51
+ return states.get(ctx) ?? null;
52
+ }
53
+ /**
54
+ * Middleware do site: um navegador de agente segue o estado ATUAL da Session
55
+ * (§5 — logou/deslogou/mudou de scopes, o próximo request reflete; o cookie não
56
+ * sobrevive à Session). Coloque-o depois do middleware de sessão.
57
+ *
58
+ * @experimental Poppy (Personal Agent Protocol) Draft 0.1 — acompanha a spec em desenvolvimento e PODE MUDAR
59
+ * de forma incompatível fora de majors enquanto ela for draft.
60
+ */
61
+ export function poppyWebSession() {
62
+ return async (httpCtx, next) => {
63
+ const ctx = httpCtx;
64
+ const marker = ctx.session?.get(POPPY_WEB_SESSION_KEY);
65
+ if (!marker?.sessionId)
66
+ return next();
67
+ const runtime = await personalAgentsFor(ctx);
68
+ const state = runtime?.poppy ? await runtime.poppy.sessionState(marker.sessionId) : null;
69
+ const service = await ctx.containerResolver.make('authkit.server');
70
+ const cfg = service.config;
71
+ const login = cfg.personalAgents?.poppy?.web?.login;
72
+ if (!state?.active || state.clientId !== marker.clientId) {
73
+ // A Session terminou: o cookie também.
74
+ ctx.session.forget(POPPY_WEB_SESSION_KEY);
75
+ ctx.session.forget(ACCOUNT_SESSION_KEY);
76
+ await ctx.session.regenerate();
77
+ await syncAdonisAuthLogout(ctx, cfg);
78
+ return next();
79
+ }
80
+ states.set(ctx, state);
81
+ if (login) {
82
+ await login(ctx, {
83
+ sessionId: state.sessionId,
84
+ clientId: state.clientId,
85
+ userId: state.userId,
86
+ accountId: state.accountId,
87
+ scopes: state.scopes,
88
+ });
89
+ return next();
90
+ }
91
+ const current = ctx.session.get(ACCOUNT_SESSION_KEY);
92
+ if (state.signedIn && state.accountId && current !== state.accountId) {
93
+ ctx.session.put(ACCOUNT_SESSION_KEY, state.accountId);
94
+ const account = await cfg.accountStore.findById(state.accountId);
95
+ if (account)
96
+ await syncAdonisAuthLogin(ctx, cfg, account);
97
+ }
98
+ else if (!state.signedIn && current) {
99
+ ctx.session.forget(ACCOUNT_SESSION_KEY);
100
+ await syncAdonisAuthLogout(ctx, cfg);
101
+ }
102
+ return next();
103
+ };
104
+ }
@@ -6,10 +6,14 @@ import type { DelegationReceipt } from './delegation_service.js';
6
6
  * header de delegação, challenge do 401, bloco de descoberta, formato do
7
7
  * step-up e do recibo.
8
8
  *
9
- * Embutido hoje: `'pact'` (https://openpactprotocol.org, v1.0). Outro protocolo
10
- * (ex.: o Personal Agent Protocol da Sierra, quando sair a spec) entra como
11
- * mais um objeto desta interface — passado direto em `personalAgents.protocol`
12
- * enquanto não for embutido.
9
+ * Embutido hoje: `'pact'` (https://openpactprotocol.org, v1.0). Um protocolo da
10
+ * MESMA família (JWT do agente + token de delegação) entra como mais um objeto
11
+ * desta interface, passado direto em `personalAgents.protocol`.
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
17
  */
14
18
  export interface PersonalAgentProtocol {
15
19
  /** Identificador estável (`'pact'`). */
@@ -0,0 +1,41 @@
1
+ export declare const REGISTRY_TABLE = "auth_agent_registry";
2
+ export type AgentProtocolId = 'pact' | 'poppy';
3
+ export type AgentRegistryStatus = 'allowed' | 'blocked';
4
+ export interface AgentRegistryRow {
5
+ id: string;
6
+ protocol: AgentProtocolId;
7
+ /** PACT: o `iss` do agente. Poppy: o `client_id` (URL do documento de metadata). */
8
+ identifier: string;
9
+ status: AgentRegistryStatus;
10
+ name: string | null;
11
+ /** JWKS do agente PACT (obrigatório num `allowed` PACT). */
12
+ jwksUri: string | null;
13
+ /** Conta do admin que fez a mudança. */
14
+ updatedBy: string | null;
15
+ createdAt: Date;
16
+ updatedAt: Date;
17
+ }
18
+ /** Quanto tempo o snapshot vale antes de reler a tabela. */
19
+ export declare const SNAPSHOT_TTL_MS: number;
20
+ export declare class AgentRegistryStore {
21
+ #private;
22
+ constructor(conn: () => any, options?: {
23
+ now?: () => number;
24
+ });
25
+ /** Todas as linhas, mais recentes primeiro. Lê do banco (console). */
26
+ list(): Promise<AgentRegistryRow[]>;
27
+ /** A linha do agente, pelo snapshot. `null` = o console não diz nada sobre ele. */
28
+ find(protocol: AgentProtocolId, identifier: string): Promise<AgentRegistryRow | null>;
29
+ findById(id: string): Promise<AgentRegistryRow | null>;
30
+ /** Cria ou troca a linha de (protocolo, identificador). */
31
+ upsert(input: {
32
+ protocol: AgentProtocolId;
33
+ identifier: string;
34
+ status: AgentRegistryStatus;
35
+ name?: string | null;
36
+ jwksUri?: string | null;
37
+ updatedBy?: string | null;
38
+ }): Promise<AgentRegistryRow>;
39
+ delete(id: string): Promise<boolean>;
40
+ invalidate(): void;
41
+ }