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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +3637 -0
  2. package/build/commands/clients_create.d.ts +1 -0
  3. package/build/commands/clients_create.js +15 -2
  4. package/build/commands/commands.json +7 -0
  5. package/build/host/ui/admin.html +1 -1
  6. package/build/host/views/account/apps.edge +8 -3
  7. package/build/host/views/partials/styles.edge +2 -2
  8. package/build/index.d.ts +3 -3
  9. package/build/index.js +3 -5
  10. package/build/src/accounts/lucid_account_store.d.ts +0 -18
  11. package/build/src/accounts/lucid_account_store.js +7 -48
  12. package/build/src/accounts/lucid_store/mfa.js +1 -1
  13. package/build/src/accounts/lucid_store/organizations.d.ts +1 -1
  14. package/build/src/accounts/lucid_store/password_hygiene.d.ts +2 -3
  15. package/build/src/accounts/lucid_store/password_hygiene.js +2 -3
  16. package/build/src/accounts/lucid_store/shared.d.ts +0 -7
  17. package/build/src/accounts/lucid_store/shared.js +0 -14
  18. package/build/src/accounts/lucid_stores.d.ts +1 -1
  19. package/build/src/adapters/redis_adapter.js +12 -2
  20. package/build/src/define_config.d.ts +6 -12
  21. package/build/src/define_config.js +3 -2
  22. package/build/src/doctor/checks.d.ts +3 -4
  23. package/build/src/doctor/checks.js +5 -7
  24. package/build/src/host/account_api/account_api_controller.d.ts +2 -1
  25. package/build/src/host/account_api/account_api_controller.js +5 -11
  26. package/build/src/host/admin_api/api_clients_controller.d.ts +9 -3
  27. package/build/src/host/admin_api/dto.d.ts +3 -1
  28. package/build/src/host/admin_api/dto.js +3 -1
  29. package/build/src/host/admin_clients_service.d.ts +13 -5
  30. package/build/src/host/admin_clients_service.js +18 -5
  31. package/build/src/host/admin_console/admin_shell_controller.d.ts +16 -1
  32. package/build/src/host/admin_console/admin_shell_controller.js +21 -7
  33. package/build/src/host/admin_console/console_clients_controller.d.ts +21 -2
  34. package/build/src/host/admin_console/console_clients_controller.js +9 -0
  35. package/build/src/host/admin_console/console_impersonation_controller.js +3 -8
  36. package/build/src/host/admin_console/console_overview_controller.d.ts +2 -2
  37. package/build/src/host/admin_console/console_overview_controller.js +4 -2
  38. package/build/src/host/admin_validators.d.ts +15 -11
  39. package/build/src/host/admin_validators.js +16 -9
  40. package/build/src/host/client_names.d.ts +9 -2
  41. package/build/src/host/client_names.js +11 -3
  42. package/build/src/host/consent_params.d.ts +7 -0
  43. package/build/src/host/consent_params.js +27 -0
  44. package/build/src/host/controllers/account_apps_controller.js +4 -3
  45. package/build/src/host/controllers/account_confirm_controller.d.ts +0 -7
  46. package/build/src/host/controllers/account_confirm_controller.js +0 -6
  47. package/build/src/host/controllers/account_orgs_controller.d.ts +0 -50
  48. package/build/src/host/controllers/account_orgs_controller.js +7 -113
  49. package/build/src/host/controllers/account_security_controller.js +7 -11
  50. package/build/src/host/controllers/agent_consent_controller.js +23 -5
  51. package/build/src/host/controllers/interaction_controller.js +6 -2
  52. package/build/src/host/controllers/poppy_consent_controller.js +33 -7
  53. package/build/src/host/controllers/registration_controller.js +7 -2
  54. package/build/src/host/i18n.d.ts +0 -4
  55. package/build/src/host/i18n.js +0 -6
  56. package/build/src/host/impersonation.d.ts +7 -15
  57. package/build/src/host/impersonation.js +7 -16
  58. package/build/src/host/interaction_recovery.js +4 -1
  59. package/build/src/host/login_attempt.js +5 -1
  60. package/build/src/host/middleware/account_auth.js +6 -0
  61. package/build/src/host/rate_limit.js +5 -1
  62. package/build/src/host/register_auth_host.d.ts +6 -0
  63. package/build/src/host/register_auth_host.js +32 -8
  64. package/build/src/host/renderers/inertia_renderer.d.ts +9 -21
  65. package/build/src/host/renderers/inertia_renderer.js +5 -6
  66. package/build/src/host/screen_props.d.ts +309 -0
  67. package/build/src/host/screen_props.js +11 -0
  68. package/build/src/host/sudo_mode.d.ts +5 -11
  69. package/build/src/host/sudo_mode.js +5 -11
  70. package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +67 -0
  71. package/build/src/host/ui-dist/index.html +1 -1
  72. package/build/src/password/password_manager.js +5 -0
  73. package/build/src/provider/build_provider.js +0 -34
  74. package/build/src/provider/oidc_service.d.ts +0 -2
  75. package/build/src/provider/oidc_service.js +1 -12
  76. package/build/src/provider/token_exchange.d.ts +1 -3
  77. package/build/src/provider/token_exchange.js +1 -1
  78. package/build/stubs/config/authkit_react.stub +3 -3
  79. package/package.json +18 -17
  80. package/skills/authkit-interactions/SKILL.md +32 -5
  81. package/stubs/config/authkit_react.stub +3 -3
  82. package/build/src/commands/import_clients.d.ts +0 -40
  83. package/build/src/commands/import_clients.js +0 -81
  84. package/build/src/host/ui-dist/assets/index-C180q-la.js +0 -67
  85. package/build/src/mixins/with_mfa.d.ts +0 -35
  86. package/build/src/mixins/with_mfa.js +0 -21
