@adonis-agora/authkit-server 0.76.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 (88) hide show
  1. package/README.md +149 -0
  2. package/build/host/views/agents/consent.edge +13 -2
  3. package/build/host/views/agents/done.edge +3 -0
  4. package/build/host/views/mfa-challenge.edge +42 -3
  5. package/build/index.d.ts +19 -1
  6. package/build/index.js +13 -1
  7. package/build/providers/authkit_server_provider.js +15 -1
  8. package/build/src/accounts/account_store.d.ts +2 -0
  9. package/build/src/accounts/lucid_account_store.js +2 -0
  10. package/build/src/adapters/adapter_contract.d.ts +2 -0
  11. package/build/src/adapters/database_adapter.d.ts +1 -0
  12. package/build/src/adapters/database_adapter.js +26 -0
  13. package/build/src/adapters/redis_adapter.d.ts +1 -0
  14. package/build/src/adapters/redis_adapter.js +30 -0
  15. package/build/src/agents/config.d.ts +20 -3
  16. package/build/src/agents/config.js +19 -4
  17. package/build/src/agents/delegation_service.js +18 -8
  18. package/build/src/agents/middleware.js +2 -1
  19. package/build/src/agents/poppy/client_registry.d.ts +45 -0
  20. package/build/src/agents/poppy/client_registry.js +226 -0
  21. package/build/src/agents/poppy/config.d.ts +362 -0
  22. package/build/src/agents/poppy/config.js +201 -0
  23. package/build/src/agents/poppy/dpop.d.ts +61 -0
  24. package/build/src/agents/poppy/dpop.js +158 -0
  25. package/build/src/agents/poppy/errors.d.ts +20 -0
  26. package/build/src/agents/poppy/errors.js +22 -0
  27. package/build/src/agents/poppy/replay.d.ts +25 -0
  28. package/build/src/agents/poppy/replay.js +106 -0
  29. package/build/src/agents/poppy/resource.d.ts +95 -0
  30. package/build/src/agents/poppy/resource.js +125 -0
  31. package/build/src/agents/poppy/safe_fetch.d.ts +48 -0
  32. package/build/src/agents/poppy/safe_fetch.js +256 -0
  33. package/build/src/agents/poppy/service.d.ts +280 -0
  34. package/build/src/agents/poppy/service.js +1452 -0
  35. package/build/src/agents/poppy/store.d.ts +109 -0
  36. package/build/src/agents/poppy/store.js +260 -0
  37. package/build/src/agents/poppy/web_session.d.ts +32 -0
  38. package/build/src/agents/poppy/web_session.js +104 -0
  39. package/build/src/agents/protocol.d.ts +8 -4
  40. package/build/src/agents/runtime.d.ts +17 -1
  41. package/build/src/agents/runtime.js +138 -13
  42. package/build/src/define_config.d.ts +24 -0
  43. package/build/src/define_config.js +14 -0
  44. package/build/src/host/account_api/account_api_controller.d.ts +12 -1
  45. package/build/src/host/account_api/account_api_controller.js +10 -5
  46. package/build/src/host/admin_sessions_service.js +8 -5
  47. package/build/src/host/auth_host_config.d.ts +2 -1
  48. package/build/src/host/controllers/account_apps_controller.js +13 -3
  49. package/build/src/host/controllers/account_security_controller.js +8 -4
  50. package/build/src/host/controllers/interaction_controller.d.ts +13 -1
  51. package/build/src/host/controllers/interaction_controller.js +352 -24
  52. package/build/src/host/controllers/poppy_consent_controller.d.ts +19 -0
  53. package/build/src/host/controllers/poppy_consent_controller.js +173 -0
  54. package/build/src/host/controllers/poppy_controller.d.ts +25 -0
  55. package/build/src/host/controllers/poppy_controller.js +194 -0
  56. package/build/src/host/csrf.d.ts +7 -0
  57. package/build/src/host/csrf.js +16 -1
  58. package/build/src/host/custom_login.d.ts +36 -0
  59. package/build/src/host/custom_login.js +76 -0
  60. package/build/src/host/custom_login_completion.d.ts +4 -0
  61. package/build/src/host/custom_login_completion.js +33 -0
  62. package/build/src/host/custom_mfa.d.ts +77 -0
  63. package/build/src/host/custom_mfa.js +268 -0
  64. package/build/src/host/i18n.d.ts +8 -0
  65. package/build/src/host/i18n.js +14 -6
  66. package/build/src/host/idp_session_bridge.d.ts +5 -3
  67. package/build/src/host/idp_session_bridge.js +5 -3
  68. package/build/src/host/oidc_rp_guard.d.ts +5 -1
  69. package/build/src/host/oidc_rp_guard.js +15 -2
  70. package/build/src/host/persistent_rp_session.d.ts +8 -0
  71. package/build/src/host/persistent_rp_session.js +74 -0
  72. package/build/src/host/poppy_session_key.d.ts +9 -0
  73. package/build/src/host/poppy_session_key.js +9 -0
  74. package/build/src/host/register_auth_host.js +72 -0
  75. package/build/src/host/whatsapp_code_sender.d.ts +20 -0
  76. package/build/src/host/whatsapp_code_sender.js +9 -0
  77. package/build/src/host/whatsapp_senders/http.d.ts +5 -0
  78. package/build/src/host/whatsapp_senders/http.js +33 -0
  79. package/build/src/host/whatsapp_senders/meta_whatsapp_code_sender.d.ts +20 -0
  80. package/build/src/host/whatsapp_senders/meta_whatsapp_code_sender.js +51 -0
  81. package/build/src/host/whatsapp_senders/whatsmiau_code_sender.d.ts +14 -0
  82. package/build/src/host/whatsapp_senders/whatsmiau_code_sender.js +36 -0
  83. package/build/src/provider/oidc_service.d.ts +5 -0
  84. package/build/src/provider/oidc_service.js +15 -4
  85. package/build/src/schema/ensure.js +124 -0
  86. package/build/stubs/ui/react/pages/mfa-challenge.tsx +207 -37
  87. package/package.json +2 -1
  88. package/stubs/ui/react/pages/mfa-challenge.tsx +207 -37
