@adonis-agora/authkit-server 1.0.0-rc.1 → 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 (163) hide show
  1. package/CHANGELOG.md +94 -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 +12 -12
  22. package/build/index.js +7 -9
  23. package/build/providers/authkit_server_provider.js +28 -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_switcher.d.ts +132 -0
  54. package/build/src/console_switcher.js +206 -0
  55. package/build/src/define_config.d.ts +34 -12
  56. package/build/src/define_config.js +15 -7
  57. package/build/src/dev_tools/contract.d.ts +72 -0
  58. package/build/src/dev_tools/contract.js +10 -0
  59. package/build/src/dev_tools/gate.d.ts +17 -0
  60. package/build/src/dev_tools/gate.js +69 -0
  61. package/build/src/dev_tools/register.d.ts +11 -0
  62. package/build/src/dev_tools/register.js +25 -0
  63. package/build/src/dev_tools/tools.d.ts +24 -0
  64. package/build/src/dev_tools/tools.js +384 -0
  65. package/build/src/doctor/agora_checks.d.ts +15 -0
  66. package/build/src/doctor/agora_checks.js +150 -0
  67. package/build/src/doctor/agora_doctor.d.ts +115 -0
  68. package/build/src/doctor/agora_doctor.js +692 -0
  69. package/build/src/doctor/doctor_input.d.ts +27 -0
  70. package/build/src/doctor/doctor_input.js +86 -0
  71. package/build/src/doctor/index.d.ts +5 -0
  72. package/build/src/doctor/index.js +4 -0
  73. package/build/src/host/access_token_verifier.js +7 -0
  74. package/build/src/host/account_api/account_api_controller.js +8 -6
  75. package/build/src/host/account_session_sign_in.d.ts +12 -0
  76. package/build/src/host/account_session_sign_in.js +19 -0
  77. package/build/src/host/admin_api/admin_users_service.d.ts +5 -2
  78. package/build/src/host/admin_api/api_clients_controller.d.ts +37 -0
  79. package/build/src/host/admin_api/api_clients_controller.js +31 -1
  80. package/build/src/host/admin_api/dto.d.ts +3 -1
  81. package/build/src/host/admin_api/dto.js +2 -0
  82. package/build/src/host/admin_authorize.d.ts +8 -0
  83. package/build/src/host/admin_authorize.js +25 -0
  84. package/build/src/host/admin_clients_service.d.ts +20 -0
  85. package/build/src/host/admin_clients_service.js +55 -0
  86. package/build/src/host/admin_console/admin_console_entry.d.ts +14 -0
  87. package/build/src/host/admin_console/admin_console_entry.js +80 -0
  88. package/build/src/host/admin_console/admin_shell_controller.js +8 -0
  89. package/build/src/host/admin_console/console_clients_controller.d.ts +41 -0
  90. package/build/src/host/admin_console/console_clients_controller.js +34 -1
  91. package/build/src/host/admin_console/console_personal_agents_controller.d.ts +1 -6
  92. package/build/src/host/admin_console/console_personal_agents_controller.js +50 -30
  93. package/build/src/host/admin_console/console_switcher_controller.d.ts +11 -0
  94. package/build/src/host/admin_console/console_switcher_controller.js +23 -0
  95. package/build/src/host/admin_sessions_service.js +8 -5
  96. package/build/src/host/admin_validators.d.ts +29 -0
  97. package/build/src/host/admin_validators.js +30 -0
  98. package/build/src/host/auth_host_config.d.ts +9 -2
  99. package/build/src/host/controllers/account_apps_controller.js +15 -11
  100. package/build/src/host/csrf.d.ts +3 -3
  101. package/build/src/host/csrf.js +3 -13
  102. package/build/src/host/default_mailer.d.ts +11 -6
  103. package/build/src/host/default_mailer.js +16 -12
  104. package/build/src/host/i18n.d.ts +41 -14
  105. package/build/src/host/i18n.js +55 -13
  106. package/build/src/host/rate_limit.d.ts +8 -0
  107. package/build/src/host/rate_limit.js +9 -0
  108. package/build/src/host/register_auth_host.d.ts +14 -0
  109. package/build/src/host/register_auth_host.js +167 -106
  110. package/build/src/host/renderers/edge_renderer.js +5 -0
  111. package/build/src/host/renderers/inertia_renderer.js +7 -1
  112. package/build/src/host/screen_props.d.ts +39 -8
  113. package/build/src/host/ui-dist/assets/index-Dq3dJVBv.js +67 -0
  114. package/build/src/host/ui-dist/assets/{index-DTSmD4RU.css → index-WNIyCEPA.css} +1 -1
  115. package/build/src/host/ui-dist/index.html +2 -2
  116. package/build/src/provider/build_provider.js +37 -6
  117. package/build/src/provider/oidc_service.d.ts +1 -1
  118. package/build/src/provider/oidc_service.js +1 -1
  119. package/build/src/schema/ensure.d.ts +14 -0
  120. package/build/src/schema/ensure.js +43 -150
  121. package/build/src/service_tenants/client_metadata.d.ts +39 -0
  122. package/build/src/service_tenants/client_metadata.js +80 -0
  123. package/build/src/service_tenants/config.d.ts +44 -0
  124. package/build/src/service_tenants/config.js +44 -0
  125. package/build/src/service_tenants/provider_hooks.d.ts +52 -0
  126. package/build/src/service_tenants/provider_hooks.js +124 -0
  127. package/build/src/service_tenants/tenant_verifier.d.ts +73 -0
  128. package/build/src/service_tenants/tenant_verifier.js +104 -0
  129. package/build/src/tenancy/tenant_labels.d.ts +30 -0
  130. package/build/src/tenancy/tenant_labels.js +64 -0
  131. package/build/types.d.ts +3 -1
  132. package/package.json +9 -6
  133. package/build/src/agents/poppy/client_registry.d.ts +0 -50
  134. package/build/src/agents/poppy/client_registry.js +0 -260
  135. package/build/src/agents/poppy/config.d.ts +0 -396
  136. package/build/src/agents/poppy/config.js +0 -228
  137. package/build/src/agents/poppy/dpop.d.ts +0 -61
  138. package/build/src/agents/poppy/dpop.js +0 -158
  139. package/build/src/agents/poppy/email_code_sign_in.d.ts +0 -27
  140. package/build/src/agents/poppy/email_code_sign_in.js +0 -140
  141. package/build/src/agents/poppy/errors.d.ts +0 -25
  142. package/build/src/agents/poppy/errors.js +0 -25
  143. package/build/src/agents/poppy/replay.d.ts +0 -25
  144. package/build/src/agents/poppy/replay.js +0 -106
  145. package/build/src/agents/poppy/resource.d.ts +0 -100
  146. package/build/src/agents/poppy/resource.js +0 -129
  147. package/build/src/agents/poppy/safe_fetch.d.ts +0 -48
  148. package/build/src/agents/poppy/safe_fetch.js +0 -262
  149. package/build/src/agents/poppy/service.d.ts +0 -293
  150. package/build/src/agents/poppy/service.js +0 -1458
  151. package/build/src/agents/poppy/session_jwt.d.ts +0 -30
  152. package/build/src/agents/poppy/session_jwt.js +0 -133
  153. package/build/src/agents/poppy/store.d.ts +0 -106
  154. package/build/src/agents/poppy/store.js +0 -261
  155. package/build/src/agents/poppy/web_session.d.ts +0 -39
  156. package/build/src/agents/poppy/web_session.js +0 -122
  157. package/build/src/host/controllers/poppy_consent_controller.d.ts +0 -19
  158. package/build/src/host/controllers/poppy_consent_controller.js +0 -199
  159. package/build/src/host/controllers/poppy_controller.d.ts +0 -33
  160. package/build/src/host/controllers/poppy_controller.js +0 -219
  161. package/build/src/host/poppy_session_key.d.ts +0 -9
  162. package/build/src/host/poppy_session_key.js +0 -9
  163. package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +0 -67
