@adonis-agora/authkit-server 0.77.0 → 0.78.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 (54) 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 +20 -3
  8. package/build/src/agents/config.js +19 -4
  9. package/build/src/agents/delegation_service.js +18 -8
  10. package/build/src/agents/middleware.js +2 -1
  11. package/build/src/agents/poppy/client_registry.d.ts +45 -0
  12. package/build/src/agents/poppy/client_registry.js +226 -0
  13. package/build/src/agents/poppy/config.d.ts +362 -0
  14. package/build/src/agents/poppy/config.js +201 -0
  15. package/build/src/agents/poppy/dpop.d.ts +61 -0
  16. package/build/src/agents/poppy/dpop.js +158 -0
  17. package/build/src/agents/poppy/errors.d.ts +20 -0
  18. package/build/src/agents/poppy/errors.js +22 -0
  19. package/build/src/agents/poppy/replay.d.ts +25 -0
  20. package/build/src/agents/poppy/replay.js +106 -0
  21. package/build/src/agents/poppy/resource.d.ts +95 -0
  22. package/build/src/agents/poppy/resource.js +125 -0
  23. package/build/src/agents/poppy/safe_fetch.d.ts +48 -0
  24. package/build/src/agents/poppy/safe_fetch.js +256 -0
  25. package/build/src/agents/poppy/service.d.ts +280 -0
  26. package/build/src/agents/poppy/service.js +1452 -0
  27. package/build/src/agents/poppy/store.d.ts +109 -0
  28. package/build/src/agents/poppy/store.js +260 -0
  29. package/build/src/agents/poppy/web_session.d.ts +32 -0
  30. package/build/src/agents/poppy/web_session.js +104 -0
  31. package/build/src/agents/protocol.d.ts +8 -4
  32. package/build/src/agents/runtime.d.ts +17 -1
  33. package/build/src/agents/runtime.js +138 -13
  34. package/build/src/define_config.js +6 -0
  35. package/build/src/host/account_api/account_api_controller.d.ts +12 -1
  36. package/build/src/host/account_api/account_api_controller.js +10 -5
  37. package/build/src/host/admin_sessions_service.js +8 -5
  38. package/build/src/host/auth_host_config.d.ts +2 -1
  39. package/build/src/host/controllers/account_apps_controller.js +13 -3
  40. package/build/src/host/controllers/poppy_consent_controller.d.ts +19 -0
  41. package/build/src/host/controllers/poppy_consent_controller.js +173 -0
  42. package/build/src/host/controllers/poppy_controller.d.ts +25 -0
  43. package/build/src/host/controllers/poppy_controller.js +194 -0
  44. package/build/src/host/csrf.d.ts +7 -0
  45. package/build/src/host/csrf.js +16 -1
  46. package/build/src/host/i18n.d.ts +4 -0
  47. package/build/src/host/i18n.js +4 -0
  48. package/build/src/host/poppy_session_key.d.ts +9 -0
  49. package/build/src/host/poppy_session_key.js +9 -0
  50. package/build/src/host/register_auth_host.js +60 -0
  51. package/build/src/provider/oidc_service.d.ts +5 -0
  52. package/build/src/provider/oidc_service.js +9 -2
  53. package/build/src/schema/ensure.js +124 -0
  54. package/package.json +1 -1
