@adonis-agora/authkit-server 1.0.0-rc.0 → 1.0.0-rc.10

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 (177) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/README.md +4 -5
  3. package/build/commands/agora_doctor.d.ts +22 -0
  4. package/build/commands/agora_doctor.js +109 -0
  5. package/build/commands/clients_create.d.ts +4 -0
  6. package/build/commands/clients_create.js +97 -0
  7. package/build/commands/commands.json +78 -0
  8. package/build/commands/doctor.js +8 -74
  9. package/build/host/views/account/apps.edge +1 -5
  10. package/build/host/views/account/extension.edge +77 -0
  11. package/build/host/views/account/mfa.edge +2 -0
  12. package/build/host/views/account/partials/extension_actions.edge +14 -0
  13. package/build/host/views/account/partials/extension_block.edge +197 -0
  14. package/build/host/views/account/partials/extension_value.edge +8 -0
  15. package/build/host/views/account/partials/nav.edge +14 -0
  16. package/build/host/views/account/security.edge +1 -5
  17. package/build/host/views/account/tokens.edge +2 -2
  18. package/build/host/views/agents/consent.edge +11 -2
  19. package/build/host/views/agents/done.edge +1 -1
  20. package/build/host/views/partials/styles.edge +1 -1
  21. package/build/index.d.ts +14 -12
  22. package/build/index.js +8 -9
  23. package/build/providers/authkit_server_provider.js +36 -8
  24. package/build/src/account/extension.d.ts +10 -0
  25. package/build/src/account/extension.js +9 -0
  26. package/build/src/account/http.d.ts +77 -0
  27. package/build/src/account/http.js +381 -0
  28. package/build/src/account/mount.d.ts +40 -0
  29. package/build/src/account/mount.js +171 -0
  30. package/build/src/account/registry.d.ts +41 -0
  31. package/build/src/account/registry.js +81 -0
  32. package/build/src/account/remote.d.ts +72 -0
  33. package/build/src/account/remote.js +406 -0
  34. package/build/src/account/runtime.d.ts +289 -0
  35. package/build/src/account/runtime.js +641 -0
  36. package/build/src/accounts/account_store.d.ts +6 -0
  37. package/build/src/accounts/lucid_store/organizations.js +6 -0
  38. package/build/src/agents/config.d.ts +9 -13
  39. package/build/src/agents/config.js +17 -11
  40. package/build/src/agents/delegation_service.js +3 -3
  41. package/build/src/agents/delegation_store.d.ts +3 -2
  42. package/build/src/agents/delegation_store.js +6 -5
  43. package/build/src/agents/extension.d.ts +261 -0
  44. package/build/src/agents/extension.js +40 -0
  45. package/build/src/agents/middleware.js +1 -1
  46. package/build/src/agents/protocol.d.ts +4 -4
  47. package/build/src/agents/registry_store.d.ts +3 -2
  48. package/build/src/agents/registry_store.js +1 -1
  49. package/build/src/agents/runtime.d.ts +26 -7
  50. package/build/src/agents/runtime.js +110 -75
  51. package/build/src/audit/audit_sink.d.ts +1 -1
  52. package/build/src/audit/audit_sink.js +10 -1
  53. package/build/src/console_authorize.d.ts +56 -0
  54. package/build/src/console_authorize.js +85 -0
  55. package/build/src/console_switcher.d.ts +132 -0
  56. package/build/src/console_switcher.js +206 -0
  57. package/build/src/define_config.d.ts +54 -14
  58. package/build/src/define_config.js +17 -8
  59. package/build/src/dev_tools/contract.d.ts +72 -0
  60. package/build/src/dev_tools/contract.js +10 -0
  61. package/build/src/dev_tools/gate.d.ts +17 -0
  62. package/build/src/dev_tools/gate.js +69 -0
  63. package/build/src/dev_tools/register.d.ts +11 -0
  64. package/build/src/dev_tools/register.js +25 -0
  65. package/build/src/dev_tools/tools.d.ts +24 -0
  66. package/build/src/dev_tools/tools.js +384 -0
  67. package/build/src/doctor/agora_checks.d.ts +15 -0
  68. package/build/src/doctor/agora_checks.js +150 -0
  69. package/build/src/doctor/agora_doctor.d.ts +115 -0
  70. package/build/src/doctor/agora_doctor.js +692 -0
  71. package/build/src/doctor/checks.d.ts +7 -1
  72. package/build/src/doctor/checks.js +47 -1
  73. package/build/src/doctor/doctor_input.d.ts +27 -0
  74. package/build/src/doctor/doctor_input.js +86 -0
  75. package/build/src/doctor/index.d.ts +5 -0
  76. package/build/src/doctor/index.js +4 -0
  77. package/build/src/host/access_token_verifier.js +7 -0
  78. package/build/src/host/account_api/account_api_controller.js +8 -6
  79. package/build/src/host/account_session_sign_in.d.ts +12 -0
  80. package/build/src/host/account_session_sign_in.js +19 -0
  81. package/build/src/host/admin_api/admin_users_service.d.ts +5 -2
  82. package/build/src/host/admin_api/api_clients_controller.d.ts +37 -0
  83. package/build/src/host/admin_api/api_clients_controller.js +31 -1
  84. package/build/src/host/admin_api/dto.d.ts +3 -1
  85. package/build/src/host/admin_api/dto.js +2 -0
  86. package/build/src/host/admin_authorize.d.ts +8 -0
  87. package/build/src/host/admin_authorize.js +25 -0
  88. package/build/src/host/admin_clients_service.d.ts +20 -0
  89. package/build/src/host/admin_clients_service.js +65 -1
  90. package/build/src/host/admin_console/admin_console_entry.d.ts +14 -0
  91. package/build/src/host/admin_console/admin_console_entry.js +80 -0
  92. package/build/src/host/admin_console/admin_shell_controller.js +8 -0
  93. package/build/src/host/admin_console/console_clients_controller.d.ts +41 -0
  94. package/build/src/host/admin_console/console_clients_controller.js +34 -1
  95. package/build/src/host/admin_console/console_personal_agents_controller.d.ts +1 -6
  96. package/build/src/host/admin_console/console_personal_agents_controller.js +50 -30
  97. package/build/src/host/admin_console/console_switcher_controller.d.ts +11 -0
  98. package/build/src/host/admin_console/console_switcher_controller.js +23 -0
  99. package/build/src/host/admin_console_warning.d.ts +8 -0
  100. package/build/src/host/admin_console_warning.js +18 -0
  101. package/build/src/host/admin_sessions_service.js +8 -5
  102. package/build/src/host/admin_validators.d.ts +29 -0
  103. package/build/src/host/admin_validators.js +30 -0
  104. package/build/src/host/auth_host_config.d.ts +14 -2
  105. package/build/src/host/console_denied_page.d.ts +6 -0
  106. package/build/src/host/console_denied_page.js +18 -0
  107. package/build/src/host/console_sso.d.ts +24 -0
  108. package/build/src/host/console_sso.js +70 -0
  109. package/build/src/host/controllers/account_apps_controller.js +15 -11
  110. package/build/src/host/controllers/account_session_controller.js +10 -1
  111. package/build/src/host/controllers/console_sso_controller.d.ts +12 -0
  112. package/build/src/host/controllers/console_sso_controller.js +61 -0
  113. package/build/src/host/controllers/interaction_controller.js +3 -1
  114. package/build/src/host/csrf.d.ts +3 -3
  115. package/build/src/host/csrf.js +3 -13
  116. package/build/src/host/default_mailer.d.ts +11 -6
  117. package/build/src/host/default_mailer.js +16 -12
  118. package/build/src/host/i18n.d.ts +41 -14
  119. package/build/src/host/i18n.js +55 -13
  120. package/build/src/host/rate_limit.d.ts +8 -0
  121. package/build/src/host/rate_limit.js +9 -0
  122. package/build/src/host/register_auth_host.d.ts +16 -0
  123. package/build/src/host/register_auth_host.js +230 -88
  124. package/build/src/host/renderers/edge_renderer.js +5 -0
  125. package/build/src/host/renderers/inertia_renderer.js +7 -1
  126. package/build/src/host/screen_props.d.ts +39 -8
  127. package/build/src/host/ui-dist/assets/index-Dq3dJVBv.js +67 -0
  128. package/build/src/host/ui-dist/assets/{index-DTSmD4RU.css → index-WNIyCEPA.css} +1 -1
  129. package/build/src/host/ui-dist/index.html +2 -2
  130. package/build/src/provider/build_provider.js +43 -6
  131. package/build/src/provider/oidc_service.d.ts +1 -1
  132. package/build/src/provider/oidc_service.js +1 -1
  133. package/build/src/schema/ensure.d.ts +14 -0
  134. package/build/src/schema/ensure.js +43 -150
  135. package/build/src/service_tenants/client_metadata.d.ts +39 -0
  136. package/build/src/service_tenants/client_metadata.js +80 -0
  137. package/build/src/service_tenants/config.d.ts +44 -0
  138. package/build/src/service_tenants/config.js +44 -0
  139. package/build/src/service_tenants/provider_hooks.d.ts +52 -0
  140. package/build/src/service_tenants/provider_hooks.js +124 -0
  141. package/build/src/service_tenants/tenant_verifier.d.ts +73 -0
  142. package/build/src/service_tenants/tenant_verifier.js +104 -0
  143. package/build/src/tenancy/tenant_labels.d.ts +30 -0
  144. package/build/src/tenancy/tenant_labels.js +64 -0
  145. package/build/types.d.ts +3 -1
  146. package/package.json +12 -9
  147. package/build/src/agents/poppy/client_registry.d.ts +0 -50
  148. package/build/src/agents/poppy/client_registry.js +0 -260
  149. package/build/src/agents/poppy/config.d.ts +0 -396
  150. package/build/src/agents/poppy/config.js +0 -228
  151. package/build/src/agents/poppy/dpop.d.ts +0 -61
  152. package/build/src/agents/poppy/dpop.js +0 -158
  153. package/build/src/agents/poppy/email_code_sign_in.d.ts +0 -27
  154. package/build/src/agents/poppy/email_code_sign_in.js +0 -140
  155. package/build/src/agents/poppy/errors.d.ts +0 -25
  156. package/build/src/agents/poppy/errors.js +0 -25
  157. package/build/src/agents/poppy/replay.d.ts +0 -25
  158. package/build/src/agents/poppy/replay.js +0 -106
  159. package/build/src/agents/poppy/resource.d.ts +0 -100
  160. package/build/src/agents/poppy/resource.js +0 -129
  161. package/build/src/agents/poppy/safe_fetch.d.ts +0 -48
  162. package/build/src/agents/poppy/safe_fetch.js +0 -262
  163. package/build/src/agents/poppy/service.d.ts +0 -293
  164. package/build/src/agents/poppy/service.js +0 -1458
  165. package/build/src/agents/poppy/session_jwt.d.ts +0 -30
  166. package/build/src/agents/poppy/session_jwt.js +0 -133
  167. package/build/src/agents/poppy/store.d.ts +0 -106
  168. package/build/src/agents/poppy/store.js +0 -261
  169. package/build/src/agents/poppy/web_session.d.ts +0 -39
  170. package/build/src/agents/poppy/web_session.js +0 -122
  171. package/build/src/host/controllers/poppy_consent_controller.d.ts +0 -19
  172. package/build/src/host/controllers/poppy_consent_controller.js +0 -199
  173. package/build/src/host/controllers/poppy_controller.d.ts +0 -33
  174. package/build/src/host/controllers/poppy_controller.js +0 -219
  175. package/build/src/host/poppy_session_key.d.ts +0 -9
  176. package/build/src/host/poppy_session_key.js +0 -9
  177. package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +0 -67