package/CHANGELOG.md ADDED
@@ -0,0 +1,3637 @@
1
+ # @adonis-agora/authkit-server
2
+
3
+ ## 1.0.0-rc.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#436](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/436) [`c942fe3`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/c942fe30483346217c9c89112e13db0d4a65c647) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - AuthKit 1.0. See the [Migrating to 1.0](https://github.com/DavideCarvalho/adonis-agora-authkit/blob/main/apps/docs/content/docs/migrating-to-1-0.mdx) guide.
8
+
9
+ Breaking changes (`@adonis-agora/authkit-server`):
10
+
11
+ - `withMfa` is removed. MFA state lives in the lib-owned `auth_mfa` table, so drop the mixin from your `AuthUser` model's `compose(...)`.
12
+ - `markSudo` is no longer exported. Grant sudo with `completeSudo(sudoContextFrom(ctx), methodId)`, which also writes the `sudo.confirmed` audit event, remembers the method and redirects to the validated `return_to`.
13
+ - The email-change confirmation link is sent through `mail.onEmailChangeConfirm` only. Without it, AuthKit sends its built-in email; `onEmailVerification` no longer receives email-change confirmations.
14
+ - The `clients` option of `defineConfig` is removed. Create clients in the admin console, through the Admin API or with `node ace authkit:clients:create` (new `--client-secret` flag keeps an existing secret). `buildImpersonationPanel(cfg, targetId, clients)` now requires `clients`, and the impersonation curl always shows the `<CLIENT_SECRET>` placeholder.
15
+ - `inertiaRenderer({ prefix, views })`: `views` is required. Screens not listed render with the built-in Edge view. `AuthkitScreen` is now the exact union of the screens AuthKit renders (adds `throttled`, `agents/consent`, `agents/done`; no arbitrary strings), and every screen's props are exported: `AuthkitScreenProps['<view>']` plus named types (`LoginProps`, `SessionExpiredProps`, …). The controllers are checked against them. Hosts declare screens of their own by augmenting `AuthkitScreenProps` (`declare module '@adonis-agora/authkit-server'`).
16
+ - The Admin API client body reads `grantTypes` only; the `grants` alias is gone (responses still return `grants`).
17
+ - `lucidAccountStore` always mounts password history over the model's connection (`auth_password_history`).
18
+ - `/account/orgs/json`, `/account/orgs/invitations/json` and `/account/orgs/:id/json` are removed; use `/account/api/orgs/*`.
19
+ - `GET /account/api/apps` entries gain `name` and `logoUrl`.
20
+
21
+ - The admin console reads its config from a `<script type="application/json" id="authkit-admin-config">` data block (CSP-safe) instead of an inline `window.__AUTHKIT__` script; the dev fallback is gone.
22
+ - The admin client DTO returns `grantTypes` (was `grants`), plus `clientName` and `logoUri`, which the console and the Admin API can now set (`null` removes them, as it does `backchannelLogoutUri`). `GET {admin}/api/clients/:id` exists.
23
+ - Anonymous JSON callers of the admin and account JSON APIs get `401` (and `403` without an admin role) instead of a redirect to the login page.
24
+ - Fixes: the Redis adapter's `list()` honours the connection `keyPrefix` (sessions, apps and admin lists were empty); a password login posted without a password is an invalid-credentials answer, not a 500.
25
+
26
+ Breaking changes (`@adonis-agora/authkit-core`):
27
+
28
+ - `ClientConfig` and `ResolvedAuthServerConfig.clients` are removed.
29
+
30
+ Breaking changes (`@adonis-agora/authkit-client`):
31
+
32
+ - `@adonisjs/auth` is a required peer. `ctx.auth` is `@adonisjs/auth`, backed by the AuthKit session through `authkitClientGuard`; `ctx.authkit` (from `authkit_context_middleware`) carries the AuthKit-only data (identity, `hasGlobalRole`, active org, `toSharedProps`).
33
+ - `authkit_middleware`, `auth_middleware` and `silent_auth_middleware` (and their subpaths) are removed: `ctx.auth` comes from `@adonisjs/auth`, and `middleware.auth()` / `silentAuth` are the `#middleware/auth_middleware` / `#middleware/silent_auth_middleware` files `node ace configure` publishes. `require_org_middleware` stays. `@adonis-agora/authkit-client/types` no longer declares `HttpContext.auth`; type `ctx.authkit.getUser()` through the `AuthkitUserType` interface.
34
+ - `node ace configure @adonis-agora/authkit-client` installs `@adonisjs/auth` when missing (`--no-install` prints the command), writes `config/auth.ts` with `authkitClientGuard` + `sessionUserProvider` (`--model`, default `#models/user`; an existing file is left alone and the guard to add is printed), registers `@adonisjs/auth/auth_provider`, `initialize_auth_middleware` + `authkit_context_middleware`, and publishes the named `auth` / `silentAuth` middleware.
35
+ - `authkitClientGuard` takes its user from `resolveUser` when one is configured (so `lucidMirror` provisions the row on first login, and what it loads is on `ctx.auth.user`, the same instance as `ctx.authkit.getUser()`), with the provider as the fallback.
36
+ - New `authkitIdentityProvider()`: a user provider with no database; `ctx.auth.user` is the AuthKit `Identity`.
37
+ - New `authkitClientGuard({ provider, resolver })` option: a per-guard resolver for API routes (bearer JWT, opaque, PAT).
38
+ - `resolvers.jwt({ tokenSource: 'bearer' })` accepts `Authorization: DPoP <jwt>` with a valid `DPoP` proof for `cnf.jkt`-bound tokens, and refuses DPoP-bound tokens sent as `Bearer`.
39
+ - `UnauthorizedAccessConstructor` is no longer exported, and `AuthkitClientGuard` no longer takes an exception class; it always throws `@adonisjs/auth`'s `E_UNAUTHORIZED_ACCESS`.
40
+
41
+ Breaking changes (`@adonis-agora/authkit-sdk`):
42
+
43
+ - `clients.*` return `grantTypes` (was `grants`), plus `clientName` and `logoUri`.
44
+
45
+ Breaking changes (`@adonis-agora/authkit-react`):
46
+
47
+ - `createAuthkitClient()` no longer reads `window.__AUTHKIT__`: pass `baseUrl` for `client.admin.*` and `csrfToken`.
48
+ - Admin response types match the server: `users.remove` / `clients.remove` → `{ ok, deleted }`, `users.resetPassword` → `{ ok, email }`, `users.disable` / `enable` → the user, `SettingListResult.locked`; `AdminClient.grants` → `grantTypes` (+ `clientName`, `logoUri`); `UpdateUserInput` is `{ globalRoles? }`, `CreateUserInput` takes no `null`.
49
+ - New: `client.account.agents.list()` / `revoke()`, `useAccountAgentsQueryOptions`, `useRevokeAccountAgentMutationOptions`.
50
+ - Peers: `react` / `react-dom` `^19.0.0`, `@inertiajs/react` `^2.0.0 || ^3.0.0`.
51
+
52
+ - `useProfile`, `useSessions`, `useAuthorizedApps`, `useOrganizations`, `useOrganization`, `useOrgInvitations`, `useSwitchOrganization` and their types are removed, with the `profile`, `sessions`, `apps`, `orgs` and `orgInvitations` keys of `endpoints`. Use the query options (`useMeQueryOptions`, `useAccountSessionsQueryOptions`, `useAppsQueryOptions`, `useAccountOrgsQueryOptions`, …).
53
+ - `UserProfile`, `AuthorizedApps`, `OrganizationSwitcher` and `OrganizationProfile` need `QueryClientProvider` and `AuthkitClientProvider`.
54
+
55
+ All packages:
56
+
57
+ - `@adonis-agora/*` peer ranges accept 1.x and its release candidates (`<2.0.0 || >=1.0.0-rc.0 <2.0.0`): `durable`, `media`, `telescope` (authkit-server), `resilience` (authkit-client), `authkit-server` (authkit-sdk).
58
+ - Node.js `>=24` is required (tested on 24 and 26).
59
+
60
+ ### Patch Changes
61
+
62
+ - Updated dependencies [[`c942fe3`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/c942fe30483346217c9c89112e13db0d4a65c647)]:
63
+ - @adonis-agora/authkit-core@1.0.0-rc.0
64
+
65
+ ## 0.90.2
66
+
67
+ ### Patch Changes
68
+
69
+ - fix(deps): update dependency @simplewebauthn/server to v14.1.0 ([#444](https://github.com/DavideCarvalho/adonis-agora-authkit/issues/444))
70
+
71
+ ## 0.90.1
72
+
73
+ ### Patch Changes
74
+
75
+ - fix(deps): update dependency jose to v6.2.15 ([#405](https://github.com/DavideCarvalho/adonis-agora-authkit/issues/405))
76
+
77
+ ## 0.90.0
78
+
79
+ ### Minor Changes
80
+
81
+ - [#404](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/404) [`1208413`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/120841300b97182e1b287e744e8c84c2a7860e0d) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `authkit.identities`: API pública para o host ligar uma conta a um provider (Google, GitHub, …) fora do login social, sem tocar no account store cru nem na tabela. `link()` faz o upsert na chave `(provider, providerUserId)`, mudando a identidade de conta se preciso, e devolve `previousAccountId`; `find`/`list`/`unlinkAll` completam. Store sem account linking: `supported === false` e os métodos lançam `ProviderIdentitiesUnsupportedError`.
82
+
83
+ ## 0.89.0
84
+
85
+ ### Minor Changes
86
+
87
+ - [#403](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/403) [`d1d375b`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/d1d375bf23732861dc836b73120c31e7ebab8065) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - New `mail.onNewLogin` hook for the new-IP login alert, so a host can send that email in its own template the same way `onNewDeviceLogin` already allows for the new-device alert.
88
+
89
+ It receives `{ account, ip, userAgent, timestamp }` and fires only when the IP was never seen for the account and `notifications.newLoginEmail` is on. Without the hook the bundled email goes out as before. `login.new_ip_notified` is audited either way, and an error thrown by the hook is logged without skipping the new-device alert that follows.
90
+
91
+ ## 0.88.1
92
+
93
+ ### Patch Changes
94
+
95
+ - fix(deps): update dependency jose to v6.2.14 ([#388](https://github.com/DavideCarvalho/adonis-agora-authkit/issues/388))
96
+
97
+ ## 0.88.0
98
+
99
+ ### Minor Changes
100
+
101
+ - [#381](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/381) [`787ea9f`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/787ea9f644fc3499f08ad7bd8a0e9f8955095758) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Passwordless signup also sends a code when `login.otp` is on, so a person can finish in the same browser instead of opening the link in another one.
102
+
103
+ The account still isn't created until the code or the link is confirmed. The code is bound to the interaction and the browser session, and a correct one leads to the same confirmation link. `mail.onMagicLink` now receives `code` with `kind: 'signup'`, and an e-mail that already has an account gets a login code too, so the response stays the same either way.
104
+
105
+ ## 0.87.0
106
+
107
+ ### Minor Changes
108
+
109
+ - [#380](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/380) [`2e1a39a`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/2e1a39a7fbd44807ac719ee80c09c7a618a11b63) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Poppy: update to Personal Agent Protocol Draft 0.3, without compatibility with Draft 0.1 (experimental API).
110
+
111
+ Sessions now come from the `Poppy-Session` header, a JWT signed with a delegated key, and account access is plain OAuth. Sign-in no longer knows the Session, and DPoP is optional. Discovery adds protected resource metadata and `protected_resources`, and scopes are defined by the app. Browsers join through a one-time code, and Mediated Sign-In is now an OAuth extension grant.
112
+
113
+ Breaking: `POPPY_READ`/`POPPY_WRITE`, `broadScopes`, `sessionTokenTtl`/`accountTokenTtl` (now `accessTokenTtl`/`refreshTokenTtl`) and the `/poppy/sign-in` routes are gone; `signIn.mediated.scopes` and `poppyEmailCodeSignIn({ scopes })` are required; `www.` is no longer ignored in `organization.domain`; the authenticate slot returns `invalid_session` (400) and a code-less 401 instead of `sign_in_required`.
114
+
115
+ ## 0.86.0
116
+
117
+ ### Minor Changes
118
+
119
+ - [`9e8665d`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/9e8665d3605706242ed15362f18767d31df94cfd) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Throttle no `/identifier` e escape hatch para o host cuidar das rotas do login.
120
+
121
+ **Throttle nos passos anônimos do login.** `POST /auth/interaction/:uid/identifier` e
122
+ `POST /auth/interaction/:uid/passkey/options` eram as duas rotas do fluxo sem rate-limit: o
123
+ `/magic` (envio do e-mail) já tinha, os passos anteriores não. O fluxo é passwordless, então o risco não é força bruta nem enumeração (a resposta é
124
+ uniforme) — é criação ilimitada de estado de interaction por um anônimo. Os dois agora
125
+ consomem o bucket `login` (por IP), o mesmo do `magic`: um login normal gasta 2 a 3 dos 10
126
+ pontos por minuto.
127
+
128
+ **Rotas exportadas.** `registerAuthHost()` passou a devolver, no `AuthHostRouteMap`:
129
+
130
+ - `routes`: handle do AdonisJS de cada rota do fluxo (`identifier`, `magic`, `consent`, …),
131
+ para pendurar middleware depois do registro;
132
+ - `urls`: o pattern de cada uma, e `interactionUrl(key, uid)` para a URL concreta;
133
+ - `extraMiddleware`: opção para declarar middleware por rota (aplicado **depois** do
134
+ throttle da lib), sem precisar de fork.
135
+
136
+ ```ts
137
+ registerAuthHost(router, {
138
+ extraMiddleware: {
139
+ identifier: [throttle({ limit: 5, window: '1 minute' })],
140
+ },
141
+ })
142
+ ```
143
+
144
+ Não é opção de política: não é travada pelo `defineConfig` e não substitui o limite
145
+ padrão — soma-se a ele.
146
+
147
+ ## 0.85.1
148
+
149
+ ### Patch Changes
150
+
151
+ - [#371](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/371) [`380e058`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/380e0589a0c34cad2c7e05484b95a1b3516f9a36) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - The console's "Save roles" button returned 404. It calls `client.admin.users.update`, which sends `PATCH {console}/api/users/:id` with `{ globalRoles }`, and the console only had `PATCH /api/users/:id/roles`. That route now exists and goes through the same checks as `/roles`: 404, 409 when roles are managed outside authkit, last admin, self-demotion, and validation against the catalog.
152
+
153
+ ## 0.85.0
154
+
155
+ ### Minor Changes
156
+
157
+ - [#370](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/370) [`77362ee`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/77362ee015873271808a688c6010bf8553e4d39f) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - feat(settings): the social login second factor is a console setting, locked by the config file.
158
+
159
+ `social.secondFactor` (0.83.0) only existed in `defineConfig`, unlike the other policies. It now follows the config-locks rule: declared in the file it wins and locks the setting; omitted, the new runtime setting `social_second_factor` decides, editable in the admin console (Settings → "Login social: 2º fator") and via the Admin API / `authkit:settings` CLI:
160
+
161
+ - `policy`: `'enrolled'` (default) | `'required'` | `'never'`.
162
+ - `requiredRoles`: roles that always require the factor, whatever the policy (e.g. `['ADMIN', 'COORDINATOR']`). Roles come from `resolveAccountRoles`, so they follow `resolveTokenRoles` (e.g. `@adonis-agora/authz`).
163
+
164
+ Without the setting nothing changes (`'enrolled'`). The console's settings fields gain `select` and `list` types.
165
+
166
+ ## 0.84.0
167
+
168
+ ### Minor Changes
169
+
170
+ - [#369](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/369) [`431e9aa`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/431e9aa99fb1b28e4f0d9ceeac1f3d15f9f7106e) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - "Last used" badge on the login screen. The server remembers, in a cookie on the device (`authkit_last_login`, httpOnly, no identity in it), the primary method the last login used — password, magic link, email code, passkey, a social provider or a custom method — and passes it as `lastUsedLoginMethod` to every `login` render. The built-in Edge screen marks the matching button; Inertia hosts use the new `<LastUsedBadge>` / `isLastUsedLoginMethod()` from authkit-react. It is per device on purpose: a per-account hint would tell anyone who types an email how that account signs in. Opt out with `login: { lastUsedMethod: false }`.
171
+
172
+ Also fixes cookies written during the final login request being dropped: `interactionFinished` ends the raw response before AdonisJS flushes its headers, so `completeLogin` now relays pending `Set-Cookie` headers first. This affected the "trust this device" cookie set after a successful second factor.
173
+
174
+ ## 0.83.0
175
+
176
+ ### Minor Changes
177
+
178
+ - [#368](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/368) [`b7870c2`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/b7870c2bbd0fbdc347b91eb585f70e76a7c27143) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - feat(admin): roles are read-only in the console and the Admin API when they come from outside authkit.
179
+
180
+ With `resolveTokenRoles` configured (roles kept in `@adonis-agora/authz` or the host's own table), `globalRoles` decides nothing, but the console and `PATCH /users/:id` still wrote to it and answered "saved". Now:
181
+
182
+ - User DTOs (console and Admin API) carry `roles`: the effective roles from `resolveAccountRoles`, the same source as the `roles` claim. Without the hook it equals `globalRoles`. `globalRoles` keeps meaning the stored column.
183
+ - Editing roles answers `409 roles_managed_externally` (console `PATCH /api/users/:id/roles`, Admin API `PATCH /users/:id` with `globalRoles`). Other fields stay editable.
184
+ - The console shows the effective roles as read-only, with a note that the application manages them (`features.rolesManagedExternally` in the injected config).
185
+ - `AdminUser.roles?: string[]` in `@adonis-agora/authkit-react`.
186
+
187
+ Hosts without `resolveTokenRoles` keep editing `globalRoles` as before.
188
+
189
+ - [#367](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/367) [`7903c64`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/7903c64b500377cfaf772c40674675d97d8419c4) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - fix(signup): passwordless signup no longer creates the account before the e-mail is confirmed.
190
+
191
+ The signup POST used to create the account, then send the magic link. A failed send left an account behind, and anyone could create an account (and fire `account.created`) for someone else's e-mail. Now, for an e-mail without an account, the POST only sends a confirmation link carrying e-mail + name in a token encrypted with the app's APP_KEY (15 min, its own purpose). The account is created by the new `GET /auth/interaction/:uid/signup/confirm` when the owner opens the link, with `emailVerified: true`, and the login continues through the existing magic link flow (status, maintenance and second-factor gates). If the account already exists when the link is opened, the link is refused, which makes it single-use without database state. E-mails that already have an account still get a login magic link, and the response stays the same either way.
192
+
193
+ `mail.onMagicLink` receives `kind: 'signup'` for the confirmation link; hosts that ignore it keep working. The `signup` audit event and `account.created` now fire on confirmation.
194
+
195
+ Password signup: when the login right after creating the account fails, the new account is deleted (stores with `deleteAccount`), instead of leaving an "e-mail already registered" account nobody logged into.
196
+
197
+ - [#366](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/366) [`e235133`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/e235133c00cc28a671b882e8fe21c33dec8ad03e) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - fix(social): social login now honors the account's second factor, and no longer links or creates accounts from an e-mail the provider reports as unverified.
198
+
199
+ - The social callback used to call `completeLogin` right after the provider, so an account with TOTP or a passkey skipped it on "Continue with Google" while password, magic link and OTP went through `secondFactorGate`. When the account owes a factor, the callback now stores a one-shot pending login and redirects to the new `GET /auth/interaction/:uid/social/continue`, which runs the same gate (`amr: ['fed', ...]`).
200
+ - New `social.secondFactor` option: `'enrolled'` (default: challenge when a factor is enrolled), `'required'` (always; accounts without a factor see the "set up MFA" screen) or `'never'` (previous behavior), or a function per account that receives `{ id, email, roles }`, where `roles` comes from `resolveTokenRoles` when configured (e.g. `@adonis-agora/authz`), else `account.globalRoles`.
201
+ - When the provider reports `emailVerificationState: 'unverified'`, the callback no longer links the identity to an existing account by e-mail nor creates a new one. New accounts are created with `emailVerified` only when the provider confirmed the address (previously always `true`).
202
+
203
+ Accounts without an enrolled factor log in exactly as before.
204
+
205
+ ### Patch Changes
206
+
207
+ - [#366](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/366) [`e235133`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/e235133c00cc28a671b882e8fe21c33dec8ad03e) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - fix(roles): every role gate reads the host's role authority. When `resolveTokenRoles` is configured (e.g. roles kept in `@adonis-agora/authz`), the `roles` claim and the admin console already used it, but other gates read `account.globalRoles` directly: the maintenance-mode admin bypass (an admin whose role lives in authz was locked out during maintenance), PAT introspection (`/authkit/pat/introspect`) and the Admin API `POST /tokens/verify` returned the stored column. They now go through `resolveAccountRoles`; opaque access-token introspection passes the token's `clientId` to the hook, as at mint time. Without the hook nothing changes.
208
+
209
+ ## 0.82.3
210
+
211
+ ### Patch Changes
212
+
213
+ - [#364](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/364) [`8b6dae4`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/8b6dae4dc88e0a3841c28f8feeba1de6c7020e49) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - perf(admin): list organizations in a bounded number of queries. The admin org list (`AdminOrgsService.listOrgs`, console + Admin REST API) used to walk every account and query its orgs and each org's members, so the query count grew with the account base. The Lucid store now answers it in 3 queries via a new optional `listAllOrgsWithMemberCount` store capability, returning exactly the same list in the same order; custom stores without it keep the previous path.
214
+
215
+ ## 0.82.2
216
+
217
+ ### Patch Changes
218
+
219
+ - [#363](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/363) [`5656057`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/5656057d3b2a701eea5515004fcd000086c38f25) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Load `@simplewebauthn/server` on the first passkey ceremony instead of at import time. Importing the package no longer evaluates it (~290 modules, ~4.5 MB of heap per process), which most processes — queue workers, ace commands, apps without passkeys — never need. The `webauthnCeremonies` override seam and every public export are unchanged.
220
+
221
+ ## 0.82.1
222
+
223
+ ### Patch Changes
224
+
225
+ - [#361](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/361) [`1b9c90c`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/1b9c90ced4356d2c754e850e307954455ddeeb3a) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Performance: fewer queries and round trips on several paths.
226
+
227
+ - `listOrgsForAccount` (Lucid store) loads every org of the account in one query instead of one query per membership. It runs on the account orgs page, `GET /account/api/orgs`, the account export and the admin org list. The result and its order are unchanged.
228
+ - With `acceptIdpSession`, the RP guard no longer renews the IdP session (an UPDATE plus a `Set-Cookie`) on every request. A linked session that was renewed with the current TTL less than 60 s ago is left as it is. The session lookup, the account checks and revocation still run on every request. A session that would outlive the current TTL is still renewed, which shortens it, and a restore always renews.
229
+ - `RedisAdapter.consume` reads the payload and its TTL in one pipelined round trip instead of two.
230
+ - `DatabaseAdapter.upsert` checks whether the row exists without reading its payload back.
231
+ - Updated dependencies [[`1b9c90c`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/1b9c90ced4356d2c754e850e307954455ddeeb3a)]:
232
+ - @adonis-agora/authkit-core@0.10.1
233
+
234
+ ## 0.82.0
235
+
236
+ ### Minor Changes
237
+
238
+ - [#349](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/349) [`5512d90`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/5512d907f1a1960f7387651616c2a12d25abfa14) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Per-account session operations no longer read the whole OIDC store. `authkit_oidc_payloads` gains an indexed `account_id` column and an `expires_at` index (added by `ensureAuthkitSchema`, which runs on boot with `schema.autoManage`; with `autoManage: false`, run `ensureAuthkitSchema` in a migration to get them). Each index is checked by name and created when missing, independently of the column, so a table whose `account_id` was added by hand still gets both; `ensureAuthkitSchema`'s report lists the indexes it created under `indexed`. The database adapter answers the new optional `listByAccount`/`listByGrantIds` adapter methods in SQL, so single-session login, the account sessions page and `revokeAll` read only the account's rows; rows written before the column existed are still found and get classified on read. Expired rows are pruned every 10 minutes. The Redis adapter reads each `SCAN` page with one `MGET`.
239
+
240
+ The runtime-settings cache now actually caches across requests: the account and admin guards no longer query `auth_settings` on every request. Writes through any `RuntimeSettings` instance invalidate the shared cache.
241
+
242
+ `lucidPatStore` writes a token's `lastUsedAt` at most once a minute instead of on every request.
243
+
244
+ Fix: the database adapter now works on MySQL. It wrote and compared `expires_at` as an ISO-8601 string ending in `Z`, which MySQL rejects for `TIMESTAMP` columns ("Incorrect datetime value"), so every write failed. It now binds a `Date` (serialized by the driver in the database's own format) on every dialect except SQLite, which keeps the ISO text it already stores.
245
+
246
+ Fix: `ensureAuthkitSchema` inside a host migration. It checked whether each table and column existed by selecting from it, and on Postgres a SELECT on a missing table (such as an absent account table) aborts the migration's transaction, so every later statement failed. Existence is now read from the catalog (`to_regclass`/`pg_attribute` on Postgres, `information_schema` on MySQL, `sqlite_master`/`pragma_table_info` on SQLite). It also accepts the migration's own client (`ensureAuthkitSchema(this.db)`, as documented), not only the `Database` manager.
247
+
248
+ ## 0.81.3
249
+
250
+ ### Patch Changes
251
+
252
+ - [#326](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/326) [`e9c1987`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/e9c19871f238a1c9cef9dcf5918cc848a2352cf1) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Durable account-lifecycle runs are now attributed to `@adonis-agora/authkit-server` (`WorkflowRun.origin`), so the durable console's ORIGIN facet separates them from the app's own runs. `enqueueAccountDeletion` / `enqueueAccountExport` stamp the origin at start, and `defineAccountDeletionWorkflow` / `defineAccountExportWorkflow` now also return `options: { origin }` to pass as the fourth `engine.register` argument. Engines older than `@adonis-agora/durable` 0.36 ignore it.
253
+
254
+ ## 0.81.2
255
+
256
+ ### Patch Changes
257
+
258
+ - fix(deps): update dependency jose to v6.2.13 ([#323](https://github.com/DavideCarvalho/adonis-agora-authkit/issues/323))
259
+
260
+ ## 0.81.1
261
+
262
+ ### Patch Changes
263
+
264
+ - fix(deps): update dependency @simplewebauthn/server to v14.0.3 ([#305](https://github.com/DavideCarvalho/adonis-agora-authkit/issues/305))
265
+
266
+ ## 0.81.0
267
+
268
+ ### Minor Changes
269
+
270
+ - [#269](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/269) [`b0ca480`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/b0ca4802419aba93a9f913775f697cc4075cff88) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Drop Node 20, which reached end-of-life on 2026-04-30, and Node 22, which AdonisJS 7 does not support (it requires Node >=24): `engines.node` is now `>=24`. The supported Node lines are 24 and 26, and CI runs the test suite on each of them.
271
+
272
+ ### Patch Changes
273
+
274
+ - Updated dependencies [[`b0ca480`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/b0ca4802419aba93a9f913775f697cc4075cff88)]:
275
+ - @adonis-agora/authkit-core@0.10.0
276
+
277
+ ## 0.80.2
278
+
279
+ ### Patch Changes
280
+
281
+ - [#260](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/260) [`f374913`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/f37491386d6b7a5ce6874b8b3cd5740eceb05872) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `node ace configure @adonis-agora/authkit-server --ui=react` works again ([#194](https://github.com/DavideCarvalho/adonis-agora-authkit/issues/194)). The React pages the preset publishes went through the stub template engine, which broke on the backticks of their template literals, and their `{{{ exports() }}}` frontmatter had been rewritten by the formatter into `{ exports() }`. The pages are now plain TSX copied byte for byte (a small `ui/react/page.stub` only sets the destination), and a new test runs the real `configure` hook against a scratch app for every preset.
282
+
283
+ ## 0.80.1
284
+
285
+ ### Patch Changes
286
+
287
+ - [#255](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/255) [`481051c`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/481051c86c67b2bf5026f7a49028d260236667c9) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Publish peer dependencies as ranges again (`^7.5.0` instead of `7.5.0`, etc.). A Renovate pinning pass had turned the peer catalogs into exact versions, so any upstream patch release (e.g. `@adonisjs/core` 7.5.1) fell outside the accepted peer range.
288
+
289
+ ## 0.80.0
290
+
291
+ ### Minor Changes
292
+
293
+ - [#250](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/250) [`c2188ab`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/c2188ab8422f731de7508cb0d549d7339cd4c084) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Poppy: `poppyEmailCodeSignIn()`, a ready-made Mediated Sign-In. The agent asks for the account's
294
+ email, AuthKit emails a 6-digit code (new `mail.onPoppySignInCode` hook, or the default mailer with
295
+ branding and en/pt-BR copy) and the agent submits it — no `verify` to write. No account
296
+ enumeration (same answer for unknown, disabled, ineligible or rate-limited emails, email sent
297
+ without awaiting), per-email hourly cap, `eligible` filter.
298
+
299
+ ## 0.79.0
300
+
301
+ ### Minor Changes
302
+
303
+ - [#247](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/247) [`eba2f4d`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/eba2f4dc886cf9596a451539cff9ecce5bf6830c) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Admin console: a **Personal agents** page (PACT and Poppy). Lists every agent with its live
304
+ connection count, lists and revokes connections across all accounts, and allows or blocks agents
305
+ at runtime on top of `defineConfig` (lib-owned `auth_agent_registry` table). Blocking also revokes
306
+ every connection and ends the agent's Poppy sessions. Writes need sudo and are audited.
307
+ `@adonis-agora/authkit-react` gets the matching `client.admin.personalAgents.*` methods and query
308
+ options.
309
+
310
+ ## 0.78.0
311
+
312
+ ### Minor Changes
313
+
314
+ - [#245](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/245) [`42f9b00`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/42f9b00b540a320ec3b3903f5d710c0f3b8c7eb4) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - **Experimental:** Personal Agent Protocol ("Poppy", Draft 0.1 — https://personalagentprotocol.org/docs/spec) as a second personal-agent protocol next to PACT, under `personalAgents.poppy`. The spec is a draft in development: this config and its exports (`verifyPoppyRequest`, `poppyAuth`, `poppyOf`, `poppyWebSession`, `verifyDpopProof`, the `@adonis-agora/poppy:*` global slots…) track it and WILL change, possibly in breaking ways outside semver majors, while the spec is a draft.
315
+
316
+ - Discovery (`/.well-known/poppy.json`, per company domain) and RFC 8414 metadata with `poppy_domains`.
317
+ - Agent identity by Client ID Metadata Document (SSRF-safe fetch, cache, validation, registration/blocklist) and `private_key_jwt` with `jti` replay protection.
318
+ - Sessions via the JWT bearer grant, opaque DPoP-bound Session Tokens (RFC 9449, nonces, replay store: database/memory/redis), Account Tokens as refresh tokens (reusing the personal-agent grants), scope/resource narrowing and the spec's error codes.
319
+ - Direct (PKCE + RFC 9207 `iss`), Device (RFC 8628) and pluggable Mediated Sign-In (off by default).
320
+ - Resource-server verification (`verifyPoppyRequest`/`poppyAuth`) with `invalid_token`/`sign_in_required`/`insufficient_scope`; MCP Bearer tokens minted as oidc-provider access tokens so the existing MCP integration accepts them.
321
+ - Global slots for `@adonis-agora/agent`: `Symbol.for('@adonis-agora/poppy:authenticate')` (request authentication) and `Symbol.for('@adonis-agora/poppy:endpoints')` (conversation endpoint fallback for `poppy.json`).
322
+ - Browser session endpoint (signed assertion → app session cookie, 303), `poppyWebSession()` to follow the session state, RFC 7009 revocation and disconnect from the account console.
323
+ - `personalAgents.audience` is now optional when only Poppy is configured.
324
+
325
+ ## 0.77.0
326
+
327
+ ### Minor Changes
328
+
329
+ - [#242](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/242) [`7e22bc6`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/7e22bc6d092aa8ad2d0d97c7463b8ab1f0a258a5) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Support host-defined primary login methods as dependency-injected classes or instances. Add beginCustomLogin, authenticateCustomLogin, and trusted completeCustomLogin APIs, preserving account policies, maintenance, MFA, audit and OIDC authentication method claims.
330
+
331
+ Support verified phone identity in the Lucid account DTO and account deletion confirmation for accounts without email. Omit absent email claims and report actual email verification state.
332
+
333
+ - [#242](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/242) [`279d08c`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/279d08c297a66fe18b7fc2137f512602e1d13c96) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Support provider-neutral custom MFA classes or instances, explicit enrollment checks, distinct factor groups and policies requiring two or more factors. Extend shared login gates, protected challenge routes, Edge and generated React forms, while preserving native behavior when the extension is not configured.
334
+
335
+ - [#242](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/242) [`56bce87`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/56bce878bec9fc09bec7a6c5aad682ef36c2671f) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Support opt-in same-host RP session restoration and rolling remembered-session renewal directly in AuthKit. Database and Redis adapters renew existing persistent sessions atomically without recreating expired or revoked records. RP logout revokes the signed IdP credential, and bridged console login preserves its IdP session association after synchronizing the host guard.
336
+
337
+ - [#242](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/242) [`688025b`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/688025bcd31305120adf595646af261bf78396d3) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add extensible WhatsApp OTP delivery contracts, class/instance sender registration through whatsapp.sender, dependency-injected sender resolution, and native Meta Cloud API authentication template and Whatsmiau text adapters. Code generation, verification and account signup remain host-owned.
338
+
339
+ ## 0.76.0
340
+
341
+ ### Minor Changes
342
+
343
+ - [#240](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/240) [`d25b30a`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/d25b30a4a2fd5e68fbec23aa05c70703c3634a16) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `mcp: true` makes AuthKit the authorization server of your MCP servers, so `claude mcp add <url>` (and Claude, ChatGPT, VS Code, Cursor) signs users in with their own accounts and no app code:
344
+
345
+ - Dynamic registration opens, restricted to the redirects of the known MCP clients (loopback, claude.ai/claude.com, ChatGPT, VS Code, Cursor); `mcp.redirectUris` adds more. A declared `dynamicRegistration` still wins.
346
+ - Clients registered through `/reg` with the `refresh_token` grant get `offline_access` and `prompt=consent` on their authorize, so they stay connected. Static and console/CLI clients are untouched.
347
+ - Tokens are bound to the MCP server (`resource`, RFC 8707) declared in `mcp.resources` or registered at runtime with `registerOAuthResource()`, which `@adonis-agora/agent`'s MCP server does on its own.
348
+ - The RFC 8414 metadata is also served at the root path (`/.well-known/oauth-authorization-server/oidc`).
349
+
350
+ The consent screen now names the client that asks (its registered `client_name`, escaped) instead of the IdP's own app name, and lists the scopes it asked for; the Connected apps page (and `GET /account/api/apps`, as `name`) shows clients by that name.
351
+
352
+ - [#239](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/239) [`dbfac1e`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/dbfac1e0fe60f448eba8b05056044fb18c67db7e) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Personal agents: assistentes de IA (ChatGPT, Meta AI, um agente próprio…) que falam com o app em nome de um usuário, com o PACT 1.0 (https://openpactprotocol.org) como protocolo embutido.
353
+
354
+ Desligado por default; liga com `personalAgents` no `defineConfig`.
355
+
356
+ - **Identidade do agente**: JWT assinado pela chave do agente e verificado pelo JWKS dele, sem segredo compartilhado (ES256/RS256, vida ≤ 300 s, 30 s de relógio, `aud` único). Os agentes vêm de uma lista estática, de uma função (registro no banco) ou, com `open: true`, de qualquer `iss` com OIDC discovery.
357
+ - **Delegação** (`personalAgents.delegation`): device flow RFC 8628 em que o cliente OAuth é o agente. O usuário loga NESTE app e aprova os scopes do app em checkboxes que pode desmarcar. A tela recusa durante impersonation. O agente recebe um token assinado pelo keystore do IdP, preso ao agente e ao usuário do agente, com refresh rotativo. Aprovar de novo (step-up) soma scopes ao grant existente.
358
+ - **Revogação imediata** no console de conta (`/account/apps`) e na JSON API (`/account/api/agents`). Apagar ou desabilitar a conta corta a delegação do mesmo jeito, assim como toda revogação total (sair de todas as sessões, reset de senha, revogação pelo admin) — o `RevokeResult` ganha `agentGrants`. Reusar um refresh token já gasto revoga o grant (RFC 9700). Auditoria: `agent.delegation_approved`, `agent.delegation_denied`, `agent.grant_revoked`.
359
+ - **Tela de consentimento** protegida por CSRF, com rate limit por IP (bucket do OTP) e sem poder ser emoldurada (`X-Frame-Options: DENY`). Delegação com keystore sem chave ES256/RS256 falha no boot.
360
+ - **Rotas do app**: `personalAgentAuth()` protege o endpoint que os agentes chamam e `personalAgentOf(ctx)` devolve o agente e a delegação. `personalAgentSecurity`, `personalAgentStepUp` e `personalAgentReceipt` geram o bloco do Agent Card, o metadata de step-up e o recibo assinado.
361
+ - **Protocolo plugável** (`personalAgents.protocol`): o núcleo não depende do fio. `'pact'` é o default; o esquema de identidade do card se chama `platformJwt`, como na implementação de referência e na suíte de conformidade do PACT (o texto da spec diz `paJwt`; `pact({ identitySchemeName: 'paJwt' })` troca), e o `pactProtocol` exportado serve de modelo para um adapter próprio.
362
+ - **Schema**: três tabelas novas da lib no `ensureAuthkitSchema`: `auth_agent_device_codes`, `auth_agent_grants` e `auth_agent_refresh_tokens`.
363
+ - **CSRF**: `authkitCsrfExceptions` isenta `{prefix}/oauth/*` sozinho quando `personalAgents` está ligado. A opção nova `personalAgentsPrefix` sobrepõe isso; `false` desliga.
364
+
365
+ ### Patch Changes
366
+
367
+ - [#239](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/239) [`dbfac1e`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/dbfac1e0fe60f448eba8b05056044fb18c67db7e) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - O config que não resolve no `boot()` do provider é tentado de novo no `booted`, em vez de ser descartado em silêncio.
368
+
369
+ Num host com keystore criptografado, o `jwks` precisa do serviço de encryption, que pode ainda não estar pronto durante o boot dos providers. A resolução falhava, o catch engolia o erro, e nada derivado do config valia: os locks de settings, o stash que o `registerAuthHost` lê (`sudo.methods`, headless, personal agents) e o `config.routes`. Agora a resolução é refeita no `booted`, depois de todos os providers e antes do preload `start/routes.ts`. Se falhar de novo, vira um warning.
370
+
371
+ - [#239](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/239) [`dbfac1e`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/dbfac1e0fe60f448eba8b05056044fb18c67db7e) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Os redirects que a lib monta com query (`return_to` do login e do sudo, convite de organização) saíam quebrados em apps com `redirect.forwardQueryString: true`, o default do starter do AdonisJS.
372
+
373
+ O `response.redirect(url)` colava a query da request atual no fim da URL que já tinha a dela: `/login?return_to=%2Fx%3Fa%3D1?a=1`. Agora esses redirects vão exatamente como a lib os monta (`redirect().clearQs().toPath(url)`). O argumento `forwardQueryString = false` do `redirect()` não bastava, porque não desliga o que vem do config.
374
+
375
+ - [#239](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/239) [`dbfac1e`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/dbfac1e0fe60f448eba8b05056044fb18c67db7e) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - O redirect para o login (e para a confirmação de sudo) perdia a query string do `return_to` no AdonisJS 7.
376
+
377
+ O código lia `request.parsedUrl.search`, que o AdonisJS 7 não tem mais (`parsedUrl` virou `{ pathname, query }`). Quem caía no login a partir de `/admin/users?page=3` voltava para `/admin/users`. Agora o destino vem de `request.url(true)`, a API que inclui a query.
378
+
379
+ ## 0.75.0
380
+
381
+ ### Minor Changes
382
+
383
+ - [#235](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/235) [`574d243`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/574d243bc88f3fd3663f8ab44a35df5867f9ebaa) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - A impersonation passa a valer também para o access token (app nativo), não só para a sessão do console.
384
+
385
+ O token-exchange (RFC 8693) punha o ator (`act`) só no `id_token`. O ACCESS token trocado saía idêntico a um token do próprio alvo, então num resource server com `oidcBearerGuard` (ex.: app nativo) nenhuma regra "negado durante impersonation" funcionava: `impersonationState` e `realAccountId` só olhavam a sessão.
386
+
387
+ - O access token trocado carrega `extra.act = { sub: <admin> }` (via `extraTokenClaims` do provider; sai no JWT e na introspecção). A resposta do token endpoint também traz `act`, o que finalmente preenche o `actSub` que o `startImpersonation` grava na sessão.
388
+ - `VerifiedAccessToken.actor` expõe o ator (in-process, JWT e introspecção remota). O `oidcBearerGuard` grava a impersonation na request e recusa o token se a conta do ator não existe mais.
389
+ - `impersonationState(ctx)` e `realAccountId(ctx)` entendem a impersonation pelo bearer (`source: 'bearer'`, `impersonatorId` = o admin, `expiresAt` = `exp` do token). Sessão de console logada continua mandando.
390
+ - O token-exchange recusa: impersonation encadeada (token que já é de impersonation como `subject_token`) e impersonar a si mesmo.
391
+ - Nova opção `admin.impersonateAdmins` (default `true`, back-compat): com `false`, alvo que também é admin vira `invalid_grant`.
392
+
393
+ ## 0.74.0
394
+
395
+ ### Minor Changes
396
+
397
+ - [#233](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/233) [`94644e6`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/94644e6b1d8d10e0fd3c4b57706cce46b70412cb) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Clientes nativos (RFC 8252) e guard de access token (`oidcBearerGuard`)
398
+
399
+ **Clientes nativos.** Um client pode ser `applicationType: 'native'` (config estática,
400
+ Admin API/console — select "Application Type" — e `node ace authkit:clients:create --native`).
401
+ Antes todo client virava `application_type: 'web'` e o oidc-provider recusava redirect de app
402
+ mobile. Um client nativo aceita redirect de esquema privado (`com.example.app:/oauth`), https
403
+ "claimed" e loopback `http://127.0.0.1/…` com qualquer porta; é sempre público (sem secret,
404
+ `tokenEndpointAuthMethod: 'none'`, sem `client_credentials`) e o PKCE continua obrigatório.
405
+ O `AdminClientsService` valida a metadata pelo tipo antes de gravar (`ClientMetadataError`,
406
+ 422 `invalid_client_metadata` na API); client web continua só com http/https e o payload dele
407
+ não muda. `offline_access` + rotação de refresh token valem igual para clients públicos
408
+ (reusar um RT já rotacionado revoga o grant), e o consent é pulado para o client nativo
409
+ listado em `branding.firstParty`, como para os web.
410
+
411
+ **`oidcBearerGuard`.** Guard de `@adonisjs/auth` para APIs chamadas com
412
+ `Authorization: Bearer <access token>` — app nativo, SPA, serviço. Verifica in-process contra
413
+ o issuer embarcado (token opaco pelo adapter, JWT RFC 9068 pelo JWKS do provider) ou, com
414
+ `remote`, como resource server (JWKS do issuer e introspecção RFC 7662 com client
415
+ credentials). Aplica `scopes`/`audience`/`clientIds`, recusa token expirado, revogado,
416
+ refresh token e sender-constrained, e responde `E_UNAUTHORIZED_ACCESS` (401 com
417
+ `WWW-Authenticate: Bearer …`, nunca redirect). `authenticateAsClient` emite um token real
418
+ (`loginAs` nos testes). Os verificadores também são exportados
419
+ (`inProcessAccessTokenVerifier`, `remoteAccessTokenVerifier`).
420
+
421
+ `getAccountId(ctx)` passa a devolver, quando não há conta na sessão, a conta que o
422
+ `oidcBearerGuard` autenticou na request — basta pôr o guard em `auth.guards` e usar
423
+ `middleware.auth({ guards: ['web', 'api'] })`, sem reescrever as chamadas. A sessão sempre
424
+ ganha, e quem não usa o guard bearer não vê diferença. `realAccountId` segue a mesma regra;
425
+ `hasAccountSession` continua olhando só a sessão.
426
+
427
+ Guia novo: "Native apps (React Native / Expo)".
428
+
429
+ ### Patch Changes
430
+
431
+ - Updated dependencies [[`94644e6`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/94644e6b1d8d10e0fd3c4b57706cce46b70412cb)]:
432
+ - @adonis-agora/authkit-core@0.9.0
433
+
434
+ ## 0.73.0
435
+
436
+ ### Minor Changes
437
+
438
+ - [#231](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/231) [`244b9d2`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/244b9d29efe9ed1b4301ca9b1d34e0f05ae3590b) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - O login com segundo fator passa a emitir `amr` com o fator primário
439
+
440
+ Quando o login passava pelo desafio do segundo fator, `mfaVerify` só passava acr/amr ao
441
+ `completeLogin` no step-up. Fora dele o `id_token` saía **sem `amr` nenhum** (o relying
442
+ party não tinha como saber que houve MFA), e o fator primário (senha `pwd` ou e-mail
443
+ `email`) se perdia até no step-up, que emitia só `['mfa', <método>]`.
444
+
445
+ O `secondFactorGate`, ponto de entrada comum dos quatro caminhos (senha, link mágico,
446
+ código por e-mail, troca forçada de senha), passa a guardar o fator primário na sessão
447
+ junto do accountId pendente. TOTP, recovery code e passkey como segundo fator completam
448
+ com `amr: [primário, 'mfa', método]` (ex.: `['pwd', 'mfa', 'totp']`,
449
+ `['email', 'mfa', 'webauthn']`). O step-up continua carimbando o `acr`. Passkey-first
450
+ continua `['webauthn']`.
451
+
452
+ Quem comparava `amr` do step-up por igualdade exata (`['mfa', 'totp']`) deve passar a
453
+ checar pertinência (`amr.includes('mfa')`).
454
+
455
+ - [#230](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/230) [`4e424bf`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/4e424bf18f8c0cea30ae1352056b2017f661510d) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - O claim de organização passa a refletir a membership atual, e remover um membro revoga o acesso na hora
456
+
457
+ O `org_id`/`org_slug`/`org_role` do token vinha do `activeOrg` gravado no `Grant` no
458
+ consent, e o refresh o reemitia **sem perguntar se a conta ainda era membro** da org,
459
+ nem se a org ainda existia. Com refresh token de 30 dias, quem era removido da equipe
460
+ seguia recebendo token com o claim da org por até um mês — e um rebaixado seguia com
461
+ o papel antigo.
462
+
463
+ - **Emissão/refresh:** antes de emitir `org_*`, a lib confere no account store que a
464
+ conta ainda é membro da org (`getOrgMembership`) e que a org existe (`findOrgById`).
465
+ Se não for, o token sai **sem** claims de org. `org_role` é sempre o papel **atual**
466
+ da membership e `org_slug` o slug atual — nunca o retrato do consent. O
467
+ `resolveTokenRoles` recebe o `activeOrg` já conferido.
468
+ - **Remoção:** sair da org, remover um membro (form `/account/orgs`, `/account/api/orgs`,
469
+ console admin e Admin API) ou apagar a org revoga os grants que carregam aquela org
470
+ para aquela conta (ou para todas, no `deleteOrg`), com os access/refresh tokens deles,
471
+ e grava a revogação por `sub` para clients cookie-based. Grants de outras orgs e sem
472
+ org ficam intactos. Novo método `AdminSessionsService.revokeOrgGrants(orgId, accountId?)`.
473
+ O driver embedded do `@adonis-agora/authkit-sdk` (`organizations.delete`,
474
+ `organizations.members.remove`) revoga do mesmo jeito.
475
+
476
+ Mudança de comportamento: um store sem `findOrgById`/`getOrgMembership` não tem como
477
+ conferir a membership, então deixa de emitir `org_*` (antes emitia o que viesse do
478
+ cookie/grant). As rotas que gravam o cookie de org só existem com a capacidade.
479
+
480
+ ## 0.72.0
481
+
482
+ ### Minor Changes
483
+
484
+ - [#228](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/228) [`74b64d0`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/74b64d0f34c29f57ce2d2b74d828eaa14b7a6993) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - O segundo fator passa a valer no login por link mágico e por código de e-mail
485
+
486
+ Só o login por senha passava pelo gate de MFA. O link mágico e o código por e-mail
487
+ chamavam `completeLogin(..., { amr: ['email'] })` direto, então uma conta com TOTP
488
+ confirmado ou passkey registrada entrava apresentando **só o e-mail** — quem tinha
489
+ ligado o segundo fator achava ter endurecido a conta, e na prática o link continuava
490
+ sendo uma porta de fator único.
491
+
492
+ Os quatro caminhos de login (senha, link, código e troca forçada de senha) agora
493
+ passam pelo mesmo `secondFactorGate`.
494
+
495
+ No mesmo movimento, a decisão deixou de ser `mfa.enabled` e passou a ser "existe um
496
+ fator que esta pessoa consegue apresentar agora?": TOTP confirmado ou ao menos uma
497
+ passkey. `enabled` também liga ao registrar uma passkey e **não** desliga ao remover
498
+ a última — pela regra antiga, quem registrava uma passkey e depois a removia, sem
499
+ nunca ter enrolado TOTP, caía num desafio de código de 6 dígitos impossível de
500
+ responder. `getMfaState` ganhou `totp?: boolean` para sustentar a distinção; stores
501
+ que não reportam a chave caem no `enabled`, o comportamento de antes.
502
+
503
+ A tela de desafio recebe `totpAvailable` e esconde o campo de código (e a seção de
504
+ códigos de recuperação) quando a conta só tem passkey.
505
+
506
+ **Quem usa MFA vai notar:** contas com segundo fator que antes entravam direto pelo
507
+ link agora veem o desafio. É a correção, não uma regressão.
508
+
509
+ ## 0.71.0
510
+
511
+ ### Minor Changes
512
+
513
+ - [#226](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/226) [`35195ae`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/35195ae7b60a7c165c61ae5068c74f05937db254) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - feat(authkit-server)!: a ponte de compatibilidade de e-mail sai; entra a migração `authkit:users:normalize-emails`
514
+
515
+ **PASSO OBRIGATÓRIO DE UPGRADE.** Quem tem contas criadas antes da 0.69 precisa rodar
516
+ `node ace authkit:users:normalize-emails --apply` ao subir esta versão. Sem isso, **toda
517
+ conta gravada com o endereço mutilado (ou com maiúsculas) fica inalcançável** — e, por o
518
+ login ser à prova de enumeração, sem nenhuma mensagem de erro para o dono dela.
519
+
520
+ **O que saiu.** A ponte `login.legacyEmailFallback` (0.70), que no login tentava o endereço
521
+ exatamente como digitado e a normalização legada do validator.js quando a forma normalizada
522
+ não achava conta. Saíram com ela: a opção de config, `legacyNormalizeEmailIdentifier`,
523
+ `resolveEmailIdentifier`, os tipos `EmailIdentifierLookup` e `ResolvedEmailIdentifier`, a
524
+ segunda chave de sessão do login (`authkit_login_email_lookup`) e o `options.legacyFallback`
525
+ de `importUsers`. Um `login: { legacyEmailFallback: ... }` no config vira uma chave
526
+ desconhecida (ignorada) — apague-a. Fica **uma** normalização, `normalizeEmailIdentifier`
527
+ (`trim` + `toLowerCase`), e a busca direta por ela.
528
+
529
+ **O que entrou.** `authkit:users:normalize-emails`: varre as contas, calcula a forma
530
+ normalizada e, por padrão, **só relata** — quantas mudariam, quais, e as **colisões** (duas
531
+ contas que colapsam no mesmo endereço). Com `--apply` grava, **recusando-se a tocar em
532
+ qualquer conta envolvida numa colisão** e listando-as para decisão humana: a migração nunca
533
+ funde contas nem escolhe vencedor. Sai com código != 0 quando sobram colisões. A escrita
534
+ usa a capacidade nova `AccountEmailRewriteCapability` (`rewriteAccountEmail`, presente no
535
+ store Lucid; probe por `supportsAccountEmailRewrite`) — que não manda e-mail, não pede
536
+ confirmação e **não mexe em `email_verified_at`**, porque a caixa postal é a mesma, só a
537
+ grafia gravada muda. Store sem a capacidade: o relatório funciona e o `--apply` recusa.
538
+
539
+ **Por quê.** A ponte eram ~120 linhas de compatibilidade, uma opção de config, uma segunda
540
+ chave de sessão e uma chamada em cada ponto que resolve e-mail (login, cadastro, social,
541
+ admin, import) para um problema que se resolve UMA vez, no banco. E ela cobrava caro no pior
542
+ lugar: buscas extras a cada e-mail desconhecido no login, que é o caminho de ataque.
543
+
544
+ **Anti-enumeração intacta** — na verdade, mais apertada: o passo de identificador agora não
545
+ consulta o account store nenhuma vez (a ponte consultava), então não há nem diferença de
546
+ tempo entre "achei" e "não achei". A tela continua mostrando o que a pessoa digitou e o
547
+ passo 1 continua respondendo o mesmo redirect incondicional.
548
+
549
+ ## 0.70.0
550
+
551
+ ### Minor Changes
552
+
553
+ - [#223](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/223) [`6adae6d`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/6adae6dff8cf9d81892a0377ee25395bf36b632f) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - feat(authkit-server): escritas de organização e de segundo fator em JSON (`/account/api/*`)
554
+
555
+ Um host que desenha as PRÓPRIAS telas de conta não tinha como criar org, convidar, trocar
556
+ papel, remover membro, revogar/aceitar convite nem gerenciar TOTP/passkeys sem mandar o
557
+ usuário ao console `/account/*`: essas operações só existiam como POST de formulário, com
558
+ redirect. As leituras já eram JSON; faltava o outro lado.
559
+
560
+ - **Orgs**: `POST /account/api/orgs`, `/orgs/deactivate`, `/orgs/invitations/:token/accept`,
561
+ `/orgs/:id/{activate,leave,invitations}`, `DELETE /orgs/:id/invitations/:invId`,
562
+ `PATCH|DELETE /orgs/:id/members/:accountId`. Mesmos guards do formulário (escopo por
563
+ conta, owner/admin na org do path, catálogo de papéis, `owner` só por owner) e a MESMA
564
+ política efetiva — o resolver saiu para `host/org_policy.ts`, compartilhado com o console.
565
+ - **Segundo fator**: `POST /account/api/mfa/totp/{enroll,confirm,disable}`,
566
+ `/mfa/recovery-codes` e a cerimônia de passkey em JSON
567
+ (`/mfa/passkeys/{options,verify}`) — a clássica responde 302 e não cabe numa SPA. Mesmos
568
+ gates de sudo do console; a recusa vira `403 sudo_required` em vez de um redirect.
569
+ `requireSudo` passa a delegar a decisão ao novo `isSudoSatisfied`, que é o que o caminho
570
+ JSON consome — uma política só, duas formas de recusar. O `confirm` leva o throttle do
571
+ bucket de sudo (o form não tem; o JSON fica mais apertado, nunca mais frouxo).
572
+ - **`MfaCapability`** ganha `countRecoveryCodes` e `regenerateRecoveryCodes` OPCIONAIS
573
+ (capability-probe via `supportsRecoveryCodeCount` / `supportsRecoveryCodeRegeneration`),
574
+ implementados no `lucidAccountStore`. `GET /account/api/mfa` passa a devolver
575
+ `recovery.remaining` e `recovery.regenerable`. Novo evento de auditoria
576
+ `mfa.recovery_codes_regenerated`.
577
+
578
+ Compatível: o console HTML não muda: rota nova, resposta nova. As rotas JSON são montadas
579
+ mesmo com as telas `orgs`/`mfa` desligadas — é justamente o host com telas próprias que
580
+ precisa delas.
581
+
582
+ - [#224](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/224) [`db70f9d`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/db70f9d27a5c5226a6bc74d927cc294069efa5e2) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - fix(authkit-server): uma normalização só para a identidade por e-mail (cadastro e login)
583
+
584
+ O cadastro validava o e-mail com `.normalizeEmail()` do VineJS — os defaults do
585
+ validator.js, que no gmail **removem os pontos e o sub-endereço `+tag`** — enquanto o passo
586
+ de identificador do login não normalizava nada e buscava por igualdade exata. Quem se
587
+ cadastrou com `davi.carvalho96@gmail.com` teve a conta gravada como
588
+ `davicarvalho96@gmail.com` e, ao tentar entrar com o endereço certo, nunca recebia e-mail;
589
+ por a tela ser à prova de enumeração, sem nenhuma mensagem de erro. E, por o login ser
590
+ sensível a maiúsculas, nem `Davi@x.com` achava `davi@x.com`.
591
+
592
+ Agora há UMA normalização, conservadora e exportada — `normalizeEmailIdentifier`
593
+ (`trim` + `toLowerCase`, nada além disso) —, aplicada no cadastro (com e sem senha), no
594
+ "esqueci a senha", na troca de e-mail, na criação de usuário por admin, no convite de
595
+ organização, no import de usuários, no cadastro social e **no passo de identificador do
596
+ login**, que antes não usava nenhuma.
597
+
598
+ **Mudança de comportamento observável:** o cadastro passa a gravar o endereço que a pessoa
599
+ digitou. `davi.carvalho96@gmail.com` e `davi.carvalho96+lastro@gmail.com` deixam de colapsar
600
+ em `davicarvalho96@gmail.com` — são identidades distintas, como em qualquer outro provedor.
601
+ Contas já gravadas não são alteradas.
602
+
603
+ **Compatibilidade:** `login.legacyEmailFallback` (default `true`) é uma ponte temporária —
604
+ quando o endereço normalizado não acha conta, o login (OIDC e console de conta) e o
605
+ "esqueci a senha" tentam a forma exatamente como digitada e a normalização legada, e só
606
+ aceitam se apontarem para **exatamente uma** conta; empate é tratado como "não achei". O
607
+ cadastro, o "Continuar com o Google", a criação de usuário por admin e o
608
+ `authkit:users:import` usam a mesma resolução para não criar uma SEGUNDA conta para quem já
609
+ tem uma gravada mutilada (`importUsers` recebe o flag por `options.legacyFallback`). Nada
610
+ disso muda a tela, a mensagem ou o comportamento à prova de enumeração. Migre os endereços
611
+ gravados e desligue a ponte.
612
+
613
+ ## 0.69.0
614
+
615
+ ### Minor Changes
616
+
617
+ - [#221](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/221) [`7a56652`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/7a566522678cf4ab8136e0e4e7124ee8f686b0e9) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - feat(authkit-server): área de conta aceita a sessão do IdP (SSO) — `accountSession.acceptIdpSession`
618
+
619
+ O console de conta (`/account/*`, admin) só aceitava a própria sessão
620
+ (`ACCOUNT_SESSION_KEY`, criada pelo `POST /account/login`); o login da interaction OIDC
621
+ cria só a sessão do oidc-provider. Resultado: quem acabou de entrar num app OIDC (ou no
622
+ próprio host, quando ele é IdP e RP) levava um segundo pedido de login ao abrir
623
+ `/account/*`.
624
+
625
+ Com `accountSession: { acceptIdpSession: true }`, os guards do console (e o
626
+ `AccountAuthMiddleware`/aceite de convite de org) abrem o console para a conta de uma
627
+ sessão do IdP válida (cookie assinado, não expirada, conta existente e habilitada). O
628
+ console aberto assim fica amarrado a essa sessão: termina quando ela termina, e o "Sair"
629
+ do console encerra também a sessão do IdP. Novo helper público `ensureConsoleSession(ctx)`
630
+ para guards do host. Default `false` — nada muda para quem não liga.
631
+
632
+ - [#221](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/221) [`7a56652`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/7a566522678cf4ab8136e0e4e7124ee8f686b0e9) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - feat(authkit-server): política de redirect URI no registro dinâmico (RFC 7591/7592)
633
+
634
+ O `/reg` aceitava qualquer redirect sintaticamente válido (qualquer `https://`): com
635
+ registro aberto, qualquer um registrava um client com callback no próprio domínio e
636
+ usava a tela de consent do IdP como isca. Nova opção
637
+ `dynamicRegistration.redirectUriPolicy` (`loopback`, `exact`, `appSchemes`, `anyHttps`),
638
+ aplicada antes do provider no `POST /reg` e no `PUT /reg/:id`, que também restringe o
639
+ client ao fluxo de código (`authorization_code` + `refresh_token`, `response_type=code`)
640
+ e registra clientes só-loopback/app instalado como `application_type: native`. Gancho
641
+ `dynamicRegistration.validateRegistration` para regras do host (`RegistrationPolicyError`
642
+ → `400`).
643
+
644
+ **Mudança de default:** registro **aberto** (sem `initialAccessToken`) passa a aceitar só
645
+ redirects loopback (`http://localhost|127.0.0.1|[::1]`, qualquer porta). Callbacks web de
646
+ fornecedores e esquemas de app precisam ser listados em `exact`/`appSchemes`
647
+ (`anyHttps: true` ou `redirectUriPolicy: false` restauram o comportamento anterior).
648
+ Registro com `initialAccessToken` não muda.
649
+
650
+ - [#221](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/221) [`7a56652`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/7a566522678cf4ab8136e0e4e7124ee8f686b0e9) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - feat(authkit-server): resource indicators (RFC 8707) com access tokens opacos
651
+
652
+ A feature `resourceIndicators` do oidc-provider só era montada quando algum AT era JWT;
653
+ com tokens opacos, um `resource` no authorize (clientes MCP sempre mandam) era recusado
654
+ com `invalid_target`. Agora `accessTokens.resources` também liga a feature com tokens
655
+ opacos: o `resource` declarado (tolerando barra final) é concedido no consent e amarrado
656
+ ao AT (`aud`), que continua opaco, encontrável por `AccessToken.find` e introspecionável.
657
+ Pedidos sem `resource` não mudam (AT opaco sem `aud`, userinfo funciona).
658
+
659
+ `resource` fora da lista declarada (chaves de `resources` + o `audience` no modo JWT)
660
+ passa a ser recusado com `invalid_target` também no modo JWT — antes, um resource
661
+ desconhecido saía como token opaco com `aud` arbitrário.
662
+
663
+ ### Patch Changes
664
+
665
+ - [#221](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/221) [`7a56652`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/7a566522678cf4ab8136e0e4e7124ee8f686b0e9) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - fix(authkit-server): política de organizations (`allowSelfCreate`, `roles`, `invitationTtlHours`) volta a valer
666
+
667
+ Desde a remoção da config de política legada, `resolveOrganizations` fixava
668
+ `allowSelfCreate: false` (e roles/TTL no default), e o `AccountOrgsController` lia só
669
+ esse valor estático — nunca a setting `organizations_policy` que a doc manda usar. Pior:
670
+ declarar `organizations` no `defineConfig` **trava** a setting, então um host com
671
+ `organizations: { enabled: true }` não tinha jeito nenhum de ligar o self-create
672
+ (`POST /account/orgs` → 403).
673
+
674
+ - `OrganizationsConfigInput` aceita de novo `allowSelfCreate`, `roles` e
675
+ `invitationTtlHours` — com a setting travada, eles são a política efetiva.
676
+ - `/account/orgs` (tela, criação e convites) e o TTL dos convites do admin/Admin API
677
+ resolvem a política **efetiva**: setting da org → setting global → config → default.
678
+
679
+ ## 0.68.4
680
+
681
+ ### Patch Changes
682
+
683
+ - [#219](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/219) [`9279596`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/927959666d4e0fd809371b5611417cd20a7a5359) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - fix(authkit-server): cookie de org assinado chega URL-encoded no contexto Koa
684
+
685
+ Complemento do fix anterior (0.68.3). A verificação da assinatura checava
686
+ `startsWith('s:')` no valor **cru**, mas o jar Koa do oidc-provider devolve o valor
687
+ como está no header — **sem URL-decode** — e o browser reenvia o cookie exatamente
688
+ como o host o escreveu. Na prática chega `s%3A<b64>.<hmac>`, então a checagem nunca
689
+ casava e a org continuava sendo descartada: o `loadExistingGrant` seguia sem
690
+ reconciliar o `activeOrg`.
691
+
692
+ O leitor agora **normaliza antes de decidir o formato** (tenta `decodeURIComponent`
693
+ e só então checa o prefixo assinado). Confirmado contra o cookie real do app, com a
694
+ `APP_KEY` real:
695
+
696
+ ```
697
+ raw (como no header): s%3AeyJtZXNzYWdlIjoib3JnLWZtby1kZW1v…
698
+ SEM appKey : null
699
+ COM appKey : { orgId: 'org-fmo-demo', orgSlug: 'demo', orgRole: 'owner' }
700
+ ```
701
+
702
+ O teste ganhou o caso da forma URL-encoded, que é a que o browser de fato envia.
703
+
704
+ ## 0.68.3
705
+
706
+ ### Patch Changes
707
+
708
+ - [#217](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/217) [`927c70b`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/927c70bc95df414dfec0862ee3efcf3b6cafc360) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - fix(authkit-server): cookie de org assinado não era verificado no contexto Koa
709
+
710
+ O host grava o cookie `authkit_active_org` via `ctx.response.cookie`, e o AdonisJS
711
+ o **assina** (`s:<base64url>.<hmac>`, `MessageVerifier`). As duas leituras no
712
+ contexto do oidc-provider liam o valor cru e só tentavam URL-decode, então o
713
+ envelope assinado não parseava e a org era descartada:
714
+
715
+ - `readActiveOrgFromKoaCtx` — usada por `loadExistingGrant` no authorize.
716
+ - `readActiveOrgFromHostCtx` — no caminho Koa (o caminho Adonis já desassina sozinho).
717
+
718
+ Efeito: `loadExistingGrant` nunca reconciliava o `activeOrg` do Grant. Como o
719
+ consent só roda **uma vez por grant**, quem ativava a organização **depois** do
720
+ primeiro login não recebia `org_id`/`org_slug`/`org_role` no token — e
721
+ `/o/:orgSlug/...` (protegida pelo `requireOrg`) redirecionava de volta para a
722
+ escolha de organização, indefinidamente.
723
+
724
+ O leitor agora **verifica a assinatura** com a `appKey` antes de aceitar o valor:
725
+ HMAC-SHA256 sobre o payload, `purpose` = nome do cookie, comparação em tempo
726
+ constante. Sem a chave o valor assinado é recusado — este cookie decide a claim de
727
+ organização, e aceitar um valor não verificado deixaria o usuário trocar de tenant
728
+ forjando o cookie. As formas crua e URL-encoded continuam aceitas para hosts que
729
+ não assinam.
730
+
731
+ ## 0.68.2
732
+
733
+ ### Patch Changes
734
+
735
+ - [#211](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/211) [`035f277`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/035f27788dc5b759d227c880bc4260a473162303) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - fix(authkit-server): formulários de organizações sem token CSRF
736
+
737
+ `account/orgs.edge` era a **única** view da lib que renderizava formulários POST
738
+ sem `<input name="_csrf">` (ativar, desativar, sair, aceitar convite e criar org),
739
+ e o `accountOrgsController.index` não passava `csrfToken` no contexto de render —
740
+ ao contrário de todos os outros controllers, que passam
741
+ `csrfToken: ctx.request.csrfToken`.
742
+
743
+ Com CSRF ligado (o padrão de um app Adonis com `@adonisjs/shield`), **todo POST de
744
+ organização falhava com "Invalid or expired CSRF token"**: ativar ou trocar de
745
+ organização nunca funcionou. O usuário ficava preso no seletor de organização e,
746
+ como o token de sessão nunca ganhava a claim de org, **nenhuma rota de tenant
747
+ (`/o/:orgSlug/...`, protegida pelo `requireOrg`) abria** — mesmo o fluxo de código
748
+ já emitindo as claims de org corretamente.
749
+
750
+ O teste de `_csrf` em `edge_views` tinha a lista de views escrita à mão, e
751
+ `account/orgs.edge` não estava nela, então a falha passou despercebida. Ele agora
752
+ **varre o diretório de views** e exige `_csrf` em qualquer view que tenha form
753
+ POST, para que uma view nova não escape do mesmo jeito.
754
+
755
+ ## 0.68.1
756
+
757
+ ### Patch Changes
758
+
759
+ - [#209](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/209) [`73f6723`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/73f672323431e56b52b0765dccb23a9b632bd1cf) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - fix(authkit-server): claims de organização no fluxo authorization code
760
+
761
+ As claims `org_id`/`org_slug`/`org_role` nunca eram emitidas no fluxo `response_type=code`: a org ativa tem uma única fonte — o cookie do browser — e o `id_token` desse fluxo é emitido no `/oidc/token`, uma requisição **server-a-servidor** do app, sem os cookies do usuário. O `findAccount` lia apenas o cookie, `activeOrg` era `null` e as três claims (opcionais) sumiam em silêncio — o `requireOrg` do `authkit-client` então recusava toda rota de tenant.
762
+
763
+ A correção persiste a org no **Grant** durante o consent (aí a request é do browser, com o cookie presente) e a lê no mint via `ctx.oidc.entities.Grant.activeOrg`, com o cookie como fallback para fluxos em que o `id_token` sai no próprio authorize. Como o modelo `Grant` do oidc-provider filtra o payload por `IN_PAYLOAD`, a lib estende `IN_PAYLOAD` com `activeOrg` e injeta a subclasse na instância do provider (o oidc-provider v9 não expõe a opção `models`). Grants reaproveitados (consent já lembrado) são reconciliados com o cookie no authorize, para que uma troca de org não fique presa na org antiga até o grant expirar. O leitor do cookie via jar Koa também passou a URL-decodificar o valor, que o `cookies` devolve como está no header.
764
+
765
+ ## 0.68.0
766
+
767
+ ### Minor Changes
768
+
769
+ - [#206](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/206) [`84c62dc`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/84c62dc69e40a14e740e59539022e4e0891cfdc9) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Organizations passa a existir por padrão: a `OrganizationsCapability` não some mais em silêncio quando o host esquece `organizationModels`.
770
+
771
+ O `lucidAccountStore(Model, options)` só anexava a capability quando `options.organizationModels` era `true` ou o trio explícito:
772
+
773
+ ```ts
774
+ ...(OrgModels ? buildOrganizations({...}) : {})
775
+ ```
776
+
777
+ Consequência: um host que já tinha dito `organizations: { enabled: true }` — e cujas rotas `/account/orgs*` **já eram montadas por default** (`register_auth_host`, bloco `if (mountOrgs)`, controller capability-probed) — recebia a tela respondendo 403 em vez de funcionar, sem nenhum erro de boot. O `authkit:doctor` avisava, mas só para quem o rodasse.
778
+
779
+ Agora `organizationModels` é **opcional**:
780
+
781
+ - **Ausente (novo default)** → usa os models default da lib (`defaultOrganizationModels`, as três tabelas lib-owned criadas pelo `ensureAuthkitSchema`). A capability existe; a mesma tela que já estava montada passa a funcionar.
782
+ - `true` → idêntico ao default. É o atalho explícito que apps existentes já usam e continua valendo.
783
+ - `{ OrgModel, MemberModel, InvitationModel }` → escape hatch inalterado, para quem guarda as tabelas de auth numa conexão/schema próprios.
784
+ - **`false` (novo opt-out explícito)** → capability ausente. Preserva o comportamento antigo para quem hoje depende de `supportsOrganizations === false`.
785
+
786
+ **Como migrar:** se você dependia da ausência da capability sem declarar nada, passe `organizationModels: false`. Se quer conexão/schema próprios, continue passando o trio. Caso contrário, nada a fazer — a feature que você já habilitava agora funciona.
787
+
788
+ O `authkit:doctor` (`checkOrganizations`) acompanha: o `warn` de "enabled=true sem capability" passa a ser inalcançável no caminho default e menciona o `false`; a mensagem de `ok` diz de onde vêm os models (default da lib ou trio explícito). O `lucidStores` também aceita `organizations: false` para o mesmo opt-out.
789
+
790
+ Cobertura em `tests/organizations/organizations_store.spec.ts`: `lucidAccountStore(Model)` sem options liga a capability e roda CRUD de ponta a ponta; `organizationModels: false` desliga mesmo com as tabelas presentes; `true` e o trio explícito seguem iguais; e o `lucidStores` cobre default + opt-out. O teste "sem tabelas org → supportsOrganizations false" foi removido porque a premissa dele era exatamente o contrato que deixou de valer.
791
+
792
+ ## 0.67.0
793
+
794
+ ### Minor Changes
795
+
796
+ - [#200](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/200) [`f250d6e`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/f250d6e73aff029854d7815dbb5d2c4be1f484d2) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Garante a coluna `login_methods` na tabela da **conta** em vez de assumir `users`, e faz o boot avisar quando não deu para garantir.
797
+
798
+ O `ensureAuthkitSchema` adicionava a coluna numa tabela de nome fixo `users`. Como o nome da tabela da conta é decisão do host, isso errava nos dois sentidos: com a conta em `auth_users` (o nome que o próprio scaffold da lib usa) e uma tabela `users` qualquer no banco — o starter do AdonisJS cria uma — o `ALTER` acertava a tabela errada e **passava**; sem nenhuma `users`, nada acontecia. Nos dois casos a `LoginMethodsPreferenceCapability` ficava sem a coluna, sem erro e sem aviso.
799
+
800
+ Agora o `lucidAccountStore` expõe `accountTable` (de `Model.table` e, se o model ainda não bootou, da naming strategy — a mesma função que o Lucid usa), o provider repassa em `EnsureSchemaOptions.accountTable`, e o `ensure` usa esse nome. Store próprio que não exponha o metadado continua no `users` de antes, então nada muda para quem já estava certo.
801
+
802
+ O `EnsureSchemaReport` passa a trazer `loginMethods: { table, ensured }`: quando a tabela da conta não existe, o provider loga um warning — antes o sintoma só aparecia longe da causa, como login/callback OIDC quebrado por "column ...login_methods does not exist".
803
+
804
+ Cobertura em `tests/schema/ensure_schema.spec.ts`: o caso da tabela homônima (a coluna vai para `auth_users` e **não** para `users`), o back-compat sem `accountTable`, o `ensured: false` e a derivação do nome pelo store. Reverter só o `ensure.ts` derruba três desses casos.
805
+
806
+ E o `catch` do bloco não mente mais: o re-probe passou a ser da **coluna**, não da tabela. Antes, um `ALTER` que falhasse (permissão, lock, DDL) virava `ensured: true` — o report dizia sucesso com a coluna ausente e o boot não avisava nada, que é exatamente o silêncio que este bloco existe para acabar. Agora só o caso de corrida (a coluna já existe porque outra instância a criou) é engolido; qualquer outra falha propaga para o provider, que já sabe degradar logando warning. Dois testes cobrem os dois lados.
807
+
808
+ - [#200](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/200) [`f250d6e`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/f250d6e73aff029854d7815dbb5d2c4be1f484d2) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `organizationModels: true` passa a usar models default das tabelas de organizations — sem o host precisar escrever model nenhum.
809
+
810
+ As três tabelas (`auth_organizations`, `auth_organization_members`, `auth_organization_invitations`) são **lib-owned**: quem as cria e evolui é o `ensureAuthkitSchema`. Ainda assim, para ligar a `OrganizationsCapability` cada host tinha que transcrever à mão os models que espelham essas colunas. Isso rendia duas coisas ruins: boilerplate sem nenhuma decisão do host, e drift silencioso — uma coluna nova chega pelo `autoManage` e o model escrito à mão no host não sabe dela, sem erro nenhum. Foi o que aconteceu no próprio fixture de teste deste pacote: ele criava `auth_organization_members` sem `updated_at`, que existe na tabela real.
811
+
812
+ Agora `lucidAccountStore(Model, { organizationModels: true })` usa os models default da lib (exportados como `defaultOrganizationModels`, junto de `AuthOrganization`, `AuthOrganizationMember` e `AuthOrganizationInvitation`). O caminho explícito `{ OrgModel, MemberModel, InvitationModel }` continua valendo como escape hatch — é para quem guarda as tabelas de auth numa conexão/schema próprios, que os defaults não declaram (`static connection`).
813
+
814
+ A mensagem do `authkit:doctor` para o caso "organizations.enabled: true sem capability" passa a apontar o `organizationModels: true` primeiro.
815
+
816
+ Cobertura em `tests/organizations/organizations_store.spec.ts`: `organizationModels: true` liga a capability e roda `createOrg`/`findOrgBySlug`/`getOrgMembership`/`listOrgsForAccount` de ponta a ponta contra as tabelas reais; o escape hatch explícito continua funcionando; e ficou registrado o comportamento de desenho — sem as tabelas, a capability está ligada e o erro é alto no uso (barulhento de propósito: o silêncio era o problema).
817
+
818
+ O mesmo vale no caminho do `lucidStores`, que tem tipo próprio: `organizations: true` também é aceito lá — sem isso o atalho não chegava no wiring "declarado uma vez", que é o recomendado em app maior.
819
+
820
+ A documentação foi atualizada junto (`organizations.mdx`, `account-store.mdx`): o `true` aparece como caminho recomendado, o objeto explícito fica como escape hatch, e a linha do `enabled` deixou de dizer que ele "detecta tabelas" — ele é sinal de intenção para o `authkit:doctor`, e quem monta as rotas é a capability.
821
+
822
+ ### Patch Changes
823
+
824
+ - [#200](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/200) [`f250d6e`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/f250d6e73aff029854d7815dbb5d2c4be1f484d2) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Conserta o `node ace configure`, que quebrava em **todos** os presets, e o `config/authkit.ts` ejetado pelo preset React, que vinha com a API de identidade antiga.
825
+
826
+ O `configure` gerava os arquivos com `codemods.makeUsingStub`, e o gerador de stubs compila o `.stub` como template delimitado por crase. Qualquer crase **dentro** do stub fecha o template mais cedo e o texto seguinte passa a ser parseado como JavaScript — daí os `SyntaxError` que apareciam com o nome de um identificador qualquer do comentário (`Unexpected identifier 'views'` no stub do React, `Unexpected identifier 'id'` no do model, `Invalid or unexpected token` no da migration). Só `stubs/config/authkit.stub` compilava, e é justamente o único sem crase: era ele que fazia o `--ui=edge` avançar até o stub da migration antes de estourar.
827
+
828
+ Isso derrubava o caminho documentado de instalação (`pnpm add` → `node ace configure --ui=...`) para todo consumidor, que passava a ter que reconstruir os arquivos ejetados à mão a partir de `stubs/`.
829
+
830
+ As crases saíram dos comentários dos três stubs afetados. Junto, o `stubs/config/authkit_react.stub` deixou de declarar `findAccount`/`verifyCredentials`: essas chaves não existem no `AuthServerConfigInput` (que aceita `accountStore`) e a lib as deriva do store — o preset React ejetava um config sem `accountStore`, ou seja, sem verificação de credenciais e sem o resto do store (MFA, capabilities). Agora usa o mesmo `accountStore: lucidAccountStore(AuthUser)` do stub sem preset.
831
+
832
+ A lacuna que deixou isso passar foi de teste: nenhum spec chamava o `makeUsingStub`. O e2e da migration lê o stub direto e remove o frontmatter `{{{ ... }}}`, então o stub nunca era compilado como template. Entra `tests/stubs_compile.spec.ts`, que compila todos os `.stub` do build pelo mesmo mecanismo e falha listando os que quebram — com um caso separado para a crase, que é a causa raiz.
833
+
834
+ ## 0.66.3
835
+
836
+ ### Patch Changes
837
+
838
+ - fix(deps): update dependency @simplewebauthn/server to v14.0.2 ([#197](https://github.com/DavideCarvalho/adonis-agora-authkit/issues/197))
839
+
840
+ ## 0.66.2
841
+
842
+ ### Patch Changes
843
+
844
+ - d787b52: Restore the file-level docblock in `host/augmentations.ts` to the top of the file, above the imports it documents
845
+
846
+ ## 0.66.1
847
+
848
+ ### Patch Changes
849
+
850
+ - 8c5f617: Migra o CSS das views do host (login/consent/signup/account/*) do Tailwind 3 para o Tailwind 4 e regenera o `partials/styles.edge` commitado.
851
+
852
+ O `tailwindcss` estava travado em `3.4.19` neste pacote enquanto todo o resto do repo já tinha subido: o v4 tirou o binário `tailwindcss` do pacote principal (agora é `@tailwindcss/cli`) e derrubou a flag `-c <config>` que o `scripts/build_host_css.mjs` usava para apontar um config JS gerado em tempo de build. A configuração agora é CSS-first, num arquivo commitado (`src/host/host.css`): `@import "tailwindcss" source(none)` + `@source "./views/**/*.edge"` é o equivalente exato do `content` do config v3. O `source(none)` é o que impede a detecção automática de varrer o pacote inteiro (e arrastar para o CSS server-rendered os utilitários do SPA React em `ui/`).
853
+
854
+ Duas classes renomeadas no v4 foram traduzidas nas views, porque no v4 elas mudaram de significado em vez de sumir — trocariam o visual em silêncio:
855
+
856
+ - `shadow-sm` → `shadow-xs` (16 ocorrências). No v4 `shadow-sm` passou a valer o que o v3 chamava de `shadow`, ou seja, os cards de `account/*` e `otp-unlock` ganhariam uma sombra maior.
857
+ - `outline-none` → `outline-hidden` (20 ocorrências). No v4 `outline-none` virou `outline-style: none`; `outline-hidden` é que mantém o outline transparente do v3, inclusive o fallback de `forced-colors`.
858
+
859
+ O Preflight do v4 também deixou de aplicar `cursor: pointer` em `button`/`[role=button]`. Como todas as telas do host são formulários, isso é restaurado numa `@layer base` do `host.css` em vez de classe por classe nas views.
860
+
861
+ Renderizando as 18 views que incluem o partial lado a lado com a versão anterior, a geometria é idêntica (236 elementos, zero diferença de posição/tamanho). O que muda de fato é o que o v4 muda por padrão: a paleta passa a ser declarada em `oklch` (cinzas com desvio ≤ 3/255 em sRGB; acentos saturados como `red-600` e `green-700` ficam mais vivos), a stack de fonte sans default do v4.3 não é mais a do v3, `::placeholder` passa a ser `currentColor` a 50% em vez de `gray-400` fixo, `hover:*` agora só vale sob `@media (hover: hover)`, e `input`/`select`/`textarea` ganham fundo transparente no Preflight (sem efeito aqui: todo input das views está sobre um card `bg-white`).
862
+
863
+ O `@source` também passou a excluir o próprio `partials/styles.edge`. Ele mora dentro de `views/`, então o extrator vinha lendo os seletores do CSS gerado como se fossem classes em uso e o build se auto-alimentava: ~50 utilitários que nenhuma view referencia estavam presos no arquivo commitado se perpetuando a cada regeneração.
864
+
865
+ ## 0.66.0
866
+
867
+ ### Minor Changes
868
+
869
+ - 9231887: **BREAKING (0.x minor):** todas as listagens paginadas do authkit passam a usar `{ page, size }` no lugar de `{ page, limit }` (e de `perPage`, na API do console), alinhando a interface de paginação com `@adonis-agora/filter` e com o restante do ecossistema `@adonis-agora/*`. `page` continua **1-based** com default `1`; `limit` foi apenas **renomeado** para `size` — a semântica (itens por página) não mudou. O casamento com `@adonis-agora/filter` é **estrutural** de propósito: nenhum pacote passa a depender dele.
870
+
871
+ Na **resposta**, as mesmas listagens passam a devolver o envelope `{ meta, data }` — com `page`, `size` e `total` dentro de `meta` — no lugar do antigo shape achatado `{ data, total, page, size }`. `meta` é a convenção do próprio `.paginate()` do Lucid (`{ meta, data }`), que é onde o caminho por offset de `@adonis-agora/filter` desemboca, e agora é o envelope de TODAS as listagens paginadas do ecossistema `@adonis-agora/*`.
872
+
873
+ Junto vem um **teto de `size`** que antes não existia de forma unificada: toda borda HTTP (Admin REST API e API JSON do console) agora corta `size` em `ADMIN_LIST_MAX_SIZE` (200), e as varreduras internas usam `ADMIN_LIST_DEFAULT_SIZE` (100) em vez de um `100` hardcoded por serviço.
874
+
875
+ ### O que mudou
876
+
877
+ - **Wire HTTP** — `GET {adminApi}/users`, `GET {adminApi}/audit`, `GET {consolePrefix}/api/users` e `GET {consolePrefix}/api/audit` leem `?page=&size=` (antes `?page=&limit=`; o de usuários do console lia `?perPage=`) e respondem `{ meta: { page, size, total }, data }` (antes `{ data, total, page, size }` achatado). Endpoints de listagem NÃO paginados (`/clients`, `/settings`, `/organizations`, `/roles`, `/sessions`) continuam como estão — eles nunca tiveram metadados de paginação e não ganham envelope agora.
878
+ - **Tipos do servidor** — `ListAccountsParams.limit` → `.size` e `ListAuditParams.limit` → `.size`. Stores de conta e sinks de auditoria customizados precisam ler o novo campo.
879
+ - **SDK** (`@adonis-agora/authkit-sdk`) — `ListUsersParams` e `ListAuditParams` passam a usar `size`; `ListUsersResult` e `ListAuditResult` viram `{ meta: ListMeta; data: T[] }` (novo tipo `ListMeta` exportado), nos dois drivers (remoto e embedded).
880
+ - **React** (`@adonis-agora/authkit-react`) — `client.admin.users.list()`, `client.admin.audit.list()`, `useUsersQueryOptions()`, `useAuditQueryOptions()` e as query keys `authkitKeys.admin.users/audit` passam a usar `size`; `AdminUserListResult` e `AuditListResult` viram `{ meta: ListMeta; data: T[] }` (novo tipo `ListMeta` exportado). Efeito colateral: o console admin enviava `limit=` para um endpoint que lia `perPage`, então o tamanho de página pedido pela UI era silenciosamente ignorado — agora vale.
881
+ - **Novas constantes exportadas** — `LIST_FIRST_PAGE`, `ADMIN_LIST_DEFAULT_SIZE` (100), `ADMIN_LIST_HTTP_DEFAULT_SIZE` (20), `ADMIN_LIST_MAX_SIZE` (200), mais os helpers `resolveListPage`, `normalizeListSize`, `clampListSize`, `parseListPage` e `parseListSize` (o SDK reexporta as três primeiras constantes com os mesmos nomes).
882
+ - **Novos tipos exportados** — `ListMeta` (`{ page, size, total }`) e `PaginatedResponse<T>` (`{ meta, data }`) em `@adonis-agora/authkit-server`; `ListMeta` também em `@adonis-agora/authkit-sdk` e `@adonis-agora/authkit-react`.
883
+ - **Inalterado** — o contrato INTERNO dos stores continua `Paginated<T> = { data, total }`: `AccountStore.listAccounts()` e `AuditSink.list()` NÃO ganham envelope `meta` (quem monta o `meta` é a borda HTTP/SDK). Implementações customizadas de store/sink só precisam da renomeação `limit` → `size`.
884
+
885
+ ### Migração
886
+
887
+ ```diff
888
+ // HTTP
889
+ - GET /api/authkit/v1/users?page=2&limit=50
890
+ + GET /api/authkit/v1/users?page=2&size=50
891
+
892
+ // HTTP — resposta
893
+ - { "data": [...], "total": 137, "page": 2, "size": 50 }
894
+ + { "meta": { "page": 2, "size": 50, "total": 137 }, "data": [...] }
895
+
896
+ // SDK
897
+ - const { data, total, page, limit } = await authkit.users.list({ page: 2, limit: 50 })
898
+ + const { data, meta } = await authkit.users.list({ page: 2, size: 50 })
899
+ + // meta.page / meta.size / meta.total
900
+
901
+ // React
902
+ - useUsersQueryOptions({ search, page, limit: 20 })
903
+ + useUsersQueryOptions({ search, page, size: 20 })
904
+ - const total = data?.total ?? 0
905
+ + const total = data?.meta.total ?? 0
906
+
907
+ // AccountStore / AuditSink customizados
908
+ - async listAccounts({ search, page = 1, limit = 20 }: ListAccountsParams) {
909
+ - return { data: rows.slice((page - 1) * limit, page * limit), total }
910
+ + async listAccounts({ search, page = 1, size = 20 }: ListAccountsParams) {
911
+ + return { data: rows.slice((page - 1) * size, page * size), total }
912
+ }
913
+ ```
914
+
915
+ Callers que pediam `limit > 200` via HTTP passam a receber 200 itens por página — pagine com `page` para cobrir o resto.
916
+
917
+ ### Patch Changes
918
+
919
+ - bc32ea4: Bump runtime dependencies to their latest releases.
920
+
921
+ - `@simplewebauthn/server` and `@simplewebauthn/browser` go to `14.x`. The committed
922
+ `src/host/assets/webauthn.js` bundle was regenerated from the new `@simplewebauthn/browser`
923
+ (the `check:webauthn-bundle` guard would have caught the drift otherwise). The `authkit-react`
924
+ peer was **widened** to `^13.3.0 || ^14.0.0` instead of moved, so apps still on 13 keep
925
+ installing cleanly.
926
+ - `oidc-provider` 9.12.0 → 9.12.2 and `jose` 6.2.10 → 6.2.12 (patch releases on the token/JWT path).
927
+
928
+ No API change on our side: typecheck, tests, packaging smoke and the WebAuthn bundle check all pass.
929
+
930
+ ## 0.65.3
931
+
932
+ ### Patch Changes
933
+
934
+ - dcb3408: O `AuthUser` publicado por `node ace configure @adonis-agora/authkit-server` (`models/auth_user.stub`) — e o modelo mínimo mostrado em getting-started/quickstart/account-store — produzia contas quebradas. Dois problemas compunham: (a) nem `withAuthUser()` nem `withCredentials()` geram o `id`, então sem um hook `@beforeCreate` o Lucid insere `NULL` na coluna string `id` e a conta volta com o rowid interno do banco em vez de um id real, tornando-a inalcançável na request seguinte; (b) a tela de signup embutida sempre coleta um campo "Nome" que o Lucid store passa direto para `AuthUser.create({ fullName, ... })`, e sem essa coluna o primeiro signup quebra.
935
+
936
+ O stub agora inclui `@beforeCreate() assignUuid` (gera um `randomUUID()`) e a coluna `fullName: string | null`. Os docs (`starter.mdx`, `account-store.mdx`) foram atualizados para mostrar o mesmo modelo completo, com uma nota explicando por que cada peça é necessária.
937
+ - 837027f: Completa o fix do `AuthUser` scaffoldado (ver changeset anterior, "auth-user-stub-id-fullname"): mesmo com o stub do model corrigido, um host seguindo o getting-started ao pé da letra ainda batia em `SqliteError: no such table: auth_users` (ou o equivalente Postgres) no primeiro signup/login, porque `node ace configure` nunca publicava uma migration para essa tabela — só o model.
938
+
939
+ - `node ace configure @adonis-agora/authkit-server` agora também scaffolda `database/migrations/<timestamp>_create_auth_users_table.ts`, com exatamente as colunas que `models/auth_user.stub` e os mixins `withAuthUser`/`withCredentials` esperam (`id` string não auto-increment, `email` único, `password`, `global_roles`, as quatro colunas de `withCredentials`, `full_name`).
940
+ - Corrigido um segundo bug, descoberto pelo novo teste e2e desta mudança: o stub do model tinha o hook `@beforeCreate` que atribui o `randomUUID()`, mas faltava `static selfAssignPrimaryKey = true` — sem essa flag o Lucid sobrescrevia silenciosamente o id atribuído pelo hook com o retorno bruto do INSERT (o rowid interno do SQLite) assim que a linha era salva, reproduzindo exatamente a mesma falha ("conta inalcançável pelo id real na request seguinte") que o hook deveria ter resolvido.
941
+ - `docs/starter.mdx` e `docs/account-store.mdx` agora mostram a migration completa lado a lado com o model, e o texto do model foi atualizado com a flag `selfAssignPrimaryKey`.
942
+ - Novos testes: `tests/configure.spec.ts` passa a asserir o conteúdo da migration scaffoldada (e o `selfAssignPrimaryKey` no model), e `tests/e2e/scaffolded_auth_users_migration.spec.ts` roda a migration real contra um SQLite em memória e exercita signup → login, reset de senha e verificação de e-mail de ponta a ponta pelo `lucidAccountStore`.
943
+ - dcb3408: As telas built-in de login/consent/signup crashavam com `TypeError: Cannot read properties of undefined (reading 'clients')` quando o host não declarava `branding` no `defineConfig` — a config MÍNIMA que getting-started, quickstart e reference documentam. `branding` é tipado opcional, mas o interaction controller (e o registration controller) liam `cfg.branding!.clients`/`cfg.branding!.default` incondicionalmente, sem nenhum default aplicado na resolução do config.
944
+
945
+ Adicionado `resolveBranding` (`src/host/branding.ts`), seguindo o mesmo padrão já usado para as demais seções opcionais do config (`resolveRateLimit`, `resolveLockout`, `resolveAdmin`, etc.): quando o host não declara `branding`, `defineConfig` agora resolve um `BrandingConfig` default neutro (`clients: {}`, `firstParty: []`, um `default` de tema genérico) em vez de deixar o campo `undefined`. `ResolvedServerConfig.branding` deixou de ser opcional — está sempre presente após a resolução.
946
+ - dcb3408: Hosts montando `registerAuthHost` atrás do `@adonisjs/shield` (CSRF ligado por default no `web` starter kit) tomavam a negação HTML de CSRF em `POST {mountPath}/token` em vez da resposta JSON — `exchangeCode()` no client quebrava com `SyntaxError: Unexpected token '<' ... is not valid JSON`. O helper exportado `authkitCsrfExceptions` (que resolve exatamente essa isenção, acompanhando o `mountPath` real) já existia, mas não era mencionado em nenhum doc de getting-started/quickstart, nem citado pelo nome no aviso do `authkit:doctor` (`checkShield`) — só um lembrete genérico de "coloque as rotas do IdP nas exceções de CSRF".
947
+
948
+ Adicionado um callout proeminente com um snippet copy-paste em getting-started.mdx (e starter.mdx) logo onde `registerAuthHost` é introduzido, e a mensagem de `checkShield` agora referencia `authkitCsrfExceptions` pelo nome. Adicionado um teste (`tests/host/csrf.spec.ts`) que registra as rotas reais via `registerAuthHost` e verifica que `authkitCsrfExceptions` cobre exatamente o `mountPath` do provider (e nenhuma rota interativa de `/auth/interaction/*`), para que o helper não saia de sincronia com o que a lib efetivamente monta.
949
+
950
+ ## 0.65.2
951
+
952
+ ### Patch Changes
953
+
954
+ - 527e3c0: Widen the optional `@adonisjs/redis` peer to include `^11.0.0` — apps upgrading to redis 11 no
955
+ longer hit a peer conflict. No code change: the surface authkit-server touches is unchanged across
956
+ the major.
957
+
958
+ ## 0.65.1
959
+
960
+ ### Patch Changes
961
+
962
+ - 8ff1e99: Ações destrutivas do console admin (delete user, disable, reset-password, revoke-sessions, rotate da chave de assinatura managed) não exigiam sudo/reautenticação recente — só a sessão de admin já autenticada. Um admin com a sessão sequestrada (XSS, cookie roubado, aba esquecida logada) podia executá-las direto, sem reconfirmar a própria identidade.
963
+
964
+ `ConsoleUsersController` (`disable`, `resetPassword`, `destroy`), `ConsoleSessionsController` (`revokeAll`, `userRevokeSessions`) e `ConsoleKeysController` (`rotate`) agora exigem sudo recente via `requireSudo` — a mesma infraestrutura (`/account/confirm`) já usada pelo self-service da própria conta. Sem sudo confirmado, a API JSON responde `403 sudo_required` em vez de seguir a ação (mesmo padrão do `account/api` self-service). `enable()` (reverter um disable) continua sem gate — reabilitar uma conta não é destrutivo.
965
+
966
+ A REST Admin API (Bearer, server-to-server) fica de fora deste fix: sudo é um conceito de sessão de browser (confirmação de identidade recente do usuário), sem equivalente natural para uma API key — inventar um mecanismo novo ali é decisão em aberto, não wiring da infra existente.
967
+ - 8ff1e99: `login.edge`, `mfa-challenge.edge`, `account/confirm.edge`, `account/mfa.edge` e `partials/submit_lock.edge` embutiam `<script>`/`<script type="module">` **inline** (autofill/botão de passkey, verificação WebAuthn do `/account/confirm`, anti-duplo-submit). Hosts com CSP restritivo (`script-src 'self'`, sem `'unsafe-inline'`, sem nonce/hash — a postura recomendada num IdP) bloqueiam esses scripts silenciosamente: os botões de passkey não fazem nada e a trava anti-duplo-submit não liga, sem erro visível.
968
+
969
+ Os cinco scripts agora são servidos como assets same-origin em `/authkit/assets/{passkey_autofill,passkey_button,passkey_register,webauthn_confirm,submit_lock}.js` (permitidos por `'self'`, sem depender de nonce/hash), seguindo o padrão já usado pelo splash de logout (`/authkit/assets/logout.js`, `0.61.3`) e pelo bundle WebAuthn (`/authkit/assets/webauthn.js`). Valores por-request (URLs de options/verify, csrf) passam a ir em atributos `data-*` HTML-escapados pelo Edge, em vez de interpolados dentro do `<script>`. `registerAuthHost` registra as novas rotas automaticamente — hosts que atualizarem a lib ganham os assets sem mudança de código.
970
+
971
+ ## 0.65.0
972
+
973
+ ### Minor Changes
974
+
975
+ - 66657e1: feat(adapters): `adapters.redis()` without `connection` uses the app default
976
+
977
+ `RedisAdapterConfig.connection` is now optional. Omitted, the resolver uses
978
+ the app's DEFAULT redis connection (`manager.connection()`, i.e.
979
+ `config/redis.ts#connection`) — the same one the app session uses. This is
980
+ the recommended form for `session: { adapter: adapters.redis() }`: both
981
+ session layers land on the same connection by construction, with no
982
+ duplicated string to drift apart. Explicit `connection` still works for a
983
+ dedicated Redis.
984
+
985
+ ## 0.64.0
986
+
987
+ ### Minor Changes
988
+
989
+ - dd28d23: feat(config): `session.adapter` splits OIDC storage by model lifetime
990
+
991
+ New opt-in `session: { adapter }` in `defineConfig`: session-scoped models
992
+ (`Session`, `Grant`, `AccessToken`, `RefreshToken`, `AuthorizationCode`,
993
+ `Interaction`, `DeviceCode`, PAR/CIBA — see `SESSION_SCOPED_MODELS`) use the
994
+ session adapter (e.g. Redis), everything else (`Client` in particular) stays
995
+ on the default `adapter` (e.g. database). Without `session`, both resolve to
996
+ the same class — zero behavior change.
997
+
998
+ Recommended split: sessions in Redis (ephemeral, native TTL, same fate as the
999
+ app session) and the rest in the database (losing `Client` is a full auth
1000
+ outage). `AdminSessionsService`, the account-API session revoke and the
1001
+ provider dispatcher all follow the same `pickModelAdapterClass` rule, so the
1002
+ console reads sessions from the right backend.
1003
+
1004
+ ## 0.63.0
1005
+
1006
+ ### Minor Changes
1007
+
1008
+ - 793746b: feat(impersonation): typed start errors with IdP error code (no secret leak)
1009
+
1010
+ `startImpersonation` failures are now `ImpersonationStartError` with a
1011
+ machine-readable `code` (`already_active`, `invalid_max_age`,
1012
+ `no_admin_access_token`, `no_account_session`, `no_refresh_token`,
1013
+ `refresh_rejected`, `exchange_rejected`), plus the token-endpoint `status`
1014
+ and the IdP `error` code (`invalid_grant`, …) — allowlist codes only, never
1015
+ tokens or `error_description`. The 4xx-without-refresh-token case (expired
1016
+ admin access token, nothing to renew with) now throws `no_refresh_token`
1017
+ with the original exchange error as `cause`, instead of rethrowing a bare
1018
+ `Token exchange failed: 400` identical to a hard IdP refusal. Message
1019
+ prefixes are preserved (`Token exchange failed: <status>`), so existing
1020
+ `rejects(..., /Token exchange failed: 400/)` assertions keep passing.
1021
+ Also exports `ImpersonationStartError`, `ImpersonationStartErrorCode`,
1022
+ `StopImpersonationOptions` and `TokenExchangeResult` from the package root.
1023
+
1024
+ ## 0.62.0
1025
+
1026
+ ### Minor Changes
1027
+
1028
+ - 3477cad: feat(impersonation): registry per impersonation (id, timings, exchange proof, stop audit)
1029
+
1030
+ `startImpersonation` now records each impersonation: a unique `impersonationId`,
1031
+ `startedAt`, optional `expiresAt` (only with the new opt-in `maxAge`, in seconds —
1032
+ no expiry by default, preserving the historic stay-until-stopped behavior), plus the
1033
+ non-secret metadata from the token-exchange response (`actSub`, `exchangeExpiresIn`).
1034
+ The exchanged access token itself is still deliberately discarded (bearer credential
1035
+ of the target with no consumer in the flow). `startImpersonation` returns the created
1036
+ `ImpersonationState`, and `impersonationState()` exposes the new fields, reading
1037
+ `active: false` past the deadline (lazy expiry — stopping stays explicit).
1038
+
1039
+ `stopImpersonation` now clears all impersonation session keys, accepts an optional
1040
+ `{ audit, ip }`, emits `impersonation.stopped` (closing the pair opened by
1041
+ `impersonation`), and returns the stopped state (or `null` when no-op).
1042
+
1043
+ ## 0.61.4
1044
+
1045
+ ### Patch Changes
1046
+
1047
+ - 0fe0b65: Accept `@adonisjs/redis` 10 as a peer (`^9.2 || ^10`), alongside `@adonisjs/session` 8.1 and
1048
+ `@adonisjs/lock` 2.1 which already require it. TOTP now goes through `otplib` 13 and JOSE through
1049
+ `jose` 6 internally — no change to the public API.
1050
+
1051
+ ## 0.61.3
1052
+
1053
+ ### Patch Changes
1054
+
1055
+ - d595dd4: O splash de logout (RP-initiated logout) auto-submetia o form de confirmação com um `<script>` **inline**. Hosts com CSP restritivo (`script-src 'self'` + nonce/hash, sem `'unsafe-inline'`) bloqueiam script inline sem nonce — o POST `/session/end/confirm` nunca rodava e a tela "Saindo / Encerrando sua sessão…" ficava presa para sempre: a sessão SSO nunca era encerrada.
1056
+
1057
+ Agora o script de auto-confirmação é servido como asset same-origin em `/authkit/assets/logout.js` (permitido por `'self'`, sem depender de nonce/hash), seguindo o padrão já usado pelo bundle WebAuthn em `/authkit/assets/webauthn.js`. O host precisa registrar a nova rota — `registerAuthHost` faz isso automaticamente; apps que atualizarem a lib ganham a rota sem mudança de código.
1058
+
1059
+ ## 0.61.2
1060
+
1061
+ ### Patch Changes
1062
+
1063
+ - bc54b69: Auto-create the `login_methods` column on an existing host `users` table
1064
+
1065
+ The `login_methods` column (used by the `LoginMethodsPreferenceCapability`) is
1066
+ host-owned — it normally enters via a host migration on the `auth` schema.
1067
+ Some host deploys only migrate the domain schema, not `auth`, so that migration
1068
+ can silently never run. The moment the host model started declaring the column,
1069
+ every user read (including the OIDC login/callback) failed with
1070
+ `column users.login_methods does not exist`, turning login into a 500.
1071
+
1072
+ `ensureAuthkitSchema` (run automatically at boot when `schema.autoManage` is
1073
+ on) now also guarantees `login_methods` on `auth.users`: if the host `users`
1074
+ table exists and the column is missing, it adds a nullable `jsonb` column.
1075
+ Idempotent and additive — it never creates the host-owned `users` table, only
1076
+ adds the column the library consumes. Existing deployments self-heal on the
1077
+ next boot/deploy without a manual migration step.
1078
+
1079
+ ## 0.61.1
1080
+
1081
+ ### Patch Changes
1082
+
1083
+ - e27798a: Fix `headless.resolveAccountId` type to accept a `Promise`
1084
+
1085
+ The host resolver is commonly async (e.g. `await ctx.auth.getUserOrFail()` to
1086
+ return the account `user.id`). The signature published in `0.61.0` was sync
1087
+ (`(ctx) => string | null`), which broke the app's typecheck for an `async`
1088
+ resolver. The type now accepts `string | null | Promise<string | null>` — the
1089
+ controller already `await`s the return. No runtime change.
1090
+
1091
+ ## 0.61.0
1092
+
1093
+ ### Minor Changes
1094
+
1095
+ - 7c7a54b: API headless (Clerk-style) para tipos de login
1096
+
1097
+ Nova opção `headless` no `defineConfig` que monta uma JSON API autenticada pela
1098
+ SESSÃO DO APP (não pelo `accountGuard` do console de conta):
1099
+
1100
+ ```ts
1101
+ defineConfig({
1102
+ // ...
1103
+ headless: {
1104
+ baseUrl: "/api/authkit", // default
1105
+ resolveAccountId: async (ctx) => (await ctx.auth.getUserOrFail()).id,
1106
+ },
1107
+ });
1108
+ ```
1109
+
1110
+ O host-kit passa a servir `GET/PUT {baseUrl}/login-methods` — o mesmo contrato da
1111
+ JSON API do console, mas com a conta resolvida pelo `resolveAccountId` fornecido
1112
+ pelo HOST (tipicamente `ctx.auth.getUserOrFail()`). Assim um RP usa AuthKit como
1113
+ frontend (os hooks `useLoginMethodsQueryOptions` /
1114
+ `useUpdateLoginMethodsMutationOptions` basta apontar o `accountBaseUrl` para a
1115
+ base headless) sem expor o console `/account/*`.
1116
+
1117
+ - `GET` devolve `{ supported, methods, available, locked }` (interseção global ×
1118
+ preferência, com os pins de config computados).
1119
+ - `PUT` grava a preferência; mantém o all-off guard (`all_methods_off` → 400) e
1120
+ validação de body.
1121
+
1122
+ O resolver do host retornando `null` (ou lançando, ex.
1123
+ `auth.getUserOrFail()`) → 401. A feature é opt-in (ausente = nada montado;
1124
+ back-compat total). A lógica de estado foi extraída para
1125
+ `host/login_methods_state.ts` (reuso entre console e headless sem duplicar regra).
1126
+
1127
+ ### Patch Changes
1128
+
1129
+ - a6d271c: Ship TanStack Intent AI-agent skills with each package (`skills/` added to `files`), add the `tanstack-intent` keyword, and take a devDependency on `@tanstack/intent` for skill validation.
1130
+
1131
+ ## 0.60.0
1132
+
1133
+ ### Minor Changes
1134
+
1135
+ - cbc19f5: Tipos de login por usuário — preferência self-service no console de conta
1136
+
1137
+ O usuário passa a poder escolher, no console de conta, **quais métodos de login
1138
+ ficam habilitados para a SUA conta** (senha, magic link, passkey, social). É uma
1139
+ camada por usuário sobre os switches globais existentes (`auth_methods` + pins
1140
+ de config): o usuário nunca LIGA um método que o host desligou, só restringe.
1141
+
1142
+ **Storage**: coluna `login_methods` (JSONB) em `auth.users` — o host adota por
1143
+ migração própria; sem a coluna no model, a feature é no-op (mesmo padrão de
1144
+ `hasColumn` das colunas opcionais). `NULL` = sem preferência = herda os globais.
1145
+
1146
+ **Account API**: `GET/PUT /account/api/login-methods`. O GET devolve `{ supported,
1147
+ methods, available, locked }` — `methods` é a preferência crua do usuário,
1148
+ `available` o estado final (global ∩ preferência, com derivações de
1149
+ forgotPassword/passkeyAutofill) e `locked` os métodos fora do controle dele
1150
+ (globalmente off ou fixados em `cfg.authMethods`). O PUT recusa com 400
1151
+ `all_methods_off` quando a preferência zeraria todos os métodos globais.
1152
+
1153
+ **Enforcement no login** (interseção global × preferência):
1154
+
1155
+ - Passo 2+ da tela de login (`show()`): métodos que o usuário desligou não
1156
+ aparecem — inclusive `magicLinkAvailable`, `otpEnabled` e `passkeyFirstAvailable`.
1157
+ - POST de senha: método desligado → erro de credencial inválida ANTES de verificar
1158
+ a senha (sem vazar preferência), auditado como `login.failure`
1159
+ `method_disabled_by_user`.
1160
+ - POST magic link: não emite token; resposta uniforme "link enviado".
1161
+ - Consumo de magic link/OTP: token emitido antes do desligamento também é
1162
+ barrado — a sessão não materializa.
1163
+ - Passkey: assertion válida é recusada quando o usuário desligou explicitamente
1164
+ a passkey (switches globais continuam governando a exibição).
1165
+ - Callback social: identidade ligada + social desligado para a conta → redireciona
1166
+ de volta ao login.
1167
+
1168
+ Fail-safe em todo caminho: store sem a capacidade, conta sem preferência ou erro
1169
+ qualquer → comportamento atual intacto. Fail-safe all-off: preferência que zera
1170
+ tudo é ignorada (nunca trancar o dono fora da própria conta).
1171
+
1172
+ Novos exports: `UserLoginMethods`, `UserLoginMethodKey`,
1173
+ `ResolvedUserLoginMethods`, `resolveEffectiveUserLoginMethods`,
1174
+ `normalizeUserLoginMethods`, `parseUserLoginMethodsPayload`,
1175
+ `USER_LOGIN_METHOD_KEYS`, `supportsLoginMethodsPreference`,
1176
+ `LoginMethodsPreferenceCapability`.
1177
+
1178
+ ## 0.59.0
1179
+
1180
+ ### Minor Changes
1181
+
1182
+ - ed83a01: Adoção brownfield: `sub` string com id INTEGER, check de colunas no doctor, e a página que faltava
1183
+
1184
+ Ao seguir o caminho de quem "já tem uma tabela `users` com gente dentro" — a
1185
+ pergunta mais comum de quem avalia o AuthKit — apareceram três problemas:
1186
+
1187
+ **1. Id inteiro vazava como número no `sub`.** `toAccount` fazia `id: row.id`
1188
+ sem coerção. `AuthAccount.id` é tipado `string` e vira o claim `sub`, que a spec
1189
+ OIDC exige que seja string — mas tabela legada quase sempre tem `id` INTEGER
1190
+ auto-increment, e nesse caso o número ia direto para o token (e para todo
1191
+ call-site tipado como string). Provado contra sqlite real: `findById`,
1192
+ `verifyCredentials` e `findByEmail` devolviam `1`, não `'1'`. Agora coagido nos
1193
+ dois `toAccount`. Efeito colateral bom: adotar o AuthKit não exige migrar para
1194
+ UUID.
1195
+
1196
+ **2. Coluna faltando só quebrava em produção.** O `authkit:doctor` não olhava o
1197
+ model por trás do account store. Faltando `password_reset_token`, o app subia
1198
+ normalmente e a falha aparecia quando o primeiro usuário clicava em "esqueci
1199
+ minha senha". Novo `checkAccountStoreColumns` (roteado no `runAllChecks`) lê
1200
+ `$columnsDefinitions` e reporta as propriedades ausentes agrupadas pelo FLUXO
1201
+ que quebra — citando também o `columnName` real, porque em brownfield a
1202
+ propriedade `password` costuma morar numa coluna `senha`. Colunas opcionais
1203
+ (`fullName`/`avatarUrl`/`disabledAt`/`passwordChangedAt`) são reportadas como
1204
+ capability desligada, nunca erro. Store não-Lucid (sem `__model`) → silencioso.
1205
+ Para isso o `lucidAccountStore` passou a expor `__model`, no mesmo idioma de
1206
+ `__mfaIssuer`/`__webauthn`/`connectionName`.
1207
+
1208
+ **3. O caminho existia mas era invisível.** Nova página **Adopting an Existing
1209
+ User Table**: o mapa exato de propriedades por fluxo, a migration aditiva (sem
1210
+ drop/rename/cópia), mapeamento por `columnName` para schema em outra língua,
1211
+ `legacyVerifier` por origem (Laravel `$2y$` / Rails `$2a$` / Django
1212
+ `pbkdf2_sha256$` / node `$2b$`) com a distinção entre devolver `null` ("não é
1213
+ meu formato") e `false` ("senha errada"), e como o lazy rehash migra a base
1214
+ sozinha conforme as pessoas logam. Inclui o aviso de backfill de
1215
+ `email_verified_at` — sem ele, uma política `requireVerifiedEmail` tranca a base
1216
+ inteira no primeiro login.
1217
+
1218
+ Corrigido também um snippet que quebrava em runtime em duas páginas
1219
+ (`passwords-and-migration`, `organizations`): mostravam
1220
+ `lucidAccountStore({ model: () => import('#models/user'), … })`, assinatura que
1221
+ não existe — a real é `lucidAccountStore(Model, options)`, sem overload de
1222
+ objeto.
1223
+
1224
+ - 813a321: Multi-tenancy: `orgId` de primeira classe no evento, `orgId`/`orgSlug`/`orgRole` na `Identity`, e o middleware `requireOrg`
1225
+
1226
+ Três buracos no caminho de multi-tenancy por organização, achados ao seguir o
1227
+ fluxo inteiro (org criada → claim → contexto → RBAC escopado):
1228
+
1229
+ **1. O barramento de diagnostics não dizia QUAL org.** `redactAuditEventForDiagnostics`
1230
+ dropa `metadata` inteiro por LGPD (é campo livre e carrega PII — o e-mail do
1231
+ convidado, por exemplo). Só que o `orgId` dos eventos de organização vivia
1232
+ justamente ali. Resultado: um assinante de `agora:authkit:organization.created`
1233
+ — o provisioning do `@adonis-agora/authz`, entre outros — recebia o tipo do
1234
+ evento e nada mais, o que torna o hook inútil na prática. Além disso o
1235
+ `organization.created` self-service auditava só `{ slug }`, sem id nenhum.
1236
+
1237
+ `orgId` agora é campo de primeira classe do `AuditEvent`, preenchido nos 15
1238
+ call-sites de eventos de organização (`organization.deactivated` não tem org, é
1239
+ a limpeza), preservado na projeção de diagnostics junto dos demais ids opacos
1240
+ (`accountId`/`actorId`/`clientId`) e incluído no corpo do webhook. A redação de
1241
+ PII continua intacta: `email`, `ip` e `metadata` seguem fora do barramento.
1242
+
1243
+ **2. A `Identity` não expunha a org.** O tenant ativo só existia em
1244
+ `identity.raw.org_id`, sem tipo, e cada app reescrevia a derivação. Agora
1245
+ `identity.orgId`, `identity.orgSlug` e `identity.orgRole` são campos da
1246
+ `Identity`, preenchidos por `buildIdentityFromClaims` (portanto nos três
1247
+ resolvers: jwt, pat e opaque). A tabela de claims aceitas — que já existia
1248
+ duplicada dentro da ponte de contexto — virou `ORG_ID_CLAIMS` +
1249
+ `deriveOrgId`/`deriveOrgSlug`/`deriveOrgRole` no `authkit-core`, e a ponte
1250
+ passou a consumi-la. Os aliases de IdPs de terceiros (`tid`,
1251
+ `organization_id`, `tenant_id`, `active_organization_id`) continuam valendo,
1252
+ agora num lugar só.
1253
+
1254
+ **3. Não havia como exigir org ativa.** `RequireOrgMiddleware`
1255
+ (`@adonis-agora/authkit-client/require_org_middleware`) barra request
1256
+ autenticada mas sem tenant. Isso importa porque "logado" e "dentro de um
1257
+ tenant" são coisas distintas: sem a barreira, a request chega no controller com
1258
+ tenant indefinido e a checagem escopada do authz cai no escopo GLOBAL — o pior
1259
+ lugar para se cair por acidente. Fail-closed, com `mode: 'api'` (403
1260
+ `no_active_organization` em vez de redirect) e `oneOf` para restringir a um
1261
+ conjunto conhecido de orgs.
1262
+
1263
+ ### Patch Changes
1264
+
1265
+ - Updated dependencies [813a321]
1266
+ - @adonis-agora/authkit-core@0.8.0
1267
+
1268
+ ## 0.58.3
1269
+
1270
+ ### Patch Changes
1271
+
1272
+ - cc96b9f: The package barrel no longer carries `HttpContext.adminApiKeyId` into consumers.
1273
+
1274
+ `index.ts` re-exported `ACCOUNT_SESSION_KEY` from the account-auth middleware,
1275
+ and that module side-effect-imports `../augmentations.js` to type its own
1276
+ `ctx.session`. So importing anything from `@adonis-agora/authkit-server` also
1277
+ loaded this package's `declare module '@adonisjs/core/http' { adminApiKeyId?: string }`
1278
+ into the consumer's type program.
1279
+
1280
+ Nothing broke, and that is the point of fixing it. `adminApiKeyId` is a name only
1281
+ this library claims, so the merge had nobody to collide with — nomenclature luck,
1282
+ not design. The sibling client package shipped the identical shape over `auth`, a
1283
+ name every app declares, and took a consumer from 24 type errors to 351 with
1284
+ nothing pointing at the cause. The next property added here should not have to be
1285
+ audited for how exotic its name happens to be.
1286
+
1287
+ `ACCOUNT_SESSION_KEY` now lives in its own import-free module,
1288
+ `src/host/account_session_key.ts`, which the barrel and the nineteen internal
1289
+ call sites read from. The middleware re-exports it, so nothing about the public
1290
+ surface changes: same export, same value, same path for consumers.
1291
+
1292
+ The augmentation-isolation guard was tightened to match. It asserted that no
1293
+ consumer entrypoint reaches `HttpContext.auth`; it now asserts that the barrel
1294
+ reaches **no** `HttpContext` augmentation at all, which is the bar that does not
1295
+ depend on picking safe names. Verified against a packed tarball in a consumer
1296
+ project: before, `ctx.adminApiKeyId` type-checks in an app that never asked for
1297
+ it; after, it does not — while all four container bindings still resolve through
1298
+ the provider, checked with member-level probes and a control run confirming an
1299
+ undeclared key fails.
1300
+
1301
+ ## 0.58.2
1302
+
1303
+ ### Patch Changes
1304
+
1305
+ - b97f28e: Importing the middleware no longer forces `HttpContext.auth` on your app.
1306
+
1307
+ **If you pinned to 0.17.1 to get your typecheck passing, this releases you.**
1308
+
1309
+ 0.17.2 added `import '../../types.js'` to the middleware's emitted declarations so
1310
+ that `ctx.auth` would be typed for apps that had no declaration of their own. But
1311
+ that same file also carries the library's _unparameterised_
1312
+ `auth: Authenticator`, and `start/kernel.ts` imports the middleware — so the
1313
+ declaration entered every consumer's type program.
1314
+
1315
+ `HttpContext.auth` declared twice with different types does not merge.
1316
+ TypeScript keeps whichever declaration it reached first and reports the conflict
1317
+ on the other; that conflict lands inside a `.d.ts`, where `skipLibCheck` — on in
1318
+ every AdonisJS app — discards it. The library's file is reached first through the
1319
+ kernel, so an app's own `auth: Authenticator<AppUser>` lost and `getUser()`
1320
+ collapsed to `unknown` **across the entire codebase**. One app went from 24 type
1321
+ errors to 351, another from 0 to 105, with nothing pointing at the cause.
1322
+
1323
+ The two augmentations are now separate modules:
1324
+
1325
+ - `container_bindings.ts` declares only `ContainerBindings['authkit.client']`, a
1326
+ key nothing else can claim. The provider and the middleware import this, so
1327
+ `containerResolver.make('authkit.client')` stays typed.
1328
+ - `types.ts` declares only `HttpContext.auth`, and **nothing in the package
1329
+ imports it**. Apps that want the library's default typing opt in with
1330
+ `import '@adonis-agora/authkit-client/types'`; apps that declare their own
1331
+ parameterised version are left alone.
1332
+
1333
+ The library's middleware reads and writes `ctx.auth` through a local cast rather
1334
+ than a global declaration, so the knowledge stays inside the package.
1335
+
1336
+ Nothing changes at runtime, and no app-level change is required: if you already
1337
+ declare `auth: Authenticator<AppUser>`, it now simply wins, because it is the
1338
+ only declaration in the program.
1339
+
1340
+ A spec walks the emitted declaration graph and fails if any consumer entrypoint
1341
+ reaches the `HttpContext.auth` augmentation again — the monorepo's own typecheck
1342
+ compiles every file in one program and structurally cannot catch this.
1343
+
1344
+ `@adonis-agora/authkit-server` was checked for the same defect and does not have
1345
+ it: it ships no `HttpContext.auth`, and its four container bindings still reach
1346
+ hosts through both the provider and the `./types` subpath. It gets the same guard
1347
+ anyway, because the thing protecting it is easy to break by accident —
1348
+ `src/host/augmentations.ts` loads the framework's own augmentations with
1349
+ `import type {} from '…'`, a form tsc erases from the emitted declarations, and
1350
+ rewriting any one of them as a bare `import '…'` would inherit
1351
+ `@adonisjs/auth`'s `HttpContext.auth` and reproduce this bug package-wide. Both
1352
+ guards now also reject a bare side-effect import of any external module from a
1353
+ shipped declaration file.
1354
+
1355
+ ## 0.58.1
1356
+
1357
+ ### Patch Changes
1358
+
1359
+ - f287ade: Widen the `@adonis-agora/*` peer ranges so they survive the peer's next minor.
1360
+
1361
+ Below 1.0.0 a caret does not mean "this minor and up" — it stops at the next
1362
+ minor, so `^0.22.0` expands to `>=0.22.0 <0.23.0`. Every sibling package in this
1363
+ ecosystem ships minors constantly, which meant **all four** `@adonis-agora/*`
1364
+ peers were unsatisfiable against the current release of the package they name.
1365
+ Under npm this is not a warning: installing `@adonis-agora/authkit-server`
1366
+ alongside `@adonis-agora/durable@0.24.0` failed outright with
1367
+ `ERESOLVE — Conflicting peer dependency: @adonis-agora/durable@0.22.0`.
1368
+
1369
+ Each floor was chosen from what the code actually calls, not from what the range
1370
+ happened to say:
1371
+
1372
+ | peer | was | now | why that floor |
1373
+ | -------------------------- | --------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------ |
1374
+ | `@adonis-agora/durable` | `^0.22.0` | `>=0.7.0 <1.0.0` | first release with `ctx.localStep`, absent in 0.6.1 |
1375
+ | `@adonis-agora/media` | `^0.1.0` | `>=0.5.0 <1.0.0` | first release exporting the `./single-file` subpath the avatar uploader imports, absent in 0.4.0 |
1376
+ | `@adonis-agora/telescope` | `^0.1.0` | `>=0.1.0 <1.0.0` | all five imported types are shape-identical from 0.1.0 through 0.8.1 |
1377
+ | `@adonis-agora/resilience` | `^0.1.0` | `>=0.1.0 <1.0.0` | `wrap()` has existed since 0.1.0, and the policy is duck-typed against a locally declared interface rather than imported |
1378
+
1379
+ The `^0.22.0` on durable in particular came from the `ctx.step` → `ctx.localStep`
1380
+ migration and recorded the version current on that day, some fifteen minors above
1381
+ what the migration actually required.
1382
+
1383
+ `@adonis-agora/authkit-sdk`'s peer on `@adonis-agora/authkit-server` gains the
1384
+ same upper bound (`>=0.10.0 <1.0.0`), which it was missing entirely — it would
1385
+ have accepted a future 1.0 with breaking changes.
1386
+
1387
+ A spec now scans every package manifest and fails on a caret or tilde over a 0.x
1388
+ `@adonis-agora/*` peer, so this cannot come back quietly.
1389
+
1390
+ ## 0.58.0
1391
+
1392
+ ### Minor Changes
1393
+
1394
+ - 8f23a85: Derive `AuditEventType` from a runtime list, and declare the five events that
1395
+ were missing from it.
1396
+
1397
+ The union was hand-written, and nothing checked it against the
1398
+ `audit.record({ type: … })` call sites. Five event types shipped without ever
1399
+ entering it — `login.magic_link_sent`, `session.revoked`, `account.signed_out_all`,
1400
+ `client.secret_regenerated` and `roles_catalog.updated`. A consumer writing an
1401
+ exhaustive `switch` over `AuditEventType` silently dropped all five, and
1402
+ TypeScript could not say so: the events exist only as string literals inside the
1403
+ library.
1404
+
1405
+ `AUDIT_EVENT_TYPES` is now exported as a `const` array and the union is
1406
+ `(typeof AUDIT_EVENT_TYPES)[number]`. Because the list exists at runtime, a spec
1407
+ can scan `src/` for audit emissions and fail when one is not declared — which is
1408
+ what now stops the two from drifting apart again.
1409
+
1410
+ - 8f23a85: The roles claim no longer disappears on hosts without a `branding` block.
1411
+
1412
+ The gate deciding who receives the authorization claims — `<globalRolesClaim>`,
1413
+ `org_id`, `org_slug`, `org_role` — read `config.branding ? isFirstParty(…) : false`.
1414
+ So the absence of a **theming** block meant "nobody is first-party": on any host
1415
+ that had not customised its look, every relying party received `globalRoles: []`.
1416
+ Silently, and asymmetrically, because the admin console reads roles from the
1417
+ account session rather than from a token and kept working — which is what made it
1418
+ so hard to see, on the very feature positioned as "authz is the single authority
1419
+ on roles".
1420
+
1421
+ An authorization decision must not depend on whether the host picked a colour.
1422
+ The allowlist now has its own key, `firstPartyClients: string[]`, and **not
1423
+ declaring one means every registered client is first-party**. That is not a
1424
+ free-for-all: the claims are bound to the `roles` scope in the provider, so a
1425
+ client still has to be registered by an admin _and_ ask for `scope=roles`. The
1426
+ allowlist is a third, optional barrier for hosts that register third-party
1427
+ clients. `branding.firstParty` is still read as a fallback, so hosts that already
1428
+ declared it keep exactly the restriction they have today, and an explicitly empty
1429
+ list still means "nobody".
1430
+
1431
+ `authkit:doctor` gains `checkFirstPartyClients`, which warns when no allowlist is
1432
+ declared and reports the list when it is. New export: `isFirstPartyClient`.
1433
+
1434
+ - 8f23a85: Fix the admin console's "Impersonate" button, both halves of it.
1435
+
1436
+ **It could never have worked.** The panel was built from `cfg.clients`, which is
1437
+ always `[]` in a real installation — clients live in the OIDC adapter, created
1438
+ through the console, the Admin API or `authkit:clients:create`, and the resolved
1439
+ config never carries them. So the endpoint answered `404 no_token_exchange_client`
1440
+ on every host that had not kept legacy static clients. Only the test suite, which
1441
+ declared static clients, ever saw it succeed. `buildImpersonationPanel` now takes
1442
+ the candidate clients as an argument and the controller reads them from the
1443
+ adapter, falling back to static config clients for legacy hosts and for adapters
1444
+ that cannot enumerate. A confidential runtime client renders a `<CLIENT_SECRET>`
1445
+ placeholder rather than an empty secret, because the adapter cannot hand back a
1446
+ secret that was shown once at creation.
1447
+
1448
+ **And the audit trail lied about it.** The endpoint recorded
1449
+ `impersonation.started` before the UI had done anything — so a request that then
1450
+ 404'd, or that the admin abandoned, left a record claiming an impersonation had
1451
+ begun. Viewing the exchange parameters is not assuming an identity. The console
1452
+ now records `impersonation.panel_viewed`, emitted only once a usable panel
1453
+ exists; `impersonation` still belongs to the token-exchange handler, which is
1454
+ where an identity is actually assumed. `impersonation.started` is gone from
1455
+ `AuditEventType` — nothing emitted it under a truthful reading.
1456
+
1457
+ **The UI opened a field that never existed.** `result.data.url` type-checked only
1458
+ because `ImpersonationPanel` in `@adonis-agora/authkit-react` carried an
1459
+ `[key: string]: unknown` index signature; at runtime it was always
1460
+ `window.open(undefined)`. The type is now closed over the real response, so that
1461
+ line no longer compiles, and the console renders the exchange parameters with a
1462
+ copyable request instead of trying to navigate somewhere.
1463
+
1464
+ - 8f23a85: Organization invitations send an email even without a `mail.onOrgInvitation` hook.
1465
+
1466
+ `onOrgInvitation` was the only hook in `MailHooks` with no default-mailer
1467
+ fallback. Every other one degrades to a branded, translated email the host kit
1468
+ sends itself; this one was guarded by a bare `if (cfg.mail?.onOrgInvitation)`
1469
+ with no `else`, so a host that had not written the hook created the invitation
1470
+ row and sent nothing. The invited person was never told they had been invited,
1471
+ and nothing anywhere reported a problem.
1472
+
1473
+ `sendOrgInvitationEmail` now exists alongside the other `send*Email` functions,
1474
+ with `mail.org_invitation.*` messages in both the English and pt-BR tables, and
1475
+ both HTTP call sites — the account-facing controller and the admin service — use
1476
+ the host hook when present and the library default otherwise. Delivery stays
1477
+ best-effort: a mail failure never breaks invitation creation.
1478
+
1479
+ The SDK's embedded driver reached the same service through a third door and had
1480
+ to be fixed with it. Its out-of-band context now carries the container resolver
1481
+ and a logger, which is all the default mailer reads, so an invitation created
1482
+ through `authkit.organizations.invitations.create()` reaches an inbox too rather
1483
+ than reproducing the original silent failure one layer down.
1484
+
1485
+ One latent bug fell out of the rework: the account controller resolved the
1486
+ organization and the issuer origin _outside_ its `try`, so a config with an
1487
+ unresolvable issuer would have thrown out of a path that must never break
1488
+ invitation creation. Both are inside the guarded block now.
1489
+
1490
+ ### Patch Changes
1491
+
1492
+ - 8f23a85: Ship the module augmentations so they actually reach a consuming app.
1493
+
1494
+ **The client.** `HttpContext.auth` was declared in the package's root `types.ts`,
1495
+ and nothing reachable from `build/index.d.ts` referenced that file — so TypeScript
1496
+ never loaded it in a host app and `ctx.auth` came back as `Property 'auth' does
1497
+ not exist on type 'HttpContext'`, no matter what the app imported. Verified
1498
+ against packed tarballs in a scratch AdonisJS app across five wiring variants; a
1499
+ `/// <reference path>` straight at the built `types.d.ts` type-checked cleanly,
1500
+ which isolates it to reachability rather than to the declarations. The
1501
+ augmentation is now pulled in by a bare side-effect import from
1502
+ `authkit_middleware` — the middleware that actually assigns `ctx.auth`, and the
1503
+ one `configure` registers.
1504
+
1505
+ Putting that import in `index.ts` was considered and rejected: apps that hand
1506
+ `ctx.auth` to `@adonisjs/auth` through `authkitClientGuard` would then load two
1507
+ competing `HttpContext.auth` declarations, and because `skipLibCheck` is on in
1508
+ every AdonisJS app the conflict is not reported — one simply wins by include
1509
+ order. Anchoring on the middleware keeps the two owners of `ctx.auth` mutually
1510
+ exclusive, the same way `@adonisjs/auth` does with its own
1511
+ `initialize_auth_middleware`.
1512
+
1513
+ **The server** was a smaller problem than it first looked. Hosts _were_ getting
1514
+ `app.container.make('authkit.server')` typed all along, because the service
1515
+ provider carried its own copy of the `ContainerBindings` declaration and every
1516
+ host registers the provider. What was broken is that `exports["./types"]` pointed
1517
+ at the barrel rather than at the built `types.js`, so importing
1518
+ `@adonis-agora/authkit-server/types` gave `Property 'authkit.server' does not
1519
+ exist on type 'ContainerBindings'`.
1520
+
1521
+ Retargeting that subpath exposed a second problem: the root `types.ts` declared
1522
+ one binding while the provider declared four, so the newly-live subpath was a
1523
+ partial view. Two binding tables with nothing checking they agree is how they
1524
+ drifted in the first place, so there is one table now — in `types.ts`, with the
1525
+ provider reaching it through a side-effect import.
1526
+
1527
+ **`adonisjs.types` covers neither package.** No version of `@adonisjs/core`,
1528
+ `@adonisjs/assembler` or `@adonisjs/application` in this repo's dependency tree
1529
+ reads that package.json field. It is an AdonisJS 5 leftover, left alone.
1530
+
1531
+ ## 0.57.1
1532
+
1533
+ ### Patch Changes
1534
+
1535
+ - 1f15367: Point `homepage` and `bugs.url` at the repository's current name.
1536
+
1537
+ The repository was renamed to `adonis-agora-authkit`; `repository.url` followed,
1538
+ `homepage` and `bugs.url` did not. Every package therefore shipped a manifest
1539
+ that disagreed with itself, and the "Repository"/"Homepage" links on npm leaned
1540
+ on GitHub's rename redirect — which lasts only until someone claims the old
1541
+ name. All three fields now name the same repository.
1542
+
1543
+ - 1f15367: Declare `onAccountExpirationWarning` in the `MailHooks` interface.
1544
+
1545
+ `authkit:expire-scan` has always called `mail.onAccountExpirationWarning({ email, expiresInDays })`
1546
+ before deactivating an inactive account, but the hook was never declared on
1547
+ `MailHooks` — the call site reads the config through `Record<string, any>`, so
1548
+ nothing checked it. A host that supplied the hook got no type for its payload,
1549
+ and a host that mistyped the key got no error; the docs, having no type to read,
1550
+ documented a different payload entirely.
1551
+
1552
+ The hook is now part of the interface with its real payload, so editors complete
1553
+ it and a wrong shape fails the build.
1554
+
1555
+ - 1f15367: Stop the `AuthHostOptions` docblocks telling hosts to mirror policy options.
1556
+
1557
+ `social`, `rateLimit`, `sudoMethods` and the on/off of `admin`/`adminApi` are
1558
+ policy: once `defineConfig` declares one, `registerAuthHost` ignores the matching
1559
+ argument, warns on boot, and lists the key in `AuthHostRouteMap.overriddenByConfig`.
1560
+ The docblocks still said "mirror `rateLimit` from config/authkit.ts" and "when
1561
+ enabled, the host must also pass `admin: true`" — advice that now produces a
1562
+ boot warning and an argument with no effect.
1563
+
1564
+ Each affected docblock now states the real precedence, and the `admin`/`adminApi`
1565
+ entries spell out the two axes: the on/off is policy (config wins and locks),
1566
+ the prefix is structural (the argument still wins).
1567
+
1568
+ - 1f15367: Fix the `schema` docblock, which under-counted the auto-managed tables.
1569
+
1570
+ It listed six (`authkit_oidc_payloads`, `auth_settings`, `auth_password_history`
1571
+ "and the three organization ones"), while `TABLES` in `schema/ensure.ts` has
1572
+ eight — `auth_mfa` and `auth_session_revocations` were missing. A reader sizing
1573
+ up what `autoManage: false` puts on their plate got the wrong answer. The
1574
+ docblock now names all eight and points at the array that actually decides,
1575
+ and calls out `authkit_keystore` as created on demand by `LucidKeystoreVault`
1576
+ rather than by this mechanism.
1577
+
1578
+ - 1f15367: `SettingLockedError`'s message is in English.
1579
+
1580
+ The message is not an internal log line: the Admin API's write path turns the
1581
+ error into a `423 Locked` response and puts the text in the JSON body, where an
1582
+ operator or an integrator reads it. It was Portuguese, in an otherwise
1583
+ English-facing API. The `code` (`E_SETTING_LOCKED`) and the `key` property are
1584
+ unchanged, so anything matching on those is unaffected.
1585
+
1586
+ - Updated dependencies [1f15367]
1587
+ - @adonis-agora/authkit-core@0.7.1
1588
+
1589
+ ## 0.57.0
1590
+
1591
+ ### Minor Changes
1592
+
1593
+ - bcd92de: **impersonation_session: renova o access token expirado via refresh token.**
1594
+
1595
+ O `startImpersonation` falhava com `Token exchange failed: 400` quando o access
1596
+ token do admin (curto, ~15min) expirava antes do token-exchange RFC 8693 — o
1597
+ admin precisava relogar a cada expiração. Agora:
1598
+
1599
+ - `rememberRefreshToken(ctx, refreshToken)` guarda o refresh token (do scope
1600
+ `offline_access`) na sessão, junto do access token.
1601
+ - `refreshAccessToken(ctx, params)` renova o access token via refresh grant
1602
+ (RFC 6749 §6), rotacionando o refresh token quando o IdP emite um novo.
1603
+ - `startImpersonation` detecta o 4xx do exchange (token expirado), renova via
1604
+ refresh e tenta de novo automaticamente — sem forçar relogin.
1605
+
1606
+ O app RP deve pedir `offline_access` no scope do login e chamar
1607
+ `rememberRefreshToken` no callback OIDC junto de `rememberAccessToken`.
1608
+
1609
+ ## 0.56.0
1610
+
1611
+ ### Minor Changes
1612
+
1613
+ - 685dca1: durable: workflows LGPD (export/deleção de conta) migram para `ctx.localStep` — piso do peer `@adonis-agora/durable` sobe para `^0.22.0`
1614
+
1615
+ Os dois workflows duráveis (`defineAccountExportWorkflow`, `defineAccountDeletionWorkflow`)
1616
+ usavam `ctx.step('nome', async () => { ... })` para rodar cada etapa do cascade
1617
+ IN-PROCESS — coleta/persistência do export, snapshot/revoke/delete do cascade de
1618
+ deleção. Isso funcionava no `@adonis-agora/durable@0.2.0` (o peer com que o
1619
+ authkit foi construído), mas o engine mudou o contrato: `ctx.step` virou o
1620
+ ÚNICO primitivo de dispatch remoto — sempre roteia por `callRemote` para um
1621
+ handler registrado por nome — e o corpo passado deixou de ser executado; ele é
1622
+ tratado como o `input` da chamada. Bumpar o peer para `^0.22.0` sem migrar o
1623
+ authkit deixava os dois workflows presos em `suspended` para sempre (nenhum
1624
+ worker jamais registrado sob os nomes `collect`/`audit`/`snapshot`/etc.) — 5
1625
+ testes do pacote pegavam exatamente isso.
1626
+
1627
+ A correção real não é reescrever os steps como handlers remotos: o engine
1628
+ mantém, à parte, `ctx.localStep(nome, fn, opts)` — o sucessor direto do
1629
+ `ctx.step` local pré-0.8.0, checkpointado e substituído no replay sem nunca
1630
+ sair do processo. Os dois workflows trocam `ctx.step` por `ctx.localStep` em
1631
+ todos os 15 call sites (mesmos nomes de etapa, mesma ordem, mesmos efeitos
1632
+ colaterais — só a chamada mudou), e a interface estrutural `DurableStepCtx`
1633
+ (que evita acoplar o build ao peer opcional) segue o mesmo rename.
1634
+
1635
+ Minor: em 0.x é o bump que sinaliza quebra, e forçar 1.0.0 daria um sinal de
1636
+ estabilidade que o pacote não está declarando. O `peerDependencies` sobe junto
1637
+ (`^0.2.0` → `^0.22.0`) — `ctx.localStep` não existe no engine antigo, então a
1638
+ combinação NOVO authkit + peer velho não resolve (o range de peer já barra
1639
+ isso). Quem usa o modo durável (`accountLifecycle.durable`) precisa subir
1640
+ `@adonis-agora/durable` para `^0.22.0` neste mesmo deploy.
1641
+
1642
+ ## 0.55.2
1643
+
1644
+ ### Patch Changes
1645
+
1646
+ - 41fb438: impersonation: `stopImpersonation` preserva o `admin_access_token` do admin
1647
+
1648
+ Antes, sair da personificação limpava o `admin_access_token` da sessão junto
1649
+ com o `impersonator_user_id`. Como o `startImpersonation` exige o access token
1650
+ do admin como `subject_token` do token-exchange, a PRÓXIMA personificação
1651
+ falhava com "No admin access token in session; call rememberAccessToken after
1652
+ login" — o admin precisava relogar a cada ida e volta.
1653
+
1654
+ O token é do ADMIN (não do alvo) e o admin segue logado após o stop, então
1655
+ preservá-lo é seguro: o token segue curto (TTL do access token do RP) e o
1656
+ logout do app (`ctx.session.clear()`) continua limpando a sessão inteira.
1657
+ `stopImpersonation` agora só remove `impersonator_user_id`.
1658
+
1659
+ ## 0.55.1
1660
+
1661
+ ### Patch Changes
1662
+
1663
+ - f8e3f7d: token-exchange: gate do ator passa a resolver roles via `resolveTokenRoles` e aceitar allowlist `adminRoles`
1664
+
1665
+ O gate de impersonation (RFC 8693) checava `actor.globalRoles.includes('ADMIN')` —
1666
+ a coluna `globalRoles` do authkit — ignorando o `resolveTokenRoles` que o resto
1667
+ da lib usa (o console admin via `resolveAccountRoles`, e o claim `roles` na
1668
+ mintagem). Para um host que gerencia papéis num store próprio (ex.:
1669
+ `@adonis-agora/authz`), `globalRoles` fica vazio e **nenhum admin conseguia
1670
+ personificar** (sempre `invalid_grant` 400).
1671
+
1672
+ Mudanças:
1673
+
1674
+ - **Ator**: as roles do admin são resolvidas pela mesma fonte do claim — o hook
1675
+ `resolveTokenRoles` quando configurado, senão `globalRoles` (comportamento
1676
+ histórico preservado).
1677
+ - **Allowlist**: nova dep `adminRoles` (default `['ADMIN']`). A checagem passa a
1678
+ ser "alguma role do ator ∈ allowlist". O `oidc_service` já injeta
1679
+ `config.admin.roles` — hosts com papel próprio (ex.: `admin` minúsculo) só
1680
+ precisam configurar `admin: { roles: ['admin'] }`. `adminRole` singular segue
1681
+ aceito como alias de back-compat.
1682
+
1683
+ ## 0.55.0
1684
+
1685
+ ### Minor Changes
1686
+
1687
+ - 92580af: `admin.impersonation` vira um interruptor de verdade — antes era um controle que não fazia nada
1688
+
1689
+ **O que estava errado.** `admin.impersonation` aparecia como um controle
1690
+ nomeado da lib, mas não existia como entrada de config: `AdminConfigInput`
1691
+ declarava só `enabled` e `roles`, e `resolveAdmin` devolvia
1692
+ `impersonation: false` HARDCODED, ignorando qualquer input. As consequências
1693
+ eram as duas piores possíveis, cada uma no sentido oposto:
1694
+
1695
+ - **O painel de impersonation do console admin era código morto.**
1696
+ `console_impersonation_controller.ts` recusa com 404 quando
1697
+ `!cfg.admin.impersonation` — e como a flag era sempre `false`, ninguém no
1698
+ mundo conseguia habilitar aquele endpoint.
1699
+ - **O grant de impersonation era sempre ligado, sem interruptor.** O grant
1700
+ RFC 8693 `urn:ietf:params:oauth:grant-type:token-exchange` era registrado
1701
+ INCONDICIONALMENTE no provider (`src/provider/oidc_service.ts`), independente
1702
+ da flag. Um operador que lesse `admin.impersonation` acreditaria, com toda a
1703
+ razão, poder governar impersonation. Não podia: não havia como desligar.
1704
+
1705
+ **O que mudou.** `impersonation?: boolean` agora é declarável no
1706
+ `defineConfig({ admin: { … } })`, é lido pelo `resolveAdmin` e governa as DUAS
1707
+ superfícies com uma única declaração: com `false`, o grant token-exchange
1708
+ deixa de ser registrado no provider (o token endpoint responde
1709
+ `unsupported_grant_type`, o grant não existe) E o painel do console responde 404. É um kill switch, não um esconde-UI.
1710
+
1711
+ Declarar a chave também trava a setting de runtime `admin_impersonation`
1712
+ (config vence, a UI/Admin API não altera mais o valor) — o lock já existia em
1713
+ `src/host/config_locks.ts` mas apontava para uma chave que o tipo não permitia
1714
+ escrever.
1715
+
1716
+ **O default é `true`: NENHUM host existente muda de comportamento.** O grant
1717
+ sempre foi registrado incondicionalmente, e há hosts que o consomem a partir do
1718
+ PRÓPRIO `/admin`, sem montar o console desta lib. Um default `false` os
1719
+ quebraria em runtime, em silêncio — o grant simplesmente sumiria. Então o
1720
+ default preserva exatamente o comportamento de hoje.
1721
+
1722
+ **Quem quer impersonation desligada agora tem de dizer isso explicitamente:**
1723
+
1724
+ ```ts
1725
+ defineConfig({
1726
+ admin: { enabled: true, impersonation: false },
1727
+ });
1728
+ ```
1729
+
1730
+ **Deferido para um major:** virar o default para `false`
1731
+ (secure-by-default). É uma quebra de comportamento em runtime para hosts que
1732
+ nunca declararam a chave, e não cabe num minor.
1733
+
1734
+ - 762c90b: Adiciona expiração aos tokens de verificação de e-mail (cadastro) e de troca de e-mail — antes válidos para sempre
1735
+
1736
+ **Segurança.** `emailVerificationToken` (verificação de cadastro) e o token
1737
+ `ec:` de troca de e-mail nunca expiravam — eram os únicos tokens desta lib sem
1738
+ janela de validade (reset de senha: 1h; magic link: 15min; OTP: janela
1739
+ configurável). Isso importa mais para a troca de e-mail, porque
1740
+ `confirmEmailChange` REESCREVE o identificador de recovery da conta (o e-mail
1741
+ usado por reset de senha e magic-link login); um link de confirmação enviado
1742
+ anos atrás, esquecido numa caixa de entrada antiga, continuava funcionando
1743
+ hoje.
1744
+
1745
+ **Correção.** A deadline (`exp`, epoch ms) agora vai embutida DENTRO do
1746
+ próprio token, não numa coluna nova — esta lib não é dona da tabela `users`
1747
+ (ver header de `src/schema/ensure.ts`), então não pode adicionar uma coluna
1748
+ `emailVerificationExpiresAt` como o reset de senha tem. Isso só é seguro
1749
+ porque o token já é hasheado em repouso (mesma mudança que fechou o
1750
+ plaintext-at-rest para reset/magic-link): o lookup reconstrói o valor de DB a
1751
+ partir do token bruto recebido e busca por IGUALDADE — se o `exp` for
1752
+ adulterado pelo cliente, o valor reconstruído não bate com nenhuma linha, e é
1753
+ esse MATCH (não uma assinatura separada) que prova que o `exp` não foi
1754
+ mexido. Um `exp` ausente ou não-parseável conta como EXPIRADO (fail-closed),
1755
+ o mesmo padrão já usado no reset de senha (`!row.passwordResetExpiresAt || ...`).
1756
+ Como efeito colateral de reusar o hashing, o token `ec:` também deixa de ser
1757
+ gravado em claro na coluna `emailVerificationToken`.
1758
+
1759
+ As duas janelas de validade são configuráveis via `emailTokens` nas opções do
1760
+ `lucidAccountStore`/`lucidStores` (`verificationTtlHours`, default 24h;
1761
+ `changeTtlHours`, default 1h — mesma janela do reset de senha, por reescrever
1762
+ o identificador de recovery).
1763
+
1764
+ **Consequência da atualização:** todo link de verificação de e-mail ou de
1765
+ troca de e-mail pendente no momento do upgrade passa a ser recusado —
1766
+ tokens gravados por versões anteriores não carregam `exp` nenhum, e a
1767
+ ausência conta como expirado (deliberado, não é regressão). Usuários no meio
1768
+ desses fluxos precisam solicitar um novo link. **Nenhuma migração no host é
1769
+ necessária** — a deadline vive dentro do valor já gravado na coluna
1770
+ existente, não numa coluna nova.
1771
+
1772
+ - 27a6a28: Aplica o gate de status de conta (disabled/expired) em TODOS os caminhos de login passwordless, não só no de senha
1773
+
1774
+ **Segurança.** `isDisabled`, `isPasswordExpired` e `isAccountExpired` eram
1775
+ checados em um único lugar: `attemptPasswordLogin`. Todo outro jeito de
1776
+ completar o login — magic link, código OTP por e-mail e passkey (incluindo o
1777
+ modo passkey-first) — chamava `completeLogin` direto, sem consultar o status
1778
+ da conta. Um admin que desabilita uma conta pelo console acredita ter revogado
1779
+ o acesso; em um deployment passwordless (que esta lib suporta explicitamente
1780
+ via `passwordless.magicLink`, `passwordless.passkeyFirst` e `login.otp`), o
1781
+ botão "desabilitar" não tinha efeito algum — a conta continuava logando pelo
1782
+ magic link, pelo código, ou pela passkey.
1783
+
1784
+ **Correção.** O gate de status foi extraído de `attemptPasswordLogin` para
1785
+ `assertAccountEnabled`/`assertAccountNotExpired`/`assertLoginAllowed`
1786
+ (`src/host/login_attempt.ts`, todos exportados), preservando exatamente o
1787
+ mesmo probe de capacidade (`supportsAccountStatus`) e os mesmos eventos de
1788
+ auditoria (`login.failure` com `reason: 'disabled'`,
1789
+ `password.expired_change_forced`, `account.expired_login_blocked`) do fluxo de
1790
+ senha. Magic link, OTP e passkey agora chamam esse gate imediatamente antes de
1791
+ `completeLogin`, cada um re-renderizando o erro na sua própria convenção
1792
+ (magic link e OTP re-renderizam a view `login`; passkey re-renderiza
1793
+ `mfa-challenge`) — o mesmo padrão já usado pelo gate de e-mail não verificado
1794
+ nesses fluxos. O login social (`social_controller.ts`) também passou a checar
1795
+ o gate antes de completar o login e, adicionalmente, ganhou auditoria de falha
1796
+ de login pela primeira vez (não existia nenhuma antes desta mudança).
1797
+
1798
+ **Consequência da atualização:** contas desabilitadas ou com
1799
+ `password_expiration`/`account_expiration` vencidos deixam de completar login
1800
+ por magic link, OTP, passkey ou social — onde antes conseguiam. Hosts cujo
1801
+ `accountStore` não implementa a capacidade de status (`supportsAccountStatus`
1802
+ retorna false) não são afetados: o gate degrada para "permitido", exatamente
1803
+ como já acontecia no fluxo de senha.
1804
+
1805
+ **Limitação conhecida (não fechada nesta mudança):** o alvo de um
1806
+ token-exchange (impersonation, `src/provider/token_exchange.ts`) agora aceita
1807
+ um `accountStore` opcional em `TokenExchangeDeps` para recusar impersonar uma
1808
+ conta desabilitada, e a lógica está coberta por teste isolado — mas o ÚNICO
1809
+ call site de produção (`registerTokenExchange` em
1810
+ `src/provider/oidc_service.ts:158`) ainda não passa esse campo. Até esse
1811
+ 1-line de wiring ser adicionado, impersonar uma conta desabilitada via
1812
+ token-exchange continua funcionando como antes desta mudança. Ver plano
1813
+ `plans/003-account-status-gate-all-login-paths.md` para o detalhe.
1814
+
1815
+ - 1cd2b40: Hasheia (sha256) os tokens de reset de senha e magic link em repouso — antes eram gravados em texto claro
1816
+
1817
+ **Segurança.** `passwordResetToken` guardava reset de senha, magic link (`ml:`)
1818
+ e magic link com OTP (`ml2:`) EM TEXTO CLARO, e as três leituras eram por
1819
+ igualdade/`LIKE` direto contra esse valor. Qualquer leitura da tabela de
1820
+ usuários — um dump de backup, uma réplica de leitura, uma ferramenta de
1821
+ suporte, um DBA, ou um SQL injection no host — devolvia credenciais
1822
+ diretamente usáveis para logar como (ou resetar a senha de) qualquer conta com
1823
+ um token pendente. O token de desbloqueio OTP, na MESMA coluna, já seguia o
1824
+ padrão correto (`prefix + sha256(raw)`); reset e magic link eram os únicos
1825
+ fluxos fora do padrão.
1826
+
1827
+ **Correção.** Os três fluxos agora armazenam `prefix + sha256(<parte
1828
+ aleatória>)` em vez do token bruto:
1829
+
1830
+ - Reset de senha: sem prefixo, armazena só o hash.
1831
+ - Magic link (`ml:`): o prefixo `ml:` continua em CLARO — só a parte aleatória
1832
+ é hasheada.
1833
+ - Magic link com OTP (`ml2:`): o slot é um composto
1834
+ (`ml2:<linkToken>:<codeHash>:<codeExpMs>:<attempts>`) parseado pelo código e
1835
+ buscado por `LIKE` de prefixo (a URL carrega só `ml2:<linkToken>`, não o
1836
+ slot inteiro). Só o segmento `linkToken` é hasheado; `codeHash` já era
1837
+ `sha256(uid:code)` e não é re-hasheado.
1838
+
1839
+ Os guards de discriminação de fluxo (`consumePasswordResetToken` recusando
1840
+ `ml:`/`ml2:`; `consumeMagicLinkToken` recusando o que não é `ml:`) continuam
1841
+ funcionando — eles leem o prefixo, que permanece em texto claro.
1842
+
1843
+ **Consequência da atualização:** links de reset de senha e magic links
1844
+ pendentes na hora do deploy são invalidados (o valor gravado muda de formato).
1845
+ O raio de impacto é limitado — reset expira em 1h, magic link em 15min — então
1846
+ na prática só afeta quem tinha um link em voo no momento exato do deploy;
1847
+ basta pedir um novo.
1848
+
1849
+ - 86a41ea: Constrói os links emailados (reset de senha, magic link, desbloqueio OTP, convites de organização, verificação/troca de e-mail, avisos de segurança) a partir do `issuer` configurado, não mais do header `Host` da request — fecha um vetor de password-reset/magic-link poisoning
1850
+
1851
+ **Segurança.** Dezesseis pontos em nove arquivos montavam a origem do link
1852
+ emailado concatenando `ctx.request.protocol()` + `ctx.request.host()`.
1853
+ `Request.host()` reflete o header `Host` (ou `X-Forwarded-Host` sob proxy
1854
+ confiável) — ambos client-supplied. Um atacante capaz de alcançar o servidor
1855
+ diretamente (comum quando o load balancer na frente não fixa `Host`) podia
1856
+ submeter um pedido de reset de senha ou magic link para o e-mail de uma
1857
+ vítima com um `Host` forjado à sua escolha. A vítima recebia um e-mail
1858
+ genuíno do sistema real cujo link apontava para infraestrutura controlada
1859
+ pelo atacante — um token de autenticação ou de reset válido de uso único
1860
+ entregue de bandeja. Isso cobre reset de senha, magic link, desbloqueio OTP e
1861
+ convites de organização.
1862
+
1863
+ **Correção.** Novo helper `authkitOrigin(cfg)`
1864
+ (`src/host/origin.ts`) deriva a origem canônica de `new URL(cfg.issuer).origin`
1865
+ — a mesma origem que a lib já usa para o `rpId`/`origin` do WebAuthn. Os 16
1866
+ sites foram classificados um a um (redirects em voo e usos de
1867
+ `request.ip()`/`request.header()` para audit/geo ficaram de fora — não
1868
+ constroem links emailados) e os 11 que estavam em controllers passaram a usar
1869
+ o helper diretamente; os 5 que estavam em `default_mailer.ts`
1870
+ (`sendNewLoginEmail`, `sendNewDeviceLoginEmail`, `sendEmailChangeNoticeEmail`,
1871
+ `sendEmailChangedCompletedEmail`, `sendSecurityNoticeEmail`) passaram a
1872
+ receber `cfg: ResolvedServerConfig` como parâmetro adicional (breaking a nível
1873
+ de API interna — hosts que chamam esses exports diretamente, fora dos fluxos
1874
+ padrão da lib, precisam passar `cfg`).
1875
+
1876
+ **Comportamento para deployments multi-hostname.** Hosts que servem o MESMO
1877
+ issuer sob múltiplos hostnames públicos legítimos vão ver os links emailados
1878
+ normalizarem para o hostname do issuer, em vez de seguir o `Host` da request
1879
+ como antes. Para esse caso, foi adicionado o escape hatch opcional
1880
+ `mail.origin` em `config/authkit.ts`: quando presente, sobrepõe o issuer como
1881
+ origem dos links emailados.
1882
+
1883
+ **Prova por mutação:** reverter isoladamente a conversão do reset de senha e
1884
+ a do magic link (uma de cada vez) faz os testes de "forged Host" correspondentes
1885
+ falharem; com a conversão restaurada, ambos passam — ver
1886
+ `tests/host/issuer_based_email_links.spec.ts`.
1887
+
1888
+ **Relacionado e deliberadamente não corrigido aqui** (ver
1889
+ `plans/004-issuer-based-email-links.md`): `src/provider/build_provider.ts:273`
1890
+ define `provider.proxy = true` incondicionalmente, tornando
1891
+ `X-Forwarded-Proto`/`X-Forwarded-Host`/`X-Forwarded-For` autoritativos para os
1892
+ endpoints OIDC independente do `trustProxy` do host — mesma família de
1893
+ problema, correção separada. Tokens de reset/magic-link em texto plano na
1894
+ tabela de contas e tokens de verificação/troca de e-mail sem expiração
1895
+ também são achados relacionados, não fechados nesta mudança.
1896
+
1897
+ - 962257b: Host passwordless deixa de ficar TRANCADO fora de toda operação sob sudo — o default finalmente mudou
1898
+
1899
+ **Se o seu `config/authkit.ts` tem `authMethods: { password: false }` ou
1900
+ `passwordless: { signup: true }` e a sua chamada a `registerAuthHost` NÃO passa
1901
+ `sudoMethods`, você tem este bug agora, em produção, em TODA versão anterior a
1902
+ esta.** São dez segundos de conferência e vale a pena fazê-la antes de continuar
1903
+ lendo.
1904
+
1905
+ ## O que estava quebrado
1906
+
1907
+ Sudo mode (`requireSudo`) só aceitava **senha** ou **passkey** por padrão. As duas
1908
+ exigem uma credencial cadastrada ANTES. Num host passwordless não existe nenhuma:
1909
+
1910
+ - quem entrou por magic link ou OIDC não tem senha — e o signup passwordless
1911
+ ainda grava um hash aleatório inutilizável na coluna, então a tela mostrava um
1912
+ campo de senha que ninguém no mundo conseguia preencher;
1913
+ - quem nunca cadastrou passkey não tem passkey;
1914
+ - **e cadastrar passkey exige sudo.**
1915
+
1916
+ Resultado: essas contas ficavam permanentemente fora de **exportar os próprios
1917
+ dados (LGPD), excluir a conta, ligar MFA, criar/revogar Personal Access Tokens e
1918
+ trocar o e-mail** — inclusive fora do cadastro de passkey, que é o único caminho
1919
+ que destravaria o resto. Deadlock fechado, e era o comportamento DEFAULT.
1920
+
1921
+ O `0.46.0` publicou o SPI `SudoMethod` exatamente para resolver isto, e o
1922
+ changelog dele descreve o deadlock nestes termos. Mas **o default não mudou**: o
1923
+ `oidcStepUp` e o `magicLink` existiam e ninguém os ligava. Quem soubesse
1924
+ configurar ficava bem; quem herdasse os defaults continuava bricado. Publicar uma
1925
+ capacidade não é publicar um conserto — atualizar para 0.46/0.47 não corrigia
1926
+ nada. Esta versão é a que corrige.
1927
+
1928
+ ## O que mudou por padrão
1929
+
1930
+ 1. **`registerAuthHost` passa a montar as rotas de `magicLink()`** junto com as de
1931
+ `password()`/`passkey()`. `oidcStepUp` não pode entrar nos defaults (exige uma
1932
+ URL do host), então o magic link de sudo é o único método sem credencial
1933
+ prévia que a lib consegue montar sozinha.
1934
+
1935
+ 2. **Montar não é oferecer.** O que a tela `/account/confirm` oferece — e o que os
1936
+ handlers aceitam, pela mesma função, para que os dois lados não possam
1937
+ divergir — passa a ser derivado do config resolvido:
1938
+
1939
+ | seu config | métodos de sudo por padrão |
1940
+ | ---------------------------------- | -------------------------------------------- |
1941
+ | senha ligada (a maioria) | `[password, passkey]` — **idêntico a antes** |
1942
+ | `authMethods: { password: false }` | `[passkey, magicLink]` |
1943
+
1944
+ Um host com senha **não** passa a oferecer step-up por e-mail a quem nunca
1945
+ pediu; o endpoint do magic link fica montado e inerte. Um host que declarou
1946
+ não ter senha para de mostrar o campo de senha (naquele deployment ele é uma
1947
+ opção que não pode dar certo) e ganha um método que a conta consegue
1948
+ satisfazer.
1949
+
1950
+ 3. **`sudoMethods.magicLink()` não depende mais do hook `mail.onSudoLink`.** Sem o
1951
+ hook, o próprio host-kit envia o e-mail pelo mailer default do app
1952
+ (`@adonisjs/mail`), com o seu branding e i18n — a mesma postura de todo outro
1953
+ e-mail da lib (reset de senha, verificação, magic link de login). Antes a
1954
+ ausência do hook tornava o método INDISPONÍVEL, e era isso que fazia o
1955
+ conserto do deadlock depender de alguém ter escrito um hook à mão. O hook
1956
+ continua tendo prioridade quando declarado. Novas chaves de i18n:
1957
+ `mail.sudo_link.*` (`en` e `pt-BR`), sobrescrevíveis como qualquer outra.
1958
+
1959
+ 4. **Aviso alto no boot** (`console.warn`, não erro — nada deixa de subir) quando
1960
+ o seu `sudo.methods` declarado não tem um único método satisfazível por conta
1961
+ sem senha. Ele nomeia a config que disparou e as duas saídas. Só dispara com
1962
+ `sudo.methods` DECLARADO, com deployment passwordless, e quando todos os
1963
+ métodos da lista são built-in deste pacote — diante de um método customizado
1964
+ ele se cala, porque não tem como saber se aquele exige credencial prévia.
1965
+
1966
+ ## O que NÃO mudou
1967
+
1968
+ - **Host com senha e sem `sudoMethods`: nada muda na experiência.** A tela segue
1969
+ oferecendo exatamente `password` + `passkey`, na mesma ordem.
1970
+ - **Se você passa `sudoMethods` (ou declara `sudo.methods`), você não é afetado —
1971
+ e continua tendo de resolver isto por conta própria.** A lista é sua, ela
1972
+ SUBSTITUI os defaults, e a derivação acima **não a toca em nenhuma direção**.
1973
+ Se o seu host é passwordless e a sua lista é `[password(), passkey()]`, os seus
1974
+ usuários seguem trancados: acrescente `sudoMethods.oidcStepUp({ url:
1975
+ '/auth/step-up' })` (reautenticação no seu IdP, com o callback chamando
1976
+ `completeSudo`) ou `sudoMethods.magicLink()`. É esse o caso que o aviso do item
1977
+ 4 grita no boot.
1978
+ - Nenhuma assinatura pública mudou; nenhuma rota existente mudou de path.
1979
+ - O token do magic link de sudo continua sendo **outro** token, de escopo sudo:
1980
+ `randomBytes(32)`, hasheado na sessão que o pediu, uso único, 5 minutos,
1981
+ vinculado à conta emissora, e nunca autentica ninguém. Nada é compartilhado com
1982
+ o token de login — só a infraestrutura de ENTREGA do e-mail.
1983
+
1984
+ ## Limite conhecido
1985
+
1986
+ O aviso do item 4 só vê a lista que passou pelo **config**. Um host que declare
1987
+ `sudoMethods` apenas no argumento de `registerAuthHost(router, { … })` não é
1988
+ coberto: o registro de rotas acontece antes de o config lazy resolver, e
1989
+ `authMethods` vive no config. É o caso de quem escreveu a lista à mão — não o de
1990
+ quem herdou os defaults, que é quem o aviso existe para pegar.
1991
+
1992
+ - 0da4974: Novo `realAccountId(ctx)` — o id do humano real, para checagens de permissão que impersonation não pode enganar
1993
+
1994
+ **Se o seu app tem um role check, leia isto.** Durante uma impersonation ativa,
1995
+ `getAccountId(ctx)` devolve a conta **PERSONIFICADA**, não o admin que a
1996
+ personificou. Isso é correto para "como qual conta este request está agindo?" —
1997
+ e é a entrada **ERRADA** para "esta pessoa pode fazer isto?". Um gate escrito
1998
+ como `hasRole(getAccountId(ctx), 'admin')` erra nos dois sentidos: entrega os
1999
+ privilégios do admin a quem está sendo personificado (quando o alvo é admin) e
2000
+ tira do admin real o próprio acesso (quando não é).
2001
+
2002
+ **O padrão que isto substitui.** Auditando um app real encontramos a mesma
2003
+ expressão copiada à mão em quatro gates de autorização de produção — o
2004
+ `governanceAuthorize` do agent, o gate do dashboard durable, o middleware de
2005
+ `/admin` e o listener que concede role:
2006
+
2007
+ ```ts
2008
+ const realId = impersonationState(ctx).impersonatorId ?? getAccountId(ctx);
2009
+ ```
2010
+
2011
+ Um dos quatro carregava um comentário avisando que inverter a ordem do `??`
2012
+ removeria a proteção. Expressão de segurança cuja correção depende da ordem dos
2013
+ operandos, copiada em quatro arquivos, é uma API da lib que ainda não tinha
2014
+ nome. Agora tem:
2015
+
2016
+ ```ts
2017
+ import { realAccountId } from "@adonis-agora/authkit-server";
2018
+
2019
+ const id = realAccountId(ctx);
2020
+ if (!id || !(await authz.hasRole(id, "admin"))) throw new Error("forbidden");
2021
+ ```
2022
+
2023
+ ```
2024
+ impersonation ativa → o id do impersonator (o admin real)
2025
+ sem impersonation → o id da conta logada
2026
+ sem sessão → null
2027
+ ```
2028
+
2029
+ **Qual dos dois usar.**
2030
+
2031
+ | Pergunta | Helper |
2032
+ | ----------------------------------------------------------------------------------------------------- | -------------------- |
2033
+ | "esta pessoa pode fazer isto?" (role, permissão, aprovação, auditoria de quem agiu) | `realAccountId(ctx)` |
2034
+ | "como qual conta este request está agindo?" (queries com escopo no alvo, UI, banner de impersonation) | `getAccountId(ctx)` |
2035
+
2036
+ **`getAccountId` não mudou** — nada no seu app quebra com esta versão. Isto é
2037
+ uma adição: o comportamento efetivo/aparente dele é contrato do qual a UI, o
2038
+ banner e as queries com escopo no alvo dependem. A migração é sua: troque
2039
+ `getAccountId` por `realAccountId` nas decisões de autorização, e só nelas.
2040
+
2041
+ - e5eb447: Uma convenção só de montagem de rotas: config E função, com uma regra de precedência
2042
+
2043
+ **A regra, em uma frase:** argumento omitido HERDA do config; chaves
2044
+ ESTRUTURAIS (`mountPath`, prefixos, quais telas montar, `accountLoginUrl`) o
2045
+ argumento vence; chaves de POLÍTICA (`social`, `rateLimit`, `sudoMethods` e o
2046
+ liga/desliga de `admin`/`adminApi`) o **config vence e trava** quando o
2047
+ `defineConfig` as declara — um `start/routes.ts` não pode afrouxar o que o
2048
+ arquivo de config permite, senão o config deixa de ser auditável.
2049
+
2050
+ **A mudança de comportamento mais provável de surpreender:** argumentos
2051
+ omitidos agora herdam do config em vez de caírem no default da lib. Em
2052
+ concreto, `config.sudo.methods` passa a decidir também o que é **MONTADO** —
2053
+ antes decidia só o que a tela oferecia e o que os handlers aceitavam, e a lista
2054
+ tinha de ser repetida em `registerAuthHost(router, { sudoMethods })`. Se você
2055
+ mantinha as duas listas, pode apagar a do `start/routes.ts`. Se elas
2056
+ divergiam, o que vale agora é a do config (era ela que já valia em runtime; o
2057
+ que mudava era só quais endpoints existiam, e os que sobravam falhavam
2058
+ fechados). Passar `sudoMethods` na chamada com `config.sudo.methods` presente
2059
+ passa a ser ignorado, com aviso nomeando a chave.
2060
+
2061
+ **Novidades:**
2062
+
2063
+ - `defineConfig({ routes })` — auto-montagem opcional. `true` ou um objeto faz
2064
+ o provider montar as rotas no boot chamando **a mesma** `registerAuthHost`
2065
+ exportada (não há segunda implementação); ausente ou `false` mantém o
2066
+ comportamento atual (você chama no `start/routes.ts`). Auto-montar E chamar à
2067
+ mão agora lança um erro explicando qual dos dois remover, em vez do
2068
+ `route name already exists` do AdonisJS.
2069
+ - `registerAuthHost` devolve o `AuthHostRouteMap` resolvido (prefixos, path de
2070
+ cada tela do console de conta, base da JSON API, telas montadas, rotas
2071
+ nomeadas, métodos de sudo montados). Entregue-o ao frontend — um override de
2072
+ `accountRoutes.paths` era meio recurso enquanto os `href`s do React tinham de
2073
+ ser atualizados à mão.
2074
+ - Divergências de política aparecem em `AuthHostRouteMap.overriddenByConfig` e
2075
+ como `console.warn` no boot; nunca são ignoradas em silêncio.
2076
+
2077
+ - ac19e73: Aplica a política `account_expiration` também no login social — antes só `disabled` valia ali
2078
+
2079
+ **Segurança.** O callback do login social (`AuthSocialController#callback`)
2080
+ chamava o gate de status compartilhado (`assertLoginAllowed`) **sem
2081
+ `settings`**. Como `assertAccountNotExpired` guarda as duas checagens de
2082
+ expiração atrás de `if (settings)`, o login social aplicava `disabled` e nada
2083
+ mais. Uma conta bloqueada pela política `account_expiration` (não loga há mais
2084
+ de `inactiveDays`) continuava entrando pelo "Continue with Google" — a política
2085
+ valia no login por senha, magic link, OTP e passkey, e falhava exatamente no
2086
+ caminho em que ninguém olha. Um operador que liga `account_expiration` acredita
2087
+ que ela vale em todos os caminhos de login.
2088
+
2089
+ **Correção.** O callback agora resolve os runtime settings pelo caminho
2090
+ canônico e os passa ao gate. O helper NON-NULL que faz isso
2091
+ (`resolveRuntimeSettings` + degradação para um `RuntimeSettings` no-op) era
2092
+ privado do `interaction_controller`; foi movido para
2093
+ `src/host/runtime_settings.ts` como `resolveRuntimeSettingsOrNoop` e agora é
2094
+ compartilhado pelos dois controllers, em vez de copiado.
2095
+
2096
+ **Consequência da atualização:** contas expiradas pela política
2097
+ `account_expiration` deixam de completar o login social — onde antes
2098
+ conseguiam. O evento de auditoria `account.expired_login_blocked` passa a ser
2099
+ emitido também por este caminho.
2100
+
2101
+ **`password_expiration` não passa a valer aqui — de propósito.** Nenhuma senha é
2102
+ usada no login social e este controller não renderiza a etapa de troca
2103
+ obrigatória (`step: 'password_expired'`): o único caminho até ela passa por
2104
+ `attemptPasswordLogin`, que só classifica a expiração DEPOIS de a senha correta
2105
+ ser verificada. Recusar por senha vencida num fluxo sem senha não teria para
2106
+ onde mandar o usuário, então o callback passa `passwordless: true` ao gate e
2107
+ apenas essa checagem é pulada. O que este caminho aplica é `disabled` e a
2108
+ expiração de conta por inatividade.
2109
+
2110
+ **Hosts sem a tabela `auth_settings` não são afetados:** a resolução degrada
2111
+ para um `RuntimeSettings` no-op, toda leitura vira null e o comportamento é
2112
+ idêntico ao anterior (login social conclui normalmente).
2113
+
2114
+ - 14dcb20: A setting de runtime `admin_impersonation` passa a ter leitor — governa o painel do console
2115
+
2116
+ `resolveEffectiveAdminImpersonation` existia, a key `admin_impersonation` podia
2117
+ ser escrita pelo console/Admin API/CLI, travada pelo `defineConfig` e validada —
2118
+ e **nenhum caminho de produção a lia**. Os únicos referenciadores eram testes e o
2119
+ CLI de settings. Ou seja: o admin mexia num interruptor que não fazia nada.
2120
+
2121
+ `src/host/admin_console/console_impersonation_controller.ts` agora consulta a
2122
+ setting. As duas camadas são propositalmente assimétricas:
2123
+
2124
+ 1. `config.admin.impersonation` decide se a CAPACIDADE existe. É decisão de boot:
2125
+ é ela que registra (ou não) o grant RFC 8693 no provider OIDC. Uma setting de
2126
+ runtime não desregistra rota que nunca foi registrada.
2127
+ 2. `admin_impersonation` decide se o CONSOLE OFERECE o painel. Decisão de
2128
+ runtime, mudável sem redeploy.
2129
+
2130
+ **A setting só APERTA, nunca AFROUXA.** Duas barreiras independentes: o gate de
2131
+ config é checado ANTES dela (`impersonation: false` → 404 mesmo com a setting em
2132
+ `true`), e declarar `admin.impersonation` no `defineConfig` TRAVA a key, fazendo
2133
+ `getSetting` devolver null e o resolver cair no valor do config. Config > runtime,
2134
+ a mesma precedência do resto da lib.
2135
+
2136
+ **Consequência da atualização:** em instalações que NÃO declaram
2137
+ `admin.impersonation` no `defineConfig` (a capacidade fica ligada por
2138
+ back-compat) e que já têm `admin_impersonation: { enabled: false }` gravado em
2139
+ `auth_settings`, o painel do console passa a responder 404 — antes o valor era
2140
+ ignorado. O grant RFC 8693 em si não muda: quem chama o token endpoint direto
2141
+ segue funcionando. Sem a tabela `auth_settings`, nada muda (fail-safe → config).
2142
+
2143
+ ### Patch Changes
2144
+
2145
+ - 37562df: Ativa de fato o gate de status no token-exchange (impersonation) — ele estava INERTE
2146
+
2147
+ **Segurança.** A mudança anterior (ver
2148
+ `gate-account-status-passwordless-logins`) adicionou um `accountStore` opcional
2149
+ a `TokenExchangeDeps` e uma checagem de status do `requested_subject` dentro de
2150
+ `src/provider/token_exchange.ts`. A lógica estava correta e coberta por teste —
2151
+ mas o ÚNICO call site de produção, `registerTokenExchange(provider, { … })` em
2152
+ `src/provider/oidc_service.ts`, nunca passava esse campo. Como o campo é
2153
+ opcional e degrada para "permitido" quando ausente (de propósito, para não
2154
+ quebrar hosts com stores mínimos), **a metade token-exchange daquela mudança
2155
+ nunca rodou no servidor de verdade**: um admin continuava conseguindo
2156
+ impersonar uma conta que acabara de desabilitar e receber um `access_token` e
2157
+ um `id_token` plenamente funcionais.
2158
+
2159
+ Se você rodou uma versão publicada com aquele changeset e sem este, o gate de
2160
+ impersonation estava inerte nela.
2161
+
2162
+ **Correção.** `oidc_service.ts` agora repassa `accountStore: config.accountStore`
2163
+ para `registerTokenExchange`. Nenhuma outra mudança de comportamento: o gate
2164
+ segue capability-probed (`supportsAccountStatus`), então hosts cujo
2165
+ `accountStore` não implementa a capacidade de status continuam permitindo o
2166
+ token-exchange exatamente como antes.
2167
+
2168
+ **Consequência da atualização:** um token-exchange cujo `requested_subject` é
2169
+ uma conta desabilitada passa a ser recusado com `invalid_grant` — onde antes
2170
+ mintava tokens.
2171
+
2172
+ **Nota de teste (a lição real).** O teste que existia construía o objeto de
2173
+ deps na mão e passava `accountStore` ele mesmo, então passava com ou sem o
2174
+ wiring de produção — foi exatamente assim que o gap sobreviveu. O teste
2175
+ adicionado aqui sobe o `OidcService` real e bate no endpoint `/token`, e falha
2176
+ se o wiring for removido. Dependência opcional que degrada para permissiva
2177
+ precisa de teste na raiz de composição, não só no consumidor.
2178
+
2179
+ - 3779efe: Corrige a checagem de senhas comuns (common-password block), que era um no-op em toda release publicada
2180
+
2181
+ A checagem offline de senhas comuns (`isCommonPassword`, usada por
2182
+ `assertAcceptable` no signup, na criação de usuário via admin, no reset e na
2183
+ troca forçada/self-service de senha) nunca funcionou em nenhum pacote
2184
+ publicado: o build copiava `common_passwords.txt` para `build/password/`,
2185
+ mas `tsc` emite o módulo que lê o arquivo para `build/src/password/` — um
2186
+ diretório diferente. `dirname(import.meta.url)` dentro do módulo compilado
2187
+ nunca encontrava o `.txt`, e o fail-safe (correto e necessário — um asset
2188
+ ausente não deve quebrar login) silenciosamente virava um `Set` vazio. Em
2189
+ todo host publicado, `password`, `123456` e o restante do top-10000 eram
2190
+ aceitos, a menos que o HIBP estivesse configurado _e_ acessível — e o HIBP
2191
+ também é fail-open.
2192
+
2193
+ Correção: o build agora copia o `.txt` para o diretório correto
2194
+ (`build/src/password/`), e `loadCommonPasswords` passou a resolver o
2195
+ arquivo por uma lista de candidatos (caminho-irmão primeiro, depois o
2196
+ layout antigo, depois `src/`), para sobreviver a uma futura mudança de
2197
+ `rootDir`/`outDir` sem quebrar de novo silenciosamente. O fail-safe
2198
+ (`Set` vazio quando o arquivo não é encontrado em nenhum candidato)
2199
+ continua intacto.
2200
+
2201
+ Consequência para quem já usa a lib: senhas fracas que antes eram aceitas
2202
+ (por exemplo `password`, `123456`, `qwerty`) passam a ser rejeitadas em
2203
+ **novas** escritas de senha (signup, reset, troca de senha). Hashes já
2204
+ armazenados não são tocados — nenhum usuário existente é bloqueado por
2205
+ essa mudança.
2206
+
2207
+ - 7e25837: `oidcRpGuard`: requisição não autenticada virava **500** em vez de 401/redirect, e `check()` reportava queda de banco como "não logado"
2208
+
2209
+ Três defeitos no `oidcRpGuard`, todos presentes desde a **0.54.0** — a versão que
2210
+ introduziu o guard. Se você plugou `oidcRpGuard()` em `config/auth.ts`, os três
2211
+ te afetam.
2212
+
2213
+ ## 1. Um visitante não autenticado recebia 500, não 401
2214
+
2215
+ O guard lançava `new RuntimeException('Unauthorized', { cause: 'E_UNAUTHORIZED_ACCESS' })`
2216
+ — uma exceção **genérica** carregando uma STRING em `cause`. Nada no framework
2217
+ casa com essa string: o `E_UNAUTHORIZED_ACCESS` de verdade é uma CLASSE, com
2218
+ `static status = 401`, um campo `redirectTo` e renderers html/json/jsonapi. O
2219
+ `RuntimeException` não tem nada disso.
2220
+
2221
+ **Sintoma:** por trás de `middleware.auth()`, uma visita sem sessão a qualquer
2222
+ rota protegida caía no handler de erro genérico do host e respondia **500**, com
2223
+ stack trace em vez da página de login. Em ambiente de dev isso aparece como
2224
+ "Unauthorized" numa tela de erro 500; em produção, como um 500 seco.
2225
+
2226
+ **Correção:** os dois pontos de falha de `authenticate()` (sem `account_user_id`
2227
+ na sessão; sessão válida mas usuário inexistente no provider) agora lançam o
2228
+ `E_UNAUTHORIZED_ACCESS` real do `@adonisjs/auth` — status 401, tratado pelo
2229
+ exception handler exatamente como o dos guards nativos. O `getUserOrFail()`
2230
+ também passa a lançar essa classe depois de uma tentativa de autenticação (a
2231
+ mensagem continua a mesma).
2232
+
2233
+ **Follow-up conhecido — o redirect para o login ainda não acontece.** Os
2234
+ renderers do `E_UNAUTHORIZED_ACCESS` são indexados por `guardDriverName`, e só a
2235
+ entrada `session` faz `redirect().withIntendedUrl().toPath(error.redirectTo)`.
2236
+ Como o `driverName` deste guard é `oidc_rp`, o `handle()` cai no fallback e
2237
+ responde um **401 seco** (correto, mas sem redirect). Expor um `redirectTo` na
2238
+ config do guard seria inerte enquanto o renderer não for endereçado — fica como
2239
+ mudança separada. O ganho desta versão é 500 → 401.
2240
+
2241
+ ## 2. `check()` engolia TUDO, inclusive falha de infraestrutura
2242
+
2243
+ Era `try { await this.authenticate() } catch { return false }`. Uma queda do
2244
+ Postgres dentro de `provider.findById` retornava `false` — indistinguível de
2245
+ "não está logado".
2246
+
2247
+ **Sintoma:** durante uma indisponibilidade do banco, **todo mundo aparecia
2248
+ deslogado**, sem nenhum erro nos logs vindo desse caminho. O incidente parecia
2249
+ um bug de sessão.
2250
+
2251
+ **Correção:** igual ao `SessionGuard` nativo do `@adonisjs/auth` — engole
2252
+ **apenas** `E_UNAUTHORIZED_ACCESS` e relança o resto. `check()` continua
2253
+ retornando `false` para "sem sessão"; erro de infraestrutura agora propaga.
2254
+
2255
+ ## 3. `@adonisjs/auth` estava virando peer obrigatório na prática
2256
+
2257
+ `src/host/oidc_rp_guard.ts` tinha um import **estático** de `@adonisjs/auth`
2258
+ (para `symbols`), e esse módulo é reexportado pelo `index.ts` do pacote. Ou seja:
2259
+ `import '@adonis-agora/authkit-server'` quebrava com `ERR_MODULE_NOT_FOUND` em
2260
+ qualquer host que não tivesse instalado `@adonisjs/auth` — que é um peer
2261
+ **opcional**. (O smoke de empacotamento do monorepo não pegava porque o pacote
2262
+ está instalado como devDep aqui.)
2263
+
2264
+ **Correção:** todo import de `@adonisjs/auth` neste módulo passou a ser
2265
+ type-only; o único acesso de runtime é um `await import('@adonisjs/auth')` dentro
2266
+ do resolver de `oidcRpGuard()` — que só roda quando o host resolve o
2267
+ `config/auth.ts`, ou seja, quando ele já escolheu usar `@adonisjs/auth`. Se o
2268
+ pacote não estiver instalado, o erro agora é uma mensagem explicando o que
2269
+ instalar, no boot, em vez de um crash de import.
2270
+
2271
+ - 6ab8147: Login social, magic link, OTP e passkey não aplicam mais `password_expiration`
2272
+
2273
+ **Correção de bloqueio total.** Os fluxos passwordless chamam o gate combinado
2274
+ `assertLoginAllowed`, e `assertAccountNotExpired` guardava as DUAS checagens de
2275
+ expiração atrás do mesmo `if (settings)` — não havia como ligar a de conta
2276
+ inativa sem ligar junto a de senha vencida.
2277
+
2278
+ O problema é que `isPasswordExpired` trata `password_changed_at` NULL como
2279
+ "vencida". Isso é correto no fluxo de senha, que tem o passo de troca
2280
+ obrigatória (`step: 'password_expired'`) para onde mandar o usuário. Uma conta
2281
+ que nunca definiu senha — social-only, importada via `importAccount` (que não
2282
+ escreve a coluna), anterior à migração da coluna — não tem esse passo
2283
+ alcançável: o único caminho até ele passa por `attemptPasswordLogin`, que exige
2284
+ a senha CORRETA antes de classificar a expiração.
2285
+
2286
+ Resultado, com `password_expiration` ligada: todo usuário sem senha ficava
2287
+ **permanentemente trancado fora** de "Continue with Google", do magic link, do
2288
+ OTP e do passkey-first, devolvido a uma tela que não oferece senha nenhuma que
2289
+ ele pudesse trocar. No passkey-first o beco era ainda mais fechado — a view de
2290
+ erro (`mfa-challenge`) só oferece o desafio da passkey. Contas social criadas
2291
+ por esta lib recebem `password_changed_at = now` no `create`, então para elas o
2292
+ bloqueio não era imediato: chegava sozinho depois de `maxAgeDays` (default 90).
2293
+
2294
+ **Correção.** `LoginAllowedInput` ganha `passwordless?: boolean`. Quando `true`,
2295
+ apenas a checagem de senha vencida é pulada. Os quatro fluxos passwordless
2296
+ passam a flag; `attemptPasswordLogin` e o token-exchange não, e seguem
2297
+ inalterados. O default (ausente/`false`) mantém a checagem valendo.
2298
+
2299
+ **`account_expiration` (inatividade) continua valendo em todos eles**, incluindo
2300
+ o login social — o que a versão anterior corrigiu não foi desfeito. A checagem
2301
+ de conta desabilitada também segue valendo.
2302
+
2303
+ `isPasswordExpired` não foi alterado: seu comportamento com a coluna NULL
2304
+ continua correto para o fluxo de senha, que é quem tem para onde mandar o
2305
+ usuário.
2306
+
2307
+ ## 0.54.0
2308
+
2309
+ ### Minor Changes
2310
+
2311
+ - 678b1bb: Add `oidcRpGuard` — an `@adonisjs/auth` guard for OIDC Relying Parties. RPs that consume the issuer via Authorization Code + PKCE can plug this guard in `config/auth.ts` to get native `ctx.auth.user` / `auth.check()` / `middleware.auth()` support without a custom guard implementation.
2312
+
2313
+ ```ts
2314
+ import { oidcRpGuard } from "@adonis-agora/authkit-server";
2315
+ import { sessionUserProvider } from "@adonisjs/auth/session";
2316
+
2317
+ export default defineConfig({
2318
+ default: "web",
2319
+ guards: {
2320
+ web: oidcRpGuard({
2321
+ provider: sessionUserProvider({ model: () => import("#models/user") }),
2322
+ }),
2323
+ },
2324
+ });
2325
+ ```
2326
+
2327
+ ## 0.53.0
2328
+
2329
+ ### Minor Changes
2330
+
2331
+ - Anti-duplo-submit nas views built-in e tela amigável de throttle:
2332
+
2333
+ - Novo partial `authkit::partials/submit_lock` (trava anti-duplo-submit por delegação no `document`, agendada pós-dispatch) incluído em todas as views Edge com forms POST (login, signup, forgot, reset, mfa-challenge, consent, maintenance e `account/*`). Forms disparados via `form.submit()` (auto-submit de passkey) não disparam `submit` e seguem destravados de propósito.
2334
+ - Os throttles de browser (`login`, `sudo`, `otpLogin`) agora renderizam a view themeável `throttled` (Edge built-in; allowlistável no `inertiaRenderer`) em vez do 429 cru do limiter — com header `Retry-After`, os segundos na tela e link de volta pro passo atual (raiz da interaction quando há `uid`). Os throttles de API (`introspection`, `adminIp`) seguem com o 429 cru, que é o contrato correto pra máquina.
2335
+ - Novas chaves i18n `throttled.*` nos catálogos embutidos `en` e `pt-BR`.
2336
+
2337
+ ## 0.52.1
2338
+
2339
+ ### Patch Changes
2340
+
2341
+ - c4f6d4a: Corrige a tela `session-expired` respondendo 400 com corpo vazio (recuperação de interaction perdida no modo `screen`)
2342
+
2343
+ A recuperação graciosa da sessão de interaction perdida introduzida na 0.52.0
2344
+ estava QUEBRADA out-of-the-box no modo `screen` (default): renderizava um 400
2345
+ com **corpo vazio** em qualquer host, sem uma ponte manual no
2346
+ `app/exceptions/handler.ts`.
2347
+
2348
+ Causa: no modo `screen`, `recoverLostInteraction` fazia `return render(...)`, e
2349
+ tanto o renderer Edge (`view.render`) quanto o Inertia (`inertia.render`)
2350
+ RETORNAM o HTML/payload em vez de escrever no response. No caminho de exception
2351
+ handler do AdonisJS, o valor retornado do `handle()` da exceção é DESCARTADO
2352
+ (apenas o dispatch normal de rota escreve o retorno via `useReturnValue`/
2353
+ `canWriteResponseBody`), então o corpo nunca era enviado — contradizendo o
2354
+ próprio contrato da feature ("roda de forma centralizada, sem depender de o host
2355
+ customizar o `app/exceptions/handler.ts`").
2356
+
2357
+ Correção: no modo `screen`, a lib agora ESCREVE o body ela mesma, replicando
2358
+ fielmente o contrato `canWriteResponseBody` do http-server — após
2359
+ `ctx.response.status(400)`, `const body = await render(...)` e, se
2360
+ `body !== undefined && !ctx.response.hasLazyBody && body !== ctx.response`,
2361
+ `ctx.response.send(body)`. O guard `hasLazyBody` evita double-write e cobre os
2362
+ DOIS renderers built-in (Edge e Inertia). O modo `redirect` e o fluxo normal
2363
+ (sessão válida) permanecem inalterados.
2364
+
2365
+ ## 0.52.0
2366
+
2367
+ ### Minor Changes
2368
+
2369
+ - b895a11: Recuperação graciosa da sessão de interaction OIDC perdida (`SessionNotFound`)
2370
+
2371
+ Quando a sessão de interaction do `oidc-provider` está expirada ou perdida
2372
+ (cookie velho, F5 tardio depois do TTL, restart do servidor que limpou o store
2373
+ efêmero), `provider.interactionDetails()` lançava `SessionNotFound` e o erro
2374
+ vazava cru para o usuário no meio do login. Perder essa sessão é um caso NORMAL,
2375
+ então o authkit agora RECUPERA por padrão.
2376
+
2377
+ - **Comportamento padrão (zero config): tela themeável `session-expired`.** Nova
2378
+ view Edge built-in (pt-BR + en) com mensagem amigável e link "voltar ao login".
2379
+ O host pode substituí-la por uma página React adicionando `'session-expired'`
2380
+ ao allowlist `views` do `inertiaRenderer` (props: `{ loginUrl, brand }`).
2381
+ - **Opção de redirect.** `interactionRecovery: { mode: 'redirect', redirectTo }`
2382
+ responde 302 para o login em vez de renderizar a tela. Default:
2383
+ `{ mode: 'screen' }`. O `redirectTo` cai em `accountLoginUrl` quando omitido.
2384
+ - **Centralizado.** O `SessionNotFound` é detectado num único choke point
2385
+ (`createInteractionActions().details`/`consent`) pelo nome da classe do erro
2386
+ (não por match de mensagem) e convertido na exceção self-handling
2387
+ `InteractionSessionLostException` — nenhum handler de interaction precisou de
2388
+ try/catch próprio. Fluxo normal (sessão válida) inalterado.
2389
+
2390
+ ## 0.51.1
2391
+
2392
+ ### Patch Changes
2393
+
2394
+ - 07f4191: Propaga `otpEnabled` para TODOS os renders do passo login (não só `magicLinkSent`).
2395
+
2396
+ O login choose-first (parâmetro `channel`) precisa que a tela do SELETOR — a de
2397
+ senha renderizada por `show()` depois que o e-mail entrou na sessão, com
2398
+ `magicLinkSent` ainda falso — saiba se o login por OTP está disponível, para
2399
+ oferecer a opção "código" ANTES de qualquer envio de magic link. Até agora
2400
+ `otpEnabled` só era injetado nos renders de `magicLinkRequest` e `otpVerify`,
2401
+ então o host não conseguia mostrar a opção de código no seletor.
2402
+
2403
+ `otpEnabled` (`login.otp.enabled` E o store suporta a capacidade) passa a sair do
2404
+ helper `#loginMethods` — junto de `authMethods` e `magicLinkAvailable`, por ser
2405
+ um fato de disponibilidade de método de login. Assim os renders do passo
2406
+ identifier, do seletor, do `magicLinkSent` e de erro carregam a flag por
2407
+ construção. As computações inline redundantes em `magicLinkRequest`/`otpVerify`
2408
+ foram removidas (o valor onde já era usado permanece idêntico); a emissão de
2409
+ token, o codec `ml2:`, o lockout e qualquer comportamento de segurança ficam
2410
+ intocados.
2411
+
2412
+ Back-compat total: hosts que não leem `otpEnabled` não são afetados; a `login.edge`
2413
+ default continua mostrando o campo de OTP apenas no estado `magicLinkSent`.
2414
+
2415
+ ## 0.51.0
2416
+
2417
+ ### Minor Changes
2418
+
2419
+ - 500a5ee: Adiciona o parâmetro `channel` ao login passwordless (seletor "choose-first").
2420
+
2421
+ O POST `/auth/interaction/:uid/magic` passa a aceitar `channel=code|link` no
2422
+ body. Quando presente, o e-mail e a tela mostram SÓ aquele método:
2423
+
2424
+ - `channel=code` → e-mail com SÓ o código (sem botão/link); a tela mostra apenas
2425
+ o campo de código.
2426
+ - `channel=link` → e-mail com SÓ o link mágico (código suprimido); a tela mostra
2427
+ apenas o aviso de "confira sua caixa".
2428
+ - `channel` ausente/ inválido → ambos, exatamente como hoje (back-compat total).
2429
+
2430
+ O `channel` é puramente de SUPERFÍCIE: NÃO condiciona a emissão de token e não
2431
+ toca no codec `ml2:`, no lockout nem no single-use-conjunto — a lib continua
2432
+ emitindo link E código co-locados quando `login.otp.enabled`. O que muda é só o
2433
+ que o e-mail renderiza e qual sub-view a tela exibe.
2434
+
2435
+ Threading do canal:
2436
+
2437
+ - `mail.onMagicLink` ganha o campo opcional `channel?: 'code' | 'link'` no
2438
+ payload (hosts existentes simplesmente ignoram — back-compat).
2439
+ - O `sendMagicLinkEmail` default renderiza o e-mail conforme o canal (só código,
2440
+ só link ou ambos), com degradação limpa (`channel=code` sem código emitido cai
2441
+ no e-mail de link).
2442
+ - O render do estado `magicLinkSent` ganha a prop `magicChannel`
2443
+ (`'code' | 'link' | 'both'`) para a tela escolher a sub-view. Ausente = `'both'`.
2444
+
2445
+ Sem `channel` no body, tudo é idêntico ao comportamento anterior.
2446
+
2447
+ ## 0.50.0
2448
+
2449
+ ### Minor Changes
2450
+
2451
+ - 28013ed: Adiciona login por OTP (código digitável) como extensão opt-in do magic link.
2452
+
2453
+ Hosts passwordless agora podem oferecer, ALÉM do magic link, um código numérico
2454
+ digitável — para quem lê o e-mail no celular e usa o app no desktop. O MESMO
2455
+ e-mail passa a carregar link E código, e os dois completam a MESMA interaction
2456
+ OIDC (amr `['email']`, resultado idêntico ao do link).
2457
+
2458
+ Ligue com `login.otp` no `config/authkit.ts` (default **desligado**, back-compat
2459
+ total — sem a config o comportamento e o e-mail são idênticos aos de antes):
2460
+
2461
+ ```ts
2462
+ login: {
2463
+ otp: { enabled: true, digits: 6, ttlMinutes: 10, maxAttempts: 5 },
2464
+ }
2465
+ ```
2466
+
2467
+ Segurança (código de 6 dígitos é adivinhável, ≠ magic link de 256 bits):
2468
+
2469
+ - **Lockout por interaction fail-CLOSED**: o contador de tentativas fica
2470
+ PERSISTIDO junto do código (no mesmo slot do magic link), então a proteção
2471
+ anti-brute-force funciona MESMO sem `@adonisjs/limiter` — diferente do
2472
+ `otp_lockout` do fator TOTP, que vira no-op sem limiter. Na 5ª falha o código é
2473
+ invalidado (o link continua válido). A verificação faz o read-modify-write do
2474
+ contador dentro de uma transação com row-lock (`forUpdate`), serializando as
2475
+ tentativas: N verificações concorrentes não conseguem burlar o lockout
2476
+ (o total de comparações contra um mesmo código fica limitado a `maxAttempts`).
2477
+ - **Throttle de rota dedicado** `authkit_otp_login` por IP, mais apertado que o
2478
+ login (5/min), como camada extra.
2479
+ - Geração cripto sem viés de módulo (`randomInt`, zero-padded), comparação de
2480
+ hash constant-time (`timingSafeEqual`), hash atrelado ao `uid` da interaction.
2481
+ - **Single-use conjunto**: consumir o código mata o link e vice-versa.
2482
+
2483
+ Novidades de API: `login.otp` na config; `OtpLoginCapability` +
2484
+ `supportsOtpLogin` no account store (o store Lucid default já implementa, sem
2485
+ migração — co-localiza o código no slot `passwordResetToken`); slot `code` no
2486
+ template de e-mail e no payload do hook `onMagicLink`; eventos de auditoria
2487
+ `login.otp_sent` / `login.otp_verified` / `login.otp_failed` /
2488
+ `login.otp_invalidated`; rota `POST /auth/interaction/:uid/otp-verify`; strings
2489
+ i18n pt-BR + en. A view Edge default ganha o campo de código no bloco
2490
+ "link enviado".
2491
+
2492
+ ## 0.49.0
2493
+
2494
+ ### Minor Changes
2495
+
2496
+ - efd30df: Exporta os tipos de props das telas de conta e os helpers de path do console.
2497
+
2498
+ Hosts que criam as telas do console (`/account/*`) em React próprio (via
2499
+ `inertiaRenderer`) agora tipam cada página com os tipos exportados
2500
+ `AccountLoginProps`, `AccountSecurityProps`, `AccountMfaProps`,
2501
+ `AccountConfirmProps` e `AccountEmailConfirmedProps` (mais `AccountConfirmMethod`),
2502
+ em vez de copiar o shape do docblock à mão. Esses tipos são a fonte única da
2503
+ verdade: os próprios controllers os satisfazem (`satisfies Omit<…, 'messages'>`)
2504
+ ao renderizar, então qualquer divergência quebra o build da lib. O docblock do
2505
+ `inertiaRenderer` passa a referenciar os tipos.
2506
+
2507
+ Também passam a ser exportados os helpers de path do console —
2508
+ `accountPath`, `joinAccountPath`, `accountPrefix` e o tipo `AccountPathsOptions`
2509
+ (e `AccountPathKey`) — para que um host derive rotas do console (ex.:
2510
+ `GET ${accountPath('security')}/export`) respeitando os overrides de
2511
+ `accountRoutes`, em vez de hardcodar. Eles refletem os overrides após
2512
+ `registerAuthHost` rodar.
2513
+
2514
+ ## 0.48.0
2515
+
2516
+ ### Minor Changes
2517
+
2518
+ - bb257c0: Rotas do console de conta configuráveis/localizáveis + 2 fixes de MFA
2519
+
2520
+ **Feature: `accountRoutes`.** Novo módulo `account_paths.ts` (singleton de processo, no mesmo padrão de `admin_prefix`/`account_login_url`) que torna o prefixo do console de conta (`/account` → `/conta`) e o segmento de cada tela navegável (`security` → `seguranca`, `confirm` → `confirmar`, ...) configuráveis via a opção top-level `accountRoutes: { prefix?, paths? }` no `registerAuthHost`. O prefixo/segmentos se propagam por todas as camadas: registro de rotas, redirects dos controllers, redirect de sudo, fluxo magic-link, URLs dos e-mails transacionais e as views Edge (incl. os `fetch()` do `mfa.edge`, via a prop global `accountPaths`). `getAccountLoginUrl` e `accountHome` passam a derivar dos overrides.
2521
+
2522
+ Os action-subpaths dos POSTs internos (`/password`, `/enroll`, `/passkeys/verify`, ...) e o segmento `api` da JSON API (`{prefix}/api/*`) permanecem FIXOS — são endpoints de máquina, invisíveis ao usuário. A opção é top-level de propósito: mesmo com `account: false`, as rotas de sudo e a JSON API continuam montadas e respeitam o prefixo.
2523
+
2524
+ Back-compat total: sem `accountRoutes`, tudo permanece em `/account/*`.
2525
+
2526
+ **Fix (`inertiaRenderer`):** o docblock de contrato de props da tela `account/mfa` documentava só o shape do `index`; agora inclui `enrolling`, `secret`, `qrDataUrl` e `error`, que o controller injeta nos passos de `enroll`/`confirm`.
2527
+
2528
+ **Fix (`passkeyRegisterVerify`):** no sucesso com sudo já ativo o endpoint respondia sempre `{ ok: true }` JSON — um `<form>` HTML clássico ficava encarando JSON cru. Agora detecta navegação (aceita `text/html` e não pede `application/json`) e responde redirect para a tela de MFA, mantendo o JSON para XHR/fetch.
2529
+
2530
+ ## 0.47.0
2531
+
2532
+ ### Minor Changes
2533
+
2534
+ - b1ba275: Montagem por tela do console de conta (`/account/*`) + destino de login configurável.
2535
+
2536
+ **1 — `AuthHostOptions.account`.** Nova opção `account?: false | { login?, tokens?,
2537
+ orgs?, security?, mfa?, apps? }` em `registerAuthHost`, espelhando o padrão
2538
+ `admin`/`adminApi`: a decisão de MONTAR cada grupo de rotas é tomada em tempo de
2539
+ registro. Default (opção ausente) = tudo montado — **back-compat total**. `false`
2540
+ desmonta todas as telas; um objeto desmonta seletivamente (cada flag ausente
2541
+ default `true`). As rotas de sudo (`/account/confirm`) e a JSON API
2542
+ (`/account/api/*`) permanecem sempre montadas — são infraestrutura, não telas.
2543
+
2544
+ **2 — `AuthHostOptions.accountLoginUrl`.** Destino configurável do redirect de
2545
+ "não-autenticado → faça login", default `/account/login`. Necessário porque a tela
2546
+ `account/login` passou a ser desmontável: um host OIDC passwordless aponta para a
2547
+ própria rota de login (ex.: `/login`). É respeitado por TODOS os pontos de redirect/
2548
+ link: `accountGuard`, `adminGuard`, `AccountAuthMiddleware`, o helper público
2549
+ `consoleLoginUrl()`, os fallbacks dos controllers de conta e a view Edge
2550
+ `otp-unlock` (injetada como prop global `loginUrl` pelo renderer). Novo singleton de
2551
+ processo `account_login_url.ts` (mesmo padrão de `admin_prefix.ts`).
2552
+
2553
+ **3 — `/account/tokens` sem `patStore` → 404 limpo.** As três actions de
2554
+ `account_tokens_controller` faziam `cfg.patStore!` sobre config opcional →
2555
+ `Cannot read properties of undefined` (500). Agora degradam para 404, como orgs
2556
+ sem tabelas. Mesma classe de bug corrigida em `pat_introspection_controller`
2557
+ (`/authkit/pat/introspect`, sempre montada): sem `patStore`, devolve `{ active: false }`
2558
+ em vez de 500 — resposta negativa do protocolo (RFC 7662), não um 404 HTTP, já
2559
+ que o endpoint é M2M JSON e sempre existe independente do store.
2560
+
2561
+ **4 — Contrato de props documentado.** Adicionadas as tabelas de props de
2562
+ `account/security`, `account/mfa`, `account/confirm` e `account/email-confirmed`
2563
+ ao docblock de `inertiaRenderer` (extraídas dos controllers reais), completando o
2564
+ que já existia para `account/login`.
2565
+
2566
+ **5 — `verifyCredentials` com hash null/vazio.** Coberto por teste que
2567
+ `lucidAccountStore.verifyCredentials` devolve `null` sem lançar quando a coluna
2568
+ `password` é null ou vazia (contas passwordless) — pré-requisito para o app tornar
2569
+ a coluna nullable. Nenhuma correção foi necessária: o `try/catch` de
2570
+ `PasswordManager.verify` já engolia o throw do scrypt em hash malformado; o teste
2571
+ pina o contrato.
2572
+
2573
+ ### Patch Changes
2574
+
2575
+ - 481ae7a: Corrige dois resíduos do dual-package hazard e endurece a visibilidade de falha.
2576
+
2577
+ **1 — `recordSubRevocation` resolvia o Lucid pela classe `Database`.**
2578
+ `AdminSessionsService.recordSubRevocation` ainda fazia `import('@adonisjs/lucid/services/db')`,
2579
+ que resolve a CLASSE `Database` e a usa como TOKEN do container. Com duas cópias físicas do
2580
+ lucid na árvore do host (pins distintos, ou o mesmo pin sob peer sets distintos que o pnpm
2581
+ materializa em diretórios separados), os tokens-classe diferem, o `make()` falha e — pior — a
2582
+ gravação da revogação por `sub` (`auth_session_revocations`) era engolida EM SILÊNCIO pelo catch
2583
+ best-effort: nem crash, nem log. Agora resolve pelo alias string `'lucid.db'` (o mesmo idioma de
2584
+ `runtime_settings.ts` e do provider), imune à deduplicação do host. A falha continua sem
2585
+ propagar (a invalidação server-side já é a fonte da verdade), mas nunca mais é silenciosa: é
2586
+ logada em `error`.
2587
+
2588
+ **2 — `services/main` capturava o `app` do core por import eager.**
2589
+ `services/main.ts` importava `app` de `@adonisjs/core/services/app` (eager) + `await app.booted()`
2590
+ no top-level — o mesmo hazard, agora para o singleton do core: sob pnpm este pacote pode resolver
2591
+ uma cópia física de `@adonisjs/core` diferente da que o `bin/server` bootou, cujo binding de
2592
+ `services/app` fica `undefined`. O `app` passa a vir de `services/booted_app.ts`, capturado pelo
2593
+ provider no `register()` via `setBootedApp(this.app)`. Back-compat total: o `default` continua
2594
+ sendo o `OidcService` resolvido.
2595
+
2596
+ Também: adicionado `prepack` (espelha o `build` composto — css + webauthn + ui + tsc + cópias)
2597
+ para que o pacote seja sempre construído antes de publicar.
2598
+
2599
+ **Nota de transparência:** a escrita best-effort da revogação de sessão e o
2600
+ `services/main` agora exigem o `AuthkitServerProvider` registrado; sem ele,
2601
+ lançam/logam erro explícito em vez de silenciar ou pendurar. Nenhum host real
2602
+ perde comportamento (o provider é sempre registrado), mas suítes downstream que
2603
+ exercitem deleção/revogação sem o provider verão o erro no stderr.
2604
+
2605
+ ## 0.46.0
2606
+
2607
+ ### Minor Changes
2608
+
2609
+ - 4b17ac4: SPI de métodos de sudo (`SudoMethod`), com `completeSudo` como ponto único de
2610
+ concessão de privilégio.
2611
+
2612
+ **Por quê:** hosts passwordless ficavam permanentemente presos fora de toda
2613
+ operação sensível da área de conta — exportar/excluir dados (LGPD), MFA, PATs,
2614
+ troca de e-mail. Sudo exigia senha ou passkey; o host não tem senha; e cadastrar
2615
+ passkey também exige sudo. Deadlock fechado, e era o comportamento DEFAULT:
2616
+ `requireSudo` cai em `SUDO_MODE_DEFAULTS.enabled = true` quando o host não tem
2617
+ `SettingsCapability`.
2618
+
2619
+ **Novos métodos:** `sudoMethods.oidcStepUp({ url })` (sempre disponível — é o que
2620
+ quebra o deadlock, via `prompt=login`) e `sudoMethods.magicLink()` (token de
2621
+ escopo próprio, nunca o de login: `randomBytes(32)`, hash na sessão, single-use,
2622
+ 5 min, vinculado à conta emissora). `password` e `passkey` foram migrados para o
2623
+ SPI mantendo suas URLs históricas (`POST /account/confirm`,
2624
+ `POST /account/confirm/passkey[/options]`).
2625
+
2626
+ **Como configurar.** A lista vai em DOIS lugares e eles precisam casar:
2627
+
2628
+ ```ts
2629
+ // config/authkit.ts — o que a TELA oferece e o que os handlers ACEITAM
2630
+ defineConfig({
2631
+ sudo: {
2632
+ methods: [
2633
+ sudoMethods.oidcStepUp({ url: "/auth/step-up" }),
2634
+ sudoMethods.password(),
2635
+ ],
2636
+ },
2637
+ });
2638
+
2639
+ // start/routes.ts — o que tem ROTA montada
2640
+ registerAuthHost(router, {
2641
+ mountPath: "/oidc",
2642
+ sudoMethods: [sudoMethods.password()],
2643
+ });
2644
+ ```
2645
+
2646
+ **`sudoMethods` SUBSTITUI os defaults, não acrescenta a eles.** O exemplo acima
2647
+ monta SÓ `password`: `passkey`, que vinha por default, some sem aviso nenhum.
2648
+ Passar a opção é declarar a lista COMPLETA do host — quem quiser os built-in
2649
+ junto do seu método precisa repeti-los (`[sudoMethods.password(),
2650
+ sudoMethods.passkey(), meuMetodo()]`). Vale igual para `config.sudo.methods`.
2651
+
2652
+ São dois porque a montagem de rotas acontece em tempo de registro, antes de o
2653
+ config (lazy) resolver — mesma razão de `social`/`admin`/`rateLimit`. Sem
2654
+ `AuthHostOptions.sudoMethods`, um método fora dos built-in apareceria na tela
2655
+ com endpoint 404 — e `magicLink()` não seria alcançável em runtime de jeito
2656
+ nenhum. Divergiram, a tela loga um aviso de flag-drift.
2657
+
2658
+ **Sem `config.sudo.methods`, os dois lados não têm como divergir:** a tela
2659
+ oferece a lista que `registerAuthHost` MONTOU, que é exatamente a que os
2660
+ handlers aceitam. Um host que passe só
2661
+
2662
+ ```ts
2663
+ registerAuthHost(router, { sudoMethods: [sudoMethods.magicLink()] });
2664
+ ```
2665
+
2666
+ vê a tela oferecer magic-link, e só. (Antes ela caía numa lista de defaults
2667
+ própria e oferecia password + passkey, ambos 404, escondendo o único método
2668
+ que de fato funcionava.)
2669
+
2670
+ `config.sudo.methods` desabilita o endpoint DE FATO, não só a opção da tela: o
2671
+ runtime embrulha os handlers no ponto de registro, então a barreira vale
2672
+ inclusive para um método customizado que nunca a tenha consultado.
2673
+
2674
+ O escopo exato: a rota tem de ter sido montada por `SudoMethod.register()` **e**
2675
+ com handler-FUNÇÃO — é a única forma que o wrapper sabe embrulhar. Registrar por
2676
+ tupla (`[Controller, 'metodo']`) ou por `resource()`/`shallowResource()` agora
2677
+ **lança no boot**, com a mensagem explicando a saída
2678
+ (`(ctx) => new Controller().metodo(ctx)`). Antes passava direto e produzia uma
2679
+ rota de sudo desguardada, em silêncio.
2680
+
2681
+ Fora disso o escopo acaba: com `completeSudo` público, qualquer rota que o host
2682
+ escreva à mão em `start/routes.ts` concede sudo sem passar por
2683
+ `config.sudo.methods`. Não é bug, é a consequência inevitável de exportar
2684
+ `completeSudo` — o `oidcStepUp` exige isso, porque quem valida o grant é o
2685
+ callback do host.
2686
+
2687
+ ***
2688
+
2689
+ ### Novos exports
2690
+
2691
+ `sudoMethods`, `completeSudo`, `sudoContextFrom`, `failSudo`,
2692
+ `LAST_METHOD_SESSION_KEY` e os tipos `SudoMethod`, `SudoContext`,
2693
+ `SudoMethodDescriptor`, `SudoRouteHelpers`.
2694
+
2695
+ `completeSudo` é público porque o host PRECISA chamá-lo: `oidcStepUp` não
2696
+ registra rotas — quem valida o grant é o callback do host. `markSudo` continua
2697
+ exportada mas está `@deprecated`: ela grava a marca e nada mais (sem audit
2698
+ `sudo.confirmed`, sem preferência de método, sem redirect para o `return_to`).
2699
+
2700
+ ***
2701
+
2702
+ ### BREAKING — telas custom da view `account/confirm`
2703
+
2704
+ As props mudaram de `{ passwordless, passkeyAvailable }` para
2705
+ `{ methods, preferredId, notice }`:
2706
+
2707
+ - `methods`: `Array<{ id, labelKey, kind: 'form' | 'action' | 'redirect' | 'webauthn', endpoint, fields? }>`;
2708
+ - `preferredId`: id do último método usado com sucesso, para destaque;
2709
+ - `notice`: aviso já traduzido (ex.: "link enviado"), irmão de `error`.
2710
+
2711
+ Hosts que usam o template Edge embutido não precisam fazer nada.
2712
+
2713
+ ### BREAKING NO DEPLOY — sessões e challenges vivos perdem a confirmação
2714
+
2715
+ A vinculação à conta virou ESTRITA (fail-closed) em dois pontos: o challenge de
2716
+ passkey do confirm e a marca de sudo (`authkit_sudo_account`). Marca ou
2717
+ challenge sem vinculação é recusado — e é exatamente essa a forma que as
2718
+ sessões anteriores ao deploy têm. Consequência prática: quem estava com sudo
2719
+ ativo, ou com um challenge de passkey pendente, no momento do deploy precisa
2720
+ reconfirmar UMA vez. Não há migração possível: o dado que faltava (de quem era a
2721
+ marca) não existe retroativamente.
2722
+
2723
+ O caminho de reconfirmação continua existindo para todo mundo, inclusive para
2724
+ contas passkey-only em host Edge: o template embutido roda o handshake WebAuthn
2725
+ completo (ver `kind: 'webauthn'` abaixo). Quem usa renderer próprio
2726
+ (React/Inertia) precisa garantir que a sua tela também o faça.
2727
+
2728
+ ***
2729
+
2730
+ ### Comportamentos deliberados, para não parecerem bug
2731
+
2732
+ **O login primário concede sudo à revelia de `config.sudo.methods`.** É correto
2733
+ e intencional: autenticação primária recente É confirmação de identidade — o
2734
+ modelo do GitHub. `sudo.methods` governa a TELA DE RE-CONFIRMAÇÃO (o que
2735
+ oferecer a quem já está logado há um tempo), não o login. Um host que remova
2736
+ `password` de `sudo.methods` continua vendo sudo concedido logo após um login
2737
+ por senha, e isso não é a config sendo ignorada.
2738
+
2739
+ **`kind: 'webauthn'` — um método pode exigir JavaScript, e a tela sabe disso.**
2740
+ WebAuthn não é um submit: o navegador tem de buscar as options, assinar o
2741
+ challenge e só então postar a assertion. Renderizado como form comum, o campo
2742
+ `response` vai vazio e o handler recusa sempre. Por isso `passkey().describe()`
2743
+ devolve `kind: 'webauthn'` (não `'action'`), e o template embutido roda o
2744
+ handshake: pede `POST <endpoint>/options`, chama `startAuthentication` e posta a
2745
+ assertion serializada em `<endpoint>`.
2746
+
2747
+ O kind é genérico de propósito — o endpoint de options é DERIVADO do descritor
2748
+ (`<endpoint>/options`) e a tela não conhece o id `passkey`. Qualquer método do
2749
+ SPI que implemente o mesmo par de rotas ganha a tela embutida de graça.
2750
+
2751
+ Renderer próprio (React/Inertia) precisa tratar `'webauthn'`: um kind
2752
+ desconhecido caindo no ramo de form produz exatamente o botão que nunca
2753
+ funciona.
2754
+
2755
+ ***
2756
+
2757
+ ### Correções
2758
+
2759
+ - **Escalação via impersonação (segurança).** A marca de sudo não era vinculada
2760
+ à conta, e a impersonação troca a conta da sessão sem invalidá-la: dentro da
2761
+ graça de 15 min, o sudo do admin valia sobre a conta impersonada — e o do
2762
+ impersonado, de volta sobre o admin. A marca agora carrega o `accountId`
2763
+ e `isSudoActive` recusa quando não bate.
2764
+ - **`completeSudo` recusa sem conta carregada.** `sudoContextFrom` deixa
2765
+ `account: null` quando o `accountStore` não acha a conta (sessão viva de conta
2766
+ apagada/anonimizada). Os built-in já checavam por dentro; agora o ÚNICO ponto
2767
+ de concessão impõe, então um método que não cheque também fica seguro — mesma
2768
+ garantia estrutural de `guardSudoRoutes`, do outro eixo.
2769
+ - **`accountHome` nunca era propagado ao config resolvido**, então o redirect
2770
+ pós-confirmação sempre caía no default `/account/security`, ignorando o valor
2771
+ do host.
2772
+ - **`isPasswordless`** foi removido; o docblock descrevia uma heurística com
2773
+ passkeys que o código não implementava.
2774
+
2775
+ ***
2776
+
2777
+ ### Rate limit nas rotas dos métodos de sudo
2778
+
2779
+ TODA rota registrada por um `SudoMethod` leva o throttle do host (no-op quando o
2780
+ rate-limit está desligado). Importa sobretudo para o POST que emite o magic link
2781
+ de sudo, que dispara um e-mail por chamada: o `accountGuard` na frente só exige
2782
+ uma sessão de conta viva, que o abusador tem.
2783
+
2784
+ O bucket é PRÓPRIO do sudo (`authkit_sudo`), keyed por IP e com os MESMOS
2785
+ limites do login (10/min) — o que muda é a CONTAGEM, não o teto. Os dois
2786
+ orçamentos respondem a perguntas diferentes: `login` mede um anônimo adivinhando
2787
+ credenciais, `sudo` mede um usuário JÁ autenticado reprovando a própria
2788
+ identidade na tela de confirmação. Compartilhados, contaminavam-se nos dois
2789
+ sentidos — quem errasse a senha no `/account/confirm` ficava sem conseguir logar
2790
+ em outra aba, e um ataque de credencial no login trancava a confirmação de quem
2791
+ está legitimamente logado atrás do mesmo NAT.
2792
+
2793
+ Aplicado no WRAPPER de registro (`guardSudoRoutes`), não pelo método. Isso é
2794
+ deliberado e é a mesma escolha da barreira de `config.sudo.methods`: um método
2795
+ que pudesse PEDIR throttle poderia também não pedir, e a cobertura voltaria a
2796
+ depender de o autor lembrar. Métodos customizados ganham o throttle sem fazer
2797
+ nada, e `SudoRouteHelpers` não precisou mudar de forma.
2798
+
2799
+ ## 0.45.0
2800
+
2801
+ ### Minor Changes
2802
+
2803
+ - 257131f: Avatar storage can now delegate to `@adonis-agora/media` when it is installed (declared as an optional peer). New `uploads.avatars` config: `storage` (`'auto'` | `'builtin'` | `'media'`, default `'auto'`), `collection` (default `'avatar'`), and `ownerType` (default `'AuthAccount'`). When media is present it stores the avatar via media's `single-file` helper and persists the returned URL; otherwise it falls back to the built-in `@adonisjs/drive` uploader (behavior unchanged). Adds an exported `isAvatarUploadSupported()` used to gate the avatar file input on whichever backend is actually available.
2804
+
2805
+ ## 0.44.0
2806
+
2807
+ ### Minor Changes
2808
+
2809
+ - 1dc4233: The admin console now resolves an account's roles through `resolveTokenRoles` when configured — the same source the OIDC `roles` claim is minted from — so an app-role admin (e.g. one whose roles live in a `@adonis-agora/authz` table) reaches the console without needing the role duplicated in `auth.users.global_roles`. Applies to the `adminGuard` route gate and the console shell's current-user display. Default is unchanged: with no `resolveTokenRoles` configured, both fall back to `account.globalRoles`.
2810
+
2811
+ ## 0.43.0
2812
+
2813
+ ### Minor Changes
2814
+
2815
+ - 304722c: Add a pluggable `resolveTokenRoles` hook to source the global-roles claim from an external authority (e.g. `@adonis-agora/authz`) or a custom store at token-mint time. Applies to both the authorization-code flow (first-party only) and token exchange. Default unchanged (`account.globalRoles`).
2816
+
2817
+ ## 0.42.0
2818
+
2819
+ ### Minor Changes
2820
+
2821
+ - a09ded9: `@vinejs/vine` passa de `dependencies` para `peerDependencies` (range `^4.3.0`). Como lib do ecossistema AdonisJS, o vine deve ser fornecido pelo app consumidor — embuti-lo criava uma segunda cópia do vine e, como o `@adonisjs/core` é peer-chaveado pelo vine, uma segunda instância do core no bundle do consumidor (quebra de boot: `Cannot read properties of undefined (reading 'booted')`). Todo app AdonisJS já tem o vine instalado.
2822
+
2823
+ ## 0.41.0
2824
+
2825
+ ### Minor Changes
2826
+
2827
+ - 878bb0d: Permite configurar o remetente (`from`) dos e-mails internos da lib (alertas de novo acesso/dispositivo, reset de senha, verificação, magic link default, avisos de segurança) via `defineConfig({ mail: { from } })`. Tem prioridade sobre o `from` global do `config/mail.ts` do host — assim o auth pode usar um remetente próprio (ex.: `Segurança <no-reply-auth@dominio>`) sem trocar o remetente dos e-mails gerais do app. Sem `from` em lugar nenhum, o envelope MAIL FROM ficava vazio e provedores como o Resend rejeitavam com `550 Invalid from`; agora o `defaultFrom` resolve authkit → host → default do @adonisjs/mail.
2828
+
2829
+ ## 0.40.0
2830
+
2831
+ ### Minor Changes
2832
+
2833
+ - eec8b82: Add RP-side session-impersonation helpers (`rememberAccessToken`, `startImpersonation`, `impersonationState`, `stopImpersonation`) in `src/host/impersonation_session.ts`.
2834
+
2835
+ These give a relying party a reusable, ergonomic way to impersonate a user and browse as them. The flow is routed through the IdP's existing RFC 8693 token-exchange, so it inherits the IdP's central audit trail and the `act` claim — the IdP stays the sole authorization gatekeeper (a non-admin `subject_token` is rejected, so the session is only swapped on a successful exchange). The helpers are pure session glue over the `account_user_id` key, with anti-fixation session regeneration on start/stop.
2836
+
2837
+ ## 0.39.0
2838
+
2839
+ ### Minor Changes
2840
+
2841
+ - 67feccb: Adiciona o acessor singleton `@adonis-agora/authkit-server/services/main` (convenção `services/main` do
2842
+ Adonis, como `@adonisjs/lucid/services/db`, `@adonisjs/drive/services/main` e `@adonisjs/lock/services/main`).
2843
+ Deixa o app usar `import authkit from "@adonis-agora/authkit-server/services/main"` e ler `authkit.config` /
2844
+ acessar `authkit.provider` etc., em vez de resolver a binding string-keyed `"authkit.server"` pelo container
2845
+ na mão (`ctx.containerResolver.make("authkit.server")`). Funciona tanto em controllers-classe quanto em
2846
+ route handlers inline.
2847
+
2848
+ Espelha o que o `authkit-client` já expõe. A binding `"authkit.server"` continua registrada e é a forma
2849
+ suportada de resolver o serviço DENTRO da lib — que é o idioma das libs first-party do Adonis (ver
2850
+ `@adonisjs/auth`, que resolve `ctx.containerResolver.make("auth.manager")` no próprio middleware).
2851
+
2852
+ ## 0.38.1
2853
+
2854
+ ### Patch Changes
2855
+
2856
+ - 4ac368e: Corrige: os re-renders do passo de login (erro de senha, lockout, magic link enviado, e-mail não
2857
+ verificado) mandavam `authMethods` undefined pra view — só o GET `show()` passava. Com `authMethods`
2858
+ ausente, a tela voltava ao default (senha ligada), **ignorando `cfg.authMethods` / o setting de runtime**:
2859
+ o input de senha aparecia mesmo com `authMethods: { password: false }`.
2860
+
2861
+ Agora um helper `#loginMethods(ctx, cfg)` resolve os métodos efetivos (com os pins do config) e todos os
2862
+ renders do passo login passam `authMethods` + `magicLinkAvailable`. O input de senha respeita a config em
2863
+ qualquer caminho de render.
2864
+
2865
+ ## 0.38.0
2866
+
2867
+ ### Minor Changes
2868
+
2869
+ - 3b26725: Console admin: a página **Settings** agora é plugada nas settings de runtime REAIS de `auth_settings`
2870
+ (antes eram keys placeholder que não batiam com nenhum resolver). Cada seção mapeia uma
2871
+ `SETTING_KEYS` estruturada e edita seus campos, gravando o objeto inteiro via `PUT /api/settings/:key`.
2872
+
2873
+ Seções: **Métodos de login** (`auth_methods` — password/magicLink/passkey/forgotPassword/passkeyAutofill),
2874
+ Cadastro (`registration`), Verificação de e-mail (`require_verified_email`), Manutenção
2875
+ (`maintenance_mode`), Lockout (`lockout`), TTL dos tokens (`token_ttl`).
2876
+
2877
+ Settings travadas via `defineConfig()` (config-locks) aparecem com o selo "definido via config",
2878
+ os controles desabilitados e o aviso "Travado no defineConfig() — config tem prioridade sobre runtime".
2879
+ Ex.: `defineConfig({ authMethods: { password: false } })` deixa a seção Métodos de login read-only.
2880
+
2881
+ ## 0.37.0
2882
+
2883
+ ### Minor Changes
2884
+
2885
+ - 2fc6371: `defineConfig` agora aceita `authMethods` para FIXAR métodos de login pelo arquivo de config, com
2886
+ PRIORIDADE sobre o runtime setting `auth_methods` (integra ao mecanismo de config-locks existente).
2887
+
2888
+ Declarar `authMethods` trava a key `auth_methods`: o valor do config manda, o console admin/Admin API
2889
+ não altera em runtime (rejeita com 423) e a UI lê `lockedSettingKeys()` pra desabilitar o controle.
2890
+ Cada campo declarado (`password`, `magicLink`, `passkey`, `forgotPassword`) sobrescreve o resolvido do
2891
+ setting. Guards preservados: ligar respeita a capacidade (magicLink/passkey só ligam se capable);
2892
+ desligar sempre vale; fail-safe all-off volta aos defaults (nunca tranca todo mundo pra fora).
2893
+
2894
+ ```ts
2895
+ // Login sem senha (magic-link + passkey), fixado pelo config — sem comando por ambiente:
2896
+ defineConfig({
2897
+ authMethods: { password: false },
2898
+ passwordless: { magicLink: true },
2899
+ });
2900
+ ```
2901
+
2902
+ Substitui a necessidade de rodar `node ace authkit:disable-password` por ambiente quando o objetivo é
2903
+ declarar a política no código.
2904
+
2905
+ ## 0.36.0
2906
+
2907
+ ### Minor Changes
2908
+
2909
+ - f919d69: Add passwordless public signup (`passwordless.signup`)
2910
+
2911
+ When `passwordless: { signup: true }` (and the account store implements
2912
+ `MagicLinkCapability`), the public signup asks for e-mail + name only — no
2913
+ password. It creates the account with an unusable random password (same
2914
+ precedent as social-identity accounts), issues a magic link, and e-mails it;
2915
+ opening the link finishes the login through the existing magic-link flow. The
2916
+ response is uniform ("link sent") whether or not the account already exists
2917
+ (anti-enumeration), and an existing e-mail simply gets a login link. The
2918
+ password-based signup is unchanged when the flag is off.
2919
+
2920
+ ## 0.35.0
2921
+
2922
+ ### Minor Changes
2923
+
2924
+ - c3e0309: Add optional `@adonisjs/auth` integration. `authkitUserProvider()` plugs authkit's own `accountStore` into `@adonisjs/auth`'s `sessionGuard()` (for `config/auth.ts`), and a new `adonisAuth: { guard: '...' }` option in `config/authkit.ts` makes `AccountSessionController#login`/`logout` (and the other self-service logout endpoints) also call `ctx.auth.use(guard).login()/.logout()` — so `ctx.auth.user`, `middleware.auth()`, and Bouncer's `() => ctx.auth.user` now work for apps built on authkit. Fully opt-in and additive: `ctx.auth` is never touched unless both the guard is configured in `config/authkit.ts` and `@adonisjs/auth` is actually installed and initialized.
2925
+
2926
+ ### Patch Changes
2927
+
2928
+ - 2d55d68: Fail fast and loudly at boot when `config/app.ts` is missing `appKey`, instead of only surfacing a `RuntimeException` lazily the first time something resolves the `authkit.server` binding (which could otherwise be silently swallowed by the keystore-reload poller/key-rotation scheduler's fail-safe `.catch(() => null)`, or surface as an unexplained 500 on the first `/account/*` request).
2929
+ - 25ef01f: Default `render` to `edgeRenderer()` when `config/authkit.ts` omits it. Previously `render` had no runtime default: every `/account/*` and `/auth/interaction/*` request would throw `TypeError: render is not a function` (a 500 with no explanation) the moment a controller called `cfg.render!(...)`.
2930
+ - 70f5721: Ship peer dependencies as ranges instead of exact versions
2931
+
2932
+ `peerDependencies` pointed at the pinned `adonis`/`frontend` catalogs, and pnpm
2933
+ inlines a catalog's literal value at publish time — so every published peer came
2934
+ out exact. `@adonis-agora/authkit-server@0.34.1` on npm requires
2935
+ `"@adonisjs/core": "7.3.3"`, which no app on 7.3.5 can satisfy;
2936
+ `@adonis-agora/authkit-react@0.13.0` requires `"react": "19.2.6"`, which locks
2937
+ out every consumer not on that exact patch.
2938
+
2939
+ Peers now resolve from three new range-only catalogs (`adonisPeers`,
2940
+ `frontendPeers`, `miscPeers`). Dependencies keep the pinned catalogs — a pin is
2941
+ right for reproducible installs and wrong for consumer compatibility, and the
2942
+ two were sharing one source.
2943
+
2944
+ No source or runtime behaviour changes.
2945
+
2946
+ ## 0.34.1
2947
+
2948
+ ### Patch Changes
2949
+
2950
+ - 84190a1: Redact PII from the audit→diagnostics bridge so a deleted account's data never survives in Telescope's store (LGPD/GDPR completeness).
2951
+
2952
+ The diagnostics bridge mirrors every `AuditEvent` onto the `@agora/diagnostics` bus, where Telescope captures it as an independent `diagnostic` entry in its own store — a store the account-deletion cascade's `anonymizeAudit` step does not reach. The bridge now emits a **redacted projection** of each event: `email`, `ip`, and the free-form `metadata` (which can itself carry addresses such as `oldEmail`/`newEmail`) are dropped at the source, leaving only the event `type` and the opaque internal ids (`accountId`/`actorId`/`clientId`) the security dashboard needs. The Telescope dashboard's token-activity table drops its now-empty "IP" column. The `onEvent` callback and outbound `webhook` integrations are unchanged — they still receive the complete event.
2953
+
2954
+ ## 0.34.0
2955
+
2956
+ ### Minor Changes
2957
+
2958
+ - 394b9aa: Bridge audit events to the @agora diagnostics bus; populate @agora context from the resolved session
2959
+ - 0542665: Re-scope to @adonis-agora/authkit-\* (join the Agora ecosystem)
2960
+ - 93fef40: Opt-in durable workflows for GDPR account deletion (sync-logout + async cascade) and export
2961
+ - d98ad01: Add a Telescope auth-dashboard extension (defineAuthkitTelescopeExtension)
2962
+
2963
+ ### Patch Changes
2964
+
2965
+ - Updated dependencies [0542665]
2966
+ - @adonis-agora/authkit-core@0.7.0
2967
+
2968
+ ## 0.33.1
2969
+
2970
+ ### Patch Changes
2971
+
2972
+ - e76bcb4: Limpeza de qualidade (sem mudança de comportamento): fábrica canônica `resolveRuntimeSettings(ctx)` substitui ~16 cópias da resolução de RuntimeSettings (3 nomes diferentes) e elimina o cast `as any` (via `connectionName` tipado no AccountStore); validação de catálogo de role de org extraída para um helper puro reusado pelos caminhos admin e member-facing; `countAdmins` passa a usar uma capability opcional `AccountStore.countByGlobalRole` quando disponível (fallback paginado mantido).
2973
+
2974
+ ## 0.33.0
2975
+
2976
+ ### Minor Changes
2977
+
2978
+ - dd80bb8: Segurança (least privilege): a claim de papéis globais e as claims de organização saem do scope `profile` para um scope dedicado `roles`, e sua emissão é gated a clients first-party (`branding.firstParty`). Clients third-party NÃO recebem papéis/org, mesmo solicitando o scope `roles`. O default de scopes do authkit-client passa a incluir `roles` (consumidores first-party continuam recebendo papéis sem mudança de comportamento). BREAKING para quem dependia de papéis no scope `profile`: o client precisa solicitar o scope `roles`.
2979
+
2980
+ ## 0.32.0
2981
+
2982
+ ### Minor Changes
2983
+
2984
+ - 685755c: MFA agora é totalmente lib-owned: o estado de TOTP/recovery/anti-replay (`totp_secret`, `mfa_enabled_at`, `recovery_codes`, `last_totp_step`) migra das colunas na tabela `users` do host para uma tabela própria auto-gerida `auth_mfa` (schema das tabelas da lib). Apps NÃO precisam mais de migration para MFA — o `withMfa()` continua sendo composto no model mas não declara mais colunas. Sem migração de dado para quem ainda não tem MFA enrolado; quem já tem precisa copiar as colunas para `auth_mfa`.
2985
+
2986
+ ## 0.31.0
2987
+
2988
+ ### Minor Changes
2989
+
2990
+ - 6c0dbb6: Correções de segurança (auditoria 2026-06-08):
2991
+
2992
+ - Token-exchange travado: subject_token deve ser do client autenticado, scope reduzido à interseção com o client (scope inválido → `invalid_scope`), audience/resource não suportado rejeitado.
2993
+ - Allowlist de grant_types nos clients (bloqueia `implicit`); redirect/post-logout URIs validados como URI http/https absoluta.
2994
+ - Proteção de "último admin" + bloqueio de auto-rebaixamento; REST API valida globalRoles contra o catálogo; throttle por IP no grupo admin-api; auditoria REST registra o id (hash) da admin key em vez de null.
2995
+ - IDOR cross-org corrigido: revogação de convite escopada por organização; role de membro/convite validada contra o catálogo (sem promoção a `owner` por admin não-owner).
2996
+ - Login resistente a enumeration por timing (dummy-hash); settings de lockout/verified-email/expiração passam a valer em runtime no fluxo OIDC E no login de sessão do console; reset/troca de senha revoga sessões/grants OIDC; `/account/login` com throttle por IP + email normalizado.
2997
+ - Sessão regenerada no login (anti-fixation) e destruída no logout; TOTP com proteção de replay; encrypter de conta fail-closed (decrypt falho → nega, não devolve ciphertext); single-session propaga revogação cookie-based; `return_to` rejeita backslash (open redirect).
2998
+
2999
+ **MIGRAÇÃO NECESSÁRIA (anti-replay TOTP):** o mixin de MFA agora declara a coluna `last_totp_step` (bigint, nullable) na tabela de contas do host. Hosts que usam TOTP DEVEM adicionar a coluna numa migração antes de subir esta versão, senão o primeiro login TOTP pós-upgrade falha ao persistir o step. Ex.: `table.bigInteger('last_totp_step').nullable()`.
3000
+
3001
+ ## 0.30.0
3002
+
3003
+ ### Minor Changes
3004
+
3005
+ - a450edb: Página "Signing Keys" no console admin: ver chaves JWKS (kids/idade/ativa), configurar rotação automática (enabled/maxAgeDays/keep), rotacionar agora e desabilitar todas + criar nova. O status de keys (`GET {base}/keys`) agora inclui a lista de chaves (`KeysStatus.keys`).
3006
+
3007
+ ## 0.29.0
3008
+
3009
+ ### Minor Changes
3010
+
3011
+ - a39352e: feat: drivers de cofre cloud do keystore JWKS via packages externos. O driver
3012
+ `{ driver: 'aws-secrets-manager' | 'gcp-secret-manager' | 'azure-key-vault' }` agora
3013
+ resolve para um `LazyExternalVault` que carrega o package dedicado no primeiro I/O
3014
+ (erro claro pedindo pra instalar se ausente). HashiCorp já está em core.
3015
+ - 6fe2aa7: feat: rotação automática de chaves JWKS (age-based) + política + endpoints + SDK.
3016
+
3017
+ Nova setting `key_rotation` (`{enabled,maxAgeDays,keep}`, default OFF). Um scheduler
3018
+ de housekeeping (web-only, fail-safe) rotaciona a chave quando ela passa de
3019
+ `maxAgeDays` e aplica AO VIVO (sem restart, via `reloadKeys` da Fatia C), com
3020
+ single-flight via `@adonisjs/lock` (peer opcional; sem ele assume single-instance).
3021
+ `OidcService` ganha `rotateKeys()`/`keystoreAgeDays()` (rotate+reload serializados).
3022
+
3023
+ Dois tiers de endpoint admin para status + "rotacionar agora":
3024
+
3025
+ - **REST API** `GET/POST /api/authkit/v1/keys` (Bearer key) — para backend/automação;
3026
+ - **Console API** `GET/POST {adminPrefix}/api/keys` (sessão + role admin) — para o browser.
3027
+
3028
+ `@adonis-agora/authkit-sdk` expõe `authkit.keys.status()` / `authkit.keys.rotate()`
3029
+ (drivers remote + embedded). `@adonisjs/lock` é peer OPCIONAL.
3030
+
3031
+ Default OFF: nada rotaciona automaticamente até um admin habilitar `key_rotation`.
3032
+
3033
+ - 93eaf69: feat: cofre do keystore JWKS no HashiCorp Vault (KV v2). Novo driver
3034
+ `{ driver: 'hashicorp-vault', endpoint, path, token?, mount?, field? }` — usa a API
3035
+ HTTP do Vault (sem SDK), então mora em core como file/drive/lucid/redis. Encryption
3036
+ at-rest fica OFF por default (o Vault tem cifra/ACL próprios; ligável p/ envelope).
3037
+ - e2582b8: feat: cofres do keystore JWKS em Lucid e Redis. Novos drivers `jwks.store`:
3038
+ `{ driver: 'lucid' }` (tabela dedicada `authkit_keystore`, auto-criada) e
3039
+ `{ driver: 'redis' }` (uma key). Diferente de `file`, ambos são COMPARTILHADOS entre
3040
+ instâncias — o melhor default para multi-instância + hot-reload (o poll lê um `head`
3041
+ barato). Encryption at-rest (APP_KEY) ON por default nos dois. Warning no boot quando
3042
+ `redis` é usado (exige persistência RDB/AOF). `resolveKeystoreVault` agora recebe um
3043
+ contexto com acesso ao container (mudança de assinatura interna).
3044
+
3045
+ ### Patch Changes
3046
+
3047
+ - Updated dependencies [93eaf69]
3048
+ - Updated dependencies [e2582b8]
3049
+ - @adonis-agora/authkit-core@0.6.0
3050
+
3051
+ ## 0.28.0
3052
+
3053
+ ### Minor Changes
3054
+
3055
+ - df4b41f: feat: keystore JWKS managed com cofre pluggável + encryption at-rest (Fatia A+B)
3056
+
3057
+ O keystore managed deixa de ser fs-síncrono-num-path e passa por uma abstração de
3058
+ cofre (`KeystoreVault`): `file` (default) e `drive` (`@adonisjs/drive`, bucket), com
3059
+ contrato para cofres custom. O keystore PRIVADO agora é encriptado em repouso por
3060
+ default (APP_KEY) para file/drive via um envelope versionado; decrypt falho lança
3061
+ (nunca regenera em silêncio). O boot e o comando `authkit:keys:rotate` usam o mesmo
3062
+ stack (defaults de encryption idênticos). Novidades: aviso no boot quando
3063
+ `jwks: 'auto'` cai no fallback de disco, e idade da chave de assinatura no
3064
+ `authkit:doctor`. Config: `jwks.store` aceita `{ driver: 'file' | 'drive' | ... }`
3065
+ além de string, e novo `jwks.encrypt`.
3066
+
3067
+ Nota (0.x): sem migração de keystore legado — um `tmp/authkit_jwks.json` plaintext
3068
+ pré-existente deve ser apagado uma vez (regenera encriptado).
3069
+
3070
+ - fc68930: feat: hot-reload das chaves de assinatura JWKS — a chave rotacionada passa a
3071
+ assinar SEM restart. `OidcService.reloadKeys()` reconstrói e troca a instância do
3072
+ oidc-provider ao vivo (o estado durável vive no adapter, então nada se perde), e um
3073
+ poll do `head` do cofre (a cada 60s, só no processo web) propaga rotações feitas por
3074
+ outro processo/instância — ex.: `authkit:keys:rotate` num worker, ou outra réplica.
3075
+
3076
+ ### Patch Changes
3077
+
3078
+ - 237c542: fix(console): "Sign out" do console admin agora desloga de verdade
3079
+
3080
+ O botão de logout do console (`Sidebar`) era um `<a href="/account/login">` — não
3081
+ encerrava a sessão. Como a sessão seguia ativa, o `/account/login` redirecionava
3082
+ pro `accountHome` (default `/account/security`), então o usuário "deslogava" mas
3083
+ continuava logado, caindo numa tela de conta. Agora é um `<form method="POST"
3084
+ action="/account/logout">` com CSRF, que faz `session.forget` e redireciona pro
3085
+ `/account/login` de verdade.
3086
+
3087
+ - Updated dependencies [df4b41f]
3088
+ - @adonis-agora/authkit-core@0.5.0
3089
+
3090
+ ## 0.27.0
3091
+
3092
+ ### Minor Changes
3093
+
3094
+ - 54535a7: refactor: controllers do admin (Admin REST API + console) validam input com VineJS
3095
+
3096
+ Os controllers administrativos liam o body na mão via `ctx.request.input(...)`
3097
+ com coerção ad-hoc (`asArray`, checagens de presença) e devolviam `400`
3098
+ `invalid_request` quando faltava campo — sem schema, o que originou o bug do
3099
+ `grantTypes` vs `grants`. Agora cada recurso tem um validator VineJS em
3100
+ `host/admin_validators.ts` (compartilhado entre a Admin REST API e o console, que
3101
+ têm as mesmas formas de input) chamado via `request.validateUsing(...)`.
3102
+
3103
+ Migrados: **clients** (create/update), **users** (create/update/roles),
3104
+ **organizations** (create/update/membros/convites), **catálogo de roles**
3105
+ (create/update), **sessions** (`revoke-all` — accountId por query/param validado
3106
+ direto com `validator.validate(...)`) e **tokens/verify**.
3107
+
3108
+ **BREAKING (admin API):** input inválido agora responde `422` com o envelope de
3109
+ erro do VineJS (`{ errors: [...] }`) em vez de `400` `{ error: { code:
3110
+ 'invalid_request' } }`. A política de senha continua no `AdminUsersService`
3111
+ (o validator de criação de usuário NÃO fixa `minLength` — não duplica a policy
3112
+ configurável do projeto). O merge do PATCH de client (campos ausentes preservados)
3113
+ e o alias `grants`↔`grantTypes` seguem funcionando.
3114
+
3115
+ Forms Edge (account/orgs, confirm, session), blobs WebAuthn, flags de checkbox,
3116
+ o endpoint RFC 7662 de introspecção e inputs opcionais-com-default ficam de fora
3117
+ de propósito (ver o doc no topo de `admin_validators.ts`).
3118
+
3119
+ ## 0.26.2
3120
+
3121
+ ### Patch Changes
3122
+
3123
+ - fe2c300: fix: admin client update (PATCH) agora MESCLA em vez de resetar campos não-enviados
3124
+
3125
+ O update de client da Admin API/console fazia full-replace: campos ausentes no
3126
+ body caíam no default — não mandar `tokenEndpointAuthMethod` virava o client
3127
+ `confidential` (client_secret_basic), e não mandar grants derrubava grants como
3128
+ `token-exchange`. Agora o `update` preserva os valores atuais para qualquer campo
3129
+ não enviado (PATCH de verdade). Além disso, os controllers passam a aceitar `grants`
3130
+ (o mesmo nome do dto de saída) como alias de `grantTypes` na entrada.
3131
+
3132
+ ## 0.26.1
3133
+
3134
+ ### Patch Changes
3135
+
3136
+ - db8879e: Expõe `clientId` no `brand` das telas de auth
3137
+
3138
+ O `brandFor()` agora inclui o `clientId` (OIDC) no objeto `brand` passado a cada tela renderizada. Hosts com IdP único e múltiplos produtos podem escolher tema/shell por client de forma robusta (`REGISTRY[brand.clientId]`) em vez de casar por `appName`. Ver recipe "Per-client auth UI".
3139
+
3140
+ ## 0.26.0
3141
+
3142
+ ### Minor Changes
3143
+
3144
+ - 262eb79: Back-Channel Logout pronto para sessões cookie-based + DX do client
3145
+
3146
+ Antes, fechar o gap de logout SSO em sessão cookie-based exigia escrever model + service + middleware à mão em cada app (e era fácil esquecer — deixando a sessão válida por até 30 dias após um logout SSO). Agora o AuthKit absorve isso:
3147
+
3148
+ **`@adonis-agora/authkit-client`**
3149
+
3150
+ - `lucidRevocationStore({ connection?, table? })` + interface `RevocationStore`: persistência append-only de revogações (sid/sub/revoked_at), sem precisar declarar model.
3151
+ - `BackchannelRevocationMiddleware` (subpath `/backchannel_revocation_middleware`): derruba a sessão revogada na próxima request.
3152
+ - `defineConfig({ backchannelLogout: { store } })`: deriva o `onBackchannelLogout` e expõe o store ao middleware.
3153
+ - `lucidMirror(Model, { sync, preload, injectGlobalRoles })`: factory do `resolveUser` "espelho local".
3154
+ - Middlewares prontos `auth_middleware` (com `roles`) e `silent_auth_middleware` (subpaths).
3155
+ - `buildAuthorizeUrl({ extraParams })`: anexa `audience`/`prompt`/`login_hint`/etc. sem manipular URL na mão.
3156
+ - `Authenticator.toSharedProps()`: `{ user, globalRoles, appRoles, abilities }` pronto p/ Inertia share.
3157
+ - `AuthkitClientManager.impersonate()` / `stopImpersonating()` / `isImpersonating()`: ciclo de impersonação (RFC 8693) gerenciado.
3158
+ - `registerOidcClient(router, { redirects, afterLogin, loginMiddleware })`: registra login/callback/logout (+back-channel) absorvendo PKCE/state/exchange/redirect-por-papel do OidcSessionController.
3159
+
3160
+ **`@adonis-agora/authkit-server`**
3161
+
3162
+ - Tabela `auth_session_revocations` gerenciada pelo `ensureAuthkitSchema()` (schema auto-manage) — compartilhável entre apps no mesmo banco.
3163
+ - Revogação em massa do admin (`AdminSessionsService.revokeAll`) grava uma revogação `sub` na tabela compartilhada → logout INSTANTÂNEO nos clients cookie-based (antes esperava o refresh token falhar, ~TTL do access token).
3164
+ - **Config locks (BREAKING semântico):** settings definidas no `defineConfig` ficam TRAVADAS — config vence e a UI/Admin API não pode alterá-las (`getSetting` → null p/ resolvers caírem no config; `setSetting`/`deleteSetting` → 423 `SettingLockedError`). O console mostra badge "definido via config" e desabilita o controle. Exports: `isSettingLocked`, `lockedSettingKeys`, `deriveLockedSettingKeys`, `SettingLockedError`.
3165
+ - **`encrypter` do TOTP agora é DEFAULT (BREAKING):** `lucidAccountStore` encripta o segredo TOTP com `APP_KEY` por padrão (`appKeyEncrypter()`); `encrypter: false` desliga. ⚠️ Segredos gravados em claro por versões anteriores deixam de decriptar — migre ou passe `false`.
3166
+ - `jwks: 'auto'` — resolve env-aware (`AUTHKIT_JWKS` inline, senão managed em arquivo); elimina o ternário no config.
3167
+ - `adminApi.apiKeys: 'env'` (lê `AUTHKIT_ADMIN_API_KEY`) + `enabled` auto quando há key — elimina o spread condicional.
3168
+ - `lucidStores({ account, pat, audit, providerIdentity, webauthnCredential, organizations }, { mfaIssuer, webauthn })`: monta os stores declarando mfaIssuer/webauthn UMA vez.
3169
+ - `defineConfig` reusa `mfaIssuer`/`webauthn` do `lucidAccountStore` quando o top-level não os fornece (declare uma vez).
3170
+ - `authkitCsrfExceptions(url, { mountPath })`: helper de isenção CSRF das rotas machine-to-machine.
3171
+ - **`registerAuthHost(router)` sem opts** — lê mountPath/social/rateLimit/admin/adminApi do `config/authkit.ts` (stash no boot do provider, que roda antes do preload do routes.ts). Acaba com o drift config↔registerAuthHost; `opts` viram só override (ex.: `{ admin: { prefix } }`). Fallback p/ defaults quando não há stash (testes).
3172
+
3173
+ ## 0.25.3
3174
+
3175
+ ### Patch Changes
3176
+
3177
+ - f67e75e: Logout deixa de mostrar a tela default do oidc-provider ("Do you want to sign-out from…?")
3178
+
3179
+ O RP-initiated logout (end_session) usava o `logoutSource`/`postLogoutSuccessSource` default do oidc-provider — HTML sem estilo, em inglês, pedindo confirmação. Agora um splash de marca ("Saindo…", i18n en/pt-BR) auto-confirma o logout (injeta `logout=yes` e submete via JS, com `<noscript>` acessível), e a tela de sucesso (quando não há `post_logout_redirect_uri`) também é tematizada.
3180
+
3181
+ ## 0.25.2
3182
+
3183
+ ### Patch Changes
3184
+
3185
+ - fa2e89f: New `accountHome` config — and the account area no longer dumps users on the PAT screen
3186
+
3187
+ Post-login at `/account/login` (without `return_to`), e-mail confirmations, and non-admin redirects away from the console used to land on `/account/tokens` (the Personal Access Tokens screen) — hostile for regular users. The default destination is now **`/account/security`** and is configurable via `accountHome` in `defineConfig` (point it at your app's home to land users straight in the product).
3188
+
3189
+ ## 0.25.1
3190
+
3191
+ ### Patch Changes
3192
+
3193
+ - e2086c2: Admin console: finish the nuqs URL-state migration and pin SPA deps
3194
+ - **nuqs URL state now covers every page.** The Audit and Sessions pages join Users and Orgs in keeping navigation and filter state (page, type filter, pagination) in the query string via [nuqs](https://nuqs.47ng.com/)'s generic React adapter — completing the migration shipped in 0.25.0. Every view + filter combination is deep-linkable and survives refresh; switching pages clears shared filter params so state never leaks between views. Ephemeral UI (modals/forms) stays in React state.
3195
+ - **Per-user "Disconnect all devices"** (shipped in 0.25.0, now documented): the admin user drawer's Actions row revokes a single user's sessions + grants via `POST {prefix}/api/users/:id/revoke-sessions` — the admin-side equivalent of the self-service "Sign out of all devices" on `/account/security`.
3196
+ - **Pinned SPA dependencies** to exact versions: `nuqs@2.8.9` and `recharts@3.8.1` (no `^` range).
3197
+ - **Console internals refactored for maintainability** (no behavior change): the 1.1k-line `orgs.containers.tsx` was split into focused modules (`org_settings.containers.tsx`, `org_members.containers.tsx`, shared `UserPicker` and form primitives); the org-settings forms got real types (`OrgPolicyValue`, `RolesCatalogValue`) with boundary normalization instead of `any`; `catch (err: any)` normalized to the canonical `unknown` pattern; the debounce hook deduplicated into `lib/use_debounce.ts`.
3198
+
3199
+ ## 0.25.0
3200
+
3201
+ ### Minor Changes
3202
+
3203
+ - 6a011b2: Admin console UX: real forms for org settings, user search everywhere, interactive charts
3204
+ - **Organization settings got a real UI**: `organizations_policy` is now a proper form (self-create toggle, invitation TTL, role chips editor) and `roles_catalog` an inline role list editor (name + description, ADMIN locked) — no more raw JSON textareas. A read-only summary of the effective value shows even when not editing.
3205
+ - **Linking users to an org no longer requires a UUID**: "Add member" and the create-org "Owner" field are now a user search (by email/name, debounced) with a picker; member/invite roles are selects instead of free-text.
3206
+ - **Overview charts are interactive**: sign-ins/sign-ups per day rebuilt with Recharts — gradient area, dotted grid, hover tooltip with per-day values (shadcn-style), replacing the static SVG sparkline.
3207
+
3208
+ ## 0.24.0
3209
+
3210
+ ### Minor Changes
3211
+
3212
+ - 55467df: Automatic schema management + admin console is React-only
3213
+ - **Schema auto-management (default on)**: AuthKit now creates its own tables on boot (`authkit_oidc_payloads`, `auth_settings`, `auth_password_history` and the three organizations tables) and additively adds columns introduced by updates — never drops or alters existing columns. Disable with `schema: { autoManage: false }` and call the new exported `ensureAuthkitSchema(db)` inside a migration you own (idempotent, additive). Runtime settings, password history and organizations now work out of the box.
3214
+ - **Edge admin console removed**: the React SPA is the only admin console. `admin: { ui: 'edge' }` and the `ui` config field are gone, along with the Edge admin controllers and views (~30 routes). The SPA was already the default; this deletes the parallel legacy surface.
3215
+ - **`views` autocomplete**: `inertiaRenderer({ views })` is now typed with the `AuthkitScreen` union — IDE autocomplete for every known screen name, still open for custom strings. The array is a set: order never mattered, now the docs say so.
3216
+ - Fix: packaging import-smoke no longer tries to import the console SPA's Vite bundles in Node.
3217
+
3218
+ ### Patch Changes
3219
+
3220
+ - Export sudo mode helpers (`requireSudo`, `isSudoActive`, `markSudo`, `SUDO_SESSION_KEY`, `SUDO_MODE_DEFAULTS`, `resolveEffectiveSudoMode`, `SudoModeSetting`, `ResolvedSudoModeSetting`) from the server package so host applications can enforce step-up authentication in their own controllers.
3221
+
3222
+ ## 0.23.0
3223
+
3224
+ ### Minor Changes
3225
+
3226
+ - feat(account): global sign-out — revoke all sessions across all devices
3227
+
3228
+ **Server (`@adonis-agora/authkit-server`):**
3229
+
3230
+ - `POST /account/api/sessions/revoke-all` — revokes all OIDC sessions/grants for the account
3231
+ and terminates the current Adonis console session (global logout).
3232
+ Returns `{ ok: true, signedOut: true }` so the UI can redirect to login.
3233
+ Emits audit event `account.signed_out_all`.
3234
+
3235
+ **React SDK (`@adonis-agora/authkit-react`):**
3236
+
3237
+ - `RevokeAllResult` type (`{ ok, signedOut, ...rest }`)
3238
+ - `client.account.sessions.revokeAll()` method
3239
+ - `useAccountRevokeAllSessionsMutationOptions()` hook (account namespace;
3240
+ distinct from the admin `useRevokeAllSessionsMutationOptions`)
3241
+
3242
+ ## 0.22.0
3243
+
3244
+ ### Minor Changes
3245
+
3246
+ - feat(settings): org-scoped runtime settings (org → global → default resolution)
3247
+ - `auth_settings` table gains `organization_id` column (nullable; NULL = global). Unique constraint on (key, organization_id).
3248
+ - `RuntimeSettings` methods gain optional `orgId` param: `getSetting(key, orgId?)`, `setSetting(key, value, updatedBy?, orgId?)`, `deleteSetting(key, orgId?)`, `listSettings(orgId?)`. New `getEffective(key, orgId?)` helper resolves org → global → null.
3249
+ - Cache is org-scope-aware (cache key includes orgId).
3250
+ - `resolveEffectiveOrganizationsPolicy` and `resolveEffectiveRolesCatalog` accept optional `orgId` and resolve org → global → default. All other resolvers remain global-only.
3251
+ - Console JSON API (`/api/settings`) and Admin REST API accept `?organizationId=` query param for scoped reads/writes/deletes.
3252
+ - Org detail drawer in console admin shows "Organization Settings" section for org-scopable keys (`organizations_policy`, `roles_catalog`) with source badges (from org / from global / default) and inline JSON editor.
3253
+ - `@adonis-agora/authkit-react` client: `settings.list(orgId?)`, `settings.set(key, value, orgId?)`, `settings.remove(key, orgId?)`. `authkitKeys.admin.settings(orgId?)`. `useSettingsQueryOptions(orgId?)`, `useSetSettingMutationOptions(orgId?)`, `useRemoveSettingMutationOptions(orgId?)`.
3254
+ - `SettingEntry` type gains `organizationId: string | null` field.
3255
+ - Existing rows default to `organization_id = NULL` (global) — no data migration needed.
3256
+
3257
+ ## 0.21.0
3258
+
3259
+ ### Minor Changes
3260
+
3261
+ - feat(console): gestão completa de organizations no console admin React
3262
+
3263
+ Adiciona CRUD completo de organizações na JSON API do console React
3264
+ (console_orgs_controller) e na SPA (Orgs.tsx + orgs.containers.tsx):
3265
+
3266
+ **Novos endpoints no console React JSON API (`{adminPrefix}/api/orgs/*`):**
3267
+
3268
+ - `POST /api/orgs` → criar org (name + slug + ownerAccountId)
3269
+ - `PATCH /api/orgs/:id` → editar nome/logo
3270
+ - `DELETE /api/orgs/:id` → remover org
3271
+ - `POST /api/orgs/:id/members` → adicionar membro (accountId + role)
3272
+ - `PATCH /api/orgs/:id/members/:accountId` → alterar role do membro
3273
+ - `DELETE /api/orgs/:id/members/:accountId` → remover membro
3274
+ - `POST /api/orgs/:id/invitations` → criar convite (email + role)
3275
+ - `DELETE /api/orgs/:id/invitations/:invitationId` → revogar convite
3276
+
3277
+ Todos os endpoints retornam 404 `capability_unsupported` quando o store não
3278
+ suporta organizações. Lógica reutiliza `AdminOrgsService` (sem duplicação).
3279
+
3280
+ **SDK `@adonis-agora/authkit-react`:**
3281
+
3282
+ - `client.admin.orgs`: novos métodos `addMember`, `removeMember`,
3283
+ `updateMemberRole`, `createInvitation`, `revokeInvitation`
3284
+ - Novos hooks: `useAddOrgMemberMutationOptions`, `useRemoveOrgMemberMutationOptions`,
3285
+ `useUpdateOrgMemberRoleMutationOptions`, `useCreateOrgInvitationMutationOptions`,
3286
+ `useRevokeOrgInvitationMutationOptions`
3287
+
3288
+ **SPA do console:**
3289
+
3290
+ - Botão "New organization" na header (modal com name + slug auto-gerado + ownerAccountId)
3291
+ - Empty state com CTA de criar
3292
+ - Drawer da org: editar nome/logo, deletar (com confirmação), listar membros com
3293
+ add/mudar-role/remover, convites pendentes com criar e revogar
3294
+ - Padrão containers + skeleton + QueryBoundary + toasts
3295
+
3296
+ i18n: strings em inglês (interface do console); mensagens de erro do servidor em pt-BR.
3297
+
3298
+ ## 0.20.3
3299
+
3300
+ ### Patch Changes
3301
+
3302
+ - fix(console): register GET/POST api/users/:id/sessions routes before shell catch-all
3303
+
3304
+ The admin React console drawer for a user was failing with "Unexpected token '<',
3305
+ '<!doctype'... is not valid JSON" because `GET {adminBase}/api/users/:id/sessions`
3306
+ and `POST {adminBase}/api/users/:id/revoke-sessions` were not registered in React
3307
+ mode — the catch-all served the SPA shell HTML instead of JSON.
3308
+
3309
+ Adds `userSessions` and `userRevokeSessions` methods to `ConsoleSessionsController`
3310
+ (reusing the existing per-account logic via a private helper) and registers both
3311
+ routes before the `${ap}/*` catch-all in `register_auth_host.ts`.
3312
+
3313
+ ## 0.20.2
3314
+
3315
+ ### Patch Changes
3316
+
3317
+ - fix(sessions): listagem global no console admin quando accountId ausente
3318
+ - `ConsoleSessionsController.index`: sem `accountId` retorna lista global de todas as sessões ativas (todas as contas) em vez de 400
3319
+ - `AdminSessionsService.listAllSessions()`: enumera todas as sessões via adapter, resolve email por conta com cache (evita N+1), limita a 500 entradas com flag `truncated`
3320
+ - `AdminSession`: novo campo opcional `email`
3321
+ - `sessionDto`: inclui `email` na projeção JSON
3322
+ - `AdminSessionEntry` (react types): campo `email: string | null`
3323
+ - `UserSessionsResult` (react types): campo `truncated?: boolean`; renomeia `canList` → `supported` para alinhar com a resposta real do servidor
3324
+ - SPA `sessions.containers.tsx`: exibe email acima do accountId na coluna Account quando presente
3325
+ - Testes: cobre listagem global, truncamento a 500, capability ausente e resolução de email
3326
+
3327
+ ## 0.20.1
3328
+
3329
+ ### Patch Changes
3330
+
3331
+ - Fix "Failed to execute 'fetch' on 'Window': Illegal invocation": the typed client stored `globalThis.fetch` unbound and called it as an instance method, losing the Window binding. The default fetch is now bound to `globalThis`. The admin console SPA is also refactored into per-section containers, each with its own loading skeleton and a `react-error-boundary`-backed error state with retry.
3332
+
3333
+ ## 0.20.0
3334
+
3335
+ ### Minor Changes
3336
+
3337
+ - Typed front-end client, TanStack Query hooks, and account JSON API:
3338
+ - **Account self-service JSON API** (`/account/api/*`): session-authed, CSRF-protected endpoints for profile, security overview, password/email change, sessions, authorized apps, MFA/passkeys, PATs and organizations — the data layer for client-side account screens. Login/consent stay postback for security.
3339
+ - **Typed front-end client** in `@adonis-agora/authkit-react`: `createAuthkitClient()` (auto-reads `window.__AUTHKIT__`) exposing `client.admin.*` and `client.account.*`, plus `AuthkitClientError`.
3340
+ - **TanStack Query hooks** (Tuyau-style): ready-made `use*QueryOptions`/`use*MutationOptions` for every admin and account endpoint, structured `authkitKeys` for invalidation, `AuthkitClientProvider` + `createAuthkitQueryClient()`. `@tanstack/react-query` is a new peer dependency.
3341
+ - **Admin console SPA** now consumes these hooks internally (client-side fetching via TanStack Query) instead of a bespoke fetch wrapper.
3342
+
3343
+ ## 0.19.0
3344
+
3345
+ ### Minor Changes
3346
+
3347
+ - Helpers públicos da sessão do console: `getAccountId(ctx)`, `hasAccountSession(ctx)` e `consoleLoginUrl(returnTo?)` (+ re-export de `ACCOUNT_SESSION_KEY`) — para proteger rotas próprias e integrar pacotes de terceiros (ex.: adonis-telescope) sem depender de detalhes internos.
3348
+
3349
+ ## 0.18.3
3350
+
3351
+ ### Patch Changes
3352
+
3353
+ - Fix React admin console JSON API returning HTML ("Unexpected token '<'"): the shell catch-all `{prefix}/*` was registered before the `{prefix}/api/*` routes, and AdonisJS matches wildcards by registration order, so the catch-all swallowed every API request and served the HTML shell. The API and asset routes are now registered before the catch-all.
3354
+
3355
+ ## 0.18.2
3356
+
3357
+ ### Patch Changes
3358
+
3359
+ - Fix React admin console serving the "Build Required" fallback instead of the SPA: the Vite dist was emitted to build/host/ui-dist but the compiled admin_shell_controller (rootDir ./ → build/src/host/admin_console) resolves the dist at build/src/host/ui-dist, so the readFile always failed in production. Vite now outputs to the matching path and the build asserts the dist lands where the controller reads it.
3360
+
3361
+ ## 0.18.1
3362
+
3363
+ ### Patch Changes
3364
+
3365
+ - Fix boot crash with `admin: { ui: 'react' }`: the React shell was served from two GET routes (`{prefix}` and `{prefix}/*`) sharing the same controller+method, so AdonisJS auto-derived the same route name for both and threw "A route with name console_shell.serve already exists" at boot. The shell, asset and catch-all routes now carry explicit unique names.
3366
+
3367
+ ## 0.18.0
3368
+
3369
+ ### Minor Changes
3370
+
3371
+ - Rodauth parity completion + React admin console:
3372
+ - **Sudo mode**: `sudo_mode` setting + `/account/confirm` (password or passkey) re-confirmation with a grace window; `requireSudo` gates password/email change, account deletion, MFA/passkey management and PAT actions.
3373
+ - **OTP lockout**: `otp_lockout` setting locks the second factor after repeated TOTP/recovery failures and unlocks via emailed link (`GET /auth/otp-unlock/:token`, `onOtpUnlock` hook).
3374
+ - **Common-password block**: `password_policy.blockCommon` (default on) rejects the ~10k most common passwords offline, before the HIBP check.
3375
+ - **Account expiration**: `account_expiration` setting blocks login for accounts inactive beyond N days (reactivate via password reset) + `authkit:accounts:expire-scan` command for cron with warning emails.
3376
+ - **WebAuthn autofill**: `auth_methods.passkeyAutofill` enables conditional-mediation passkey suggestions on the login field; new `usePasskeyAutofill` React hook.
3377
+ - **React admin console (new default)**: `admin: { ui: 'react' }` serves a real Vite-built React SPA (build-and-serve, bundled in the package — zero host setup) with a dark/light telescope-style theme, consuming a session-authed JSON API under `{prefix}/api/*`. `ui: 'edge'` keeps the classic server-rendered console.
3378
+
3379
+ ## 0.17.1
3380
+
3381
+ ### Patch Changes
3382
+
3383
+ - Fix broken console templates in 0.17.0: the styles partial `@include` shared a line with `</head>`, which the Edge lexer cannot tokenize — every console page crashed. Do not use 0.17.0.
3384
+
3385
+ ## 0.17.0
3386
+
3387
+ ### Minor Changes
3388
+
3389
+ - Console UX:
3390
+ - **`return_to` on console login**: the account/admin guards now redirect to `/account/login?return_to=<original path>` and the login POST sends you back where you were heading (server-side validated, open-redirect proof). Custom login pages receive a `returnTo` prop and should propagate it as a hidden input.
3391
+ - **Roles catalog**: new `/admin/roles` page manages the global-role catalog (runtime setting `roles_catalog`; ADMIN is protected). The users page assigns roles via checkboxes from the catalog instead of free text; roles a user holds that left the catalog show an "out of catalog" badge and can only be removed. Doctor warns when `admin.roles` references a role missing from the catalog.
3392
+
3393
+ ### Patch Changes
3394
+
3395
+ - 55eb9d7: Elimina o FOUC (flash de página sem estilo) em todas as telas server-rendered do host (login, account, console admin): o Tailwind Play CDN (gerava CSS em runtime no browser) foi substituído por CSS estático gerado no build e embutido inline via partial Edge.
3396
+
3397
+ ## 0.16.0
3398
+
3399
+ ### Minor Changes
3400
+
3401
+ - Breaking cleanup (0.x, no external consumers): every deprecation shim is gone.
3402
+ - Policy now lives ONLY in runtime settings (DB) with library defaults — removed from config: static `clients`, lockout policy fields (`store` stays), rate-limit buckets (`enabled`/`store` stay), `notifications`, trusted-devices `enabled`/`days`, `admin.impersonation`, organizations `roles`/`allowSelfCreate`/`invitationTtlHours`, and `password.policy`/`password.checkPwned` store options (`legacyVerifier`/`pepper`/`pwnedTimeoutMs` stay — they are code/infra).
3403
+ - Removed commands `authkit:clients:import` and the legacy `authkit:rotate-keys` alias. New `authkit:clients:create` creates OIDC clients programmatically through the configured storage (confidential secret printed once; `--public`, repeatable `--redirect-uri`/`--grant`, `--json`).
3404
+ - Removed the no-op `passthroughParsed` option from `jsonColumn` and the `checkLegacyPolicyConfig` doctor check.
3405
+
3406
+ ### Patch Changes
3407
+
3408
+ - Updated dependencies
3409
+ - @adonis-agora/authkit-core@0.4.0
3410
+
3411
+ ## 0.15.2
3412
+
3413
+ ### Patch Changes
3414
+
3415
+ - Fix RuntimeSettings against a real Lucid database: queries used `db.table()` (Lucid's INSERT query builder) for SELECT/DELETE, so the table probe always failed and every runtime setting reported "table absent" on real hosts. Reads/deletes now use `db.from()`; verified end-to-end against Postgres on a named connection.
3416
+
3417
+ ## 0.15.1
3418
+
3419
+ ### Patch Changes
3420
+
3421
+ - RuntimeSettings now probes the `auth_settings` table with a real `SELECT` (search_path-aware) instead of `schema.hasTable`, and follows the account store's named connection (`lucidAccountStore` exposes `connectionName` from the model). Hosts storing auth on a named connection (e.g. `connection: 'auth'`) no longer see every runtime setting as "table absent".
3422
+
3423
+ ## 0.15.0
3424
+
3425
+ ### Minor Changes
3426
+
3427
+ - Rodauth parity + runtime-first management:
3428
+ - **Verified email change** (`verify_login_change`): logged-in users change their email with confirmation sent to the NEW address and a security warning to the CURRENT one; cancellable, hashed tokens, `email_change` runtime setting, `onEmailChangeConfirm`/`onEmailChangeNotice` mail hooks.
3429
+ - **Security notification emails**: automatic notices for password changed, MFA enabled/disabled, passkey added/removed and email changed — `security_notifications` setting, `onSecurityNotice` hook overrides defaults.
3430
+ - **Advanced password hygiene**: password reuse history (optional `auth_password_history` table + `password_history` setting), password pepper (`password.pepper: string | string[]` with rotation and lazy re-hash), password expiration (optional `password_changed_at` column + forced change step at login), email-verification grace period (`require_verified_email.graceDays`).
3431
+ - **Session policies** (`session_policy` setting): explicit remember-me checkbox backed by oidc-provider transient sessions + runtime TTL holder, single-session enforcement (revokes other sessions on login), idle timeout for the account/admin consoles.
3432
+ - **Runtime-first management**: 18 runtime setting keys are now the single source of policy (setting > legacy config fallback > library default) — lockout, rate-limit buckets, password policy/HIBP, notifications, trusted devices, token TTLs (live via holder), admin impersonation and organizations policy join the existing toggles; legacy config policy fields are deprecated (kept as fallback) and reported by the new doctor check; new `authkit:settings:list|get|set|unset` ace commands write through the configured storage; the admin settings page is organized into sections.
3433
+
3434
+ ## 0.14.0
3435
+
3436
+ ### Minor Changes
3437
+
3438
+ - Render seam hardening for SSR hosts:
3439
+ - **Admin console always renders the built-in edge views** — the management area is library chrome, never routed through the host's custom renderer (custom-rendered hosts were 500ing on `/admin` because no `admin/*` pages exist on the host).
3440
+ - **`inertiaRenderer({ prefix, views?: string[] })`**: with the new `views` allowlist only listed screens go through Inertia; everything else silently falls back to the built-in edge views instead of crashing SSR with "Cannot read properties of undefined (reading 'default')" when the host page doesn't exist. Omitting `views` keeps the previous behavior. The react configure stub now scaffolds the allowlist.
3441
+
3442
+ ## 0.13.1
3443
+
3444
+ ### Patch Changes
3445
+
3446
+ - Fix Postgres json/jsonb columns crashing model hydration: `jsonColumn`'s `consume` blindly `JSON.parse`d every value, but Postgres drivers return json/jsonb columns already deserialized (objects/arrays) — hydrating `global_roles` blew up with `"[object Object]" is not valid JSON` (500 on the admin console right after login). `consume` now passes non-strings through, parses strings, and falls back safely on invalid JSON. The `passthroughParsed` option is deprecated (always on).
3447
+
3448
+ ## 0.13.0
3449
+
3450
+ ### Minor Changes
3451
+
3452
+ - Configurable Admin REST API prefix: `registerAuthHost(router, { adminApi: { prefix: '/authkit/api' } })` mounts the API under a custom prefix (default `/api/authkit/v1` unchanged; `adminApi: true` keeps working). The SDK remote driver gains a matching `apiPrefix` option in `createAuthkit`.
3453
+
3454
+ ## 0.12.0
3455
+
3456
+ ### Minor Changes
3457
+
3458
+ - Embedding & login-surface control:
3459
+ - **Configurable admin console prefix**: `registerAuthHost(router, { admin: { prefix: '/auth/admin' } })` mounts every console route, view link and redirect under a custom prefix (default `/admin`; `admin: true` unchanged). Admin REST API path is unaffected.
3460
+ - **`auth_methods` runtime setting**: choose from the admin UI which login methods the screens offer — password, magic link, passkey and which configured social providers. `forgotPassword` is auto-derived (no password method → no forgot-password link/endpoints), the social list intersects with code-configured providers, and an all-off setting fail-safes back to config defaults. New "Authentication methods" card in `/admin/settings` with dependency hints, plus doctor checks.
3461
+
3462
+ ## 0.11.2
3463
+
3464
+ ### Patch Changes
3465
+
3466
+ - `authkit:doctor` jwks check now reads the input shape (`jwksConfig`) instead of the materialized keyset, restoring the "managed without store = ephemeral key per boot" warning on resolved configs.
3467
+
3468
+ ## 0.11.1
3469
+
3470
+ ### Patch Changes
3471
+
3472
+ - Fix ace commands reading the raw config provider: `authkit:doctor`, `authkit:users:import`, `authkit:keys:rotate` and the legacy `authkit:rotate-keys` read `config.get('authkit')` directly, which returns the UNRESOLVED config provider that `defineConfig` exports — so every field (issuer, accountStore, jwks) looked missing against a perfectly valid host config. The commands now resolve the provider via the new `resolveAuthkitConfig` helper (plain-object configs still pass through). The resolved config also gains `jwksConfig`, an echo of the jwks INPUT shape (source/store/algorithm), since the resolved `jwks` is the materialized keyset and loses those fields needed by key rotation.
3473
+
3474
+ ## 0.11.0
3475
+
3476
+ ### Minor Changes
3477
+
3478
+ - Runtime-first administration:
3479
+ - **Three new runtime toggles** (auth_settings-backed, with admin console cards showing effective state): `registration` (open/close self-service signup without affecting org invites or admin-created users; static fallback `registration.enabled`), `require_verified_email` (overrides `login.requireVerifiedEmail` across password/magic-link/passkey flows), and `maintenance_mode` (`{ enabled, message? }` — blocks login/signup/forgot for non-admins with a maintenance page while admin accounts keep logging in; userinfo/introspection/existing sessions keep working; the Admin API is never blocked, providing a guaranteed escape hatch). Audit events `maintenance.enabled`/`maintenance.disabled`.
3480
+ - **Clients are now managed at runtime** (admin console + Admin REST API are the canonical path): the static `clients` config field is optional and deprecated (boot warning, doctor warning, console banner). New `authkit:clients:import` ace command (`--dry-run`) migrates config clients to the adapter preserving secrets and skipping existing ones. Booting with zero configured clients is fully supported. New doctor check warns when clients live in a volatile adapter. Backchannel logout URI/session-required are now editable via console and API.
3481
+
3482
+ ### Patch Changes
3483
+
3484
+ - Updated dependencies
3485
+ - @adonis-agora/authkit-core@0.3.1
3486
+
3487
+ ## 0.10.0
3488
+
3489
+ ### Minor Changes
3490
+
3491
+ - Runtime settings + bot protection UI toggle:
3492
+ - **Runtime settings store**: optional capability-probed `auth_settings` table with `SettingsCapability`, `supportsSettings` type guard and a `RuntimeSettings` service (15s TTL cache, fail-safe fallback to static config on any DB error or missing table).
3493
+ - **Bot protection runtime toggle**: the `bot_protection` setting key (`{ enabled, on? }`) turns bot protection on/off and overrides protected actions without redeploying — the `verify` hook still comes from config (it is code). No setting/table = static config, zero breaking changes.
3494
+ - **Admin console**: new `/admin/settings` page with the bot-protection card (toggle + action checkboxes, disabled state when `verify` is not configured, schema hint when the table is absent). Audit event `settings.updated`.
3495
+ - **Admin REST API**: `GET/PUT/DELETE /api/authkit/v1/settings[/:key]` (404 when capability absent).
3496
+ - **SDK**: `authkit.settings.list()/get()/set()/delete()` in both remote and embedded drivers.
3497
+ - **Doctor**: `checkSettings` warns about an orphan `bot_protection` setting when `botProtection.verify` is absent from config.
3498
+
3499
+ ## 0.9.0
3500
+
3501
+ ### Minor Changes
3502
+
3503
+ - Round 7 — production pack + multi-tenancy:
3504
+ - **Organizations (multi-tenancy)**: optional capability-probed tables (`auth_organizations`, `auth_organization_members`, `auth_organization_invitations`), per-org roles (owner/admin/member), email invitations with hashed tokens, active-org session cookie with `org_id`/`org_slug`/`org_role` claims in id_token/userinfo/JWT access tokens, `/account/orgs` page, admin console section, Admin REST API (`/api/authkit/v1/organizations`), SDK `organizations.*` namespace (remote + embedded), React `useOrganizations`/`useOrganization`/`useSwitchOrganization`/`useOrgInvitations` hooks plus `<OrganizationSwitcher />` and `<OrganizationProfile />` components.
3505
+ - **LGPD/GDPR compliance**: self-service and admin account deletion with full cascade (sessions, grants, PATs, passkeys, identities, MFA, avatar) and audit anonymization (stable pseudonym), data export endpoint (`GET /account/security/export`), `login.requireVerifiedEmail` gate across password/magic-link/passkey flows, SDK `users.delete()`.
3506
+ - **User migration & password hygiene**: transparent lazy password rehash on login, `password.legacyVerifier` hook for foreign hash formats, `authkit:users:import` ace command (JSON/NDJSON, `--dry-run`), configurable `password.policy`, HaveIBeenPwned k-anonymity breach check (fail-safe), React `usePasswordStrength` + `<PasswordStrengthMeter />`.
3507
+ - **JWT access tokens (RFC 9068)**: `accessTokens: { format: 'jwt' }` with per-resource overrides, `verifyJwtAccessToken` local validation in the client package, `authkit:keys:rotate` command with grace-period JWKS rotation.
3508
+ - **Bot protection**: pluggable vendor-agnostic `botProtection` config (Turnstile/hCaptcha-ready, fail-safe) on login/signup/reset.
3509
+ - **New-device login notification**: `notifications.newDeviceEmail` + `mail.onNewDeviceLogin` hook driven by the trusted-device signal.
3510
+ - **Console polish**: session context (user-agent/OS/IP/geo via pluggable `resolveGeo`), RFC 8693 impersonation panel (`admin.impersonation`, default off), dashboard with MAU/daily sign-ins, `GET /api/authkit/v1/stats` + SDK `stats()`.
3511
+
3512
+ ### Patch Changes
3513
+
3514
+ - Updated dependencies
3515
+ - @adonis-agora/authkit-core@0.3.0
3516
+
3517
+ ## 0.8.0
3518
+
3519
+ ### Minor Changes
3520
+
3521
+ - 93bfc4f: Admin REST API (/api/authkit/v1) com API keys — base do SDK
3522
+
3523
+ ## 0.7.0
3524
+
3525
+ ### Minor Changes
3526
+
3527
+ - 40c7737: Avatar upload no console de conta via o `@adonisjs/drive` do app (config `uploads.avatars`). Por padrão usa o disk default do app, diretório `authkit/avatars`, até 5MB; sobreponível por disk/directory/maxSizeMb. Loader lazy e fail-safe: sem o drive instalado/configurado a feature degrada para o input de URL e o input de arquivo é escondido. Aceita jpg/jpeg/png/webp; tipo/tamanho inválidos flasham erro i18n (EN+PT). Audita `profile.updated` com `{ via: 'upload' | 'url' }`.
3528
+
3529
+ ## 0.6.0
3530
+
3531
+ ### Minor Changes
3532
+
3533
+ - 0c33640: Round 4 — events/webhooks, DPoP client, full e2e harness, polish.
3534
+
3535
+ **server (minor):** add `events` config — observe every audit event in-process (`onEvent`)
3536
+ and/or via an HMAC-signed webhook (`x-authkit-signature: sha256=...`, 5s fire-and-forget,
3537
+ never throws into the request path). When set, the resolved `audit` sink becomes a fan-out
3538
+ (original sink + onEvent + webhook), preserving the admin `list()` query. Also: a full
3539
+ interaction e2e harness driving login → consent → token (plus step-up MFA and device-flow
3540
+ variants) through the real host controllers, and English `authkit:doctor` messages. Builds on
3541
+ the existing consent/account-console, admin user CRUD, profile self-service, trusted-device,
3542
+ and passwordless (magic-link / passkey-first) features.
3543
+
3544
+ **client (minor):** add DPoP (RFC 9449) proof generation — `generateDpopKeyPair()` (jose
3545
+ ES256, exportable JWK) and `createDpopProof({ key, htm, htu, nonce?, accessToken? })` producing
3546
+ a signed `dpop+jwt`, plus `dpopJwkThumbprint()`.
3547
+
3548
+ ## 0.5.0
3549
+
3550
+ ### Minor Changes
3551
+
3552
+ - 687501c: Built-in UI strings now default to English; pt-BR ships as a built-in locale (`i18n: { locale: 'pt-BR' }`). BREAKING-ish for hosts relying on pt-BR defaults: set the locale explicitly.
3553
+
3554
+ ## 0.4.0
3555
+
3556
+ ### Minor Changes
3557
+
3558
+ - Console completo + protocolo: sessões/grants ativos com revogação no admin,
3559
+ troca de senha/e-mail self-service, alerta de login de IP novo; Device
3560
+ Authorization Grant (RFC 8628), DPoP, PAR e step-up acr/MFA; `authkit:doctor`
3561
+ e `authkit:rotate-keys` (keystore de JWKS com rotação).
3562
+ - 1872a30: DX & ops infra:
3563
+ - New package `@adonis-agora/authkit-testing` — test helpers for host apps:
3564
+ `createTestIdentity`, `mintTestIdToken` + `serveJwks`/`testJwks`/`jwksFromKey`
3565
+ (real RS256 tokens validated by a local JWKS), `fakeAuthenticator`, and a
3566
+ capability-aware `fakeAccountStore`.
3567
+ - `node ace authkit:doctor` — validates host config and prints ✅/⚠️/❌ findings
3568
+ (issuer/mountPath, clients, accountStore capabilities, session, shield, ally,
3569
+ rate-limit, admin, webauthn, jwks). Non-zero exit on errors.
3570
+ - `node ace authkit:rotate-keys` — rotates managed JWKS signing keys via a new
3571
+ file-backed keystore (`jwks: { source: 'managed', store }`), keeping the last
3572
+ N public keys so pre-rotation tokens still verify.
3573
+
3574
+ ### Patch Changes
3575
+
3576
+ - Updated dependencies [1872a30]
3577
+ - @adonis-agora/authkit-core@0.2.0
3578
+
3579
+ ## 0.3.0
3580
+
3581
+ ### Minor Changes
3582
+
3583
+ - Console admin (B6): CRUD de clients OIDC armazenados no adapter (DB-backed).
3584
+ `/admin/clients` agora cria/edita/deleta clients dinâmicos (client_id/secret
3585
+ gerados, secret exibido uma única vez, regenerate-secret, redirect/grants/auth
3586
+ method editáveis), além de listar os estáticos do config (read-only). Adapter
3587
+ ganha `listClients?()` opcional (implementado no database e redis via SCAN; UI
3588
+ degrada graciosamente quando não suportado). Cache de clients do oidc-provider
3589
+ invalidado a cada escrita. Novos audit events `client.created/updated/deleted`.
3590
+
3591
+ ## 0.2.0
3592
+
3593
+ ### Minor Changes
3594
+
3595
+ - Refactors do code review (comportamento preservado, contratos mais limpos):
3596
+
3597
+ **server (minor):**
3598
+
3599
+ - `AccountStore` decomposto em interfaces de capacidade (`CoreAccountStore`,
3600
+ `MfaCapability`, `WebauthnCapability`, `ProviderIdentityCapability`) com type
3601
+ guards (`supportsMfa`/`supportsPasskeys`/`supportsProviderIdentity`). O tipo
3602
+ `AccountStore` continua existindo (core & Partial<capacidades>) — compatível.
3603
+ `lucidAccountStore` agora OMITE os métodos de capacidades não configuradas em
3604
+ vez de lançar em runtime; social login sem provider-identity degrada pro login.
3605
+ - Sequência login+lockout centralizada em `attemptPasswordLogin` (era duplicada
3606
+ em 2 controllers).
3607
+ - `adminGuard` agora retorna 404 quando `admin.enabled: false` (fecha bypass de
3608
+ drift entre config e `AuthHostOptions.admin`).
3609
+ - `dynamicRegistration.management: true` sem `enabled: true` agora falha no
3610
+ resolve do config (RFC 7592 exige 7591).
3611
+ - Serialização JSON dos mixins unificada em `jsonColumn()` (semântica por coluna
3612
+ preservada).
3613
+
3614
+ **client (minor):**
3615
+
3616
+ - POST ao token endpoint unificado (`exchangeCode`/`refreshTokens`/`exchangeToken`).
3617
+ - Introspection + claims→Identity compartilhados entre resolvers; `pat`/`opaque`
3618
+ agora também mapeiam `picture→profile.avatarUrl` e `sid→sessionId` (alinhados
3619
+ ao `jwt`).
3620
+
3621
+ **react (patch):**
3622
+
3623
+ - Helpers genéricos de roles; warn de dev no `useAuth` quando não há
3624
+ `AuthProvider` nem shared prop do Inertia.
3625
+
3626
+ ## 0.1.1
3627
+
3628
+ ### Patch Changes
3629
+
3630
+ - Corrige os stubs de scaffolding (`node ace configure`):
3631
+ - Usa os nomes renomeados dos pacotes (`@adonis-agora/authkit-*`) — antes os
3632
+ stubs ainda importavam de `@authkit/*` (inexistente), gerando código quebrado.
3633
+ - O model `AuthUser` scaffoldado passa a usar a **conexão default da aplicação**
3634
+ (config/database.ts) por padrão, em vez de forçar `static connection = 'auth'`.
3635
+ Para isolar o AuthKit num schema/banco dedicado, basta definir a conexão no
3636
+ model — documentado no próprio stub. A lib cria as tabelas no banco do app, ou
3637
+ onde o dev definir.