@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
@@ -17,6 +17,19 @@ export declare const clientInputValidator: import("@vinejs/vine").VineValidator<
17
17
  backchannelLogoutSessionRequired: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine").VineBoolean>;
18
18
  clientName: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine/schema/base/literal").NullableModifier<import("@vinejs/vine").VineString>>;
19
19
  logoUri: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine/schema/base/literal").NullableModifier<import("@vinejs/vine").VineString>>;
20
+ serviceTenant: import("@vinejs/vine").OptionalModifier<import("@vinejs/vine").NullableModifier<import("@vinejs/vine").VineObject<{
21
+ partition: import("@vinejs/vine").VineString;
22
+ capabilities: import("@vinejs/vine").OptionalModifier<import("@vinejs/vine").VineArray<import("@vinejs/vine").VineString>>;
23
+ }, {
24
+ capabilities?: string[] | null | undefined;
25
+ partition: string;
26
+ }, {
27
+ capabilities?: string[] | undefined;
28
+ partition: string;
29
+ }, {
30
+ capabilities?: string[] | undefined;
31
+ partition: string;
32
+ }>>>;
20
33
  }, {
21
34
  applicationType?: "native" | "web" | null | undefined;
22
35
  backchannelLogoutSessionRequired?: string | number | boolean | null | undefined;
@@ -27,6 +40,10 @@ export declare const clientInputValidator: import("@vinejs/vine").VineValidator<
27
40
  logoUri?: string | null | undefined;
28
41
  postLogoutRedirectUris?: string[] | null | undefined;
29
42
  redirectUris?: string[] | null | undefined;
43
+ serviceTenant?: {
44
+ capabilities?: string[] | null | undefined;
45
+ partition: string;
46
+ } | null | undefined;
30
47
  tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none" | null | undefined;
31
48
  }, {
32
49
  applicationType?: "native" | "web" | undefined;
@@ -38,6 +55,10 @@ export declare const clientInputValidator: import("@vinejs/vine").VineValidator<
38
55
  logoUri?: string | null | undefined;
39
56
  postLogoutRedirectUris?: string[] | undefined;
40
57
  redirectUris?: string[] | undefined;
58
+ serviceTenant?: {
59
+ capabilities?: string[] | undefined;
60
+ partition: string;
61
+ } | null | undefined;
41
62
  tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none" | undefined;
42
63
  }, {
43
64
  applicationType?: "native" | "web" | undefined;
@@ -49,6 +70,10 @@ export declare const clientInputValidator: import("@vinejs/vine").VineValidator<
49
70
  logoUri?: string | null | undefined;
50
71
  postLogoutRedirectUris?: string[] | undefined;
51
72
  redirectUris?: string[] | undefined;
73
+ serviceTenant?: {
74
+ capabilities?: string[] | undefined;
75
+ partition: string;
76
+ } | null | undefined;
52
77
  tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none" | undefined;
53
78
  }>, Record<string, any> | undefined>;
54
79
  /** Forma validada de um client (saída do {@link clientInputValidator}). */
@@ -63,6 +88,10 @@ export type ClientInputFields = {
63
88
  backchannelLogoutSessionRequired?: boolean;
64
89
  clientName?: string | null;
65
90
  logoUri?: string | null;
91
+ serviceTenant?: {
92
+ partition: string;
93
+ capabilities?: string[];
94
+ } | null;
66
95
  };