@@ -0,0 +1,256 @@
1
+ /**
2
+ * `fetch` de documentos JSON de terceiros à prova de SSRF.
3
+ *
4
+ * O `client_id` de um agente Poppy é uma URL que QUALQUER um escolhe — buscá-la
5
+ * sem cuidado faria deste app um proxy para a rede interna. Regras:
6
+ * - só `https:`; sem credenciais na URL; porta qualquer;
7
+ * - o IP é conferido NA CONEXÃO (o `lookup` do socket), não num DNS separado —
8
+ * sem janela para DNS rebinding; loopback, privados, link-local, CGNAT,
9
+ * multicast, reservados e os equivalentes IPv6 são recusados;
10
+ * - IP literal na URL passa pela mesma regra;
11
+ * - sem redirects (o `client_id` tem de ser exatamente a URL buscada);
12
+ * - teto de tempo e de bytes.
13
+ */
14
+ import { lookup as dnsLookup } from 'node:dns';
15
+ import { request as httpsRequest } from 'node:https';
16
+ import { isIP } from 'node:net';
17
+ export class SafeFetchError extends Error {
18
+ constructor(message) {
19
+ super(message);
20
+ this.name = 'SafeFetchError';
21
+ }
22
+ }
23
+ function ipv4ToInt(ip) {
24
+ return ip.split('.').reduce((acc, part) => (acc << 8) + Number(part), 0) >>> 0;
25
+ }
26
+ function inV4(ip, cidr) {
27
+ const [base, bits] = cidr.split('/');
28
+ const mask = Number(bits) === 0 ? 0 : (~0 << (32 - Number(bits))) >>> 0;
29
+ return (ip & mask) === (ipv4ToInt(base) & mask);
30
+ }
31
+ const BLOCKED_V4 = [
32
+ '0.0.0.0/8',
33
+ '10.0.0.0/8',
34
+ '100.64.0.0/10',
35
+ '127.0.0.0/8',
36
+ '169.254.0.0/16',
37
+ '172.16.0.0/12',
38
+ '192.0.0.0/24',
39
+ '192.0.2.0/24',
40
+ '192.88.99.0/24',
41
+ '192.168.0.0/16',
42
+ '198.18.0.0/15',
43
+ '198.51.100.0/24',
44
+ '203.0.113.0/24',
45
+ '224.0.0.0/4',
46
+ '240.0.0.0/4',
47
+ ];
48
+ /** Expande um IPv6 em 8 grupos de 16 bits. */
49
+ function ipv6Groups(ip) {
50
+ let addr = ip.toLowerCase().split('%')[0];
51
+ // Sufixo IPv4 (::ffff:1.2.3.4).
52
+ const v4 = /(\d+\.\d+\.\d+\.\d+)$/.exec(addr);
53
+ if (v4) {
54
+ const n = ipv4ToInt(v4[1]);
55
+ addr = `${addr.slice(0, -v4[1].length)}${(n >>> 16).toString(16)}:${(n & 0xffff).toString(16)}`;
56
+ }
57
+ const [head, tail] = addr.split('::');
58
+ const h = head ? head.split(':') : [];
59
+ const t = tail !== undefined ? (tail ? tail.split(':') : []) : [];
60
+ const fill = addr.includes('::') ? 8 - h.length - t.length : 0;
61
+ const groups = [...h, ...Array(fill).fill('0'), ...t].map((g) => Number.parseInt(g || '0', 16));
62
+ return groups.length === 8 && groups.every((g) => g >= 0 && g <= 0xffff) ? groups : null;
63
+ }
64
+ /** `true` quando o IP não pode ser alvo de uma busca de saída. */
65
+ export function isBlockedAddress(ip) {
66
+ const family = isIP(ip);
67
+ if (family === 4) {
68
+ const n = ipv4ToInt(ip);
69
+ return BLOCKED_V4.some((cidr) => inV4(n, cidr));
70
+ }
71
+ if (family !== 6)
72
+ return true;
73
+ const g = ipv6Groups(ip);
74
+ if (!g)
75
+ return true;
76
+ if (g.every((x) => x === 0))
77
+ return true; // ::
78
+ if (g.slice(0, 7).every((x) => x === 0) && g[7] === 1)
79
+ return true; // ::1
80
+ // IPv4-mapped (::ffff:a.b.c.d) e IPv4-compatible: decide pelo IPv4.
81
+ if (g.slice(0, 5).every((x) => x === 0) && (g[5] === 0xffff || g[5] === 0)) {
82
+ const v4 = `${g[6] >> 8}.${g[6] & 255}.${g[7] >> 8}.${g[7] & 255}`;
83
+ return isBlockedAddress(v4);
84
+ }
85
+ if ((g[0] & 0xfe00) === 0xfc00)
86
+ return true; // fc00::/7 (ULA)
87
+ if ((g[0] & 0xffc0) === 0xfe80)
88
+ return true; // fe80::/10 (link-local)
89
+ if ((g[0] & 0xffc0) === 0xfec0)
90
+ return true; // fec0::/10 (site-local)
91
+ if ((g[0] & 0xff00) === 0xff00)
92
+ return true; // ff00::/8 (multicast)
93
+ if (g[0] === 0x2001 && g[1] === 0x0db8)
94
+ return true; // 2001:db8::/32 (doc)
95
+ if (g[0] === 0x64 && g[1] === 0xff9b)
96
+ return true; // 64:ff9b::/96 (NAT64)
97
+ if (g[0] === 0x2002) {
98
+ // 6to4: o IPv4 embutido.
99
+ return isBlockedAddress(`${g[1] >> 8}.${g[1] & 255}.${g[2] >> 8}.${g[2] & 255}`);
100
+ }
101
+ return false;
102
+ }
103
+ function parseMaxAge(header) {
104
+ const value = Array.isArray(header) ? header.join(',') : header;
105
+ if (!value)
106
+ return null;
107
+ if (/\bno-store\b|\bno-cache\b/i.test(value))
108
+ return 0;
109
+ const match = /\bmax-age=(\d+)/i.exec(value);
110
+ return match ? Number(match[1]) : null;
111
+ }
112
+ /**
113
+ * Busca `url` e devolve o JSON. Só HTTPS, sem redirect, IP público conferido na
114
+ * conexão, `timeoutMs` (default 5 s) e `maxBytes` (default 64 KiB).
115
+ */
116
+ export function safeFetchJson(url, options = {}) {
117
+ const timeoutMs = options.timeoutMs ?? 5000;
118
+ const maxBytes = options.maxBytes ?? 64 * 1024;
119
+ const resolve = options.lookup ?? dnsLookup;
120
+ let parsed;
121
+ try {
122
+ parsed = new URL(url);
123
+ }
124
+ catch {
125
+ return Promise.reject(new SafeFetchError('invalid URL'));
126
+ }
127
+ if (parsed.protocol !== 'https:')
128
+ return Promise.reject(new SafeFetchError('only https URLs'));
129
+ if (parsed.username || parsed.password) {
130
+ return Promise.reject(new SafeFetchError('credentials in URL'));
131
+ }
132
+ const host = parsed.hostname.replace(/^\[|\]$/g, '');
133
+ if (isIP(host) && isBlockedAddress(host)) {
134
+ return Promise.reject(new SafeFetchError('blocked address'));
135
+ }
136
+ // `lookup` do socket: resolve e recusa se QUALQUER endereço for interno.
137
+ const guardedLookup = (hostname, opts, callback) => {
138
+ resolve(hostname, { all: true }, (err, addresses) => {
139
+ if (err)
140
+ return callback(err);
141
+ const list = addresses ?? [];
142
+ if (list.length === 0)
143
+ return callback(new SafeFetchError('no address'));
144
+ if (list.some((a) => isBlockedAddress(a.address))) {
145
+ return callback(new SafeFetchError('blocked address'));
146
+ }
147
+ if (opts?.all)
148
+ return callback(null, list);
149
+ return callback(null, list[0].address, list[0].family);
150
+ });
151
+ };
152
+ return new Promise((done, reject) => {
153
+ let settled = false;
154
+ const finish = (fn) => {
155
+ if (settled)
156
+ return;
157
+ settled = true;
158
+ clearTimeout(timer);
159
+ fn();
160
+ };
161
+ const req = httpsRequest(parsed, {
162
+ method: 'GET',
163
+ headers: { accept: 'application/json', 'user-agent': 'authkit-poppy/0.1' },
164
+ lookup: guardedLookup,
165
+ // IP literal: o `lookup` não roda — já conferido acima.
166
+ }, (res) => {
167
+ const status = res.statusCode ?? 0;
168
+ if (status >= 300 && status < 400) {
169
+ res.resume();
170
+ return finish(() => reject(new SafeFetchError('redirects are not followed')));
171
+ }
172
+ const chunks = [];
173
+ let size = 0;
174
+ res.on('data', (chunk) => {
175
+ size += chunk.length;
176
+ if (size > maxBytes) {
177
+ req.destroy();
178
+ finish(() => reject(new SafeFetchError('response too large')));
179
+ return;
180
+ }
181
+ chunks.push(chunk);
182
+ });
183
+ res.on('end', () => finish(() => {
184
+ if (status !== 200)
185
+ return reject(new SafeFetchError(`HTTP ${status}`));
186
+ try {
187
+ done({
188
+ status,
189
+ body: JSON.parse(Buffer.concat(chunks).toString('utf8')),
190
+ maxAge: parseMaxAge(res.headers['cache-control']),
191
+ });
192
+ }
193
+ catch {
194
+ reject(new SafeFetchError('invalid JSON'));
195
+ }
196
+ }));
197
+ res.on('error', (e) => finish(() => reject(new SafeFetchError(e.message))));
198
+ });
199
+ const timer = setTimeout(() => {
200
+ req.destroy();
201
+ finish(() => reject(new SafeFetchError('timeout')));
202
+ }, timeoutMs);
203
+ req.on('error', (e) => finish(() => reject(e instanceof SafeFetchError ? e : new SafeFetchError(e.message))));
204
+ req.end();
205
+ });
206
+ }
207
+ /**
208
+ * SÓ DEV/TESTE (`personalAgents.poppy.allowInsecureClientIds`): busca um JSON por
209
+ * `http:` ou `https:` SEM a guarda de SSRF — loopback e rede privada liberados,
210
+ * para um agente local (`http://localhost:7777/agent.json`). Mantém sem redirect,
211
+ * teto de tempo e de bytes. Nunca use em produção (a config recusa).
212
+ */
213
+ export async function insecureDevFetchJson(url, options = {}) {
214
+ const timeoutMs = options.timeoutMs ?? 5000;
215
+ const maxBytes = options.maxBytes ?? 64 * 1024;
216
+ let parsed;
217
+ try {
218
+ parsed = new URL(url);
219
+ }
220
+ catch {
221
+ throw new SafeFetchError('invalid URL');
222
+ }
223
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
224
+ throw new SafeFetchError('only http(s) URLs');
225
+ }
226
+ if (parsed.username || parsed.password)
227
+ throw new SafeFetchError('credentials in URL');
228
+ let res;
229
+ try {
230
+ res = await fetch(parsed, {
231
+ redirect: 'manual',
232
+ headers: { accept: 'application/json', 'user-agent': 'authkit-poppy/0.1 (insecure-dev)' },
233
+ signal: AbortSignal.timeout(timeoutMs),
234
+ });
235
+ }
236
+ catch (error) {
237
+ throw new SafeFetchError(error.name === 'TimeoutError' ? 'timeout' : 'unreachable');
238
+ }
239
+ if (res.status >= 300 && res.status < 400)
240
+ throw new SafeFetchError('redirects are not followed');
241
+ const buf = Buffer.from(await res.arrayBuffer());
242
+ if (buf.length > maxBytes)
243
+ throw new SafeFetchError('response too large');
244
+ if (res.status !== 200)
245
+ throw new SafeFetchError(`HTTP ${res.status}`);
246
+ try {
247
+ return {
248
+ status: res.status,
249
+ body: JSON.parse(buf.toString('utf8')),
250
+ maxAge: parseMaxAge(res.headers.get('cache-control') ?? undefined),
251
+ };
252
+ }
253
+ catch {
254
+ throw new SafeFetchError('invalid JSON');
255
+ }
256
+ }
@@ -0,0 +1,280 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { DelegationStore } from '../delegation_store.js';
3
+ import type { PoppyClient, PoppyClientRegistry } from './client_registry.js';
4
+ import type { PoppyLimiter, PoppyPrincipalBase, PoppyReplayStore, ResolvedPoppyConfig } from './config.js';
5
+ import { type DpopNonceSource } from './dpop.js';
6
+ import type { PoppyStore, RequestRow } from './store.js';
7
+ export declare const JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer";
8
+ export declare const DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
9
+ export declare const CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
10
+ export declare const BROWSER_ASSERTION_TYP = "poppy-browser+jwt";
11
+ /** URLs públicas do authorization server Poppy. */
12
+ export interface PoppyUrls {
13
+ origin: string;
14
+ issuer: string;
15
+ /** RFC 8414 §3.1: `/.well-known/oauth-authorization-server{issuer path}`. */
16
+ metadata: string;
17
+ token: string;
18
+ revocation: string;
19
+ authorization: string;
20
+ deviceAuthorization: string;
21
+ /** Tela do device flow (`verification_uri`). */
22
+ device: string;
23
+ mediated: string;
24
+ browserSession: string;
25
+ discovery: string;
26
+ }
27
+ /** Deriva as URLs da ORIGEM do issuer configurado — nunca do Host da request. */
28
+ export declare function poppyUrls(oidcIssuer: string, prefix: string): PoppyUrls;
29
+ /** Resposta do token endpoint (§4.2, §4.4). */
30
+ export interface PoppyTokenResponse {
31
+ access_token: string;
32
+ token_type: 'DPoP' | 'Bearer';
33
+ expires_in: number;
34
+ scope: string;
35
+ session_id: string;
36
+ signed_in: boolean;
37
+ refresh_token?: string;
38
+ refresh_token_expires_in?: number;
39
+ }
40
+ /** Ponte com o oidc-provider para os tokens Bearer de MCP (§4.3, §6). */
41
+ export interface PoppyMcpBridge {
42
+ /** `url` é um servidor MCP deste app (de `apis` ou do registro de resources)? */
43
+ isMcpResource(url: string): boolean;
44
+ /**
45
+ * Emite o Bearer de uma Session LOGADA como access token do oidc-provider,
46
+ * para que a integração MCP existente (`AccessToken.find`) o aceite. `null` =
47
+ * sem ponte (o token fica só no store do Poppy).
48
+ */
49
+ mint?(input: {
50
+ grantId: string;
51
+ accountId: string;
52
+ clientId: string;
53
+ scope: string;
54
+ resource: string;
55
+ expiresIn: number;
56
+ grantExpiresAt: Date;
57
+ }): Promise<string | null>;
58
+ /** Revoga o grant (e os tokens) no oidc-provider. */
59
+ revoke?(grantId: string): Promise<void>;
60
+ }
61
+ export interface PoppyServiceDeps {
62
+ cfg: ResolvedPoppyConfig;
63
+ urls: PoppyUrls;
64
+ store: PoppyStore;
65
+ grants: DelegationStore;
66
+ registry: PoppyClientRegistry;
67
+ replay: PoppyReplayStore;
68
+ limiter: PoppyLimiter | null;
69
+ nonces: DpopNonceSource;
70
+ /** A conta ainda pode agir? (existe e não está desabilitada). Default: sempre. */
71
+ isAccountActive?: (accountId: string) => Promise<boolean>;
72
+ mcp?: PoppyMcpBridge;
73
+ now?: () => Date;
74
+ }
75
+ /** Entrada de uma request para {@link PoppyService.verifyAccess}. */
76
+ export interface PoppyRequestInput {
77
+ method: string;
78
+ url: string;
79
+ headers: Record<string, string | string[] | undefined>;
80
+ }
81
+ export interface PoppyVerifyOptions {
82
+ /** Scopes exigidos (todos). Faltando: `sign_in_required` (deslogado) ou `insufficient_scope`. */
83
+ scopes?: string[];
84
+ /** Exige Session logada. */
85
+ signedIn?: boolean;
86
+ /**
87
+ * `resource` exigido por este endpoint. Default: derivado da URL contra
88
+ * `apis`/`agent.protocols` do config (entradas com `resource`) e os MCP.
89
+ */
90
+ resource?: string | null;
91
+ }
92
+ export type PoppyAuthError = 'invalid_token' | 'sign_in_required' | 'insufficient_scope' | 'invalid_dpop_proof' | 'use_dpop_nonce';
93
+ export type PoppyVerifyResult = {
94
+ ok: true;
95
+ principal: PoppyPrincipalBase;
96
+ jkt: string | null;
97
+ } | {
98
+ ok: false;
99
+ status: 401 | 403;
100
+ error: PoppyAuthError;
101
+ description: string;
102
+ scope?: string;
103
+ wwwAuthenticate: string;
104
+ /** Headers extras (`DPoP-Nonce`). */
105
+ headers: Record<string, string>;
106
+ };
107
+ /** Pedido Direct aguardando o usuário na tela de consentimento. */
108
+ export interface PoppyConsentRequest {
109
+ id: string;
110
+ clientId: string;
111
+ agentName: string;
112
+ agentOrigin: string;
113
+ logoUri: string | null;
114
+ scopes: {
115
+ id: string;
116
+ description: string;
117
+ }[];
118
+ userCode?: string;
119
+ }
120
+ export type PoppyAuthorizeOutcome = {
121
+ kind: 'consent';
122
+ request: PoppyConsentRequest;
123
+ } | {
124
+ kind: 'redirect';
125
+ url: string;
126
+ } | {
127
+ kind: 'error';
128
+ description: string;
129
+ };
130
+ export interface PoppyGrantSummary {
131
+ id: string;
132
+ clientId: string;
133
+ scopes: {
134
+ id: string;
135
+ description: string;
136
+ }[];
137
+ createdAt: Date;
138
+ updatedAt: Date;
139
+ expiresAt: Date;
140
+ }
141
+ export type PoppyMediatedResponse = ({
142
+ status: 'complete';
143
+ } & PoppyTokenResponse) | {
144
+ status: 'code_required';
145
+ sign_in_id: string;
146
+ code: {
147
+ sent_to: string;
148
+ };
149
+ expires_at: string;
150
+ } | {
151
+ status: 'failed';
152
+ } | {
153
+ status: 'expired';
154
+ };
155
+ export interface PoppyBrowserSessionResult {
156
+ sessionId: string;
157
+ clientId: string;
158
+ userId: string;
159
+ accountId: string | null;
160
+ scopes: string[];
161
+ returnTo: string;
162
+ }
163
+ /** Estado atual de uma Session (para o cookie do site acompanhar — §5). */
164
+ export interface PoppySessionState {
165
+ active: boolean;
166
+ sessionId: string;
167
+ clientId: string;
168
+ userId: string;
169
+ accountId: string | null;
170
+ signedIn: boolean;
171
+ scopes: string[];
172
+ }
173
+ /** Aceita o código como o usuário digitar: minúsculas, sem hífen, com espaços. */
174
+ export declare function normalizePoppyUserCode(input: unknown): string | null;
175
+ /** Slot global onde o `@adonis-agora/agent` registra o endpoint de conversas Poppy. */
176
+ export declare const POPPY_ENDPOINTS_SLOT: unique symbol;
177
+ /**
178
+ * `agent.protocols` vindo do slot `Symbol.for('@adonis-agora/poppy:endpoints')`
179
+ * (`{ conversations: 'https://…' }`), usado quando o config não declara
180
+ * `agent.protocols` — o mesmo padrão do slot de resources do MCP.
181
+ */
182
+ export declare function registeredConversationProtocols(): {
183
+ type: string;
184
+ endpoint: string;
185
+ resource?: string;
186
+ }[];
187
+ /**
188
+ * @experimental Poppy (Personal Agent Protocol) Draft 0.1 — pode mudar de forma incompatível enquanto a spec for draft.
189
+ */
190
+ export declare class PoppyService {
191
+ #private;
192
+ readonly cfg: ResolvedPoppyConfig;
193
+ readonly urls: PoppyUrls;
194
+ readonly registry: PoppyClientRegistry;
195
+ constructor(deps: PoppyServiceDeps);
196
+ /** Um nonce DPoP novo (header `DPoP-Nonce`), quando o app exige nonces. */
197
+ nonce(): string | null;
198
+ /** Domínios da empresa (o principal + aliases). */
199
+ get domains(): string[];
200
+ /** Descrição de um scope para o usuário. */
201
+ describe(ids: string[]): {
202
+ id: string;
203
+ description: string;
204
+ }[];
205
+ /** `/.well-known/poppy.json` (§3) para o domínio pedido; `null` = domínio que não é nosso. */
206
+ discoveryDocument(requestHost: string): Record<string, unknown> | null;
207
+ /** RFC 8414 com `poppy_domains` (§3.2). */
208
+ authorizationServerMetadata(): Record<string, unknown>;
209
+ /**
210
+ * `private_key_jwt` (RFC 7523 §2.2, §3): `client_assertion` assinada por uma
211
+ * chave do `jwks_uri`, `iss` = `sub` = `client_id`, `aud` = o endpoint
212
+ * chamado (ou o token endpoint/issuer), `exp` curto, `jti` único.
213
+ */
214
+ authenticateClient(form: Record<string, unknown>, endpoint: string): Promise<PoppyClient>;
215
+ /** `POST token_endpoint` (§4.2, §4.3, §4.5, §4.6, §4.8). */
216
+ token(form: Record<string, unknown>, dpopHeader: string | string[] | undefined): Promise<PoppyTokenResponse>;
217
+ /** `POST device_authorization_endpoint` (§4.6). */
218
+ deviceAuthorization(form: Record<string, unknown>): Promise<Record<string, unknown>>;
219
+ /** `POST revocation_endpoint` (§4.9, RFC 7009). */
220
+ revoke(form: Record<string, unknown>): Promise<void>;
221
+ /**
222
+ * Revoga um Account Token (grant): as Sessions logadas com ele continuam
223
+ * deslogadas (§4.9) e os Bearer de MCP no oidc-provider morrem junto.
224
+ */
225
+ revokeGrant(grantId: string): Promise<void>;
226
+ /**
227
+ * `GET authorization_endpoint` com o usuário já logado neste app. Erros no
228
+ * `client_id`/`redirect_uri` não redirecionam (RFC 6749 §4.1.2.1); os demais
229
+ * voltam ao agente com `error`, `state` e `iss`.
230
+ */
231
+ startAuthorization(params: Record<string, unknown>, accountId: string): Promise<PoppyAuthorizeOutcome>;
232
+ /**
233
+ * `POST` da tela de consentimento Direct. Devolve para onde mandar o
234
+ * navegador (`code`/`error`, `state`, `iss`), ou `null` quando o pedido não
235
+ * vale mais (expirou, decidido em outra aba, de outra conta).
236
+ */
237
+ decideAuthorization(input: {
238
+ requestId: string;
239
+ accountId: string;
240
+ allow: boolean;
241
+ scopes: string[];
242
+ }): Promise<{
243
+ url: string;
244
+ approved: string[] | null;
245
+ clientId: string;
246
+ } | null>;
247
+ pendingDevice(userCodeInput: unknown): Promise<PoppyConsentRequest | null>;
248
+ /** Aprova (scopes marcados) ou nega um device code. `null` = não está mais pendente. */
249
+ decideDevice(input: {
250
+ userCode: string;
251
+ accountId: string;
252
+ allow: boolean;
253
+ scopes: string[];
254
+ }): Promise<{
255
+ approved: string[] | null;
256
+ clientId: string;
257
+ } | null>;
258
+ /** `POST auth.mediated.endpoint` — já autenticado pelo Session Token (DPoP). */
259
+ mediatedStart(principal: PoppyPrincipalBase, jkt: string | null, body: unknown, ctx: HttpContext): Promise<PoppyMediatedResponse>;
260
+ /** `POST {mediated}/{sign_in_id}` com o código de uso único. */
261
+ mediatedCode(principal: PoppyPrincipalBase, jkt: string | null, signInId: string, body: unknown): Promise<PoppyMediatedResponse | null>;
262
+ /**
263
+ * Verifica `Authorization: DPoP|Bearer` (+ prova DPoP) de uma request a uma API
264
+ * ou conversa do app. Nunca lê token da URL (§4.3).
265
+ */
266
+ verifyAccess(input: PoppyRequestInput, options?: PoppyVerifyOptions): Promise<PoppyVerifyResult>;
267
+ /**
268
+ * Confere a asserção de navegador (`typ` `poppy-browser+jwt`). `requestOrigin`
269
+ * é a origem em que o POST chegou — o cookie precisa nascer nela.
270
+ */
271
+ verifyBrowserAssertion(assertion: unknown, requestOrigin: string): Promise<PoppyBrowserSessionResult>;
272
+ /** Estado atual da Session (ativa? logada? scopes) — o cookie do site segue isto. */
273
+ sessionState(sessionId: string): Promise<PoppySessionState | null>;
274
+ listGrants(accountId: string): Promise<PoppyGrantSummary[]>;
275
+ /** Desconectar o agente pela conta (§4.9): revoga um Account Token DA CONTA. */
276
+ revokeAccountGrant(accountId: string, grantId: string): Promise<boolean>;
277
+ /** Revoga todos os Account Tokens da conta (sair de tudo, conta suspensa…). */
278
+ revokeAllAccountGrants(accountId: string): Promise<number>;
279
+ }
280
+ export type { RequestRow };