@adonis-agora/authkit-server 0.90.2 → 1.0.0-rc.1

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