67
96
  /**
68
97
  * Mapeia o input validado para um {@link ClientInput} COMPLETO (create), com defaults.
@@ -1,4 +1,5 @@
1
1
  import vine from '@vinejs/vine';
2
+ import { CAPABILITY_PATTERN, MAX_CAPABILITIES, PARTITION_PATTERN, } from '../service_tenants/client_metadata.js';
2
3
  import { APPLICATION_TYPES, redirectUriProblem } from './client_metadata.js';
3
4
  import { normalizeEmailIdentifier } from './email_identifier.js';
4
5
  /**
@@ -103,6 +104,18 @@ export const clientInputValidator = vine.compile(vine.object({
103
104
  // `null` (update) removes them.
104
105
  clientName: vine.string().trim().maxLength(200).nullable().optional(),
105
106
  logoUri: vine.string().trim().url(URL_OPTS).nullable().optional(),
107
+ // Service tenant (`client_credentials` + partition claim). `null` (update) turns it
108
+ // back into a regular client.
109
+ serviceTenant: vine
110
+ .object({
111
+ partition: vine.string().trim().regex(PARTITION_PATTERN),
112
+ capabilities: vine
113
+ .array(vine.string().trim().regex(CAPABILITY_PATTERN))
114
+ .maxLength(MAX_CAPABILITIES)
115
+ .optional(),
116
+ })
117
+ .nullable()
118
+ .optional(),
106
119
  }));
107
120
  /**
108
121
  * Mapeia o input validado para um {@link ClientInput} COMPLETO (create), com defaults.
@@ -111,6 +124,21 @@ export const clientInputValidator = vine.compile(vine.object({
111
124
  */