@@ -1,122 +0,0 @@
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 (§6, passo 3): regenera a sessão do app (session fixation) e a marca
7
- * como do agente. Uma entrada com access token faz o login na conta autorizada, no lugar de qualquer
8
- * outro, com o mecanismo do app — o hook `web.login`, ou a sessão de conta do authkit +
9
- * `adonisAuth.guard`. Uma entrada sem access token não mexe no login do navegador — a não ser que
10
- * uma entrada anterior o tenha logado: aí o sign-in dela termina aqui.
11
- */
12
- export async function loginPoppyBrowser(httpCtx, result) {
13
- // Sem augmentation no barrel (ver `augmentation_isolation.spec.ts`): a sessão é lida sem tipo.
14
- const ctx = httpCtx;
15
- const service = await ctx.containerResolver.make('authkit.server');
16
- const cfg = service.config;
17
- const login = cfg.personalAgents?.poppy?.web?.login;
18
- const previous = ctx.session.get(POPPY_WEB_SESSION_KEY);
19
- await ctx.session.regenerate();
20
- const marker = {
21
- sessionId: result.sessionId,
22
- clientId: result.clientId,
23
- userId: result.userId,
24
- accountId: result.accountId,
25
- grantId: result.grantId,
26
- scopes: result.scopes,
27
- };
28
- ctx.session.put(POPPY_WEB_SESSION_KEY, marker);
29
- if (login) {
30
- await login(ctx, {
31
- sessionId: result.sessionId,
32
- clientId: result.clientId,
33
- userId: result.userId,
34
- accountId: result.accountId,
35
- scopes: result.scopes,
36
- });
37
- return;
38
- }
39
- if (result.accountId) {
40
- ctx.session.put(ACCOUNT_SESSION_KEY, result.accountId);
41
- const account = await cfg.accountStore.findById(result.accountId);
42
- if (account)
43
- await syncAdonisAuthLogin(ctx, cfg, account);
44
- }
45
- else if (previous?.accountId) {
46
- ctx.session.forget(ACCOUNT_SESSION_KEY);
47
- await syncAdonisAuthLogout(ctx, cfg);
48
- }
49
- }
50
- const states = new WeakMap();
51
- /**
52
- * Estado da Session Poppy deste navegador (preenchido por {@link poppyWebSession}).
53
- *
54
- * @experimental Poppy (Personal Agent Protocol) Draft 0.3 — acompanha a spec em desenvolvimento e PODE MUDAR
55
- * de forma incompatível fora de majors enquanto ela for draft.
56
- */
57
- export function poppyWebSessionOf(ctx) {
58
- return states.get(ctx) ?? null;
59
- }
60
- /**
61
- * Middleware do site: o cookie age pela conta e pelos scopes autorizados na entrada e deixa de agir
62
- * quando o sign-in termina (sign-out, agente desconectado); não sobrevive à Session (§6). Um login
63
- * antigo não volta. Coloque-o depois do middleware de sessão.
64
- *
65
- * @experimental Poppy (Personal Agent Protocol) Draft 0.3 — acompanha a spec em desenvolvimento e PODE MUDAR
66
- * de forma incompatível fora de majors enquanto ela for draft.
67
- */
68
- export function poppyWebSession() {
69
- return async (httpCtx, next) => {
70
- const ctx = httpCtx;
71
- const marker = ctx.session?.get(POPPY_WEB_SESSION_KEY);
72
- if (!marker?.sessionId)
73
- return next();
74
- const runtime = await personalAgentsFor(ctx);
75
- const state = runtime?.poppy ? await runtime.poppy.browserState(marker) : null;
76
- const service = await ctx.containerResolver.make('authkit.server');
77
- const cfg = service.config;
78
- const login = cfg.personalAgents?.poppy?.web?.login;
79
- if (!state?.active || state.clientId !== marker.clientId) {
80
- // A Session terminou: o cookie também — e o login dela, não o do próprio navegador.
81
- ctx.session.forget(POPPY_WEB_SESSION_KEY);
82
- if (marker.accountId) {
83
- ctx.session.forget(ACCOUNT_SESSION_KEY);
84
- await syncAdonisAuthLogout(ctx, cfg);
85
- }
86
- await ctx.session.regenerate();
87
- return next();
88
- }
89
- states.set(ctx, state);
90
- if (login) {
91
- await login(ctx, {
92
- sessionId: state.sessionId,
93
- clientId: state.clientId,
94
- userId: state.userId,
95
- accountId: state.accountId,
96
- scopes: state.scopes,
97
- });
98
- return next();
99
- }
100
- const current = ctx.session.get(ACCOUNT_SESSION_KEY);
101
- if (state.signedIn && state.accountId) {
102
- if (current !== state.accountId) {
103
- ctx.session.put(ACCOUNT_SESSION_KEY, state.accountId);
104
- const account = await cfg.accountStore.findById(state.accountId);
105
- if (account)
106
- await syncAdonisAuthLogin(ctx, cfg, account);
107
- }
108
- }
109
- else if (marker.accountId) {
110
- // O sign-in da entrada terminou: o navegador sai, e a Session segue deslogada.
111
- ctx.session.put(POPPY_WEB_SESSION_KEY, {
112
- ...marker,
113
- accountId: null,
114
- grantId: null,
115
- scopes: [],
116
- });
117
- ctx.session.forget(ACCOUNT_SESSION_KEY);
118
- await syncAdonisAuthLogout(ctx, cfg);
119
- }
120
- return next();
121
- };
122
- }
@@ -1,19 +0,0 @@
1
- import '../augmentations.js';
2
- import type { HttpContext } from '@adonisjs/core/http';
3
- /**
4
- * Telas do usuário no Poppy: o consentimento do Direct Sign-In (o
5
- * `authorization_endpoint`, §4.2) e a do Device Sign-In (o `verification_uri`,
6
- * §4.3). Montadas atrás do `accountGuard`: sem sessão, o usuário passa pelo login
7
- * DESTE app e volta. Abrir o link nunca aprova sozinho — a aprovação é um POST
8
- * com CSRF. Recusadas durante impersonation.
9
- */
10
- export default class PoppyConsentController {
11
- /** GET {prefix}/oauth/authorize */
12
- authorize(ctx: HttpContext): Promise<any>;
13
- /** POST {prefix}/oauth/authorize — `request_id`, `decision=allow|deny`, `scope[]`. */
14
- decideAuthorization(ctx: HttpContext): Promise<any>;
15
- /** GET {prefix}/device?user_code=XXXX-XXXX */
16
- device(ctx: HttpContext): Promise<any>;
17
- /** POST {prefix}/device — `user_code`, `decision`, `scope[]`. */
18
- decideDevice(ctx: HttpContext): Promise<any>;
19
- }
@@ -1,199 +0,0 @@
1
- import '../augmentations.js';
2
- import { personalAgentsFor } from '../../agents/runtime.js';
3
- import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
4
- import { impersonationState } from '../impersonation_session.js';
5
- import { redirectExact } from '../redirect_exact.js';
6
- /** A tela nunca pode ser emoldurada (clickjacking — ver `agent_consent_controller.ts`). */
7
- function denyFraming(ctx) {
8
- ctx.response.header('X-Frame-Options', 'DENY');
9
- ctx.response.header('Content-Security-Policy', "frame-ancestors 'none'");
10
- }
11
- function chosenScopes(ctx) {
12
- const raw = ctx.request.input('scope');
13
- return (Array.isArray(raw) ? raw : raw ? [raw] : []).map(String);
14
- }
15
- /**
16
- * Telas do usuário no Poppy: o consentimento do Direct Sign-In (o
17
- * `authorization_endpoint`, §4.2) e a do Device Sign-In (o `verification_uri`,
18
- * §4.3). Montadas atrás do `accountGuard`: sem sessão, o usuário passa pelo login
19
- * DESTE app e volta. Abrir o link nunca aprova sozinho — a aprovação é um POST
20
- * com CSRF. Recusadas durante impersonation.
21
- */
22
- export default class PoppyConsentController {
23
- /** GET {prefix}/oauth/authorize */
24
- async authorize(ctx) {
25
- const runtime = await personalAgentsFor(ctx);
26
- const poppy = runtime?.poppy;
27
- if (!poppy?.cfg.signIn.direct)
28
- return ctx.response.notFound();
29
- denyFraming(ctx);
30
- const service = await ctx.containerResolver.make('authkit.server');
31
- const render = service.config.render;
32
- if (impersonationState(ctx).active) {
33
- return render(ctx, 'agents/consent', {
34
- csrfToken: ctx.request.csrfToken,
35
- action: poppy.urls.authorization,
36
- request: null,
37
- account: null,
38
- error: 'agents.consent.impersonating',
39
- });
40
- }
41
- const accountId = ctx.session.get(ACCOUNT_SESSION_KEY);
42
- const outcome = await poppy.startAuthorization(ctx.request.qs(), accountId);
43
- if (outcome.kind === 'redirect')
44
- return redirectExact(ctx.response, outcome.url);
45
- if (outcome.kind === 'error') {
46
- ctx.response.status(400);
47
- return render(ctx, 'agents/done', {
48
- status: 'invalid',
49
- agentName: null,
50
- scopes: [],
51
- });
52
- }
53
- const account = await service.config.accountStore.findById(accountId);
54
- return render(ctx, 'agents/consent', {
55
- csrfToken: ctx.request.csrfToken,
56
- action: poppy.urls.authorization,
57
- request: {
58
- agentName: outcome.request.agentName,
59
- agentOrigin: outcome.request.agentOrigin,
60
- logoUri: outcome.request.logoUri,
61
- scopes: outcome.request.scopes,
62
- hidden: [{ name: 'request_id', value: outcome.request.id }],
63
- },
64
- account: { email: account?.email ?? null },
65
- error: null,
66
- });
67
- }
68
- /** POST {prefix}/oauth/authorize — `request_id`, `decision=allow|deny`, `scope[]`. */
69
- async decideAuthorization(ctx) {
70
- const runtime = await personalAgentsFor(ctx);
71
- const poppy = runtime?.poppy;
72
- if (!poppy?.cfg.signIn.direct)
73
- return ctx.response.notFound();
74
- denyFraming(ctx);
75
- const service = await ctx.containerResolver.make('authkit.server');
76
- const cfg = service.config;
77
- const render = cfg.render;
78
- if (impersonationState(ctx).active) {
79
- return render(ctx, 'agents/done', {
80
- status: 'expired',
81
- agentName: null,
82
- scopes: [],
83
- });
84
- }
85
- const accountId = ctx.session.get(ACCOUNT_SESSION_KEY);
86
- const decided = await poppy.decideAuthorization({
87
- requestId: String(ctx.request.input('request_id') ?? ''),
88
- accountId,
89
- allow: ctx.request.input('decision') === 'allow',
90
- scopes: chosenScopes(ctx),
91
- });
92
- if (!decided) {
93
- return render(ctx, 'agents/done', {
94
- status: 'expired',
95
- agentName: null,
96
- scopes: [],
97
- });
98
- }
99
- await cfg.audit?.record({
100
- type: decided.approved ? 'agent.delegation_approved' : 'agent.delegation_denied',
101
- accountId,
102
- clientId: decided.clientId,
103
- ip: ctx.request.ip?.() ?? null,
104
- metadata: { protocol: 'poppy', signIn: 'direct', scopes: decided.approved ?? [] },
105
- });
106
- return redirectExact(ctx.response, decided.url);
107
- }
108
- /** GET {prefix}/device?user_code=XXXX-XXXX */
109
- async device(ctx) {
110
- const runtime = await personalAgentsFor(ctx);
111
- const poppy = runtime?.poppy;
112
- if (!poppy?.cfg.signIn.device)
113
- return ctx.response.notFound();
114
- denyFraming(ctx);
115
- const service = await ctx.containerResolver.make('authkit.server');
116
- const render = service.config.render;
117
- const base = {
118
- csrfToken: ctx.request.csrfToken,
119
- action: poppy.urls.device,
120
- request: null,
121
- account: null,
122
- error: null,
123
- };
124
- if (impersonationState(ctx).active) {
125
- return render(ctx, 'agents/consent', {
126
- ...base,
127
- error: 'agents.consent.impersonating',
128
- });
129
- }
130
- const input = ctx.request.input('user_code');
131
- if (!input)
132
- return render(ctx, 'agents/consent', base);
133
- const pending = await poppy.pendingDevice(input);
134
- if (!pending) {
135
- return render(ctx, 'agents/consent', {
136
- ...base,
137
- error: 'agents.consent.invalid_code',
138
- });
139
- }
140
- const accountId = ctx.session.get(ACCOUNT_SESSION_KEY);
141
- const account = await service.config.accountStore.findById(accountId);
142
- return render(ctx, 'agents/consent', {
143
- ...base,
144
- request: {
145
- userCode: pending.userCode,
146
- agentName: pending.agentName,
147
- agentOrigin: pending.agentOrigin,
148
- scopes: pending.scopes,
149
- },
150
- account: { email: account?.email ?? null },
151
- });
152
- }
153
- /** POST {prefix}/device — `user_code`, `decision`, `scope[]`. */
154
- async decideDevice(ctx) {
155
- const runtime = await personalAgentsFor(ctx);
156
- const poppy = runtime?.poppy;
157
- if (!poppy?.cfg.signIn.device)
158
- return ctx.response.notFound();
159
- denyFraming(ctx);
160
- const service = await ctx.containerResolver.make('authkit.server');
161
- const cfg = service.config;
162
- const render = cfg.render;
163
- if (impersonationState(ctx).active) {
164
- return render(ctx, 'agents/done', {
165
- status: 'expired',
166
- agentName: null,
167
- scopes: [],
168
- });
169
- }
170
- const accountId = ctx.session.get(ACCOUNT_SESSION_KEY);
171
- const userCode = String(ctx.request.input('user_code') ?? '');
172
- const pending = await poppy.pendingDevice(userCode);
173
- const decided = await poppy.decideDevice({
174
- userCode,
175
- accountId,
176
- allow: ctx.request.input('decision') === 'allow',
177
- scopes: chosenScopes(ctx),
178
- });
179
- if (!decided || !pending) {
180
- return render(ctx, 'agents/done', {
181
- status: 'expired',
182
- agentName: null,
183
- scopes: [],
184
- });
185
- }
186
- await cfg.audit?.record({
187
- type: decided.approved ? 'agent.delegation_approved' : 'agent.delegation_denied',
188
- accountId,
189
- clientId: decided.clientId,
190
- ip: ctx.request.ip?.() ?? null,
191
- metadata: { protocol: 'poppy', signIn: 'device', scopes: decided.approved ?? [] },
192
- });
193
- return render(ctx, 'agents/done', {
194
- status: decided.approved ? 'approved' : 'denied',
195
- agentName: pending.agentName,
196
- scopes: decided.approved ? poppy.describe(decided.approved) : [],
197
- });
198
- }
199
- }
@@ -1,33 +0,0 @@
1
- import type { HttpContext } from '@adonisjs/core/http';
2
- /**
3
- * Endpoints do Poppy (§3, §4, §6): descoberta, protected resource metadata, metadata RFC 8414,
4
- * token, revogação, device authorization e a entrada do navegador na Session. Os POSTs são isentos
5
- * de CSRF — ver `authkitCsrfExceptions`.
6
- */
7
- export default class PoppyController {
8
- /** `GET /.well-known/poppy.json` — só no domínio da empresa; `www.` redireciona para ele. */
9
- discovery(ctx: HttpContext): Promise<void>;
10
- /** `GET /.well-known/oauth-protected-resource` do domínio (§3.2, RFC 9728). */
11
- protectedResource(ctx: HttpContext): Promise<void>;
12
- /** `GET /.well-known/oauth-authorization-server{prefix}` (RFC 8414 + `protected_resources`). */
13
- metadata(ctx: HttpContext): Promise<void>;
14
- token(ctx: HttpContext): Promise<void>;
15
- deviceAuthorization(ctx: HttpContext): Promise<void>;
16
- /** RFC 7009: 200 também para token desconhecido. */
17
- revoke(ctx: HttpContext): Promise<void>;
18
- /**
19
- * `GET web.browser_session_endpoint` (§6, passo 1): a página com o código de uso único e o cookie
20
- * que o prende a este navegador. Não mexe em Session nem nos outros cookies.
21
- */
22
- browserSessionPage(ctx: HttpContext): Promise<void>;
23
- /**
24
- * `POST {browser_session_endpoint}/authorize` (§6, passo 2): o agente autoriza o código, com o
25
- * header da Session e, se logado, o access token. 204; código inválido → 400 `invalid_request`.
26
- */
27
- browserSessionAuthorize(ctx: HttpContext): Promise<void>;
28
- /**
29
- * `POST web.browser_session_endpoint` (§6, passo 3): o navegador submete o código → cookie do app
30
- * para a Session, limpa o cookie do passo 1 e redireciona para o site; falha → página de erro.
31
- */
32
- browserSession(ctx: HttpContext): Promise<void>;
33
- }
@@ -1,219 +0,0 @@
1
- import { PoppyError } from '../../agents/poppy/errors.js';
2
- import { publicRequestUrl, writePoppyAuthError, } from '../../agents/poppy/resource.js';
3
- import { loginPoppyBrowser } from '../../agents/poppy/web_session.js';
4
- import { personalAgentsFor } from '../../agents/runtime.js';
5
- function noStore(ctx) {
6
- ctx.response.header('Cache-Control', 'no-store');
7
- ctx.response.header('Pragma', 'no-cache');
8
- }
9
- function withNonce(ctx, poppy) {
10
- const nonce = poppy.nonce();
11
- if (nonce)
12
- ctx.response.header('DPoP-Nonce', nonce);
13
- }
14
- function oauthError(ctx, error) {
15
- noStore(ctx);
16
- for (const [k, v] of Object.entries(error.headers))
17
- ctx.response.header(k, v);
18
- // Limite de taxa: 429 com `Retry-After`, sem corpo próprio (§5.6).
19
- if (error.status === 429)
20
- return ctx.response.status(429).send('');
21
- if (error.code === 'invalid_dpop_proof' || error.code === 'use_dpop_nonce') {
22
- ctx.response.header('WWW-Authenticate', `DPoP error="${error.code}"`);
23
- }
24
- return ctx.response.status(error.status).json(error.toJSON());
25
- }
26
- async function poppyOr404(ctx) {
27
- const poppy = (await personalAgentsFor(ctx))?.poppy ?? null;
28
- if (!poppy)
29
- ctx.response.notFound();
30
- return poppy;
31
- }
32
- /** Formulário (`application/x-www-form-urlencoded`) da request. */
33
- function form(ctx) {
34
- return ctx.request.all();
35
- }
36
- /**
37
- * Endpoints do Poppy (§3, §4, §6): descoberta, protected resource metadata, metadata RFC 8414,
38
- * token, revogação, device authorization e a entrada do navegador na Session. Os POSTs são isentos
39
- * de CSRF — ver `authkitCsrfExceptions`.
40
- */
41
- export default class PoppyController {
42
- /** `GET /.well-known/poppy.json` — só no domínio da empresa; `www.` redireciona para ele. */
43
- async discovery(ctx) {
44
- const poppy = await poppyOr404(ctx);
45
- if (!poppy)
46
- return;
47
- // `host()` mantém a porta (dev em `localhost:3333`): o `browser_session_endpoint` sai dele.
48
- const host = ctx.request.host() ?? ctx.request.hostname() ?? '';
49
- const redirect = poppy.discoveryRedirect(host);
50
- if (redirect) {
51
- ctx.response.header('Location', redirect);
52
- return ctx.response.status(308).send('');
53
- }
54
- const doc = poppy.discoveryDocument(host);
55
- if (!doc)
56
- return ctx.response.notFound();
57
- ctx.response.header('Access-Control-Allow-Origin', '*');
58
- ctx.response.header('Cache-Control', 'public, max-age=300');
59
- return ctx.response.json(doc);
60
- }
61
- /** `GET /.well-known/oauth-protected-resource` do domínio (§3.2, RFC 9728). */
62
- async protectedResource(ctx) {
63
- const poppy = await poppyOr404(ctx);
64
- if (!poppy)
65
- return;
66
- const doc = poppy.protectedResourceMetadata(ctx.request.host() ?? ctx.request.hostname() ?? '');
67
- if (!doc)
68
- return ctx.response.notFound();
69
- ctx.response.header('Access-Control-Allow-Origin', '*');
70
- ctx.response.header('Cache-Control', 'public, max-age=300');
71
- return ctx.response.json(doc);
72
- }
73
- /** `GET /.well-known/oauth-authorization-server{prefix}` (RFC 8414 + `protected_resources`). */
74
- async metadata(ctx) {
75
- const poppy = await poppyOr404(ctx);
76
- if (!poppy)
77
- return;
78
- ctx.response.header('Access-Control-Allow-Origin', '*');
79
- ctx.response.header('Cache-Control', 'public, max-age=300');
80
- return ctx.response.json(poppy.authorizationServerMetadata());
81
- }
82
- async token(ctx) {
83
- const poppy = await poppyOr404(ctx);
84
- if (!poppy)
85
- return;
86
- withNonce(ctx, poppy);
87
- try {
88
- const body = await poppy.token(form(ctx), ctx.request.header('dpop'), ctx);
89
- noStore(ctx);
90
- return ctx.response.json(body);
91
- }
92
- catch (error) {
93
- if (error instanceof PoppyError)
94
- return oauthError(ctx, error);
95
- throw error;
96
- }
97
- }
98
- async deviceAuthorization(ctx) {
99
- const poppy = await poppyOr404(ctx);
100
- if (!poppy)
101
- return;
102
- try {
103
- const body = await poppy.deviceAuthorization(form(ctx));
104
- noStore(ctx);
105
- return ctx.response.json(body);
106
- }
107
- catch (error) {
108
- if (error instanceof PoppyError)
109
- return oauthError(ctx, error);
110
- throw error;
111
- }
112
- }
113
- /** RFC 7009: 200 também para token desconhecido. */
114
- async revoke(ctx) {
115
- const poppy = await poppyOr404(ctx);
116
- if (!poppy)
117
- return;
118
- try {
119
- await poppy.revoke(form(ctx));
120
- noStore(ctx);
121
- return ctx.response.status(200).send('');
122
- }
123
- catch (error) {
124
- if (error instanceof PoppyError)
125
- return oauthError(ctx, error);
126
- throw error;
127
- }
128
- }
129
- /**
130
- * `GET web.browser_session_endpoint` (§6, passo 1): a página com o código de uso único e o cookie
131
- * que o prende a este navegador. Não mexe em Session nem nos outros cookies.
132
- */
133
- async browserSessionPage(ctx) {
134
- const poppy = await poppyOr404(ctx);
135
- if (!poppy?.cfg.web?.browserSession)
136
- return poppy ? ctx.response.notFound() : undefined;
137
- const { code, cookie, maxAge } = await poppy.startBrowserJoin();
138
- const name = joinCookieName(poppy);
139
- ctx.response.append('Set-Cookie', `${name}=${cookie}; Path=/; Max-Age=${maxAge}; ${cookieSecure(poppy)}HttpOnly; SameSite=Strict`);
140
- browserHeaders(ctx);
141
- // `code` só tem caracteres base64url; o `action` é o path do próprio endpoint.
142
- const action = new URL(poppy.urls.browserSession).pathname;
143
- return ctx.response
144
- .status(200)
145
- .send(`<!doctype html><html><head><meta charset="utf-8"><title>Connecting</title></head><body><form method="post" action="${action}"><input type="hidden" name="code" value="${code}"><button>Continue</button></form></body></html>`);
146
- }
147
- /**
148
- * `POST {browser_session_endpoint}/authorize` (§6, passo 2): o agente autoriza o código, com o
149
- * header da Session e, se logado, o access token. 204; código inválido → 400 `invalid_request`.
150
- */
151
- async browserSessionAuthorize(ctx) {
152
- const poppy = await poppyOr404(ctx);
153
- if (!poppy?.cfg.web?.browserSession)
154
- return poppy ? ctx.response.notFound() : undefined;
155
- noStore(ctx);
156
- try {
157
- const result = await poppy.authorizeBrowserJoin({
158
- method: ctx.request.method(),
159
- url: publicRequestUrl(ctx, poppy),
160
- headers: ctx.request.headers(),
161
- }, ctx.request.body()?.code);
162
- if (!result.ok)
163
- return writePoppyAuthError(ctx, result);
164
- return ctx.response.status(204).send('');
165
- }
166
- catch (error) {
167
- if (error instanceof PoppyError) {
168
- return ctx.response.status(400).json({ error: 'invalid_request' });
169
- }
170
- throw error;
171
- }
172
- }
173
- /**
174
- * `POST web.browser_session_endpoint` (§6, passo 3): o navegador submete o código → cookie do app
175
- * para a Session, limpa o cookie do passo 1 e redireciona para o site; falha → página de erro.
176
- */
177
- async browserSession(ctx) {
178
- const poppy = await poppyOr404(ctx);
179
- if (!poppy?.cfg.web?.browserSession)
180
- return poppy ? ctx.response.notFound() : undefined;
181
- browserHeaders(ctx);
182
- const name = joinCookieName(poppy);
183
- const result = await poppy.completeBrowserJoin(ctx.request.input('code'), readCookie(ctx.request.header('cookie'), name));
184
- if (!result)
185
- return badBrowserRequest(ctx);
186
- await loginPoppyBrowser(ctx, result);
187
- ctx.response.append('Set-Cookie', `${name}=; Path=/; Max-Age=0; ${cookieSecure(poppy)}HttpOnly; SameSite=Strict`);
188
- ctx.response.header('Location', poppy.cfg.web.redirectTo);
189
- return ctx.response.status(303).send('');
190
- }
191
- }
192
- /** `__Host-` exige `Secure`; em dev com o issuer em http o cookie sai sem os dois. */
193
- function joinCookieName(poppy) {
194
- return new URL(poppy.urls.origin).protocol === 'https:' ? '__Host-poppy_join' : 'poppy_join';
195
- }
196
- function cookieSecure(poppy) {
197
- return new URL(poppy.urls.origin).protocol === 'https:' ? 'Secure; ' : '';
198
- }
199
- function readCookie(header, name) {
200
- for (const part of (header ?? '').split(';')) {
201
- const eq = part.indexOf('=');
202
- if (eq > 0 && part.slice(0, eq).trim() === name)
203
- return part.slice(eq + 1).trim();
204
- }
205
- return undefined;
206
- }
207
- /** Sem cache, sem moldura e sem referência da página que abriu (§6, passo 1). */
208
- function browserHeaders(ctx) {
209
- noStore(ctx);
210
- ctx.response.header('Content-Type', 'text/html; charset=utf-8');
211
- ctx.response.header('Content-Security-Policy', "frame-ancestors 'none'");
212
- ctx.response.header('Cross-Origin-Opener-Policy', 'same-origin');
213
- ctx.response.header('Referrer-Policy', 'no-referrer');
214
- }
215
- function badBrowserRequest(ctx) {
216
- return ctx.response
217
- .status(400)
218
- .send('<!doctype html><html><head><meta charset="utf-8"><title>Bad request</title></head><body><h1>Invalid browser session request</h1></body></html>');
219
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * Session key que marca a sessão do navegador de um personal agent Poppy (§5):
3
- * `{ sessionId, clientId, userId }`. Import-free (o barrel a reexporta — ver
4
- * `account_session_key.ts`).
5
- *
6
- * Uma sessão marcada é do AGENTE, não do usuário: o console de conta, o admin e
7
- * as telas de consentimento a recusam.
8
- */
9
- export declare const POPPY_WEB_SESSION_KEY = "authkit_poppy_session";
@@ -1,9 +0,0 @@
1
- /**
2
- * Session key que marca a sessão do navegador de um personal agent Poppy (§5):
3
- * `{ sessionId, clientId, userId }`. Import-free (o barrel a reexporta — ver
4
- * `account_session_key.ts`).
5
- *
6
- * Uma sessão marcada é do AGENTE, não do usuário: o console de conta, o admin e
7
- * as telas de consentimento a recusam.
8
- */
9
- export const POPPY_WEB_SESSION_KEY = 'authkit_poppy_session';