@adonis-agora/authkit-server 0.68.4 → 0.70.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/commands/import_users.js +6 -1
- package/build/index.d.ts +5 -1
- package/build/index.js +7 -1
- package/build/src/accounts/account_store.d.ts +40 -0
- package/build/src/accounts/account_store.js +20 -0
- package/build/src/accounts/lucid_store/mfa.js +22 -0
- package/build/src/audit/audit_sink.d.ts +1 -1
- package/build/src/audit/audit_sink.js +4 -0
- package/build/src/commands/import_users.d.ts +7 -0
- package/build/src/commands/import_users.js +15 -3
- package/build/src/define_config.d.ts +81 -0
- package/build/src/define_config.js +18 -3
- package/build/src/host/account_api/account_api_controller.d.ts +2 -0
- package/build/src/host/account_api/account_api_controller.js +22 -3
- package/build/src/host/account_api/account_mfa_api_controller.d.ts +101 -0
- package/build/src/host/account_api/account_mfa_api_controller.js +286 -0
- package/build/src/host/account_api/account_orgs_api_controller.d.ts +126 -0
- package/build/src/host/account_api/account_orgs_api_controller.js +468 -0
- package/build/src/host/account_lockout.js +7 -2
- package/build/src/host/admin_api/admin_orgs_service.js +3 -2
- package/build/src/host/admin_api/admin_users_service.js +13 -3
- package/build/src/host/admin_api/dto.d.ts +1 -1
- package/build/src/host/admin_validators.d.ts +2 -2
- package/build/src/host/admin_validators.js +3 -2
- package/build/src/host/controllers/account_mfa_controller.js +1 -2
- package/build/src/host/controllers/account_orgs_controller.js +19 -11
- package/build/src/host/controllers/account_security_controller.js +3 -1
- package/build/src/host/controllers/account_session_controller.js +17 -6
- package/build/src/host/controllers/interaction_controller.d.ts +10 -0
- package/build/src/host/controllers/interaction_controller.js +93 -34
- package/build/src/host/controllers/registration_controller.js +35 -9
- package/build/src/host/controllers/social_controller.js +11 -2
- package/build/src/host/email_identifier.d.ts +92 -0
- package/build/src/host/email_identifier.js +234 -0
- package/build/src/host/idp_session_bridge.d.ts +55 -0
- package/build/src/host/idp_session_bridge.js +108 -0
- package/build/src/host/middleware/account_auth.js +3 -2
- package/build/src/host/org_policy.d.ts +26 -0
- package/build/src/host/org_policy.js +35 -0
- package/build/src/host/passkey_registration_challenge.d.ts +12 -0
- package/build/src/host/passkey_registration_challenge.js +12 -0
- package/build/src/host/register_auth_host.js +56 -1
- package/build/src/host/runtime_toggles.d.ts +2 -2
- package/build/src/host/runtime_toggles.js +3 -1
- package/build/src/host/sudo_mode.d.ts +17 -0
- package/build/src/host/sudo_mode.js +31 -12
- package/build/src/host/ui-dist/assets/{index-D9CYQnZR.js → index-Dct63ai-.js} +2 -2
- package/build/src/host/ui-dist/index.html +1 -1
- package/build/src/host/validators.d.ts +5 -5
- package/build/src/host/validators.js +17 -5
- package/build/src/provider/build_provider.js +46 -14
- package/build/src/provider/registration_policy.d.ts +99 -0
- package/build/src/provider/registration_policy.js +229 -0
- package/package.json +2 -2
|
@@ -7,6 +7,7 @@ import { ACCOUNT_SESSION_KEY } from './account_session_key.js';
|
|
|
7
7
|
import { adminApiGuard } from './admin_api/admin_api_guard.js';
|
|
8
8
|
import { normalizeAdminApiPrefix, normalizeAdminPrefix, setAdminApiPrefix, setAdminPrefix, } from './admin_prefix.js';
|
|
9
9
|
import { getAuthHostConfig, markAuthHostAutoMounted, wasAuthHostAutoMounted, } from './auth_host_config.js';
|
|
10
|
+
import { ensureConsoleSession } from './idp_session_bridge.js';
|
|
10
11
|
import { createAuthThrottles } from './rate_limit.js';
|
|
11
12
|
import { resolveRuntimeSettings } from './runtime_settings.js';
|
|
12
13
|
import { resolveEffectiveSessionPolicy } from './runtime_toggles.js';
|
|
@@ -113,7 +114,8 @@ function buildLoginRedirect(ctx, extra) {
|
|
|
113
114
|
* /account/mfa acessíveis sem sessão.
|
|
114
115
|
*/
|
|
115
116
|
const accountGuard = async (ctx, next) => {
|
|
116
|
-
|
|
117
|
+
// Sessão do console — ou, com `accountSession.acceptIdpSession`, a do IdP (SSO).
|
|
118
|
+
if (!(await ensureConsoleSession(ctx))) {
|
|
117
119
|
return ctx.response.redirect(buildLoginRedirect(ctx));
|
|
118
120
|
}
|
|
119
121
|
// Idle timeout: encerra e redireciona com query param de motivo.
|
|
@@ -143,6 +145,8 @@ export const adminGuard = async (ctx, next) => {
|
|
|
143
145
|
if (!cfg.admin.enabled) {
|
|
144
146
|
return ctx.response.notFound();
|
|
145
147
|
}
|
|
148
|
+
// Com `accountSession.acceptIdpSession`, a sessão do IdP também abre o console.
|
|
149
|
+
await ensureConsoleSession(ctx);
|
|
146
150
|
const accountId = ctx.session?.get(ACCOUNT_SESSION_KEY);
|
|
147
151
|
if (!accountId) {
|
|
148
152
|
// `/account/login` é sempre o login da conta — NÃO muda com o prefixo admin.
|
|
@@ -207,6 +211,10 @@ const C = {
|
|
|
207
211
|
apiKeys: () => import('./admin_api/api_keys_controller.js'),
|
|
208
212
|
// Account self-service JSON API (session-authed, under /account/api/*).
|
|
209
213
|
accountApi: () => import('./account_api/account_api_controller.js'),
|
|
214
|
+
// Espelhos JSON das ESCRITAS de org e do segundo fator — mesmos guards e
|
|
215
|
+
// mesma política do console HTML, resposta em JSON (ver os docblocks deles).
|
|
216
|
+
accountOrgsApi: () => import('./account_api/account_orgs_api_controller.js'),
|
|
217
|
+
accountMfaApi: () => import('./account_api/account_mfa_api_controller.js'),
|
|
210
218
|
// API headless (Clerk-style) — host-session-authed via `headless.resolveAccountId`.
|
|
211
219
|
headlessLoginMethods: () => import('./controllers/headless_login_methods_controller.js'),
|
|
212
220
|
};
|
|
@@ -649,6 +657,53 @@ export function registerAuthHost(router, opts = {}) {
|
|
|
649
657
|
router.get(`${apiBase}/orgs`, [C.accountApi, 'listOrgs']);
|
|
650
658
|
router.get(`${apiBase}/orgs/invitations`, [C.accountApi, 'listOrgInvitations']);
|
|
651
659
|
router.get(`${apiBase}/orgs/:id`, [C.accountApi, 'showOrg']);
|
|
660
|
+
// ─── Orgs: ESCRITA em JSON (espelho dos POSTs de formulário) ─────────
|
|
661
|
+
// SEMPRE montadas, como as leituras JSON logo acima — e NÃO amarradas ao
|
|
662
|
+
// `mountOrgs` da TELA. A tela é a UI do console; `/account/api/*` é a
|
|
663
|
+
// superfície de máquina, e quem desliga a tela é justamente o host que
|
|
664
|
+
// desenha as próprias telas e mais precisa destes endpoints. Amarrar as
|
|
665
|
+
// duas coisas tornaria o modo headless inalcançável.
|
|
666
|
+
//
|
|
667
|
+
// Desligar a tela não afrouxa nada: o que decide quem pode o quê aqui é o
|
|
668
|
+
// `accountGuard` + capability-probe do store + política efetiva
|
|
669
|
+
// (`allowSelfCreate` continua `false` por default) + papel na org — os
|
|
670
|
+
// mesmos gates do formulário, nunca a presença de uma rota HTML.
|
|
671
|
+
// ⚠️ ORDER MATTERS, de novo: segmento fixo antes de paramétrico.
|
|
672
|
+
router.post(`${apiBase}/orgs`, [C.accountOrgsApi, 'createOrg']);
|
|
673
|
+
router.post(`${apiBase}/orgs/deactivate`, [C.accountOrgsApi, 'deactivateOrg']);
|
|
674
|
+
router.post(`${apiBase}/orgs/invitations/:token/accept`, [
|
|
675
|
+
C.accountOrgsApi,
|
|
676
|
+
'acceptInvitation',
|
|
677
|
+
]);
|
|
678
|
+
router.post(`${apiBase}/orgs/:id/activate`, [C.accountOrgsApi, 'activateOrg']);
|
|
679
|
+
router.post(`${apiBase}/orgs/:id/leave`, [C.accountOrgsApi, 'leaveOrg']);
|
|
680
|
+
router.post(`${apiBase}/orgs/:id/invitations`, [C.accountOrgsApi, 'inviteMember']);
|
|
681
|
+
router.delete(`${apiBase}/orgs/:id/invitations/:invId`, [
|
|
682
|
+
C.accountOrgsApi,
|
|
683
|
+
'revokeInvitation',
|
|
684
|
+
]);
|
|
685
|
+
router.patch(`${apiBase}/orgs/:id/members/:accountId`, [
|
|
686
|
+
C.accountOrgsApi,
|
|
687
|
+
'updateMemberRole',
|
|
688
|
+
]);
|
|
689
|
+
router.delete(`${apiBase}/orgs/:id/members/:accountId`, [C.accountOrgsApi, 'removeMember']);
|
|
690
|
+
// ─── Segundo fator em JSON (espelho do console de MFA) ───────────────
|
|
691
|
+
// Mesma decisão das de org: superfície de máquina, montada sempre. Sem a
|
|
692
|
+
// capacidade de MFA no store, cada handler responde 422
|
|
693
|
+
// `capability_unsupported` — capability-probed, como o resto do
|
|
694
|
+
// `/account/api/*`.
|
|
695
|
+
router.post(`${apiBase}/mfa/totp/enroll`, [C.accountMfaApi, 'enrollTotp']);
|
|
696
|
+
// THROTTLE no confirm: o código TOTP é adivinhável (6 dígitos), e o
|
|
697
|
+
// `accountGuard` sozinho só exige uma sessão viva — que quem está
|
|
698
|
+
// tentando adivinhar tem. Bucket de SUDO (por IP): mesma natureza —
|
|
699
|
+
// usuário autenticado reprovando um fator —, contagem separada do login.
|
|
700
|
+
// O form clássico não tem isto; o JSON fica MAIS apertado, que é a única
|
|
701
|
+
// direção em que os dois caminhos podem divergir.
|
|
702
|
+
withSudo(router.post(`${apiBase}/mfa/totp/confirm`, [C.accountMfaApi, 'confirmTotp']));
|
|
703
|
+
router.post(`${apiBase}/mfa/totp/disable`, [C.accountMfaApi, 'disableTotp']);
|
|
704
|
+
router.post(`${apiBase}/mfa/recovery-codes`, [C.accountMfaApi, 'regenerateRecoveryCodes']);
|
|
705
|
+
router.post(`${apiBase}/mfa/passkeys/options`, [C.accountMfaApi, 'passkeyRegisterOptions']);
|
|
706
|
+
router.post(`${apiBase}/mfa/passkeys/verify`, [C.accountMfaApi, 'passkeyRegisterVerify']);
|
|
652
707
|
})
|
|
653
708
|
.use([accountGuard]);
|
|
654
709
|
// API headless (Clerk-style) — montada fora do `accountGuard` (não depende da
|
|
@@ -762,11 +762,11 @@ export declare function resolveEffectiveRolesCatalog(settings: SettingsCapabilit
|
|
|
762
762
|
*
|
|
763
763
|
* Invariante mantida: 'owner' é sempre incluído na lista de roles (governance).
|
|
764
764
|
*
|
|
765
|
-
* @param settings - SettingsCapability
|
|
765
|
+
* @param settings - SettingsCapability, ou null (runtime settings indisponível → config/defaults)
|
|
766
766
|
* @param configDefault - defaults estáticos do config do host
|
|
767
767
|
* @param orgId - opcional; quando fornecido, tenta settings da org antes do global
|
|
768
768
|
*/
|
|
769
|
-
export declare function resolveEffectiveOrganizationsPolicy(settings: SettingsCapability, configDefault?: OrganizationsPolicyConfigDefaults, orgId?: string | null): Promise<ResolvedOrganizationsPolicySetting>;
|
|
769
|
+
export declare function resolveEffectiveOrganizationsPolicy(settings: SettingsCapability | null, configDefault?: OrganizationsPolicyConfigDefaults, orgId?: string | null): Promise<ResolvedOrganizationsPolicySetting>;
|
|
770
770
|
/**
|
|
771
771
|
* Resolve o CATÁLOGO efetivo de roles de org (lista de roles permitidas),
|
|
772
772
|
* garantindo a invariante de governance: `owner` está sempre presente.
|
|
@@ -737,7 +737,7 @@ export async function resolveEffectiveRolesCatalog(settings, orgId) {
|
|
|
737
737
|
*
|
|
738
738
|
* Invariante mantida: 'owner' é sempre incluído na lista de roles (governance).
|
|
739
739
|
*
|
|
740
|
-
* @param settings - SettingsCapability
|
|
740
|
+
* @param settings - SettingsCapability, ou null (runtime settings indisponível → config/defaults)
|
|
741
741
|
* @param configDefault - defaults estáticos do config do host
|
|
742
742
|
* @param orgId - opcional; quando fornecido, tenta settings da org antes do global
|
|
743
743
|
*/
|
|
@@ -769,6 +769,8 @@ export async function resolveEffectiveOrganizationsPolicy(settings, configDefaul
|
|
|
769
769
|
roles,
|
|
770
770
|
};
|
|
771
771
|
}
|
|
772
|
+
if (!settings)
|
|
773
|
+
return defaults;
|
|
772
774
|
try {
|
|
773
775
|
// Resolução org → global → defaults
|
|
774
776
|
if (orgId) {
|
|
@@ -108,6 +108,23 @@ export declare function markSudo(ctx: HttpContext): void;
|
|
|
108
108
|
* @returns `true` se o sudo está ativo (dentro da graça); `false` caso contrário.
|
|
109
109
|
*/
|
|
110
110
|
export declare function isSudoActive(ctx: HttpContext, graceMinutes: number): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* A DECISÃO de sudo, sem efeito colateral nenhum na resposta: `true` quando a
|
|
113
|
+
* requisição pode seguir, `false` quando o usuário precisa reconfirmar a
|
|
114
|
+
* identidade.
|
|
115
|
+
*
|
|
116
|
+
* Existe porque há DUAS formas de recusar a mesma coisa. O console HTML
|
|
117
|
+
* redireciona para `/account/confirm` (`requireSudo`); o espelho JSON de
|
|
118
|
+
* `/account/api/*` responde `403 { error: { code: 'sudo_required' } }`, porque
|
|
119
|
+
* uma SPA que recebe uma página de login onde esperava JSON não tem como
|
|
120
|
+
* reagir. A POLÍTICA — toggle `sudo_mode`, janela de graça, vinculação à conta,
|
|
121
|
+
* fail-safe da setting e fail-closed da marca — tem de ser UMA só: duas cópias
|
|
122
|
+
* dela são como o caminho JSON acaba mais frouxo que o formulário.
|
|
123
|
+
*
|
|
124
|
+
* Toda a discussão de fail-safe × fail-closed do {@link requireSudo} vale aqui
|
|
125
|
+
* sem mudança: é literalmente o mesmo código.
|
|
126
|
+
*/
|
|
127
|
+
export declare function isSudoSatisfied(ctx: HttpContext, settings: SettingsCapability | null): Promise<boolean>;
|
|
111
128
|
/**
|
|
112
129
|
* Guard de sudo mode. Verifica se a confirmação de identidade está ativa e
|
|
113
130
|
* dentro da janela de graça. Se estiver, retorna `true`. Se não, redireciona
|
|
@@ -146,6 +146,36 @@ export function isSudoActive(ctx, graceMinutes) {
|
|
|
146
146
|
const graceMs = graceMinutes * 60 * 1000;
|
|
147
147
|
return Date.now() - sudoAt <= graceMs;
|
|
148
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* A DECISÃO de sudo, sem efeito colateral nenhum na resposta: `true` quando a
|
|
151
|
+
* requisição pode seguir, `false` quando o usuário precisa reconfirmar a
|
|
152
|
+
* identidade.
|
|
153
|
+
*
|
|
154
|
+
* Existe porque há DUAS formas de recusar a mesma coisa. O console HTML
|
|
155
|
+
* redireciona para `/account/confirm` (`requireSudo`); o espelho JSON de
|
|
156
|
+
* `/account/api/*` responde `403 { error: { code: 'sudo_required' } }`, porque
|
|
157
|
+
* uma SPA que recebe uma página de login onde esperava JSON não tem como
|
|
158
|
+
* reagir. A POLÍTICA — toggle `sudo_mode`, janela de graça, vinculação à conta,
|
|
159
|
+
* fail-safe da setting e fail-closed da marca — tem de ser UMA só: duas cópias
|
|
160
|
+
* dela são como o caminho JSON acaba mais frouxo que o formulário.
|
|
161
|
+
*
|
|
162
|
+
* Toda a discussão de fail-safe × fail-closed do {@link requireSudo} vale aqui
|
|
163
|
+
* sem mudança: é literalmente o mesmo código.
|
|
164
|
+
*/
|
|
165
|
+
export async function isSudoSatisfied(ctx, settings) {
|
|
166
|
+
try {
|
|
167
|
+
const cfg = settings ? await resolveEffectiveSudoMode(settings) : SUDO_MODE_DEFAULTS;
|
|
168
|
+
if (!cfg.enabled)
|
|
169
|
+
return true;
|
|
170
|
+
return isSudoActive(ctx, cfg.graceMinutes);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// FAIL-SAFE: erro ao resolver a setting → deixa passar. Disponibilidade, não
|
|
174
|
+
// identidade — ver o docblock de `requireSudo` para o porquê de isto NÃO
|
|
175
|
+
// contradizer o fail-closed de `isSudoActive`.
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
149
179
|
/**
|
|
150
180
|
* Guard de sudo mode. Verifica se a confirmação de identidade está ativa e
|
|
151
181
|
* dentro da janela de graça. Se estiver, retorna `true`. Se não, redireciona
|
|
@@ -202,19 +232,8 @@ export function isSudoActive(ctx, graceMinutes) {
|
|
|
202
232
|
* postura é fail-closed de novo.
|
|
203
233
|
*/
|
|
204
234
|
export async function requireSudo(ctx, settings) {
|
|
205
|
-
|
|
206
|
-
const cfg = settings ? await resolveEffectiveSudoMode(settings) : SUDO_MODE_DEFAULTS;
|
|
207
|
-
if (!cfg.enabled)
|
|
208
|
-
return true;
|
|
209
|
-
if (isSudoActive(ctx, cfg.graceMinutes))
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
catch {
|
|
213
|
-
// FAIL-SAFE: erro ao resolver a setting → deixa passar. Disponibilidade, não
|
|
214
|
-
// identidade — ver o docblock acima para o porquê de isto NÃO contradizer o
|
|
215
|
-
// fail-closed de `isSudoActive`.
|
|
235
|
+
if (await isSudoSatisfied(ctx, settings))
|
|
216
236
|
return true;
|
|
217
|
-
}
|
|
218
237
|
// Fora da graça: redireciona para confirmação.
|
|
219
238
|
const rawUrl = ctx.request.url?.() ?? '';
|
|
220
239
|
const qs = ctx.request.parsedUrl?.search ?? '';
|