@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.
- package/CHANGELOG.md +3654 -0
- package/build/commands/clients_create.d.ts +1 -0
- package/build/commands/clients_create.js +15 -2
- package/build/commands/commands.json +7 -0
- package/build/host/ui/admin.html +1 -1
- package/build/host/views/account/apps.edge +8 -3
- package/build/host/views/partials/styles.edge +1 -1
- package/build/index.d.ts +5 -3
- package/build/index.js +4 -5
- package/build/providers/authkit_server_provider.js +8 -0
- package/build/src/accounts/lucid_account_store.d.ts +0 -18
- package/build/src/accounts/lucid_account_store.js +7 -48
- package/build/src/accounts/lucid_store/mfa.js +1 -1
- package/build/src/accounts/lucid_store/organizations.d.ts +1 -1
- package/build/src/accounts/lucid_store/password_hygiene.d.ts +2 -3
- package/build/src/accounts/lucid_store/password_hygiene.js +2 -3
- package/build/src/accounts/lucid_store/shared.d.ts +0 -7
- package/build/src/accounts/lucid_store/shared.js +0 -14
- package/build/src/accounts/lucid_stores.d.ts +1 -1
- package/build/src/adapters/redis_adapter.js +12 -2
- package/build/src/console_authorize.d.ts +56 -0
- package/build/src/console_authorize.js +85 -0
- package/build/src/define_config.d.ts +26 -14
- package/build/src/define_config.js +5 -3
- package/build/src/doctor/checks.d.ts +10 -5
- package/build/src/doctor/checks.js +52 -8
- package/build/src/host/account_api/account_api_controller.d.ts +2 -1
- package/build/src/host/account_api/account_api_controller.js +5 -11
- package/build/src/host/admin_api/api_clients_controller.d.ts +9 -3
- package/build/src/host/admin_api/dto.d.ts +3 -1
- package/build/src/host/admin_api/dto.js +3 -1
- package/build/src/host/admin_clients_service.d.ts +13 -5
- package/build/src/host/admin_clients_service.js +28 -6
- package/build/src/host/admin_console/admin_shell_controller.d.ts +16 -1
- package/build/src/host/admin_console/admin_shell_controller.js +21 -7
- package/build/src/host/admin_console/console_clients_controller.d.ts +21 -2
- package/build/src/host/admin_console/console_clients_controller.js +9 -0
- package/build/src/host/admin_console/console_impersonation_controller.js +3 -8
- package/build/src/host/admin_console/console_overview_controller.d.ts +2 -2
- package/build/src/host/admin_console/console_overview_controller.js +4 -2
- package/build/src/host/admin_console_warning.d.ts +8 -0
- package/build/src/host/admin_console_warning.js +18 -0
- package/build/src/host/admin_validators.d.ts +15 -11
- package/build/src/host/admin_validators.js +16 -9
- package/build/src/host/auth_host_config.d.ts +5 -0
- package/build/src/host/client_names.d.ts +9 -2
- package/build/src/host/client_names.js +11 -3
- package/build/src/host/consent_params.d.ts +7 -0
- package/build/src/host/consent_params.js +27 -0
- package/build/src/host/console_denied_page.d.ts +6 -0
- package/build/src/host/console_denied_page.js +18 -0
- package/build/src/host/console_sso.d.ts +24 -0
- package/build/src/host/console_sso.js +70 -0
- package/build/src/host/controllers/account_apps_controller.js +4 -3
- package/build/src/host/controllers/account_confirm_controller.d.ts +0 -7
- package/build/src/host/controllers/account_confirm_controller.js +0 -6
- package/build/src/host/controllers/account_orgs_controller.d.ts +0 -50
- package/build/src/host/controllers/account_orgs_controller.js +7 -113
- package/build/src/host/controllers/account_security_controller.js +7 -11
- package/build/src/host/controllers/account_session_controller.js +10 -1
- package/build/src/host/controllers/agent_consent_controller.js +23 -5
- package/build/src/host/controllers/console_sso_controller.d.ts +12 -0
- package/build/src/host/controllers/console_sso_controller.js +61 -0
- package/build/src/host/controllers/interaction_controller.js +9 -3
- package/build/src/host/controllers/poppy_consent_controller.js +33 -7
- package/build/src/host/controllers/registration_controller.js +7 -2
- package/build/src/host/i18n.d.ts +0 -4
- package/build/src/host/i18n.js +0 -6
- package/build/src/host/impersonation.d.ts +7 -15
- package/build/src/host/impersonation.js +7 -16
- package/build/src/host/interaction_recovery.js +4 -1
- package/build/src/host/login_attempt.js +5 -1
- package/build/src/host/middleware/account_auth.js +6 -0
- package/build/src/host/rate_limit.js +5 -1
- package/build/src/host/register_auth_host.d.ts +8 -0
- package/build/src/host/register_auth_host.js +116 -11
- package/build/src/host/renderers/inertia_renderer.d.ts +9 -21
- package/build/src/host/renderers/inertia_renderer.js +5 -6
- package/build/src/host/screen_props.d.ts +309 -0
- package/build/src/host/screen_props.js +11 -0
- package/build/src/host/sudo_mode.d.ts +5 -11
- package/build/src/host/sudo_mode.js +5 -11
- package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +67 -0
- package/build/src/host/ui-dist/index.html +1 -1
- package/build/src/password/password_manager.js +5 -0
- package/build/src/provider/build_provider.js +6 -34
- package/build/src/provider/oidc_service.d.ts +0 -2
- package/build/src/provider/oidc_service.js +1 -12
- package/build/src/provider/token_exchange.d.ts +1 -3
- package/build/src/provider/token_exchange.js +1 -1
- package/build/stubs/config/authkit_react.stub +3 -3
- package/package.json +12 -11
- package/skills/authkit-interactions/SKILL.md +32 -5
- package/stubs/config/authkit_react.stub +3 -3
- package/build/src/commands/import_clients.d.ts +0 -40
- package/build/src/commands/import_clients.js +0 -81
- package/build/src/host/ui-dist/assets/index-DUVBDAyh.js +0 -67
- package/build/src/mixins/with_mfa.d.ts +0 -35
- 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.
|