@adonis-agora/authkit-server 0.46.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/host/views/otp-unlock.edge +2 -2
- package/build/providers/authkit_server_provider.js +7 -0
- package/build/services/booted_app.d.ts +8 -0
- package/build/services/booted_app.js +27 -0
- package/build/services/main.d.ts +7 -0
- package/build/services/main.js +9 -1
- package/build/src/host/account_login_url.d.ts +41 -0
- package/build/src/host/account_login_url.js +50 -0
- package/build/src/host/admin_sessions_service.d.ts +3 -1
- package/build/src/host/admin_sessions_service.js +35 -3
- package/build/src/host/console_session.d.ts +4 -0
- package/build/src/host/console_session.js +9 -2
- package/build/src/host/controllers/account_orgs_controller.js +7 -4
- package/build/src/host/controllers/account_security_controller.js +3 -2
- package/build/src/host/controllers/account_session_controller.js +3 -1
- package/build/src/host/controllers/account_tokens_controller.js +11 -0
- package/build/src/host/controllers/pat_introspection_controller.js +10 -0
- package/build/src/host/middleware/account_auth.js +3 -1
- package/build/src/host/register_auth_host.d.ts +66 -2
- package/build/src/host/register_auth_host.js +116 -57
- package/build/src/host/renderers/edge_renderer.js +6 -1
- package/build/src/host/renderers/inertia_renderer.d.ts +55 -0
- package/build/src/host/renderers/inertia_renderer.js +55 -0
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
</div>
|
|
16
16
|
<h1 class="mb-2 text-lg font-semibold text-gray-900">{{ t('otp_unlock.ok_title') }}</h1>
|
|
17
17
|
<p class="mb-6 text-sm text-gray-600">{{ t('otp_unlock.ok_body') }}</p>
|
|
18
|
-
<a href="/account/login" class="inline-block rounded-lg bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-700">{{ t('otp_unlock.login_link') }}</a>
|
|
18
|
+
<a href="{{ loginUrl ?? '/account/login' }}" class="inline-block rounded-lg bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-700">{{ t('otp_unlock.login_link') }}</a>
|
|
19
19
|
@else
|
|
20
20
|
<div class="mb-4 flex justify-center">
|
|
21
21
|
<span class="inline-flex h-12 w-12 items-center justify-center rounded-full bg-red-100 text-red-600">
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
{{ t('otp_unlock.invalid_body') }}
|
|
33
33
|
@end
|
|
34
34
|
</p>
|
|
35
|
-
<a href="/account/login" class="inline-block rounded-lg bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-700">{{ t('otp_unlock.login_link') }}</a>
|
|
35
|
+
<a href="{{ loginUrl ?? '/account/login' }}" class="inline-block rounded-lg bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-700">{{ t('otp_unlock.login_link') }}</a>
|
|
36
36
|
@end
|
|
37
37
|
</div>
|
|
38
38
|
</div>
|
|
@@ -13,6 +13,7 @@ import { resolveKeystoreVault, KeystoreManager } from '../src/keys/keystore_mana
|
|
|
13
13
|
import { KeystoreCodec } from '../src/keys/keystore_codec.js';
|
|
14
14
|
import { loadEncryptionService } from '../src/keys/keystore_crypto.js';
|
|
15
15
|
import { resolveAppKey } from '../src/host/app_key.js';
|
|
16
|
+
import { setBootedApp } from '../services/booted_app.js';
|
|
16
17
|
export default class AuthkitServerProvider {
|
|
17
18
|
app;
|
|
18
19
|
constructor(app) {
|
|
@@ -85,6 +86,12 @@ export default class AuthkitServerProvider {
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
register() {
|
|
89
|
+
// Entrega o app BOOTADO ao singleton de `services/main` (e ao
|
|
90
|
+
// `recordSubRevocation` do AdminSessionsService) para que nunca precisem
|
|
91
|
+
// `import`ar uma cópia de `@adonisjs/core`/`services/app` que pode não ser a
|
|
92
|
+
// que o `bin/server` bootou — dual-package hazard do core (ver
|
|
93
|
+
// `services/booted_app.ts`).
|
|
94
|
+
setBootedApp(this.app);
|
|
88
95
|
this.app.container.singleton('authkit.server', async () => {
|
|
89
96
|
const configProviderValue = this.app.config.get('authkit');
|
|
90
97
|
const config = (await configProvider.resolve(this.app, configProviderValue));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ApplicationService } from '@adonisjs/core/types';
|
|
2
|
+
/** Registra o app bootado. Chamado uma vez pelo {@link AuthkitServerProvider} no `register()`. */
|
|
3
|
+
export declare function setBootedApp(app: ApplicationService): void;
|
|
4
|
+
/**
|
|
5
|
+
* O app bootado capturado pelo provider. Lança se lido antes do provider registrar — um sinal claro
|
|
6
|
+
* de que `@adonis-agora/authkit-server/authkit_server_provider` está ausente dos providers do app.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getBootedApp(): ApplicationService;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A {@link ApplicationService} BOOTADA, capturada por `AuthkitServerProvider.register()` — que o app
|
|
3
|
+
* instancia com a SUA própria cópia bootada do app.
|
|
4
|
+
*
|
|
5
|
+
* Por que capturar aqui em vez de `import app from '@adonisjs/core/services/app'`: num install pnpm
|
|
6
|
+
* (workspace / hoisted), este pacote pode resolver uma cópia FÍSICA de `@adonisjs/core` DIFERENTE da
|
|
7
|
+
* que o `bin/server` bootou. `services/app` expõe o app por um binding de nível de módulo definido no
|
|
8
|
+
* boot (`setApp`); numa cópia NÃO-bootada esse binding fica `undefined` — importar de lá devolve um
|
|
9
|
+
* app indefinido (`Cannot read properties of undefined (reading 'booted')`). A instância que o
|
|
10
|
+
* provider recebe é SEMPRE a bootada, então lê-la aqui é imune a splits de cópia / variantes de peer
|
|
11
|
+
* do core — o mesmo hazard de dual-package que já corrigimos no `'lucid.db'`.
|
|
12
|
+
*/
|
|
13
|
+
let bootedApp;
|
|
14
|
+
/** Registra o app bootado. Chamado uma vez pelo {@link AuthkitServerProvider} no `register()`. */
|
|
15
|
+
export function setBootedApp(app) {
|
|
16
|
+
bootedApp = app;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* O app bootado capturado pelo provider. Lança se lido antes do provider registrar — um sinal claro
|
|
20
|
+
* de que `@adonis-agora/authkit-server/authkit_server_provider` está ausente dos providers do app.
|
|
21
|
+
*/
|
|
22
|
+
export function getBootedApp() {
|
|
23
|
+
if (!bootedApp) {
|
|
24
|
+
throw new Error('@adonis-agora/authkit-server: app acessado antes de AuthkitServerProvider registrar. Adicione "@adonis-agora/authkit-server/authkit_server_provider" aos providers do adonisrc.ts.');
|
|
25
|
+
}
|
|
26
|
+
return bootedApp;
|
|
27
|
+
}
|
package/build/services/main.d.ts
CHANGED
|
@@ -10,6 +10,13 @@ import type { OidcService } from "../src/provider/oidc_service.js";
|
|
|
10
10
|
* Roda `await app.booted()` no top-level, então SÓ funciona dentro de um app
|
|
11
11
|
* booted — por isso `scripts/import-smoke.mjs` pula o diretório `services`.
|
|
12
12
|
*
|
|
13
|
+
* O `app` vem do {@link getBootedApp} (capturado pelo provider no `register()`), NÃO de
|
|
14
|
+
* `import app from "@adonisjs/core/services/app"`: sob pnpm este pacote pode resolver uma cópia
|
|
15
|
+
* FÍSICA de `@adonisjs/core` diferente da que o `bin/server` bootou, cujo binding de `services/app`
|
|
16
|
+
* fica `undefined` — mesmo dual-package hazard do `'lucid.db'`, aqui para o singleton do core. Ver
|
|
17
|
+
* {@link ./booted_app.js}. A instância que o provider recebe é sempre a bootada. Back-compat total:
|
|
18
|
+
* o `default` continua sendo o {@link OidcService} resolvido.
|
|
19
|
+
*
|
|
13
20
|
* Dentro da própria lib continuamos resolvendo via `ctx.containerResolver.make("authkit.server")`,
|
|
14
21
|
* que é o idioma das libs first-party (ver `@adonisjs/auth`, `initialize_auth_middleware`).
|
|
15
22
|
*/
|
package/build/services/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { getBootedApp } from "./booted_app.js";
|
|
2
2
|
/**
|
|
3
3
|
* Acessor singleton do {@link OidcService}, seguindo a convenção `services/main` do
|
|
4
4
|
* AdonisJS (igual `@adonisjs/lucid/services/db`, `@adonisjs/drive/services/main` e
|
|
@@ -10,10 +10,18 @@ import app from "@adonisjs/core/services/app";
|
|
|
10
10
|
* Roda `await app.booted()` no top-level, então SÓ funciona dentro de um app
|
|
11
11
|
* booted — por isso `scripts/import-smoke.mjs` pula o diretório `services`.
|
|
12
12
|
*
|
|
13
|
+
* O `app` vem do {@link getBootedApp} (capturado pelo provider no `register()`), NÃO de
|
|
14
|
+
* `import app from "@adonisjs/core/services/app"`: sob pnpm este pacote pode resolver uma cópia
|
|
15
|
+
* FÍSICA de `@adonisjs/core` diferente da que o `bin/server` bootou, cujo binding de `services/app`
|
|
16
|
+
* fica `undefined` — mesmo dual-package hazard do `'lucid.db'`, aqui para o singleton do core. Ver
|
|
17
|
+
* {@link ./booted_app.js}. A instância que o provider recebe é sempre a bootada. Back-compat total:
|
|
18
|
+
* o `default` continua sendo o {@link OidcService} resolvido.
|
|
19
|
+
*
|
|
13
20
|
* Dentro da própria lib continuamos resolvendo via `ctx.containerResolver.make("authkit.server")`,
|
|
14
21
|
* que é o idioma das libs first-party (ver `@adonisjs/auth`, `initialize_auth_middleware`).
|
|
15
22
|
*/
|
|
16
23
|
let service;
|
|
24
|
+
const app = getBootedApp();
|
|
17
25
|
await app.booted(async () => {
|
|
18
26
|
service = await app.container.make("authkit.server");
|
|
19
27
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL de login do console de conta — singleton de processo.
|
|
3
|
+
*
|
|
4
|
+
* Definida em tempo de registro das rotas (`registerAuthHost`, via a opção
|
|
5
|
+
* `accountLoginUrl`) e lida em runtime por TODOS os pontos que redirecionam o
|
|
6
|
+
* visitante não-autenticado para "faça login": os guards (`accountGuard`/
|
|
7
|
+
* `adminGuard`), o middleware `AccountAuthMiddleware`, o helper público
|
|
8
|
+
* `consoleLoginUrl()`, os redirects de fallback dos controllers de conta e a
|
|
9
|
+
* view Edge `otp-unlock` (injetada como prop `loginUrl` pelo renderer).
|
|
10
|
+
*
|
|
11
|
+
* Existe porque a tela `account/login` é DESMONTÁVEL (`account: { login: false }`):
|
|
12
|
+
* um host OIDC passwordless não monta o login por senha da lib e aponta o
|
|
13
|
+
* redirect de não-autenticado para a própria rota de login dele (ex.: `/login`).
|
|
14
|
+
* Sem esta indireção, esses destinos ficariam presos no `/account/login` que
|
|
15
|
+
* deixou de existir.
|
|
16
|
+
*
|
|
17
|
+
* Default `/account/login` (back-compat total: hosts que não passam a opção
|
|
18
|
+
* seguem redirecionando para a tela montada de sempre).
|
|
19
|
+
*
|
|
20
|
+
* Por que módulo singleton e não binding do container? Mesma razão do
|
|
21
|
+
* `admin_prefix`: os guards são closures construídas em tempo de registro,
|
|
22
|
+
* ANTES da primeira request, quando o container ainda não existe. Um módulo ESM
|
|
23
|
+
* é inicializado uma vez por processo — ideal para configuração imutável de boot.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Define a URL de login do console de conta para este processo.
|
|
27
|
+
* Chamado UMA VEZ por `registerAuthHost` no boot da aplicação.
|
|
28
|
+
*
|
|
29
|
+
* Não normaliza o path: aceita qualquer caminho interno que o host queira
|
|
30
|
+
* (ex.: `'/login'`, `'/auth/entrar'`). Valor vazio/whitespace cai no default.
|
|
31
|
+
*
|
|
32
|
+
* @param url Caminho de destino do redirect de não-autenticado.
|
|
33
|
+
*/
|
|
34
|
+
export declare function setAccountLoginUrl(url: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Retorna a URL de login do console de conta (default `'/account/login'`).
|
|
37
|
+
* Usada por todo redirect/link de "faça login" da lib.
|
|
38
|
+
*/
|
|
39
|
+
export declare function getAccountLoginUrl(): string;
|
|
40
|
+
/** Restaura o default — uso em testes (isola o singleton entre casos). */
|
|
41
|
+
export declare function resetAccountLoginUrl(): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL de login do console de conta — singleton de processo.
|
|
3
|
+
*
|
|
4
|
+
* Definida em tempo de registro das rotas (`registerAuthHost`, via a opção
|
|
5
|
+
* `accountLoginUrl`) e lida em runtime por TODOS os pontos que redirecionam o
|
|
6
|
+
* visitante não-autenticado para "faça login": os guards (`accountGuard`/
|
|
7
|
+
* `adminGuard`), o middleware `AccountAuthMiddleware`, o helper público
|
|
8
|
+
* `consoleLoginUrl()`, os redirects de fallback dos controllers de conta e a
|
|
9
|
+
* view Edge `otp-unlock` (injetada como prop `loginUrl` pelo renderer).
|
|
10
|
+
*
|
|
11
|
+
* Existe porque a tela `account/login` é DESMONTÁVEL (`account: { login: false }`):
|
|
12
|
+
* um host OIDC passwordless não monta o login por senha da lib e aponta o
|
|
13
|
+
* redirect de não-autenticado para a própria rota de login dele (ex.: `/login`).
|
|
14
|
+
* Sem esta indireção, esses destinos ficariam presos no `/account/login` que
|
|
15
|
+
* deixou de existir.
|
|
16
|
+
*
|
|
17
|
+
* Default `/account/login` (back-compat total: hosts que não passam a opção
|
|
18
|
+
* seguem redirecionando para a tela montada de sempre).
|
|
19
|
+
*
|
|
20
|
+
* Por que módulo singleton e não binding do container? Mesma razão do
|
|
21
|
+
* `admin_prefix`: os guards são closures construídas em tempo de registro,
|
|
22
|
+
* ANTES da primeira request, quando o container ainda não existe. Um módulo ESM
|
|
23
|
+
* é inicializado uma vez por processo — ideal para configuração imutável de boot.
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_ACCOUNT_LOGIN_URL = '/account/login';
|
|
26
|
+
let _loginUrl = DEFAULT_ACCOUNT_LOGIN_URL;
|
|
27
|
+
/**
|
|
28
|
+
* Define a URL de login do console de conta para este processo.
|
|
29
|
+
* Chamado UMA VEZ por `registerAuthHost` no boot da aplicação.
|
|
30
|
+
*
|
|
31
|
+
* Não normaliza o path: aceita qualquer caminho interno que o host queira
|
|
32
|
+
* (ex.: `'/login'`, `'/auth/entrar'`). Valor vazio/whitespace cai no default.
|
|
33
|
+
*
|
|
34
|
+
* @param url Caminho de destino do redirect de não-autenticado.
|
|
35
|
+
*/
|
|
36
|
+
export function setAccountLoginUrl(url) {
|
|
37
|
+
const trimmed = (url ?? '').trim();
|
|
38
|
+
_loginUrl = trimmed || DEFAULT_ACCOUNT_LOGIN_URL;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Retorna a URL de login do console de conta (default `'/account/login'`).
|
|
42
|
+
* Usada por todo redirect/link de "faça login" da lib.
|
|
43
|
+
*/
|
|
44
|
+
export function getAccountLoginUrl() {
|
|
45
|
+
return _loginUrl;
|
|
46
|
+
}
|
|
47
|
+
/** Restaura o default — uso em testes (isola o singleton entre casos). */
|
|
48
|
+
export function resetAccountLoginUrl() {
|
|
49
|
+
_loginUrl = DEFAULT_ACCOUNT_LOGIN_URL;
|
|
50
|
+
}
|
|
@@ -55,7 +55,9 @@ export declare class AdminSessionsService {
|
|
|
55
55
|
* a sessão na próxima request — INSTANTÂNEO, sem esperar o refresh token falhar.
|
|
56
56
|
*
|
|
57
57
|
* Best-effort: a destruição de grants/tokens acima já é a fonte da verdade; esta
|
|
58
|
-
* linha só acelera o efeito nos clients. Falha (tabela ausente
|
|
58
|
+
* linha só acelera o efeito nos clients. Falha (tabela ausente / lucid indisponível)
|
|
59
|
+
* NÃO propaga — mas NUNCA é silenciosa: é logada em `error`, porque uma revogação de
|
|
60
|
+
* sessão que não persiste é um risco de segurança que não pode ficar invisível.
|
|
59
61
|
*
|
|
60
62
|
* @param accountId Conta cujas sessões devem ser derrubadas (gravado em `sub`).
|
|
61
63
|
* @param revokedAt Timestamp da revogação. Default: agora. A checagem do client
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getBootedApp } from '../../services/booted_app.js';
|
|
1
2
|
/**
|
|
2
3
|
* Serviço de inspeção/revogação das SESSÕES e GRANTS ativos de uma conta,
|
|
3
4
|
* persistidos pelo oidc-provider via o MESMO `AdapterClass` (mesmo padrão do
|
|
@@ -32,7 +33,9 @@ export class AdminSessionsService {
|
|
|
32
33
|
* a sessão na próxima request — INSTANTÂNEO, sem esperar o refresh token falhar.
|
|
33
34
|
*
|
|
34
35
|
* Best-effort: a destruição de grants/tokens acima já é a fonte da verdade; esta
|
|
35
|
-
* linha só acelera o efeito nos clients. Falha (tabela ausente
|
|
36
|
+
* linha só acelera o efeito nos clients. Falha (tabela ausente / lucid indisponível)
|
|
37
|
+
* NÃO propaga — mas NUNCA é silenciosa: é logada em `error`, porque uma revogação de
|
|
38
|
+
* sessão que não persiste é um risco de segurança que não pode ficar invisível.
|
|
36
39
|
*
|
|
37
40
|
* @param accountId Conta cujas sessões devem ser derrubadas (gravado em `sub`).
|
|
38
41
|
* @param revokedAt Timestamp da revogação. Default: agora. A checagem do client
|
|
@@ -43,7 +46,16 @@ export class AdminSessionsService {
|
|
|
43
46
|
*/
|
|
44
47
|
async recordSubRevocation(accountId, revokedAt = new Date()) {
|
|
45
48
|
try {
|
|
46
|
-
|
|
49
|
+
// Resolve o Lucid pelo ALIAS STRING `'lucid.db'` (o mesmo idioma de
|
|
50
|
+
// `runtime_settings.ts` e do provider), NUNCA por `import('@adonisjs/lucid/services/db')`
|
|
51
|
+
// — esse resolve a CLASSE `Database` e a usa como TOKEN do container. Com duas cópias
|
|
52
|
+
// físicas do lucid na árvore do host (pins distintos ou o mesmo pin sob peer sets
|
|
53
|
+
// distintos, que o pnpm materializa em diretórios separados) os tokens-classe diferem, o
|
|
54
|
+
// `make()` falha (`Cannot construct "[class Database]"`) e a gravação era engolida em
|
|
55
|
+
// SILÊNCIO por este catch — a pior variante do bug. Um token string não pode ser
|
|
56
|
+
// duplicado. O app vem do provider (ver `services/booted_app.ts`), não de `services/app`.
|
|
57
|
+
const app = getBootedApp();
|
|
58
|
+
const db = await app.container.make('lucid.db');
|
|
47
59
|
const conn = this.#schemaConnection
|
|
48
60
|
? db.connection(this.#schemaConnection)
|
|
49
61
|
: db.connection();
|
|
@@ -53,8 +65,28 @@ export class AdminSessionsService {
|
|
|
53
65
|
revoked_at: revokedAt,
|
|
54
66
|
});
|
|
55
67
|
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
// A invalidação server-side (destruição de grants/tokens) já é a fonte da verdade, então
|
|
70
|
+
// NÃO propagamos. Mas NUNCA engolimos em silêncio: logamos em `error` para que a falha da
|
|
71
|
+
// propagação cookie-based (tabela ausente, lucid indisponível, provider não registrado)
|
|
72
|
+
// seja visível — uma revogação que não persiste é um risco de segurança invisível.
|
|
73
|
+
await this.#logRevocationFailure(accountId, error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Loga (best-effort, sem nunca lançar) a falha de {@link recordSubRevocation}. Prefere o
|
|
78
|
+
* `logger` do container; se ele não puder ser resolvido (ex.: o próprio `getBootedApp()` foi o
|
|
79
|
+
* que falhou), cai em `console.error` — a falha PRECISA aparecer em algum lugar.
|
|
80
|
+
*/
|
|
81
|
+
async #logRevocationFailure(accountId, error) {
|
|
82
|
+
const msg = 'authkit: falha ao gravar a revogação por sub (auth_session_revocations) — a propagação cookie-based da revogação de sessão não foi persistida';
|
|
83
|
+
try {
|
|
84
|
+
const logger = await getBootedApp().container.make('logger');
|
|
85
|
+
logger.error({ err: error, accountId }, msg);
|
|
86
|
+
}
|
|
56
87
|
catch {
|
|
57
|
-
|
|
88
|
+
// eslint-disable-next-line no-console
|
|
89
|
+
console.error(msg, { accountId, error });
|
|
58
90
|
}
|
|
59
91
|
}
|
|
60
92
|
/** Indica se o adapter suporta enumeração (capacidade opcional). */
|
|
@@ -21,6 +21,10 @@ export declare function hasAccountSession(ctx: HttpContext): boolean;
|
|
|
21
21
|
* URL do login do console, com `return_to` opcional de volta ao destino
|
|
22
22
|
* original após autenticar.
|
|
23
23
|
*
|
|
24
|
+
* Respeita a opção `accountLoginUrl` de `registerAuthHost`: quando o host
|
|
25
|
+
* desmontou a tela `account/login` e apontou para a própria rota de login
|
|
26
|
+
* (ex.: `/login`), este helper usa esse destino. Default `/account/login`.
|
|
27
|
+
*
|
|
24
28
|
* @example
|
|
25
29
|
* consoleLoginUrl('/telescope') // => '/account/login?return_to=%2Ftelescope'
|
|
26
30
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ACCOUNT_SESSION_KEY } from './middleware/account_auth.js';
|
|
2
|
+
import { getAccountLoginUrl } from './account_login_url.js';
|
|
2
3
|
/**
|
|
3
4
|
* Helpers públicos para integrar a sessão do console do AuthKit com
|
|
4
5
|
* qualquer coisa fora do pacote (ex.: proteger o dashboard do
|
|
@@ -26,11 +27,17 @@ export function hasAccountSession(ctx) {
|
|
|
26
27
|
* URL do login do console, com `return_to` opcional de volta ao destino
|
|
27
28
|
* original após autenticar.
|
|
28
29
|
*
|
|
30
|
+
* Respeita a opção `accountLoginUrl` de `registerAuthHost`: quando o host
|
|
31
|
+
* desmontou a tela `account/login` e apontou para a própria rota de login
|
|
32
|
+
* (ex.: `/login`), este helper usa esse destino. Default `/account/login`.
|
|
33
|
+
*
|
|
29
34
|
* @example
|
|
30
35
|
* consoleLoginUrl('/telescope') // => '/account/login?return_to=%2Ftelescope'
|
|
31
36
|
*/
|
|
32
37
|
export function consoleLoginUrl(returnTo) {
|
|
38
|
+
const loginUrl = getAccountLoginUrl();
|
|
33
39
|
if (!returnTo)
|
|
34
|
-
return
|
|
35
|
-
|
|
40
|
+
return loginUrl;
|
|
41
|
+
const sep = loginUrl.includes('?') ? '&' : '?';
|
|
42
|
+
return `${loginUrl}${sep}return_to=${encodeURIComponent(returnTo)}`;
|
|
36
43
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
2
|
import { supportsOrganizations } from '../../accounts/account_store.js';
|
|
3
3
|
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
4
|
+
import { getAccountLoginUrl } from '../account_login_url.js';
|
|
4
5
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
5
6
|
import { isRoleInCatalog } from '../runtime_toggles.js';
|
|
6
7
|
import { ACTIVE_ORG_COOKIE, ACTIVE_ORG_COOKIE_TTL, encodeActiveOrgCookie, } from '../active_org_cookie.js';
|
|
@@ -26,7 +27,7 @@ export default class AccountOrgsController {
|
|
|
26
27
|
const accountId = session.get(ACCOUNT_SESSION_KEY);
|
|
27
28
|
const account = await store.findById(accountId);
|
|
28
29
|
if (!account)
|
|
29
|
-
return response.redirect(
|
|
30
|
+
return response.redirect(getAccountLoginUrl());
|
|
30
31
|
const orgs = await store.listOrgsForAccount(accountId);
|
|
31
32
|
const pendingInvitations = await store.listPendingInvitationsForEmail(account.email);
|
|
32
33
|
// Enriquece convites com o nome da org
|
|
@@ -210,9 +211,11 @@ export default class AccountOrgsController {
|
|
|
210
211
|
return response.notFound();
|
|
211
212
|
const accountId = session.get(ACCOUNT_SESSION_KEY);
|
|
212
213
|
if (!accountId) {
|
|
213
|
-
// Não logado: redireciona para login com return URL
|
|
214
|
+
// Não logado: redireciona para login (configurável) com return URL
|
|
215
|
+
const loginUrl = getAccountLoginUrl();
|
|
214
216
|
const returnTo = encodeURIComponent(`/account/orgs/invitations/${params.token}/accept`);
|
|
215
|
-
|
|
217
|
+
const sep = loginUrl.includes('?') ? '&' : '?';
|
|
218
|
+
return response.redirect(`${loginUrl}${sep}returnTo=${returnTo}`);
|
|
216
219
|
}
|
|
217
220
|
const tokenHash = createHash('sha256').update(params.token).digest('hex');
|
|
218
221
|
const invitation = await store.findInvitationByTokenHash(tokenHash);
|
|
@@ -236,7 +239,7 @@ export default class AccountOrgsController {
|
|
|
236
239
|
return response.notFound();
|
|
237
240
|
const accountId = session.get(ACCOUNT_SESSION_KEY);
|
|
238
241
|
if (!accountId)
|
|
239
|
-
return response.redirect(
|
|
242
|
+
return response.redirect(getAccountLoginUrl());
|
|
240
243
|
const tokenHash = createHash('sha256').update(params.token).digest('hex');
|
|
241
244
|
const invitation = await store.findInvitationByTokenHash(tokenHash);
|
|
242
245
|
if (!invitation)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "../augmentations.js";
|
|
2
2
|
import { ACCOUNT_SESSION_KEY } from "../middleware/account_auth.js";
|
|
3
|
+
import { getAccountLoginUrl } from "../account_login_url.js";
|
|
3
4
|
import { supportsAccountSecurity, supportsAccountDeletion, supportsProfile, supportsPasswordHistory, } from "../../accounts/account_store.js";
|
|
4
5
|
import { changePasswordValidator, changeEmailValidator, deleteAccountValidator, updateProfileValidator, } from "../validators.js";
|
|
5
6
|
import { sendEmailChangeConfirmationEmail, sendEmailChangeNoticeEmail, sendEmailChangedCompletedEmail, } from "../default_mailer.js";
|
|
@@ -150,7 +151,7 @@ export default class AccountSecurityController {
|
|
|
150
151
|
return sudoResultDel;
|
|
151
152
|
const account = await store.findById(userId);
|
|
152
153
|
if (!account) {
|
|
153
|
-
return ctx.response.redirect(
|
|
154
|
+
return ctx.response.redirect(getAccountLoginUrl());
|
|
154
155
|
}
|
|
155
156
|
const { currentPassword, confirmEmail } = await ctx.request.validateUsing(deleteAccountValidator);
|
|
156
157
|
// Confirmação: senha atual correta OU e-mail digitado batendo com o da conta
|
|
@@ -196,7 +197,7 @@ export default class AccountSecurityController {
|
|
|
196
197
|
ctx.session.forget(ACCOUNT_SESSION_KEY);
|
|
197
198
|
await syncAdonisAuthLogout(ctx, cfg);
|
|
198
199
|
ctx.session.flash("accountDeleted", translate(cfg.messages, "account.delete.deleted"));
|
|
199
|
-
return ctx.response.redirect(
|
|
200
|
+
return ctx.response.redirect(getAccountLoginUrl());
|
|
200
201
|
}
|
|
201
202
|
/**
|
|
202
203
|
* POST /account/security/trusted-devices/revoke
|
|
@@ -5,6 +5,7 @@ import { attemptPasswordLogin } from '../login_attempt.js';
|
|
|
5
5
|
import { notifyLoginSuccess } from '../login_notify.js';
|
|
6
6
|
import { markSudo } from '../sudo_mode.js';
|
|
7
7
|
import { accountHome } from '../account_home.js';
|
|
8
|
+
import { getAccountLoginUrl } from '../account_login_url.js';
|
|
8
9
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
9
10
|
import { syncAdonisAuthLogin, syncAdonisAuthLogout } from '../adonis_auth_sync.js';
|
|
10
11
|
/**
|
|
@@ -132,6 +133,7 @@ export default class AccountSessionController {
|
|
|
132
133
|
await ctx.session.regenerate();
|
|
133
134
|
// Opt-in: espelha o logout no guard de @adonisjs/auth (ver adonisAuth em define_config.ts).
|
|
134
135
|
await syncAdonisAuthLogout(ctx, cfg);
|
|
135
|
-
|
|
136
|
+
// Destino configurável (`accountLoginUrl`): default `/account/login`.
|
|
137
|
+
return ctx.response.redirect(getAccountLoginUrl());
|
|
136
138
|
}
|
|
137
139
|
}
|
|
@@ -7,6 +7,11 @@ export default class AccountTokensController {
|
|
|
7
7
|
const service = await ctx.containerResolver.make('authkit.server');
|
|
8
8
|
const cfg = service.config;
|
|
9
9
|
const render = cfg.render;
|
|
10
|
+
// PAT é capacidade opcional: sem `patStore` configurado, a tela não existe
|
|
11
|
+
// (404 limpo em vez de "Cannot read properties of undefined"). Tratado como
|
|
12
|
+
// orgs — a rota pode estar montada num host que não cabeou o store.
|
|
13
|
+
if (!cfg.patStore)
|
|
14
|
+
return ctx.response.notFound();
|
|
10
15
|
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
11
16
|
const tokens = await cfg.patStore.listForAccount(userId);
|
|
12
17
|
const createdToken = ctx.session.flashMessages.get('createdToken');
|
|
@@ -26,6 +31,9 @@ export default class AccountTokensController {
|
|
|
26
31
|
async store(ctx) {
|
|
27
32
|
const service = await ctx.containerResolver.make('authkit.server');
|
|
28
33
|
const cfg = service.config;
|
|
34
|
+
// Sem `patStore`: 404 limpo (ver `index`).
|
|
35
|
+
if (!cfg.patStore)
|
|
36
|
+
return ctx.response.notFound();
|
|
29
37
|
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
30
38
|
// Sudo mode gate.
|
|
31
39
|
const sudoSettingsPat = await resolveRuntimeSettings(ctx);
|
|
@@ -46,6 +54,9 @@ export default class AccountTokensController {
|
|
|
46
54
|
async destroy(ctx) {
|
|
47
55
|
const service = await ctx.containerResolver.make('authkit.server');
|
|
48
56
|
const cfg = service.config;
|
|
57
|
+
// Sem `patStore`: 404 limpo (ver `index`).
|
|
58
|
+
if (!cfg.patStore)
|
|
59
|
+
return ctx.response.notFound();
|
|
49
60
|
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
50
61
|
// Sudo mode gate.
|
|
51
62
|
const sudoSettingsPatRev = await resolveRuntimeSettings(ctx);
|
|
@@ -19,6 +19,16 @@ export default class PatIntrospectionController {
|
|
|
19
19
|
if (!token || typeof token !== 'string') {
|
|
20
20
|
return { active: false };
|
|
21
21
|
}
|
|
22
|
+
// `patStore` é capacidade opcional; a rota `/authkit/pat/introspect` fica
|
|
23
|
+
// sempre montada (é infraestrutura M2M, não uma tela desmontável). Sem
|
|
24
|
+
// store cabeado, RFC 7662 §2.2 já cobre o caso: para o servidor de
|
|
25
|
+
// recursos, um token que não pode ser resolvido é indistinguível de um
|
|
26
|
+
// token inválido/desconhecido → `{ active: false }`, não 404. Um 404
|
|
27
|
+
// quebraria clientes de introspecção que só sabem parsear o corpo JSON do
|
|
28
|
+
// protocolo (mesma classe de bug do `account_tokens_controller`, mas a
|
|
29
|
+
// resposta correta aqui é a negativa do protocolo, não um HTTP status).
|
|
30
|
+
if (!cfg.patStore)
|
|
31
|
+
return { active: false };
|
|
22
32
|
const meta = await cfg.patStore.findActiveByToken(token);
|
|
23
33
|
if (!meta)
|
|
24
34
|
return { active: false };
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
|
+
import { getAccountLoginUrl } from '../account_login_url.js';
|
|
2
3
|
export const ACCOUNT_SESSION_KEY = 'account_user_id';
|
|
3
4
|
export default class AccountAuthMiddleware {
|
|
4
5
|
async handle(ctx, next) {
|
|
5
6
|
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
6
7
|
if (!userId) {
|
|
7
|
-
|
|
8
|
+
// Destino configurável (`accountLoginUrl`): default `/account/login`.
|
|
9
|
+
return ctx.response.redirect(getAccountLoginUrl());
|
|
8
10
|
}
|
|
9
11
|
return next();
|
|
10
12
|
}
|
|
@@ -7,9 +7,11 @@ export declare const ACCOUNT_LAST_SEEN_KEY = "authkit_last_seen";
|
|
|
7
7
|
* Guard do console admin (B6). Como o `accountGuard`, é uma closure inline (forma
|
|
8
8
|
* confiável do `.use()` num grupo). Exige:
|
|
9
9
|
* 0. `config.admin.enabled` ligado (senão → 404; ver nota de flag-drift abaixo);
|
|
10
|
-
* 1. sessão de conta ativa (senão → /account/login);
|
|
10
|
+
* 1. sessão de conta ativa (senão → `accountLoginUrl`, default /account/login);
|
|
11
11
|
* 2. a conta logada com pelo menos UMA das `config.admin.roles` nas roles globais
|
|
12
|
-
* (senão → /account/
|
|
12
|
+
* (senão → `accountHome(cfg)`, default /account/security — NÃO revela a
|
|
13
|
+
* existência do /admin, e cai numa tela que o host controla via `accountHome`;
|
|
14
|
+
* se a tela default estiver desmontada, aponte `config.accountHome` para uma montada).
|
|
13
15
|
* As roles permitidas são resolvidas em runtime do `authkit.server` (config lazy).
|
|
14
16
|
*/
|
|
15
17
|
export declare const adminGuard: (ctx: any, next: () => Promise<void>) => Promise<any>;
|
|
@@ -108,6 +110,68 @@ export interface AuthHostOptions {
|
|
|
108
110
|
* Ausente → `[password(), passkey()]`.
|
|
109
111
|
*/
|
|
110
112
|
sudoMethods?: SudoMethod[];
|
|
113
|
+
/**
|
|
114
|
+
* Montagem por tela do console de conta (`/account/*`). Espelha o padrão
|
|
115
|
+
* `admin`/`adminApi`: a decisão de MONTAR cada grupo de rotas é tomada em
|
|
116
|
+
* tempo de registro, antes de o config (lazy) resolver.
|
|
117
|
+
*
|
|
118
|
+
* - Ausente → tudo montado (back-compat total).
|
|
119
|
+
* - `false` → NENHUMA tela do console de conta é montada (as rotas sudo
|
|
120
|
+
* `/account/confirm` e a JSON API `/account/api/*` continuam — são
|
|
121
|
+
* infraestrutura, não telas navegáveis).
|
|
122
|
+
* - objeto → montagem seletiva; cada flag ausente default `true`.
|
|
123
|
+
* - `login` → `/account/login`, `/account/logout` (a porta por senha do console).
|
|
124
|
+
* - `tokens` → `/account/tokens*` (Personal Access Tokens).
|
|
125
|
+
* - `orgs` → `/account/orgs*` (multi-tenancy, incl. o accept de convite público).
|
|
126
|
+
* - `security` → `/account/security*` + `/account/email/confirm` (perfil, senha, troca de e-mail, export/LGPD, deleção).
|
|
127
|
+
* - `mfa` → `/account/mfa*` (TOTP + passkeys).
|
|
128
|
+
* - `apps` → `/account/apps*` (grants de consentimento OIDC).
|
|
129
|
+
*
|
|
130
|
+
* NOTA (flag-drift): como `admin`/`adminApi`, estas flags controlam apenas se
|
|
131
|
+
* as ROTAS existem; o comportamento em runtime continua vindo do config
|
|
132
|
+
* resolvido. Mantenha em sincronia — os guards são a rede de segurança.
|
|
133
|
+
*
|
|
134
|
+
* ⚠️ Ao desmontar `login`, os redirects internos de "faça login"
|
|
135
|
+
* (`accountGuard`, `adminGuard`, `AccountAuthMiddleware`, `consoleLoginUrl()`,
|
|
136
|
+
* a view `otp-unlock`) apontariam para uma rota inexistente — passe
|
|
137
|
+
* `accountLoginUrl` com a rota de login do host (ex.: `'/login'`).
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* // Console passwordless: só segurança + MFA, login delegado ao OIDC do host.
|
|
141
|
+
* registerAuthHost(router, {
|
|
142
|
+
* account: { login: false, tokens: false, orgs: false },
|
|
143
|
+
* accountLoginUrl: '/login',
|
|
144
|
+
* })
|
|
145
|
+
*/
|
|
146
|
+
account?: false | AccountScreensOptions;
|
|
147
|
+
/**
|
|
148
|
+
* Destino do redirect de "não-autenticado → faça login" do console de conta.
|
|
149
|
+
* Default `/account/login`. Aponte para a rota de login do host quando a tela
|
|
150
|
+
* `account/login` da lib estiver desmontada (`account: { login: false }`).
|
|
151
|
+
*
|
|
152
|
+
* Usado por TODOS os pontos de redirect/link de login: `accountGuard`,
|
|
153
|
+
* `adminGuard`, `AccountAuthMiddleware`, `consoleLoginUrl()`, os fallbacks dos
|
|
154
|
+
* controllers de conta e a view `otp-unlock`. Ver `account_login_url.ts`.
|
|
155
|
+
*/
|
|
156
|
+
accountLoginUrl?: string;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Flags de montagem por tela do console de conta. Cada campo ausente é `true`
|
|
160
|
+
* (montado). Ver {@link AuthHostOptions.account}.
|
|
161
|
+
*/
|
|
162
|
+
export interface AccountScreensOptions {
|
|
163
|
+
/** Tela de login por senha do console (`/account/login`, `/account/logout`). */
|
|
164
|
+
login?: boolean;
|
|
165
|
+
/** Personal Access Tokens (`/account/tokens*`). */
|
|
166
|
+
tokens?: boolean;
|
|
167
|
+
/** Organizations / multi-tenancy (`/account/orgs*`). */
|
|
168
|
+
orgs?: boolean;
|
|
169
|
+
/** Perfil, senha, troca de e-mail, export/LGPD e deleção (`/account/security*`). */
|
|
170
|
+
security?: boolean;
|
|
171
|
+
/** MFA — TOTP + passkeys (`/account/mfa*`). */
|
|
172
|
+
mfa?: boolean;
|
|
173
|
+
/** Apps com acesso / grants de consentimento OIDC (`/account/apps*`). */
|
|
174
|
+
apps?: boolean;
|
|
111
175
|
}
|
|
112
176
|
/**
|
|
113
177
|
* Monta todas as rotas do host-kit do Authorization Server numa chamada.
|
|
@@ -8,6 +8,7 @@ import { setAdminPrefix, normalizeAdminPrefix, setAdminApiPrefix, normalizeAdmin
|
|
|
8
8
|
import { resolveRuntimeSettings } from './runtime_settings.js';
|
|
9
9
|
import { resolveEffectiveSessionPolicy } from './runtime_toggles.js';
|
|
10
10
|
import { getAuthHostConfig } from './auth_host_config.js';
|
|
11
|
+
import { setAccountLoginUrl, getAccountLoginUrl } from './account_login_url.js';
|
|
11
12
|
import { completeSudo, fail, guardSudoRoutes, sudoContextFrom, setMountedSudoMethods, } from './sudo/runtime.js';
|
|
12
13
|
import { password as sudoPassword } from './sudo/methods/password.js';
|
|
13
14
|
import { passkey as sudoPasskey } from './sudo/methods/passkey.js';
|
|
@@ -68,16 +69,26 @@ async function checkAndRefreshIdle(ctx) {
|
|
|
68
69
|
* @internal
|
|
69
70
|
*/
|
|
70
71
|
function buildLoginRedirect(ctx, extra) {
|
|
72
|
+
// Destino configurável (`accountLoginUrl`): default `/account/login`, mas um
|
|
73
|
+
// host que desmontou a tela de login (`account: { login: false }`) aponta para
|
|
74
|
+
// a própria rota de login dele (ex.: `/login`). Ver `account_login_url.ts`.
|
|
75
|
+
const loginUrl = getAccountLoginUrl();
|
|
71
76
|
const url = ctx.request?.url?.() ?? '';
|
|
72
77
|
const qs = ctx.request?.parsedUrl?.search ?? '';
|
|
73
78
|
const dest = qs ? `${url}${qs}` : url;
|
|
74
79
|
// Só inclui return_to quando há um caminho real (não vazio, não é o próprio login).
|
|
75
|
-
if (dest && dest !== '/' && !dest.startsWith(
|
|
80
|
+
if (dest && dest !== '/' && !dest.startsWith(loginUrl)) {
|
|
76
81
|
const encoded = encodeURIComponent(dest);
|
|
77
|
-
const
|
|
82
|
+
const sep = loginUrl.includes('?') ? '&' : '?';
|
|
83
|
+
const base = extra
|
|
84
|
+
? `${loginUrl}${sep}${extra}&return_to=${encoded}`
|
|
85
|
+
: `${loginUrl}${sep}return_to=${encoded}`;
|
|
78
86
|
return base;
|
|
79
87
|
}
|
|
80
|
-
|
|
88
|
+
if (!extra)
|
|
89
|
+
return loginUrl;
|
|
90
|
+
const sep = loginUrl.includes('?') ? '&' : '?';
|
|
91
|
+
return `${loginUrl}${sep}${extra}`;
|
|
81
92
|
}
|
|
82
93
|
/**
|
|
83
94
|
* Guard inline do console de conta. Usamos uma closure (forma confiável do
|
|
@@ -100,9 +111,11 @@ const accountGuard = async (ctx, next) => {
|
|
|
100
111
|
* Guard do console admin (B6). Como o `accountGuard`, é uma closure inline (forma
|
|
101
112
|
* confiável do `.use()` num grupo). Exige:
|
|
102
113
|
* 0. `config.admin.enabled` ligado (senão → 404; ver nota de flag-drift abaixo);
|
|
103
|
-
* 1. sessão de conta ativa (senão → /account/login);
|
|
114
|
+
* 1. sessão de conta ativa (senão → `accountLoginUrl`, default /account/login);
|
|
104
115
|
* 2. a conta logada com pelo menos UMA das `config.admin.roles` nas roles globais
|
|
105
|
-
* (senão → /account/
|
|
116
|
+
* (senão → `accountHome(cfg)`, default /account/security — NÃO revela a
|
|
117
|
+
* existência do /admin, e cai numa tela que o host controla via `accountHome`;
|
|
118
|
+
* se a tela default estiver desmontada, aponte `config.accountHome` para uma montada).
|
|
106
119
|
* As roles permitidas são resolvidas em runtime do `authkit.server` (config lazy).
|
|
107
120
|
*/
|
|
108
121
|
export const adminGuard = async (ctx, next) => {
|
|
@@ -188,6 +201,29 @@ export function registerAuthHost(router, opts = {}) {
|
|
|
188
201
|
const social = opts.social ?? hostCfg?.social;
|
|
189
202
|
const adminOpt = opts.admin ?? (hostCfg?.adminEnabled ? true : undefined);
|
|
190
203
|
const adminApiOpt = opts.adminApi ?? (hostCfg?.adminApiEnabled ? true : undefined);
|
|
204
|
+
// Destino do redirect de "faça login" — persiste no singleton de processo para
|
|
205
|
+
// que os guards (closures de tempo de registro), o middleware, os controllers e
|
|
206
|
+
// as views leiam o mesmo valor. Só quando a opção foi passada (senão fica o
|
|
207
|
+
// default `/account/login` do singleton — back-compat).
|
|
208
|
+
if (opts.accountLoginUrl !== undefined) {
|
|
209
|
+
setAccountLoginUrl(opts.accountLoginUrl);
|
|
210
|
+
}
|
|
211
|
+
// Montagem por tela do console de conta. `undefined` → tudo montado;
|
|
212
|
+
// `false` → nada; objeto → cada flag ausente default `true`.
|
|
213
|
+
const accountOpt = opts.account;
|
|
214
|
+
const mountScreen = (key) => {
|
|
215
|
+
if (accountOpt === false)
|
|
216
|
+
return false;
|
|
217
|
+
if (accountOpt && typeof accountOpt === 'object')
|
|
218
|
+
return accountOpt[key] !== false;
|
|
219
|
+
return true;
|
|
220
|
+
};
|
|
221
|
+
const mountLogin = mountScreen('login');
|
|
222
|
+
const mountTokens = mountScreen('tokens');
|
|
223
|
+
const mountOrgs = mountScreen('orgs');
|
|
224
|
+
const mountSecurity = mountScreen('security');
|
|
225
|
+
const mountMfa = mountScreen('mfa');
|
|
226
|
+
const mountApps = mountScreen('apps');
|
|
191
227
|
// Throttles opt-in (anti-brute-force). `undefined` quando rate-limit desligado.
|
|
192
228
|
const resolvedRateLimit = opts.rateLimit !== undefined
|
|
193
229
|
? resolveRateLimit(opts.rateLimit)
|
|
@@ -258,50 +294,70 @@ export function registerAuthHost(router, opts = {}) {
|
|
|
258
294
|
// PAT introspection (server-to-server).
|
|
259
295
|
withIntrospection(router.post('/authkit/pat/introspect', [C.patIntrospection, 'handle']));
|
|
260
296
|
// Organizations — invitation accept (sem guard: controller lida com não-autenticado).
|
|
261
|
-
|
|
262
|
-
|
|
297
|
+
// Parte da tela `orgs`: desmontada junto (sem multi-tenancy, não há convite a aceitar).
|
|
298
|
+
if (mountOrgs) {
|
|
299
|
+
router.get('/account/orgs/invitations/:token/accept', [C.accountOrgs, 'showAcceptInvitation']);
|
|
300
|
+
router.post('/account/orgs/invitations/:token/accept', [C.accountOrgs, 'acceptInvitation']);
|
|
301
|
+
}
|
|
263
302
|
// Console de conta (login de sessão do IdP + gerência de PAT).
|
|
264
|
-
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
303
|
+
// Tela `login` desmontável: hosts passwordless delegam ao OIDC próprio e
|
|
304
|
+
// apontam `accountLoginUrl` para a rota de login deles.
|
|
305
|
+
if (mountLogin) {
|
|
306
|
+
router.get('/account/login', [C.accountSession, 'show']);
|
|
307
|
+
// L6: throttle por IP no login/logout do console de conta (anti-brute-force),
|
|
308
|
+
// alinhado com as demais rotas de credencial (interaction, forgot, reset).
|
|
309
|
+
withLogin(router.post('/account/login', [C.accountSession, 'login']));
|
|
310
|
+
withLogin(router.post('/account/logout', [C.accountSession, 'logout']));
|
|
311
|
+
}
|
|
269
312
|
// Confirmação de troca de e-mail (standalone, GET-only — consome o token do link;
|
|
270
|
-
// pode ser aberta em outro dispositivo, então NÃO exige sessão).
|
|
271
|
-
|
|
313
|
+
// pode ser aberta em outro dispositivo, então NÃO exige sessão). Parte da tela
|
|
314
|
+
// `security` (o terminal do fluxo de troca de e-mail).
|
|
315
|
+
if (mountSecurity) {
|
|
316
|
+
router.get('/account/email/confirm', [C.accountSecurity, 'confirmEmail']);
|
|
317
|
+
}
|
|
272
318
|
// Rotas de tokens protegidas por AccountAuthMiddleware (redireciona para /account/login se não autenticado).
|
|
273
319
|
router
|
|
274
320
|
.group(() => {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
C.accountSecurity,
|
|
291
|
-
'
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
321
|
+
// Personal Access Tokens (tela `tokens`).
|
|
322
|
+
if (mountTokens) {
|
|
323
|
+
router.get('/account/tokens', [C.accountTokens, 'index']);
|
|
324
|
+
router.post('/account/tokens', [C.accountTokens, 'store']);
|
|
325
|
+
router.post('/account/tokens/:id/revoke', [C.accountTokens, 'destroy']);
|
|
326
|
+
}
|
|
327
|
+
// Segurança da conta: trocar senha + solicitar troca de e-mail + perfil (tela `security`).
|
|
328
|
+
if (mountSecurity) {
|
|
329
|
+
router.get('/account/security', [C.accountSecurity, 'index']);
|
|
330
|
+
router.post('/account/security/password', [C.accountSecurity, 'changePassword']);
|
|
331
|
+
router.post('/account/security/email', [C.accountSecurity, 'changeEmail']);
|
|
332
|
+
router.post('/account/security/email/cancel', [C.accountSecurity, 'cancelEmailChange']);
|
|
333
|
+
router.post('/account/security/profile', [C.accountSecurity, 'updateProfile']);
|
|
334
|
+
// LGPD/GDPR: export de dados (portabilidade) + deleção self-service (danger zone).
|
|
335
|
+
// O export carrega o throttle de login (anti-abuso) quando o rate-limit existe.
|
|
336
|
+
withLogin(router.get('/account/security/export', [C.accountSecurity, 'exportData']));
|
|
337
|
+
router.post('/account/security/delete', [C.accountSecurity, 'deleteAccount']);
|
|
338
|
+
// Trusted devices: limpa o cookie de confiança DESTE navegador.
|
|
339
|
+
router.post('/account/security/trusted-devices/revoke', [
|
|
340
|
+
C.accountSecurity,
|
|
341
|
+
'revokeTrustedDevices',
|
|
342
|
+
]);
|
|
343
|
+
}
|
|
344
|
+
// Apps com acesso (consentimento): lista os grants da conta + revogação por client (tela `apps`).
|
|
345
|
+
if (mountApps) {
|
|
346
|
+
router.get('/account/apps', [C.accountApps, 'index']);
|
|
347
|
+
router.post('/account/apps/:clientId/revoke', [C.accountApps, 'revoke']);
|
|
348
|
+
}
|
|
349
|
+
// MFA — TOTP + passkeys (tela `mfa`).
|
|
350
|
+
if (mountMfa) {
|
|
351
|
+
// MFA / TOTP (enrollment, confirmação, disable).
|
|
352
|
+
router.get('/account/mfa', [C.accountMfa, 'index']);
|
|
353
|
+
router.post('/account/mfa/enroll', [C.accountMfa, 'enroll']);
|
|
354
|
+
router.post('/account/mfa/confirm', [C.accountMfa, 'confirm']);
|
|
355
|
+
router.post('/account/mfa/disable', [C.accountMfa, 'disable']);
|
|
356
|
+
// MFA / WebAuthn (passkeys): registro (begin/finish) + remoção.
|
|
357
|
+
router.post('/account/mfa/passkeys/options', [C.accountMfa, 'passkeyRegisterOptions']);
|
|
358
|
+
router.post('/account/mfa/passkeys/verify', [C.accountMfa, 'passkeyRegisterVerify']);
|
|
359
|
+
router.post('/account/mfa/passkeys/:id/remove', [C.accountMfa, 'passkeyRemove']);
|
|
360
|
+
}
|
|
305
361
|
// Sudo mode (confirm identity): o GET lista os métodos; cada método
|
|
306
362
|
// registra suas próprias rotas de verificação (SPI `SudoMethod`).
|
|
307
363
|
router.get('/account/confirm', [C.accountConfirm, 'show']);
|
|
@@ -342,19 +398,22 @@ export function registerAuthHost(router, opts = {}) {
|
|
|
342
398
|
// configura `config.sudo.methods`: a tela oferece exatamente isto, e os
|
|
343
399
|
// handlers aceitam exatamente isto.
|
|
344
400
|
setMountedSudoMethods(sudoMethodsToMount);
|
|
345
|
-
// Organizations (multi-tenancy) —
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
401
|
+
// Organizations (multi-tenancy) — tela `orgs`. Montadas por default;
|
|
402
|
+
// controller retorna 404/403 sem tabelas (capability-probed).
|
|
403
|
+
if (mountOrgs) {
|
|
404
|
+
router.get('/account/orgs', [C.accountOrgs, 'index']);
|
|
405
|
+
router.post('/account/orgs', [C.accountOrgs, 'store']);
|
|
406
|
+
router.post('/account/orgs/deactivate', [C.accountOrgs, 'deactivate']);
|
|
407
|
+
router.post('/account/orgs/:id/activate', [C.accountOrgs, 'activate']);
|
|
408
|
+
router.post('/account/orgs/:id/leave', [C.accountOrgs, 'leave']);
|
|
409
|
+
router.post('/account/orgs/:id/invite', [C.accountOrgs, 'invite']);
|
|
410
|
+
router.post('/account/orgs/:id/members/:accountId/remove', [C.accountOrgs, 'removeMember']);
|
|
411
|
+
router.post('/account/orgs/:id/invitations/:invId/revoke', [C.accountOrgs, 'revokeInvitation']);
|
|
412
|
+
// JSON endpoints for React hooks (authkit-react).
|
|
413
|
+
router.get('/account/orgs/json', [C.accountOrgs, 'listJson']);
|
|
414
|
+
router.get('/account/orgs/invitations/json', [C.accountOrgs, 'listInvitationsJson']);
|
|
415
|
+
router.get('/account/orgs/:id/json', [C.accountOrgs, 'showJson']);
|
|
416
|
+
}
|
|
358
417
|
// ─── Account self-service JSON API (authkit-react TanStack hooks) ─────
|
|
359
418
|
// ⚠️ ORDER MATTERS: fixed-segment routes before parameterised ones.
|
|
360
419
|
// Registered INSIDE the accountGuard group → same session-auth protection.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_MESSAGES, translate } from '../i18n.js';
|
|
2
|
+
import { getAccountLoginUrl } from '../account_login_url.js';
|
|
2
3
|
/**
|
|
3
4
|
* Resolve o catálogo de mensagens ativo a partir do `authkit.server` (config
|
|
4
5
|
* resolvida com o locale do host). Defensivo: se o container/serviço não estiver
|
|
@@ -28,7 +29,11 @@ async function resolveMessagesFromCtx(ctx) {
|
|
|
28
29
|
export async function renderEdgeView(ctx, view, props) {
|
|
29
30
|
const messages = await resolveMessagesFromCtx(ctx);
|
|
30
31
|
const t = (key, params) => translate(messages, key, params);
|
|
31
|
-
|
|
32
|
+
// `loginUrl` como prop global: as views que linkam "faça login" (ex.: `otp-unlock`)
|
|
33
|
+
// usam o destino configurável (`accountLoginUrl`) em vez do `/account/login` fixo,
|
|
34
|
+
// que pode estar desmontado. Props explícitas ainda têm precedência (spread depois).
|
|
35
|
+
const loginUrl = getAccountLoginUrl();
|
|
36
|
+
return ctx.view.render(`authkit::${view}`, { loginUrl, ...props, t, messages });
|
|
32
37
|
}
|
|
33
38
|
/** Renderer do seam para hosts Edge. As views são donas-da-lib (disco `authkit::`). */
|
|
34
39
|
export function edgeRenderer() {
|
|
@@ -66,5 +66,60 @@ export type AuthkitScreen = 'login' | 'signup' | 'consent' | 'forgot' | 'reset'
|
|
|
66
66
|
* | `returnTo` | `string \| null` | Caminho interno de destino pós-login (já validado pelo servidor — só caminhos internos). Quando presente, o formulário deve incluir `<input type="hidden" name="return_to" value={returnTo} />`. O servidor revalida o valor no POST; hosts com tela custom precisam propagar esse hidden input. |
|
|
67
67
|
* | `error` | `string \| undefined` | Mensagem de erro de autenticação localizada (credenciais inválidas, conta bloqueada, etc.). |
|
|
68
68
|
* | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
|
|
69
|
+
*
|
|
70
|
+
* ### Props da tela `account/security`
|
|
71
|
+
*
|
|
72
|
+
* | Prop | Tipo | Descrição |
|
|
73
|
+
* |-------------------------|---------------------|-----------|
|
|
74
|
+
* | `csrfToken` | `string` | Token CSRF para os formulários da tela. |
|
|
75
|
+
* | `supported` | `boolean` | `false` quando o store não suporta o self-service de segurança (troca de senha/e-mail) — a tela deve degradar. |
|
|
76
|
+
* | `profileSupported` | `boolean` | `true` quando o store suporta editar nome/avatar (`updateProfile`). |
|
|
77
|
+
* | `avatarUploadSupported` | `boolean` | `true` quando algum backend (drive OU media) pode armazenar o upload de avatar. |
|
|
78
|
+
* | `email` | `string` | E-mail atual da conta (`''` se ausente). |
|
|
79
|
+
* | `name` | `string` | Nome atual da conta (`''` se ausente). |
|
|
80
|
+
* | `avatarUrl` | `string` | URL do avatar atual (`''` se ausente). |
|
|
81
|
+
* | `passwordChanged` | `string \| null` | Flash de sucesso da troca de senha (mensagem localizada) ou `null`. |
|
|
82
|
+
* | `emailChangeRequested` | `string \| null` | Flash: link de confirmação de troca de e-mail enviado (ou cancelamento) ou `null`. |
|
|
83
|
+
* | `emailChanged` | `string \| null` | Flash: troca de e-mail concluída ou `null`. |
|
|
84
|
+
* | `profileUpdated` | `string \| null` | Flash: perfil atualizado ou `null`. |
|
|
85
|
+
* | `error` | `string \| null` | Flash de erro de segurança (senha inválida, e-mail em uso, política violada) ou `null`. |
|
|
86
|
+
* | `trustedDevicesEnabled` | `boolean` | `true` quando o recurso de dispositivos confiáveis está ligado. |
|
|
87
|
+
* | `trustedDevicesRevoked` | `string \| null` | Flash: confiança deste navegador revogada ou `null`. |
|
|
88
|
+
* | `sessionsSupported` | `boolean` | `true` quando o adapter OIDC enumera as sessões ativas da conta. |
|
|
89
|
+
* | `sessions` | `Array<{ loginTs: string; browser: string; os: string; ip: string; location: string }>` | Sessões ativas da própria conta (vazio quando não suportado). `loginTs` é ISO ou `''`. |
|
|
90
|
+
* | `exportSupported` | `boolean` | Sempre `true` — export de dados (portabilidade/LGPD) disponível para a conta logada. |
|
|
91
|
+
* | `deletionSupported` | `boolean` | `true` quando o store suporta hard delete (danger zone). |
|
|
92
|
+
* | `deleteError` | `string \| null` | Flash de erro da confirmação de deleção ou `null`. |
|
|
93
|
+
* | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
|
|
94
|
+
*
|
|
95
|
+
* ### Props da tela `account/mfa`
|
|
96
|
+
*
|
|
97
|
+
* | Prop | Tipo | Descrição |
|
|
98
|
+
* |---------------------|---------------------|-----------|
|
|
99
|
+
* | `csrfToken` | `string` | Token CSRF para os formulários de enroll/confirm/disable e passkeys. |
|
|
100
|
+
* | `enabled` | `boolean` | `true` quando o TOTP já está confirmado (habilitado) para a conta. |
|
|
101
|
+
* | `recoveryCodes` | `string[] \| null` | Códigos de recuperação recém-gerados (exibidos UMA vez após enroll/confirm) ou `null`. |
|
|
102
|
+
* | `passkeysSupported` | `boolean` | `true` quando o store persiste credenciais WebAuthn (passkeys). |
|
|
103
|
+
* | `passkeys` | `Array<{ id: string; label?: string; createdAt: string }>` | Passkeys cadastradas (vazio quando não suportado). `id` é base64url; `createdAt` é ISO. |
|
|
104
|
+
* | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
|
|
105
|
+
*
|
|
106
|
+
* ### Props da tela `account/confirm` (sudo — confirmar identidade)
|
|
107
|
+
*
|
|
108
|
+
* | Prop | Tipo | Descrição |
|
|
109
|
+
* |---------------|---------------------|-----------|
|
|
110
|
+
* | `csrfToken` | `string` | Token CSRF para o POST de cada método. |
|
|
111
|
+
* | `returnTo` | `string \| null` | Caminho interno de destino após confirmar (validado pelo servidor) ou `null`. |
|
|
112
|
+
* | `error` | `string \| null` | Flash de erro da última tentativa de confirmação ou `null`. |
|
|
113
|
+
* | `notice` | `string \| null` | Flash informativo (ex.: "link de confirmação enviado") ou `null`. |
|
|
114
|
+
* | `methods` | `Array<{ id: string; labelKey: string; kind: 'form' \| 'action' \| 'redirect' \| 'webauthn'; endpoint: string; fields?: Array<{ name: string; type: 'password' \| 'text'; labelKey: string }> }>` | Métodos de sudo disponíveis para a conta. A tela renderiza por `kind`; `endpoint` é o POST de verificação (`webauthn` pede options em `${endpoint}/options`). |
|
|
115
|
+
* | `preferredId` | `string \| null` | `id` do último método usado (destaque na UI) ou `null`. |
|
|
116
|
+
* | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
|
|
117
|
+
*
|
|
118
|
+
* ### Props da tela `account/email-confirmed` (terminal do link de troca de e-mail)
|
|
119
|
+
*
|
|
120
|
+
* | Prop | Tipo | Descrição |
|
|
121
|
+
* |------------|----------------|-----------|
|
|
122
|
+
* | `ok` | `boolean` | `true` quando o token era válido e o novo e-mail foi aplicado; `false` para token inválido/expirado ou store sem suporte. A tela mostra sucesso ou falha conforme o valor. |
|
|
123
|
+
* | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
|
|
69
124
|
*/
|
|
70
125
|
export declare function inertiaRenderer(opts: InertiaRendererOptions): (ctx: HttpContext, view: string, props: Record<string, unknown>) => Promise<any>;
|
|
@@ -37,6 +37,61 @@ async function resolveMessagesFromCtx(ctx) {
|
|
|
37
37
|
* | `returnTo` | `string \| null` | Caminho interno de destino pós-login (já validado pelo servidor — só caminhos internos). Quando presente, o formulário deve incluir `<input type="hidden" name="return_to" value={returnTo} />`. O servidor revalida o valor no POST; hosts com tela custom precisam propagar esse hidden input. |
|
|
38
38
|
* | `error` | `string \| undefined` | Mensagem de erro de autenticação localizada (credenciais inválidas, conta bloqueada, etc.). |
|
|
39
39
|
* | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
|
|
40
|
+
*
|
|
41
|
+
* ### Props da tela `account/security`
|
|
42
|
+
*
|
|
43
|
+
* | Prop | Tipo | Descrição |
|
|
44
|
+
* |-------------------------|---------------------|-----------|
|
|
45
|
+
* | `csrfToken` | `string` | Token CSRF para os formulários da tela. |
|
|
46
|
+
* | `supported` | `boolean` | `false` quando o store não suporta o self-service de segurança (troca de senha/e-mail) — a tela deve degradar. |
|
|
47
|
+
* | `profileSupported` | `boolean` | `true` quando o store suporta editar nome/avatar (`updateProfile`). |
|
|
48
|
+
* | `avatarUploadSupported` | `boolean` | `true` quando algum backend (drive OU media) pode armazenar o upload de avatar. |
|
|
49
|
+
* | `email` | `string` | E-mail atual da conta (`''` se ausente). |
|
|
50
|
+
* | `name` | `string` | Nome atual da conta (`''` se ausente). |
|
|
51
|
+
* | `avatarUrl` | `string` | URL do avatar atual (`''` se ausente). |
|
|
52
|
+
* | `passwordChanged` | `string \| null` | Flash de sucesso da troca de senha (mensagem localizada) ou `null`. |
|
|
53
|
+
* | `emailChangeRequested` | `string \| null` | Flash: link de confirmação de troca de e-mail enviado (ou cancelamento) ou `null`. |
|
|
54
|
+
* | `emailChanged` | `string \| null` | Flash: troca de e-mail concluída ou `null`. |
|
|
55
|
+
* | `profileUpdated` | `string \| null` | Flash: perfil atualizado ou `null`. |
|
|
56
|
+
* | `error` | `string \| null` | Flash de erro de segurança (senha inválida, e-mail em uso, política violada) ou `null`. |
|
|
57
|
+
* | `trustedDevicesEnabled` | `boolean` | `true` quando o recurso de dispositivos confiáveis está ligado. |
|
|
58
|
+
* | `trustedDevicesRevoked` | `string \| null` | Flash: confiança deste navegador revogada ou `null`. |
|
|
59
|
+
* | `sessionsSupported` | `boolean` | `true` quando o adapter OIDC enumera as sessões ativas da conta. |
|
|
60
|
+
* | `sessions` | `Array<{ loginTs: string; browser: string; os: string; ip: string; location: string }>` | Sessões ativas da própria conta (vazio quando não suportado). `loginTs` é ISO ou `''`. |
|
|
61
|
+
* | `exportSupported` | `boolean` | Sempre `true` — export de dados (portabilidade/LGPD) disponível para a conta logada. |
|
|
62
|
+
* | `deletionSupported` | `boolean` | `true` quando o store suporta hard delete (danger zone). |
|
|
63
|
+
* | `deleteError` | `string \| null` | Flash de erro da confirmação de deleção ou `null`. |
|
|
64
|
+
* | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
|
|
65
|
+
*
|
|
66
|
+
* ### Props da tela `account/mfa`
|
|
67
|
+
*
|
|
68
|
+
* | Prop | Tipo | Descrição |
|
|
69
|
+
* |---------------------|---------------------|-----------|
|
|
70
|
+
* | `csrfToken` | `string` | Token CSRF para os formulários de enroll/confirm/disable e passkeys. |
|
|
71
|
+
* | `enabled` | `boolean` | `true` quando o TOTP já está confirmado (habilitado) para a conta. |
|
|
72
|
+
* | `recoveryCodes` | `string[] \| null` | Códigos de recuperação recém-gerados (exibidos UMA vez após enroll/confirm) ou `null`. |
|
|
73
|
+
* | `passkeysSupported` | `boolean` | `true` quando o store persiste credenciais WebAuthn (passkeys). |
|
|
74
|
+
* | `passkeys` | `Array<{ id: string; label?: string; createdAt: string }>` | Passkeys cadastradas (vazio quando não suportado). `id` é base64url; `createdAt` é ISO. |
|
|
75
|
+
* | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
|
|
76
|
+
*
|
|
77
|
+
* ### Props da tela `account/confirm` (sudo — confirmar identidade)
|
|
78
|
+
*
|
|
79
|
+
* | Prop | Tipo | Descrição |
|
|
80
|
+
* |---------------|---------------------|-----------|
|
|
81
|
+
* | `csrfToken` | `string` | Token CSRF para o POST de cada método. |
|
|
82
|
+
* | `returnTo` | `string \| null` | Caminho interno de destino após confirmar (validado pelo servidor) ou `null`. |
|
|
83
|
+
* | `error` | `string \| null` | Flash de erro da última tentativa de confirmação ou `null`. |
|
|
84
|
+
* | `notice` | `string \| null` | Flash informativo (ex.: "link de confirmação enviado") ou `null`. |
|
|
85
|
+
* | `methods` | `Array<{ id: string; labelKey: string; kind: 'form' \| 'action' \| 'redirect' \| 'webauthn'; endpoint: string; fields?: Array<{ name: string; type: 'password' \| 'text'; labelKey: string }> }>` | Métodos de sudo disponíveis para a conta. A tela renderiza por `kind`; `endpoint` é o POST de verificação (`webauthn` pede options em `${endpoint}/options`). |
|
|
86
|
+
* | `preferredId` | `string \| null` | `id` do último método usado (destaque na UI) ou `null`. |
|
|
87
|
+
* | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
|
|
88
|
+
*
|
|
89
|
+
* ### Props da tela `account/email-confirmed` (terminal do link de troca de e-mail)
|
|
90
|
+
*
|
|
91
|
+
* | Prop | Tipo | Descrição |
|
|
92
|
+
* |------------|----------------|-----------|
|
|
93
|
+
* | `ok` | `boolean` | `true` quando o token era válido e o novo e-mail foi aplicado; `false` para token inválido/expirado ou store sem suporte. A tela mostra sucesso ou falha conforme o valor. |
|
|
94
|
+
* | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
|
|
40
95
|
*/
|
|
41
96
|
export function inertiaRenderer(opts) {
|
|
42
97
|
const allowed = opts.views ? new Set(opts.views) : null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/authkit-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"description": "AdonisJS OIDC/OAuth2 provider (Identity Provider) toolkit: ejectable auth server with sessions, rate-limiting, MFA/TOTP, audit log, federated logout and OpenTelemetry metrics.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dudousxd",
|