@@ -0,0 +1,109 @@
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
+ insertToken(row: TokenRow): Promise<void>;
86
+ findToken(hash: string): Promise<TokenRow | null>;
87
+ deleteToken(hash: string): Promise<boolean>;
88
+ deleteTokensExpiredBefore(before: Date): Promise<void>;
89
+ insertRequest(row: Omit<RequestRow, 'attempts' | 'lastPolledAt' | 'grantedScope'> & {
90
+ grantedScope?: string | null;
91
+ }): Promise<void>;
92
+ findRequest(id: string): Promise<RequestRow | null>;
93
+ findRequestByCodeHash(hash: string): Promise<RequestRow | null>;
94
+ findRequestByUserCode(userCode: string): Promise<RequestRow | null>;
95
+ /** Transição atômica — só se o pedido ainda está em `from`. */
96
+ transitionRequest(id: string, from: RequestStatus, patch: {
97
+ status: RequestStatus;
98
+ accountId?: string;
99
+ grantedScope?: string;
100
+ codeHash?: string;
101
+ attempts?: number;
102
+ expiresAt?: Date;
103
+ }): Promise<boolean>;
104
+ /** Conta uma tentativa errada (atômico sobre o valor lido). */
105
+ bumpAttempts(id: string, from: number): Promise<boolean>;
106
+ setRequestData(id: string, data: Record<string, unknown>): Promise<void>;
107
+ markPolled(id: string, at: Date): Promise<void>;
108
+ deleteRequestsExpiredBefore(before: Date): Promise<void>;
109
+ }
@@ -0,0 +1,260 @@
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
+ // ─── session tokens ───────────────────────────────────────────────────────
156
+ async insertToken(row) {
157
+ await this.conn().table(TOKEN_TABLE).insert({
158
+ token_hash: row.tokenHash,
159
+ session_id: row.sessionId,
160
+ client_id: row.clientId,
161
+ user_id: row.userId,
162
+ account_id: row.accountId,
163
+ grant_id: row.grantId,
164
+ scope: row.scope,
165
+ resource: row.resource,
166
+ jkt: row.jkt,
167
+ expires_at: row.expiresAt,
168
+ created_at: row.createdAt,
169
+ });
170
+ }
171
+ async findToken(hash) {
172
+ const row = await this.conn().from(TOKEN_TABLE).where('token_hash', hash).first();
173
+ return row ? toToken(row) : null;
174
+ }
175
+ async deleteToken(hash) {
176
+ return affected(await this.conn().from(TOKEN_TABLE).where('token_hash', hash).delete()) > 0;
177
+ }
178
+ async deleteTokensExpiredBefore(before) {
179
+ await this.conn().from(TOKEN_TABLE).where('expires_at', '<', before).delete();
180
+ }
181
+ // ─── sign-in requests ─────────────────────────────────────────────────────
182
+ async insertRequest(row) {
183
+ await this.conn()
184
+ .table(REQUEST_TABLE)
185
+ .insert({
186
+ id: row.id,
187
+ kind: row.kind,
188
+ code_hash: row.codeHash,
189
+ user_code: row.userCode,
190
+ client_id: row.clientId,
191
+ session_id: row.sessionId,
192
+ account_id: row.accountId,
193
+ requested_scope: row.requestedScope,
194
+ granted_scope: row.grantedScope ?? null,
195
+ redirect_uri: row.redirectUri,
196
+ code_challenge: row.codeChallenge,
197
+ status: row.status,
198
+ attempts: 0,
199
+ interval_seconds: row.intervalSeconds,
200
+ data: row.data ? JSON.stringify(row.data) : null,
201
+ expires_at: row.expiresAt,
202
+ created_at: row.createdAt,
203
+ });
204
+ }
205
+ async findRequest(id) {
206
+ if (typeof id !== 'string' || !id)
207
+ return null;
208
+ const row = await this.conn().from(REQUEST_TABLE).where('id', id).first();
209
+ return row ? toRequest(row) : null;
210
+ }
211
+ async findRequestByCodeHash(hash) {
212
+ const row = await this.conn().from(REQUEST_TABLE).where('code_hash', hash).first();
213
+ return row ? toRequest(row) : null;
214
+ }
215
+ async findRequestByUserCode(userCode) {
216
+ const row = await this.conn().from(REQUEST_TABLE).where('user_code', userCode).first();
217
+ return row ? toRequest(row) : null;
218
+ }
219
+ /** Transição atômica — só se o pedido ainda está em `from`. */
220
+ async transitionRequest(id, from, patch) {
221
+ const update = { status: patch.status };
222
+ if (patch.accountId !== undefined)
223
+ update.account_id = patch.accountId;
224
+ if (patch.grantedScope !== undefined)
225
+ update.granted_scope = patch.grantedScope;
226
+ if (patch.codeHash !== undefined)
227
+ update.code_hash = patch.codeHash;
228
+ if (patch.attempts !== undefined)
229
+ update.attempts = patch.attempts;
230
+ if (patch.expiresAt !== undefined)
231
+ update.expires_at = patch.expiresAt;
232
+ const result = await this.conn()
233
+ .from(REQUEST_TABLE)
234
+ .where('id', id)
235
+ .where('status', from)
236
+ .update(update);
237
+ return affected(result) > 0;
238
+ }
239
+ /** Conta uma tentativa errada (atômico sobre o valor lido). */
240
+ async bumpAttempts(id, from) {
241
+ const result = await this.conn()
242
+ .from(REQUEST_TABLE)
243
+ .where('id', id)
244
+ .where('attempts', from)
245
+ .update({ attempts: from + 1 });
246
+ return affected(result) > 0;
247
+ }
248
+ async setRequestData(id, data) {
249
+ await this.conn()
250
+ .from(REQUEST_TABLE)
251
+ .where('id', id)
252
+ .update({ data: JSON.stringify(data) });
253
+ }
254
+ async markPolled(id, at) {
255
+ await this.conn().from(REQUEST_TABLE).where('id', id).update({ last_polled_at: at });
256
+ }
257
+ async deleteRequestsExpiredBefore(before) {
258
+ await this.conn().from(REQUEST_TABLE).where('expires_at', '<', before).delete();
259
+ }
260
+ }
@@ -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'`). */
@@ -3,6 +3,8 @@ import type { OidcService } from '../provider/oidc_service.js';
3
3
  import { PersonalAgentVerifier } from './agent_identity.js';