112
125
  export function clientCreateInput(v) {
113
126
  const applicationType = v.applicationType ?? 'web';
127
+ // A service tenant only uses client_credentials: that is its default grant.
128
+ if (v.serviceTenant) {
129
+ return {
130
+ clientId: v.clientId?.trim() || undefined,
131
+ applicationType,
132
+ redirectUris: v.redirectUris ?? [],
133
+ postLogoutRedirectUris: v.postLogoutRedirectUris ?? [],
134
+ grantTypes: v.grantTypes ?? ['client_credentials'],
135
+ tokenEndpointAuthMethod: v.tokenEndpointAuthMethod ?? 'client_secret_basic',
136
+ backchannelLogoutUri: v.backchannelLogoutUri || undefined,
137
+ clientName: v.clientName || undefined,
138
+ logoUri: v.logoUri || undefined,
139
+ serviceTenant: v.serviceTenant,
140
+ };
141
+ }
114
142
  return {
115
143
  clientId: v.clientId?.trim() || undefined,
116
144
  applicationType,
@@ -148,6 +176,8 @@ export function clientPartialInput(v) {
148
176
  out.clientName = v.clientName?.trim() || null;
149
177
  if (v.logoUri !== undefined)
150
178
  out.logoUri = v.logoUri?.trim() || null;
179
+ if (v.serviceTenant !== undefined)
180
+ out.serviceTenant = v.serviceTenant;
151
181
  return out;
152
182
  }
153
183
  // ─── Usuários ───────────────────────────────────────────────────────────────
@@ -1,4 +1,6 @@
1
1
  import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { AccountConsoleConfig } from '../account/runtime.js';
3
+ import type { ResolvedPersonalAgentExtension } from '../agents/extension.js';
2
4
  import type { AuthSocialConfig, ResolvedRateLimitConfig } from '../define_config.js';
3
5
  import type { PolicyRouteOption } from './config_locks.js';
4
6
  import type { AuthHostOptions } from './register_auth_host.js';
@@ -35,11 +37,21 @@ export interface AuthHostRuntimeConfig {
35
37
  * Ver `deriveLockedRouteOptions`.
36
38
  */
37
39
  lockedRouteOptions?: PolicyRouteOption[];
38
- /** Personal agents ligados → prefixo das rotas deles (`/agents`) e do Poppy (`/poppy`). */
40
+ /**
41
+ * Personal agents ligados → prefixo das rotas do PACT (`/agents`) e as
42
+ * `personalAgents.extensions` resolvidas (rotas, isenções de CSRF, navegador do agente).
43
+ */
39
44
  personalAgents?: {
40
45
  prefix: string;
41
- poppyPrefix?: string;
46
+ extensions?: readonly ResolvedPersonalAgentExtension[];
42
47
  };
48
+ /**
49
+ * `accountSession.acceptIdpSession: false` com `authMethods: { password: false }`:
50
+ * o console só entra por um `accountLoginUrl` do host (`registerAuthHost` recusa sem ele).
51
+ */
52
+ consoleNeedsLoginUrl?: boolean;
53
+ /** `accountConsole` do config: os overrides das páginas de extension. */
54
+ accountConsole?: AccountConsoleConfig;
43
55
  /**
44
56
  * API headless (Clerk-style). Presente quando o host a declarou — o provider o
45
57
  * stash no boot para `registerAuthHost` montar as rotas sem reler o config inteiro.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The page a browser gets when `admin.authorize` denies the admin console
3
+ * (403), or asks for a sign-in it cannot do itself (401). Self-contained: no
4
+ * script and no external asset, so any CSP renders it.
5
+ */
6
+ export declare function consoleDeniedPage(status: 401 | 403): string;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The page a browser gets when `admin.authorize` denies the admin console
3
+ * (403), or asks for a sign-in it cannot do itself (401). Self-contained: no
4
+ * script and no external asset, so any CSP renders it.
5
+ */
6
+ export function consoleDeniedPage(status) {
7
+ const title = status === 401 ? 'Sign in required' : 'Access denied';
8
+ const body = status === 401
9
+ ? 'This console needs credentials this request did not carry.'
10
+ : 'You do not have access to this console.';
11
+ return `<!doctype html>
12
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
13
+ <title>${title}</title>
14
+ <style>body{font-family:system-ui,sans-serif;background:#f9fafb;color:#111827;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:16px}
15
+ .box{max-width:420px;text-align:center}h1{font-size:20px;margin:0 0 8px}p{color:#4b5563;font-size:14px;line-height:1.6;margin:0}
16
+ .code{font-size:12px;color:#9ca3af;margin-bottom:8px}</style>
17
+ </head><body><main class="box"><p class="code">${status}</p><h1>${title}</h1><p>${body}</p></main></body></html>`;
18
+ }
@@ -0,0 +1,24 @@
1
+ /** `client_id` do client interno do console. Reservado: não pode ser cadastrado. */
2
+ export declare const CONSOLE_SSO_CLIENT_ID = "authkit-console";
3
+ /** Rota que inicia o login do console pelo IdP. */
4
+ export declare const CONSOLE_SSO_START_PATH = "/auth/console/sso";
5
+ /** `redirect_uri` do client interno: valida o `state` e abre o console. */
6
+ export declare const CONSOLE_SSO_CALLBACK_PATH = "/auth/console/callback";
7
+ /** Chave da sessão Adonis com o `state` e o destino de um login em andamento. */
8
+ export declare const CONSOLE_SSO_STATE_KEY = "authkit_console_sso";
9
+ /** O que fica na sessão entre o início e o callback. */
10
+ export interface ConsoleSsoState {
11
+ state: string;
12
+ returnTo: string | null;
13
+ }
14
+ /** Metadata do client interno para o `clients` estático do oidc-provider. */
15
+ export declare function consoleSsoClientMetadata(issuer: string): Record<string, unknown>;
16
+ /** Novo `state` opaco para um login do console. */
17
+ export declare function newConsoleSsoState(): string;
18
+ /**
19
+ * Path do authorize do provider. O issuer termina no `mountPath` (garantido por
20
+ * `registerAuthHost`), então o mount é o path do issuer.
21
+ */
22
+ export declare function authorizationPath(issuer: string, provider?: any): string;
23
+ /** URL do login do console pelo IdP, preservando o destino como `return_to`. */
24
+ export declare function consoleSsoUrl(returnTo?: string | null): string;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Login do console pelo IdP (SSO), o jeito padrão de entrar em `/account/*` e no
3
+ * console admin.
4
+ *
5
+ * O console é tratado como mais um app do IdP, como o Keycloak faz com o
6
+ * `account-console` e o `security-admin-console`: sem sessão, o visitante vai
7
+ * para a tela de login do IdP (a mesma interaction de qualquer client: senha,
8
+ * magic link, OTP, passkey, social), volta, e a ponte de
9
+ * `idp_session_bridge.ts` abre o console para a conta da sessão do IdP.
10
+ *
11
+ * Para isso existe um client interno, `authkit-console`, injetado como client
12
+ * ESTÁTICO do provider (não fica no adapter, então não aparece na lista de
13
+ * clients do console nem pode ser editado):
14
+ *
15
+ * - `response_types: ['none']`: o authorize não emite code nem token. O client
16
+ * só usa o IdP para autenticar; o que abre o console é a sessão do IdP que o
17
+ * login criou, lida pela ponte. Não há segredo, PKCE ou troca de code.
18
+ * - o consent é concedido automaticamente (é a própria lib).
19
+ * - a única `redirect_uri` é `/auth/console/callback`, no MESMO host do issuer.
20
+ *
21
+ * Só existe com `accountSession.acceptIdpSession` ligado (o default).
22
+ */
23
+ import { randomBytes } from 'node:crypto';
24
+ /** `client_id` do client interno do console. Reservado: não pode ser cadastrado. */
25
+ export const CONSOLE_SSO_CLIENT_ID = 'authkit-console';
26
+ /** Rota que inicia o login do console pelo IdP. */
27
+ export const CONSOLE_SSO_START_PATH = '/auth/console/sso';
28
+ /** `redirect_uri` do client interno: valida o `state` e abre o console. */
29
+ export const CONSOLE_SSO_CALLBACK_PATH = '/auth/console/callback';
30
+ /** Chave da sessão Adonis com o `state` e o destino de um login em andamento. */
31
+ export const CONSOLE_SSO_STATE_KEY = 'authkit_console_sso';
32
+ /** Metadata do client interno para o `clients` estático do oidc-provider. */
33
+ export function consoleSsoClientMetadata(issuer) {
34
+ const origin = new URL(issuer).origin;
35
+ return {
36
+ client_id: CONSOLE_SSO_CLIENT_ID,
37
+ client_name: 'Account console',
38
+ application_type: 'web',
39
+ token_endpoint_auth_method: 'none',
40
+ response_types: ['none'],
41
+ grant_types: [],
42
+ redirect_uris: [`${origin}${CONSOLE_SSO_CALLBACK_PATH}`],
43
+ };
44
+ }
45
+ /** Novo `state` opaco para um login do console. */
46
+ export function newConsoleSsoState() {
47
+ return randomBytes(24).toString('base64url');
48
+ }
49
+ /**
50
+ * Path do authorize do provider. O issuer termina no `mountPath` (garantido por
51
+ * `registerAuthHost`), então o mount é o path do issuer.
52
+ */
53
+ export function authorizationPath(issuer, provider) {
54
+ const mount = new URL(issuer).pathname.replace(/\/+$/, '');
55
+ if (provider && typeof provider.pathFor === 'function') {
56
+ try {
57
+ return provider.pathFor('authorization', { mountPath: mount });
58
+ }
59
+ catch {
60
+ // cai no default do oidc-provider abaixo
61
+ }
62
+ }
63
+ return `${mount}/auth`;
64
+ }
65
+ /** URL do login do console pelo IdP, preservando o destino como `return_to`. */
66
+ export function consoleSsoUrl(returnTo) {
67
+ if (!returnTo)
68
+ return CONSOLE_SSO_START_PATH;
69
+ return `${CONSOLE_SSO_START_PATH}?return_to=${encodeURIComponent(returnTo)}`;
70
+ }
@@ -38,16 +38,19 @@ export default class AccountAppsController {
38
38
  scopes: g.scopes,
39
39
  })))