@@ -1,30 +0,0 @@
1
- import type { PoppyClient } from './client_registry.js';
2
- export declare const SESSION_JWT_TYP = "poppy-session+jwt";
3
- export declare const DELEGATION_TYP = "pap-delegation+jwt";
4
- export declare class SessionJwtError extends Error {
5
- }
6
- export interface VerifiedSessionJwt {
7
- clientId: string;
8
- sid: string;
9
- userId: string;
10
- agentId: string | null;
11
- jti: string;
12
- iat: number;
13
- }
14
- export interface VerifySessionJwtInput {
15
- /** Valor do(s) header(s) `Poppy-Session`. */
16
- header: string | string[] | undefined;
17
- method: string;
18
- url: string;
19
- /** Access token da request, ou `null`. */
20
- accessToken: string | null;
21
- /** O identificador da empresa: o issuer (§3.2). */
22
- audience: string;
23
- /** Janela de aceitação do `iat`, em segundos. */
24
- maxAge: number;
25
- /** Resolve o agente pelo `iss` (client metadata + `jwks_uri`). */
26
- resolveClient: (clientId: unknown) => Promise<PoppyClient>;
27
- now: Date;
28
- }
29
- /** Verifica o JWT (§5.3, passos 1–3). Lança {@link SessionJwtError}. */
30
- export declare function verifySessionJwt(input: VerifySessionJwtInput): Promise<VerifiedSessionJwt>;
@@ -1,133 +0,0 @@
1
- /**
2
- * O header `Poppy-Session` (Draft 0.3, §5.2–§5.3): um JWT que o personal agent assina para uma
3
- * request, com uma chave delegada. Uma chave do `jwks_uri` assina a delegação
4
- * (`pap-delegation+jwt`), que vai no header `pap_dlg` do JWT e atesta a chave delegada em `cnf.jwk`.
5
- *
6
- * Só as checagens do JWT em si: o `client_id` do token, a Session e o replay ficam no `PoppyService`.
7
- */
8
- import { calculateJwkThumbprint, decodeJwt, decodeProtectedHeader, importJWK, jwtVerify, } from 'jose';
9
- import { accessTokenHash, normalizeHtu } from './dpop.js';
10
- export const SESSION_JWT_TYP = 'poppy-session+jwt';
11
- export const DELEGATION_TYP = 'pap-delegation+jwt';
12
- /** Delegação: `ES256` obrigatório; `PS384` permite a assinatura cega (§5.2, §10.7). */
13
- const DELEGATION_ALGS = ['ES256', 'PS384'];
14
- const DELEGATION_MAX_LIFETIME_S = 24 * 3600;
15
- const CLOCK_SKEW_S = 5;
16
- /** Mesmos caracteres dos Conversation IDs (§8.2). */
17
- const SESSION_ID = /^[A-Za-z0-9_-]{1,256}$/;
18
- /** Tamanho da coluna `user_id`/`agent_id`. */
19
- const MAX_ID_LENGTH = 255;
20
- export class SessionJwtError extends Error {
21
- }
22
- const fail = (why) => new SessionJwtError(why);
23
- /** Passos 1–4 de "Delegated keys" (§5.2): devolve a chave delegada, já conferida. */
24
- async function verifyDelegation(delegation, client, now) {
25
- if (typeof delegation !== 'string' || !delegation)
26
- throw fail('pap_dlg is required');
27
- let payload;
28
- try {
29
- ({ payload } = await jwtVerify(delegation, client.keys, {
30
- algorithms: DELEGATION_ALGS,
31
- typ: DELEGATION_TYP,
32
- issuer: client.clientId,
33
- currentDate: now,
34
- clockTolerance: CLOCK_SKEW_S,
35
- requiredClaims: ['iat', 'exp', 'jti'],
36
- }));
37
- }
38
- catch {
39
- throw fail('delegation verification failed');
40
- }
41
- if (payload.exp - payload.iat > DELEGATION_MAX_LIFETIME_S) {
42
- throw fail('delegation lasts more than 24 hours');
43
- }
44
- const jwk = payload.cnf?.jwk;
45
- if (!jwk || jwk.kty !== 'EC' || jwk.crv !== 'P-256' || 'd' in jwk) {
46
- throw fail('cnf.jwk must be a public P-256 key');
47
- }
48
- return { jwk: jwk, exp: payload.exp };
49
- }
50
- /** Verifica o JWT (§5.3, passos 1–3). Lança {@link SessionJwtError}. */
51
- export async function verifySessionJwt(input) {
52
- const token = input.header;
53
- if (typeof token !== 'string' || !token)
54
- throw fail('exactly one Poppy-Session header required');
55
- let header;
56
- let iss;
57
- try {
58
- header = decodeProtectedHeader(token);
59
- iss = decodeJwt(token).iss;
60
- }
61
- catch {
62
- throw fail('malformed Session JWT');
63
- }
64
- if (header.typ !== SESSION_JWT_TYP)
65
- throw fail(`typ must be ${SESSION_JWT_TYP}`);
66
- if (header.alg !== 'ES256')
67
- throw fail('alg must be ES256');
68
- let client;
69
- try {
70
- client = await input.resolveClient(iss);
71
- }
72
- catch {
73
- throw fail('the client_id was refused');
74
- }
75
- const delegation = await verifyDelegation(header.pap_dlg, client, input.now);
76
- if (header.kid !== (await calculateJwkThumbprint(delegation.jwk, 'sha256'))) {
77
- throw fail('kid must be the thumbprint of the delegated key');
78
- }
79
- let claims;
80
- try {
81
- const key = await importJWK(delegation.jwk, 'ES256');
82
- ({ payload: claims } = await jwtVerify(token, key, {
83
- algorithms: ['ES256'],
84
- typ: SESSION_JWT_TYP,
85
- issuer: client.clientId,
86
- currentDate: input.now,
87
- }));
88
- }
89
- catch {
90
- throw fail('Session JWT signature verification failed');
91
- }
92
- if (typeof claims.aud !== 'string' || claims.aud !== input.audience) {
93
- throw fail('aud must be the Company identifier, as a single string');
94
- }
95
- if (claims.htm !== input.method.toUpperCase())
96
- throw fail('htm does not match the request');
97
- const htu = typeof claims.htu === 'string' ? normalizeHtu(claims.htu) : null;
98
- if (htu === null || htu !== normalizeHtu(input.url))
99
- throw fail('htu does not match the request');
100
- const nowS = Math.floor(input.now.getTime() / 1000);
101
- if (typeof claims.iat !== 'number' || Math.abs(nowS - claims.iat) > input.maxAge) {
102
- throw fail('iat is outside the acceptance window');
103
- }
104
- if (claims.iat > delegation.exp)
105
- throw fail('signed after the delegation expired');
106
- if (typeof claims.jti !== 'string' || !claims.jti)
107
- throw fail('jti is required');
108
- if (typeof claims.sid !== 'string' || !SESSION_ID.test(claims.sid))
109
- throw fail('invalid sid');
110
- if (typeof claims.sub !== 'string' || !claims.sub || claims.sub.length > MAX_ID_LENGTH) {
111
- throw fail('sub (the User ID) is required');
112
- }
113
- const agentId = claims.agent_id;
114
- if (agentId !== undefined &&
115
- (typeof agentId !== 'string' || !agentId || agentId.length > MAX_ID_LENGTH)) {
116
- throw fail('invalid agent_id');
117
- }
118
- if (input.accessToken === null) {
119
- if (claims.ath !== undefined)
120
- throw fail('ath must be absent without an access token');
121
- }
122
- else if (claims.ath !== accessTokenHash(input.accessToken)) {
123
- throw fail('ath does not match the access token');
124
- }
125
- return {
126
- clientId: client.clientId,
127
- sid: claims.sid,
128
- userId: claims.sub,
129
- agentId: agentId ?? null,
130
- jti: claims.jti,
131
- iat: claims.iat,
132
- };
133
- }
@@ -1,106 +0,0 @@
1
- /**
2
- * Persistência do Poppy — tabelas LIB-OWNED (ver `schema/ensure.ts`): Sessions, access tokens (só o
3
- * hash), pedidos de sign-in e de entrada de navegador, e `jti` vistos. Os refresh tokens
4
- * reaproveitam `auth_agent_grants` + `auth_agent_refresh_tokens` (ver `DelegationStore`).
5
- *
6
- * Mesmas regras do `DelegationStore`: query builder puro, datas comparadas em JS
7
- * depois de buscar a linha pela chave, transições de estado atômicas por
8
- * `where status = from`.
9
- */
10
- export declare const SESSION_TABLE = "auth_poppy_sessions";
11
- export declare const TOKEN_TABLE = "auth_poppy_tokens";
12
- export declare const REQUEST_TABLE = "auth_poppy_requests";
13
- export interface SessionRow {
14
- /** Derivado de `client_id` + `sid` (§5.3). */
15
- id: string;
16
- clientId: string;
17
- userId: string;
18
- agentId: string | null;
19
- /** Conta que a Session já usou: recusamos outra com `invalid_session` (§5.4). */
20
- accountId: string | null;
21
- /** Colunas da Draft 0.1, sem uso: o acesso à conta não passa pela Session. */
22
- grantId: string | null;
23
- scope: string;
24
- expiresAt: Date;
25
- endedAt: Date | null;
26
- createdAt: Date;
27
- updatedAt: Date;
28
- }
29
- /** Access token (§4.5). */
30
- export interface TokenRow {
31
- tokenHash: string;
32
- clientId: string;
33
- accountId: string | null;
34
- grantId: string | null;
35
- scope: string;
36
- resource: string | null;
37
- /** Thumbprint da chave DPoP; `null` = Bearer. */
38
- jkt: string | null;
39
- expiresAt: Date;
40
- createdAt: Date;
41
- }
42
- export type RequestKind = 'authorize' | 'device' | 'mediated' | 'browser';
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
- * Registra a conta que a Session usa (§5.4). Atômico: só se nenhuma foi registrada ou é a MESMA.
74
- * `false` = outra conta chegou nesse meio-tempo.
75
- */
76
- bindSessionAccount(id: string, accountId: string, at: Date): Promise<boolean>;
77
- /** Encerra todas as Sessions de um agente (bloqueio no console): param na hora. */
78
- endSessionsOfClient(clientId: string, at: Date): Promise<number>;
79
- insertToken(row: TokenRow): Promise<void>;
80
- findToken(hash: string): Promise<TokenRow | null>;
81
- deleteToken(hash: string): Promise<boolean>;
82
- deleteTokensExpiredBefore(before: Date): Promise<void>;
83
- insertRequest(row: Omit<RequestRow, 'attempts' | 'lastPolledAt' | 'grantedScope'> & {
84
- grantedScope?: string | null;
85
- }): Promise<void>;
86
- findRequest(id: string): Promise<RequestRow | null>;
87
- findRequestByCodeHash(hash: string): Promise<RequestRow | null>;
88
- findRequestByUserCode(userCode: string): Promise<RequestRow | null>;
89
- /** Transição atômica — só se o pedido ainda está em `from`. */
90
- transitionRequest(id: string, from: RequestStatus, patch: {
91
- status: RequestStatus;
92
- accountId?: string;
93
- grantedScope?: string;
94
- codeHash?: string;
95
- attempts?: number;
96
- expiresAt?: Date;
97
- clientId?: string;
98
- sessionId?: string;
99
- data?: Record<string, unknown>;
100
- }): Promise<boolean>;
101
- /** Conta uma tentativa errada (atômico sobre o valor lido). */
102
- bumpAttempts(id: string, from: number): Promise<boolean>;
103
- setRequestData(id: string, data: Record<string, unknown>): Promise<void>;
104
- markPolled(id: string, at: Date): Promise<void>;
105
- deleteRequestsExpiredBefore(before: Date): Promise<void>;
106
- }
@@ -1,261 +0,0 @@
1
- /**
2
- * Persistência do Poppy — tabelas LIB-OWNED (ver `schema/ensure.ts`): Sessions, access tokens (só o
3
- * hash), pedidos de sign-in e de entrada de navegador, e `jti` vistos. Os refresh tokens
4
- * reaproveitam `auth_agent_grants` + `auth_agent_refresh_tokens` (ver `DelegationStore`).
5
- *
6
- * Mesmas regras do `DelegationStore`: query builder puro, datas comparadas em JS
7
- * depois de buscar a linha pela chave, transições de estado atômicas por
8
- * `where status = from`.
9
- */
10
- export const SESSION_TABLE = 'auth_poppy_sessions';
11
- export const TOKEN_TABLE = 'auth_poppy_tokens';
12
- export const REQUEST_TABLE = 'auth_poppy_requests';
13
- function toDate(value) {
14
- if (value instanceof Date)
15
- return value;
16
- if (typeof value === 'number')
17
- return new Date(value);
18
- if (typeof value === 'string' && /^\d+$/.test(value))
19
- return new Date(Number(value));
20
- return new Date(String(value));
21
- }
22
- function toDateOrNull(value) {
23
- return value === null || value === undefined ? null : toDate(value);
24
- }
25
- function affected(result) {
26
- if (typeof result === 'number')
27
- return result;
28
- if (Array.isArray(result))
29
- return result.length;
30
- return 0;
31
- }
32
- function toSession(row) {
33
- return {
34
- id: row.id,
35
- clientId: row.client_id,
36
- userId: row.user_id,
37
- agentId: row.agent_id ?? null,
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
- clientId: row.client_id,
51
- accountId: row.account_id ?? null,
52
- grantId: row.grant_id ?? null,
53
- scope: row.scope ?? '',
54
- resource: row.resource ?? null,
55
- jkt: row.jkt ?? null,
56
- expiresAt: toDate(row.expires_at),
57
- createdAt: toDate(row.created_at),
58
- };
59
- }
60
- function toRequest(row) {
61
- let data = null;
62
- if (row.data) {
63
- try {
64
- data = JSON.parse(row.data);
65
- }
66
- catch {
67
- data = null;
68
- }
69
- }
70
- return {
71
- id: row.id,
72
- kind: row.kind,
73
- codeHash: row.code_hash ?? null,
74
- userCode: row.user_code ?? null,
75
- clientId: row.client_id,
76
- sessionId: row.session_id ?? null,
77
- accountId: row.account_id ?? null,
78
- requestedScope: row.requested_scope ?? '',
79
- grantedScope: row.granted_scope ?? null,
80
- redirectUri: row.redirect_uri ?? null,
81
- codeChallenge: row.code_challenge ?? null,
82
- status: row.status,
83
- attempts: Number(row.attempts ?? 0),
84
- intervalSeconds: row.interval_seconds === null || row.interval_seconds === undefined
85
- ? null
86
- : Number(row.interval_seconds),
87
- lastPolledAt: toDateOrNull(row.last_polled_at),
88
- data,
89
- expiresAt: toDate(row.expires_at),
90
- createdAt: toDate(row.created_at),
91
- };
92
- }
93
- export class PoppyStore {
94
- conn;
95
- constructor(conn) {
96
- this.conn = conn;
97
- }
98
- // ─── sessions ─────────────────────────────────────────────────────────────
99
- async insertSession(row) {
100
- await this.conn().table(SESSION_TABLE).insert({
101
- id: row.id,
102
- client_id: row.clientId,
103
- user_id: row.userId,
104
- agent_id: row.agentId,
105
- account_id: row.accountId,
106
- grant_id: row.grantId,
107
- scope: row.scope,
108
- expires_at: row.expiresAt,
109
- created_at: row.createdAt,
110
- updated_at: row.updatedAt,
111
- });
112
- }
113
- async findSession(id) {
114
- if (typeof id !== 'string' || !id)
115
- return null;
116
- const row = await this.conn().from(SESSION_TABLE).where('id', id).first();
117
- return row ? toSession(row) : null;
118
- }
119
- /** Estende a vida da Session (uso). */
120
- async touchSession(id, expiresAt, at) {
121
- await this.conn()
122
- .from(SESSION_TABLE)
123
- .where('id', id)
124
- .whereNull('ended_at')
125
- .update({ expires_at: expiresAt, updated_at: at });
126
- }
127
- /**
128
- * Registra a conta que a Session usa (§5.4). Atômico: só se nenhuma foi registrada ou é a MESMA.
129
- * `false` = outra conta chegou nesse meio-tempo.
130
- */
131
- async bindSessionAccount(id, accountId, at) {
132
- const result = await this.conn()
133
- .from(SESSION_TABLE)
134
- .where('id', id)
135
- .where((q) => q.whereNull('account_id').orWhere('account_id', accountId))
136
- .update({ account_id: accountId, updated_at: at });
137
- return affected(result) > 0;
138
- }
139
- /** Encerra todas as Sessions de um agente (bloqueio no console): param na hora. */
140
- async endSessionsOfClient(clientId, at) {
141
- const result = await this.conn()
142
- .from(SESSION_TABLE)
143
- .where('client_id', clientId)
144
- .whereNull('ended_at')
145
- .update({ ended_at: at, grant_id: null, scope: '', updated_at: at });
146
- return affected(result);
147
- }
148
- // ─── access tokens ────────────────────────────────────────────────────────
149
- async insertToken(row) {
150
- await this.conn().table(TOKEN_TABLE).insert({
151
- token_hash: row.tokenHash,
152
- // `session_id`/`user_id` são NOT NULL desde a Draft 0.1 e o schema só é aditivo; o sign-in
153
- // não conhece a Session nem o User ID (§4.4).
154
- session_id: '',
155
- client_id: row.clientId,
156
- user_id: '',
157
- account_id: row.accountId,
158
- grant_id: row.grantId,
159
- scope: row.scope,
160
- resource: row.resource,
161
- jkt: row.jkt,
162
- expires_at: row.expiresAt,
163
- created_at: row.createdAt,
164
- });
165
- }
166
- async findToken(hash) {
167
- const row = await this.conn().from(TOKEN_TABLE).where('token_hash', hash).first();
168
- return row ? toToken(row) : null;
169
- }
170
- async deleteToken(hash) {
171
- return affected(await this.conn().from(TOKEN_TABLE).where('token_hash', hash).delete()) > 0;
172
- }
173
- async deleteTokensExpiredBefore(before) {
174
- await this.conn().from(TOKEN_TABLE).where('expires_at', '<', before).delete();
175
- }
176
- // ─── sign-in requests ─────────────────────────────────────────────────────
177
- async insertRequest(row) {
178
- await this.conn()
179
- .table(REQUEST_TABLE)
180
- .insert({
181
- id: row.id,
182
- kind: row.kind,
183
- code_hash: row.codeHash,
184
- user_code: row.userCode,
185
- client_id: row.clientId,
186
- session_id: row.sessionId,
187
- account_id: row.accountId,
188
- requested_scope: row.requestedScope,
189
- granted_scope: row.grantedScope ?? null,
190
- redirect_uri: row.redirectUri,
191
- code_challenge: row.codeChallenge,
192
- status: row.status,
193
- attempts: 0,
194
- interval_seconds: row.intervalSeconds,
195
- data: row.data ? JSON.stringify(row.data) : null,
196
- expires_at: row.expiresAt,
197
- created_at: row.createdAt,
198
- });
199
- }
200
- async findRequest(id) {
201
- if (typeof id !== 'string' || !id)
202
- return null;
203
- const row = await this.conn().from(REQUEST_TABLE).where('id', id).first();
204
- return row ? toRequest(row) : null;
205
- }
206
- async findRequestByCodeHash(hash) {
207
- const row = await this.conn().from(REQUEST_TABLE).where('code_hash', hash).first();
208
- return row ? toRequest(row) : null;
209
- }
210
- async findRequestByUserCode(userCode) {
211
- const row = await this.conn().from(REQUEST_TABLE).where('user_code', userCode).first();
212
- return row ? toRequest(row) : null;
213
- }
214
- /** Transição atômica — só se o pedido ainda está em `from`. */
215
- async transitionRequest(id, from, patch) {
216
- const update = { status: patch.status };
217
- if (patch.accountId !== undefined)
218
- update.account_id = patch.accountId;
219
- if (patch.grantedScope !== undefined)
220
- update.granted_scope = patch.grantedScope;
221
- if (patch.codeHash !== undefined)
222
- update.code_hash = patch.codeHash;
223
- if (patch.attempts !== undefined)
224
- update.attempts = patch.attempts;
225
- if (patch.expiresAt !== undefined)
226
- update.expires_at = patch.expiresAt;
227
- if (patch.clientId !== undefined)
228
- update.client_id = patch.clientId;
229
- if (patch.sessionId !== undefined)
230
- update.session_id = patch.sessionId;
231
- if (patch.data !== undefined)
232
- update.data = JSON.stringify(patch.data);
233
- const result = await this.conn()
234
- .from(REQUEST_TABLE)
235
- .where('id', id)
236
- .where('status', from)
237
- .update(update);
238
- return affected(result) > 0;
239
- }
240
- /** Conta uma tentativa errada (atômico sobre o valor lido). */
241
- async bumpAttempts(id, from) {
242
- const result = await this.conn()
243
- .from(REQUEST_TABLE)
244
- .where('id', id)
245
- .where('attempts', from)
246
- .update({ attempts: from + 1 });
247
- return affected(result) > 0;
248
- }
249
- async setRequestData(id, data) {
250
- await this.conn()
251
- .from(REQUEST_TABLE)
252
- .where('id', id)
253
- .update({ data: JSON.stringify(data) });
254
- }
255
- async markPolled(id, at) {
256
- await this.conn().from(REQUEST_TABLE).where('id', id).update({ last_polled_at: at });
257
- }
258
- async deleteRequestsExpiredBefore(before) {
259
- await this.conn().from(REQUEST_TABLE).where('expires_at', '<', before).delete();
260
- }
261
- }
@@ -1,39 +0,0 @@
1
- /**
2
- * O cookie do site para o navegador de um personal agent (§6): a entrada na Session, e o
3
- * acompanhamento do acesso à conta autorizado nela a cada request.
4
- */
5
- import type { HttpContext } from '@adonisjs/core/http';
6
- import type { PoppyBrowserJoin, PoppySessionState } from './service.js';
7
- export interface PoppyWebSessionMarker {
8
- sessionId: string;
9
- clientId: string;
10
- userId: string;
11
- /** Conta, grant e scopes autorizados na entrada (§6, passo 2); `null` = entrada deslogada. */
12
- accountId: string | null;
13
- grantId: string | null;
14
- scopes: string[];
15
- }
16
- /**
17
- * Liga o navegador à Session (§6, passo 3): regenera a sessão do app (session fixation) e a marca
18
- * como do agente. Uma entrada com access token faz o login na conta autorizada, no lugar de qualquer
19
- * outro, com o mecanismo do app — o hook `web.login`, ou a sessão de conta do authkit +
20
- * `adonisAuth.guard`. Uma entrada sem access token não mexe no login do navegador — a não ser que
21
- * uma entrada anterior o tenha logado: aí o sign-in dela termina aqui.
22
- */
23
- export declare function loginPoppyBrowser(httpCtx: HttpContext, result: PoppyBrowserJoin): Promise<void>;
24
- /**
25
- * Estado da Session Poppy deste navegador (preenchido por {@link poppyWebSession}).
26
- *
27
- * @experimental Poppy (Personal Agent Protocol) Draft 0.3 — acompanha a spec em desenvolvimento e PODE MUDAR
28
- * de forma incompatível fora de majors enquanto ela for draft.
29
- */
30
- export declare function poppyWebSessionOf(ctx: HttpContext): PoppySessionState | null;
31
- /**
32
- * Middleware do site: o cookie age pela conta e pelos scopes autorizados na entrada e deixa de agir
33
- * quando o sign-in termina (sign-out, agente desconectado); não sobrevive à Session (§6). Um login
34
- * antigo não volta. Coloque-o depois do middleware de sessão.
35
- *
36
- * @experimental Poppy (Personal Agent Protocol) Draft 0.3 — acompanha a spec em desenvolvimento e PODE MUDAR
37
- * de forma incompatível fora de majors enquanto ela for draft.
38
- */
39
- export declare function poppyWebSession(): (httpCtx: HttpContext, next: () => Promise<void>) => Promise<void>;