4
4
  import type { ResolvedPersonalAgentsConfig } from './config.js';
5
5
  import { PersonalAgentDelegation } from './delegation_service.js';
6
+ import type { JsonFetcher } from './poppy/safe_fetch.js';
7
+ import { PoppyService } from './poppy/service.js';
6
8
  /** URLs públicas do authorization server de delegação. */
7
9
  export interface PersonalAgentUrls {
8
10
  /** `issuer` (RFC 8414) — também o `iss` dos tokens de delegação. */
@@ -23,14 +25,28 @@ export declare function personalAgentUrls(oidcIssuer: string, prefix: string): P
23
25
  export interface PersonalAgentsRuntime {
24
26
  config: ResolvedPersonalAgentsConfig;
25
27
  urls: PersonalAgentUrls;
28
+ /** Verificador do JWT do agente PACT (inerte quando o PACT está desligado — veja `config.pact`). */
26
29
  verifier: PersonalAgentVerifier;
27
30
  /** `null` quando o app não configurou `personalAgents.delegation`. */
28
31
  delegation: PersonalAgentDelegation | null;
32
+ /** `null` quando o app não configurou `personalAgents.poppy`. */
33
+ poppy: PoppyService | null;
34
+ /** Revoga um grant de personal agent DA CONTA (PACT ou Poppy). */
35
+ revokeAgentGrant(accountId: string, grantId: string): Promise<boolean>;
36
+ /** Revoga todos os grants de personal agents da conta. Devolve quantos estavam vivos. */
37
+ revokeAllAgentGrants(accountId: string): Promise<number>;
38
+ }
39
+ export interface BuildPersonalAgentsOptions {
40
+ /** Resolve serviços do container (redis do replay store). */
41
+ make?: (binding: string) => Promise<any>;
42
+ /** Busca de JSON (metadata/JWKS dos agentes Poppy) — injetável nos testes. */
43
+ fetchJson?: JsonFetcher;
44
+ now?: () => Date;
29
45
  }
30
46
  /**
31
47
  * Monta (uma vez por `OidcService`) o runtime de personal agents. `null` quando
32
48
  * `personalAgents` não está no config — as rotas respondem 404.
33
49
  */
34
- export declare function buildPersonalAgentsRuntime(service: OidcService, makeDb: () => Promise<any>): Promise<PersonalAgentsRuntime | null>;
50
+ export declare function buildPersonalAgentsRuntime(service: OidcService, makeDb: () => Promise<any>, options?: BuildPersonalAgentsOptions): Promise<PersonalAgentsRuntime | null>;
35
51
  /** Runtime a partir de uma request. */
36
52
  export declare function personalAgentsFor(ctx: HttpContext): Promise<PersonalAgentsRuntime | null>;