40
40
  : null;
41
- // Refresh tokens do Poppy: o agente é desconectado pela mesma lista (§5.5).
42
- const poppy = agentsRuntime?.poppy;
43
- const poppyAgents = poppy
44
- ? await Promise.all((await poppy.listGrants(accountId)).map(async (g) => ({
45
- id: g.id,
46
- name: await poppy.registry.displayName(g.clientId),
47
- scopes: g.scopes,
48
- })))
49
- : null;
50
- const agents = pactAgents || poppyAgents ? [...(pactAgents ?? []), ...(poppyAgents ?? [])] : null;
41
+ // Conexões das `personalAgents.extensions`: desconectadas pela mesma lista.
42
+ let extensionAgents = null;
43
+ for (const { runtime } of agentsRuntime?.extensions.values() ?? []) {
44
+ extensionAgents ??= [];
45
+ for (const g of await runtime.listAccountGrants(accountId)) {
46
+ extensionAgents.push({
47
+ id: g.id,
48
+ name: await runtime.displayName(g.clientId),
49
+ scopes: g.scopes,
50
+ });
51
+ }
52
+ }
53
+ const agents = pactAgents || extensionAgents ? [...(pactAgents ?? []), ...(extensionAgents ?? [])] : null;
51
54
  return render(ctx, 'account/apps', {
52
55
  csrfToken: ctx.request.csrfToken,
53
56
  supported,
@@ -89,8 +92,9 @@ export default class AccountAppsController {
89
92
  /** POST /account/apps/agents/:grantId/revoke — revoga a delegação de um personal agent. */
90
93
  async revokeAgent(ctx) {
91
94
  const runtime = await personalAgentsFor(ctx);
92
- if (!runtime?.delegation && !runtime?.poppy)
95
+ if (!runtime || (!runtime.delegation && runtime.extensions.size === 0)) {
93
96
  return ctx.response.notFound();
97
+ }
94
98
  const service = await ctx.containerResolver.make('authkit.server');
95
99
  const cfg = service.config;
96
100
  const accountId = ctx.session.get(ACCOUNT_SESSION_KEY);
@@ -3,6 +3,7 @@ import { accountHome } from '../account_home.js';
3
3
  import { getAccountLoginUrl } from '../account_login_url.js';
4
4
  import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
5
5
  import { syncAdonisAuthLogin, syncAdonisAuthLogout } from '../adonis_auth_sync.js';
6
+ import { consoleSsoUrl } from '../console_sso.js';
6
7
  import { normalizeEmailIdentifier } from '../email_identifier.js';
7
8
  import { translate } from '../i18n.js';
8
9
  import { endBridgedIdpSession } from '../idp_session_bridge.js';
@@ -49,8 +50,16 @@ export default class AccountSessionController {
49
50
  return ctx.response.redirect(accountHome(cfg));
50
51
  }
51
52
  // Lê e valida o return_to da query-string — descarta valores inválidos (open-redirect).
52
- const rawReturnTo = ctx.request.qs?.()?.return_to ?? ctx.request.input?.('return_to');
53
+ const qs = (ctx.request.qs?.() ?? {});
54
+ const rawReturnTo = qs.return_to ?? ctx.request.input?.('return_to');
53
55
  const returnTo = validateReturnTo(rawReturnTo);
56
+ // SSO (default): o console entra pelo login do IdP, a mesma tela de qualquer
57
+ // app. O formulário de senha daqui fica como porta alternativa
58
+ // (`?method=password`), e só quando a senha não foi desligada no config.
59
+ const passwordForm = qs.method === 'password' && cfg.authMethods?.password !== false;
60
+ if (cfg.accountSession?.acceptIdpSession === true && !passwordForm) {
61
+ return redirectExact(ctx.response, consoleSsoUrl(returnTo));
62
+ }
54
63
  const props = {
55
64
  csrfToken: ctx.request.csrfToken,
56
65
  returnTo,
@@ -0,0 +1,12 @@
1
+ import '../augmentations.js';
2
+ import type { HttpContext } from '@adonisjs/core/http';
3
+ /**
4
+ * Login do console pelo IdP (ver `console_sso.ts`).
5
+ *
6
+ * GET /auth/console/sso?return_to=… → authorize do client interno
7
+ * GET /auth/console/callback?state=… → valida o state, abre o console pela ponte
8
+ */
9
+ export default class ConsoleSsoController {
10
+ start(ctx: HttpContext): Promise<unknown>;
11
+ callback(ctx: HttpContext): Promise<unknown>;
12
+ }
@@ -0,0 +1,61 @@
1
+ import '../augmentations.js';
2
+ import { accountHome } from '../account_home.js';
3
+ import { getAccountLoginUrl } from '../account_login_url.js';
4
+ import { authorizationPath, CONSOLE_SSO_CALLBACK_PATH, CONSOLE_SSO_CLIENT_ID, CONSOLE_SSO_STATE_KEY, newConsoleSsoState, } from '../console_sso.js';
5
+ import { ensureConsoleSession } from '../idp_session_bridge.js';
6
+ import { redirectExact } from '../redirect_exact.js';
7
+ import { validateReturnTo } from './account_session_controller.js';
8
+ /**
9
+ * Login do console pelo IdP (ver `console_sso.ts`).
10
+ *
11
+ * GET /auth/console/sso?return_to=… → authorize do client interno
12
+ * GET /auth/console/callback?state=… → valida o state, abre o console pela ponte
13
+ */
14
+ export default class ConsoleSsoController {
15
+ async start(ctx) {
16
+ const service = await ctx.containerResolver.make('authkit.server');
17
+ const cfg = service.config;
18
+ const returnTo = validateReturnTo(ctx.request.qs?.()?.return_to ?? ctx.request.input?.('return_to'));
19
+ // SSO desligado (`acceptIdpSession: false`): o console só tem o login próprio.
20
+ if (cfg.accountSession?.acceptIdpSession !== true) {
21
+ const login = getAccountLoginUrl();
22
+ const sep = login.includes('?') ? '&' : '?';
23
+ return redirectExact(ctx.response, returnTo ? `${login}${sep}return_to=${encodeURIComponent(returnTo)}` : login);
24
+ }
25
+ // Já tem sessão (de console ou do IdP): não precisa passar pelo IdP.
26
+ if (await ensureConsoleSession(ctx)) {
27
+ return redirectExact(ctx.response, returnTo ?? accountHome(cfg));
28
+ }
29
+ const state = newConsoleSsoState();
30
+ ctx.session.put(CONSOLE_SSO_STATE_KEY, { state, returnTo });
31
+ const origin = new URL(cfg.issuer).origin;
32
+ const query = new URLSearchParams({
33
+ client_id: CONSOLE_SSO_CLIENT_ID,
34
+ response_type: 'none',
35
+ scope: 'openid',
36
+ redirect_uri: `${origin}${CONSOLE_SSO_CALLBACK_PATH}`,
37
+ state,
38
+ });
39
+ return redirectExact(ctx.response, `${authorizationPath(cfg.issuer, service.provider)}?${query.toString()}`);
40
+ }
41
+ async callback(ctx) {
42
+ const service = await ctx.containerResolver.make('authkit.server');
43
+ const cfg = service.config;
44
+ const pending = ctx.session.get(CONSOLE_SSO_STATE_KEY);
45
+ ctx.session.forget(CONSOLE_SSO_STATE_KEY);
46
+ const qs = (ctx.request.qs?.() ?? {});
47
+ // `state` amarra o callback ao navegador que iniciou o login (anti-CSRF de login).
48
+ if (!pending || typeof qs.state !== 'string' || qs.state !== pending.state) {
49
+ return ctx.response.status(400).send('Invalid or expired sign-in request.');
50
+ }
51
+ // O IdP recusou (o usuário cancelou, conta desabilitada, …): sem console.
52
+ // Vai para a raiz, e não de volta ao console, para não entrar em loop.
53
+ if (typeof qs.error === 'string') {
54
+ return redirectExact(ctx.response, '/');
55
+ }
56
+ if (!(await ensureConsoleSession(ctx))) {
57
+ return redirectExact(ctx.response, '/');
58
+ }
59
+ return redirectExact(ctx.response, pending.returnTo ?? accountHome(cfg));
60
+ }
61
+ }
@@ -5,6 +5,7 @@ import { AdminSessionsService } from '../admin_sessions_service.js';
5
5
  import { guardBotProtection, resolveEffectiveBotProtection } from '../bot_protection.js';
6
6
  import { brandFor, isFirstParty } from '../branding.js';
7
7
  import { consentRequestParams } from '../consent_params.js';
8
+ import { CONSOLE_SSO_CLIENT_ID } from '../console_sso.js';
8
9
  import { isCustomLoginMethodId } from '../custom_login.js';
9
10
  import { completeCustomLoginSession } from '../custom_login_completion.js';
10
11
  import { beginCustomMfa, clearMfaFlow, customMfaViewProps, getMfaFlow, initializeMfaFlow, recordVerifiedMfaFactor, resolveCustomMfaMethods, verifyCustomMfa, } from '../custom_mfa.js';
@@ -254,7 +255,8 @@ export default class AuthInteractionController {
254
255
  }
255
256
  }
256
257
  if (details.prompt.name === 'consent' &&
257
- isFirstParty(cfg.branding, details.params.client_id)) {
258
+ (details.params.client_id === CONSOLE_SSO_CLIENT_ID ||
259
+ isFirstParty(cfg.branding, details.params.client_id))) {
258
260
  // Clients first-party: auto-concede o consent (pula a tela de consent).
259
261
  // interactions.consent monta o Grant + interactionFinished e escreve o
260
262
  // redirect de volta para o client — opera via provider.interactionDetails,
@@ -11,10 +11,10 @@ export interface AuthkitCsrfOptions {
11
11
  */
12
12
  personalAgentsPrefix?: string | false;
13
13
  /**
14
- * Prefixo do Poppy: isenta token/revoke/device authorization e a entrada do navegador
15
- * (o código com o cookie `SameSite=Strict` é o que protege, §6).
14
+ * Isenta os `csrfExemptPaths` das `personalAgents.extensions` do config. Default: true.
15
+ * `false` desliga.
16
16
  */
17
- poppyPrefix?: string | false;
17
+ personalAgentExtensions?: boolean;
18
18
  }
19
19
  /**
20
20
  * Retorna `true` quando `url` é uma rota AuthKit que deve ser ISENTA de CSRF
@@ -16,7 +16,6 @@
16
16
  * ```
17
17
  */
18
18
  import { normalizePersonalAgentsPrefix } from '../agents/config.js';
19
- import { normalizePoppyPrefix } from '../agents/poppy/config.js';
20
19
  import { getAuthHostConfig } from './auth_host_config.js';
21
20
  /**
22
21
  * Retorna `true` quando `url` é uma rota AuthKit que deve ser ISENTA de CSRF
@@ -34,21 +33,12 @@ export function authkitCsrfExceptions(url, options = {}) {
34
33
  ? normalizePersonalAgentsPrefix(options.personalAgentsPrefix)
35
34
  : getAuthHostConfig()?.personalAgents?.prefix;
36
35
  const agentsOAuth = agentsPrefix ? `${agentsPrefix}/oauth/` : null;
37
- const poppyPrefix = options.poppyPrefix === false
38
- ? undefined
39
- : options.poppyPrefix !== undefined
40
- ? normalizePoppyPrefix(options.poppyPrefix)
41
- : getAuthHostConfig()?.personalAgents?.poppyPrefix;
42
36
  const path = url.split('?')[0];
43
- const poppyExempt = !!poppyPrefix &&
44
- (path === `${poppyPrefix}/oauth/token` ||
45
- path === `${poppyPrefix}/oauth/revoke` ||
46
- path === `${poppyPrefix}/oauth/device` ||
47
- path === `${poppyPrefix}/browser-session` ||
48
- path === `${poppyPrefix}/browser-session/authorize`);
37
+ const extensionExempt = options.personalAgentExtensions !== false &&
38
+ (getAuthHostConfig()?.personalAgents?.extensions ?? []).some((e) => e.csrfExemptPaths?.includes(path));
49
39
  return (url.includes(mountPath) ||
50
40
  url.includes('/authkit/pat') ||
51
41
  (backchannel !== null && url === backchannel) ||
52
42
  (agentsOAuth !== null && url.startsWith(agentsOAuth)) ||
53
- poppyExempt);
43
+ extensionExempt);
54
44
  }
@@ -67,15 +67,20 @@ export declare function sendMagicLinkEmail(ctx: HttpContext, data: {
67
67
  channel?: 'code' | 'link';
68
68
  }): Promise<void>;
69
69
  /**
70
- * Envia o código do Mediated Sign-In pronto do Poppy (`poppyEmailCodeSignIn`) pelo
71
- * mailer default do host. Só código, sem link: quem digita é a pessoa, no agente.
72
- * Best-effort: no fallback (sem mail) loga o código, como o magic link loga o link; nunca lança.
70
+ * Envia um e-mail só com um código (sem link: quem digita é a pessoa, em outro lugar) pelo mailer
71
+ * default do host, com o branding e o i18n do authkit. Os textos saem do catálogo pelas chaves
72
+ * `{messages}.subject`, `.heading`, `.intro`, `.code_label` e `.fallback`, com `params`
73
+ * interpolados — uma extension registra as dela em `messages`. Best-effort: sem `@adonisjs/mail`
74
+ * loga o código (dev); nunca lança.
75
+ *
76
+ * Estável no 1.x (é o mailer das `personalAgents.extensions`).
73
77
  */
74
- export declare function sendPoppySignInCodeEmail(ctx: HttpContext, data: {
78
+ export declare function sendAuthkitCodeEmail(ctx: HttpContext, data: {
75
79
  email: string;
76
80
  code: string;
77
- agentName: string;
78
- expiresInMinutes: number;
81
+ /** Prefixo das chaves no catálogo (`'mail.poppy_sign_in'`). */
82
+ messages: string;
83
+ params?: Record<string, string | number>;
79
84
  }): Promise<void>;
80
85
  /**
81
86
  * Envia o link de CONFIRMAÇÃO DE IDENTIDADE (sudo) pelo mailer default do host.
@@ -295,32 +295,36 @@ export async function sendMagicLinkEmail(ctx, data) {
295
295
  }
296
296
  }
297
297
  /**
298
- * Envia o código do Mediated Sign-In pronto do Poppy (`poppyEmailCodeSignIn`) pelo
299
- * mailer default do host. Só código, sem link: quem digita é a pessoa, no agente.
300
- * Best-effort: no fallback (sem mail) loga o código, como o magic link loga o link; nunca lança.
298
+ * Envia um e-mail só com um código (sem link: quem digita é a pessoa, em outro lugar) pelo mailer
299
+ * default do host, com o branding e o i18n do authkit. Os textos saem do catálogo pelas chaves
300
+ * `{messages}.subject`, `.heading`, `.intro`, `.code_label` e `.fallback`, com `params`
301
+ * interpolados — uma extension registra as dela em `messages`. Best-effort: sem `@adonisjs/mail`
302
+ * loga o código (dev); nunca lança.
303
+ *
304
+ * Estável no 1.x (é o mailer das `personalAgents.extensions`).
301
305
  */
302
- export async function sendPoppySignInCodeEmail(ctx, data) {
306
+ export async function sendAuthkitCodeEmail(ctx, data) {
303
307
  try {
304
308
  const brand = resolveBrand(ctx);
305
309
  const { messages: t, locale } = resolveMailMessages(ctx);
306
- const params = { agent: data.agentName, minutes: data.expiresInMinutes };
310
+ const key = (name) => `${data.messages}.${name}`;
307
311
  const content = renderTransactionalEmail({
308
312
  brand,
309
313
  locale,
310
- subject: translate(t, 'mail.poppy_sign_in.subject', params),
311
- heading: translate(t, 'mail.poppy_sign_in.heading', params),
312
- intro: translate(t, 'mail.poppy_sign_in.intro', params),
313
- footnote: translate(t, 'mail.poppy_sign_in.fallback'),
314
+ subject: translate(t, key('subject'), data.params),
315
+ heading: translate(t, key('heading'), data.params),
316
+ intro: translate(t, key('intro'), data.params),
317
+ footnote: translate(t, key('fallback'), data.params),
314
318
  code: data.code,
315
- codeLabel: translate(t, 'mail.poppy_sign_in.code_label'),
319
+ codeLabel: translate(t, key('code_label'), data.params),
316
320
  });
317
321
  const sent = await sendEmail(ctx, data.email, content);
318
322
  if (!sent) {
319
- ctx.logger.info({ email: data.email, agent: data.agentName, code: data.code }, 'authkit: código do Poppy Mediated Sign-In (dev — @adonisjs/mail ausente)');
323
+ ctx.logger.info({ email: data.email, code: data.code, messages: data.messages, ...(data.params ?? {}) }, 'authkit: código por e-mail (dev — @adonisjs/mail ausente)');
320
324
  }
321
325
  }
322
326
  catch (error) {
323
- ctx.logger.error({ err: error, email: data.email }, 'authkit: falha ao enviar o código do Poppy Mediated Sign-In');
327
+ ctx.logger.error({ err: error, email: data.email, messages: data.messages }, 'authkit: falha ao enviar o e-mail com código');
324
328
  }
325
329
  }
326
330
  /**