@dudousxd/adonis-authkit-server 0.1.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/LICENSE +21 -0
- package/README.md +137 -0
- package/build/assets/grafana/authkit-dashboard.json +118 -0
- package/build/commands/commands.json +30 -0
- package/build/commands/configure.d.ts +2 -0
- package/build/commands/configure.js +42 -0
- package/build/commands/eject.d.ts +11 -0
- package/build/commands/eject.js +96 -0
- package/build/commands/main.d.ts +12 -0
- package/build/commands/main.js +38 -0
- package/build/commands/ui_preset.d.ts +4 -0
- package/build/commands/ui_preset.js +32 -0
- package/build/database/migrations/make_authkit_oidc_table.d.ts +6 -0
- package/build/database/migrations/make_authkit_oidc_table.js +19 -0
- package/build/host/views/account/login.edge +29 -0
- package/build/host/views/account/mfa.edge +151 -0
- package/build/host/views/account/tokens.edge +70 -0
- package/build/host/views/admin/audit.edge +72 -0
- package/build/host/views/admin/clients.edge +51 -0
- package/build/host/views/admin/dashboard.edge +58 -0
- package/build/host/views/admin/users.edge +76 -0
- package/build/host/views/consent.edge +19 -0
- package/build/host/views/forgot.edge +30 -0
- package/build/host/views/login.edge +91 -0
- package/build/host/views/mfa-challenge.edge +88 -0
- package/build/host/views/reset.edge +29 -0
- package/build/host/views/signup.edge +44 -0
- package/build/host/views/verify-email.edge +16 -0
- package/build/index.d.ts +42 -0
- package/build/index.js +28 -0
- package/build/providers/authkit_server_provider.d.ts +19 -0
- package/build/providers/authkit_server_provider.js +81 -0
- package/build/src/accounts/account_store.d.ts +136 -0
- package/build/src/accounts/account_store.js +1 -0
- package/build/src/accounts/lucid_account_store.d.ts +75 -0
- package/build/src/accounts/lucid_account_store.js +396 -0
- package/build/src/adapters/adapter_contract.d.ts +18 -0
- package/build/src/adapters/adapter_contract.js +1 -0
- package/build/src/adapters/database_adapter.d.ts +15 -0
- package/build/src/adapters/database_adapter.js +63 -0
- package/build/src/adapters/factory.d.ts +30 -0
- package/build/src/adapters/factory.js +43 -0
- package/build/src/adapters/redis_adapter.d.ts +16 -0
- package/build/src/adapters/redis_adapter.js +95 -0
- package/build/src/audit/audit_sink.d.ts +54 -0
- package/build/src/audit/audit_sink.js +1 -0
- package/build/src/audit/lucid_audit_sink.d.ts +10 -0
- package/build/src/audit/lucid_audit_sink.js +60 -0
- package/build/src/controllers/oidc_callback_controller.d.ts +22 -0
- package/build/src/controllers/oidc_callback_controller.js +33 -0
- package/build/src/define_config.d.ts +261 -0
- package/build/src/define_config.js +115 -0
- package/build/src/host/account_lockout.d.ts +86 -0
- package/build/src/host/account_lockout.js +185 -0
- package/build/src/host/augmentations.d.ts +1 -0
- package/build/src/host/augmentations.js +1 -0
- package/build/src/host/branding.d.ts +17 -0
- package/build/src/host/branding.js +8 -0
- package/build/src/host/controllers/account_mfa_controller.d.ts +30 -0
- package/build/src/host/controllers/account_mfa_controller.js +157 -0
- package/build/src/host/controllers/account_session_controller.d.ts +7 -0
- package/build/src/host/controllers/account_session_controller.js +50 -0
- package/build/src/host/controllers/account_tokens_controller.d.ts +7 -0
- package/build/src/host/controllers/account_tokens_controller.js +55 -0
- package/build/src/host/controllers/admin/admin_audit_controller.d.ts +10 -0
- package/build/src/host/controllers/admin/admin_audit_controller.js +56 -0
- package/build/src/host/controllers/admin/admin_clients_controller.d.ts +10 -0
- package/build/src/host/controllers/admin/admin_clients_controller.js +24 -0
- package/build/src/host/controllers/admin/admin_dashboard_controller.d.ts +10 -0
- package/build/src/host/controllers/admin/admin_dashboard_controller.js +27 -0
- package/build/src/host/controllers/admin/admin_users_controller.d.ts +11 -0
- package/build/src/host/controllers/admin/admin_users_controller.js +53 -0
- package/build/src/host/controllers/interaction_controller.d.ts +44 -0
- package/build/src/host/controllers/interaction_controller.js +304 -0
- package/build/src/host/controllers/pat_introspection_controller.d.ts +22 -0
- package/build/src/host/controllers/pat_introspection_controller.js +46 -0
- package/build/src/host/controllers/registration_controller.d.ts +18 -0
- package/build/src/host/controllers/registration_controller.js +169 -0
- package/build/src/host/controllers/social_controller.d.ts +8 -0
- package/build/src/host/controllers/social_controller.js +82 -0
- package/build/src/host/default_mailer.d.ts +39 -0
- package/build/src/host/default_mailer.js +141 -0
- package/build/src/host/email_templates.d.ts +35 -0
- package/build/src/host/email_templates.js +66 -0
- package/build/src/host/i18n.d.ts +178 -0
- package/build/src/host/i18n.js +208 -0
- package/build/src/host/middleware/account_auth.d.ts +7 -0
- package/build/src/host/middleware/account_auth.js +11 -0
- package/build/src/host/rate_limit.d.ts +32 -0
- package/build/src/host/rate_limit.js +87 -0
- package/build/src/host/register_auth_host.d.ts +41 -0
- package/build/src/host/register_auth_host.js +133 -0
- package/build/src/host/renderers/edge_renderer.d.ts +3 -0
- package/build/src/host/renderers/edge_renderer.js +29 -0
- package/build/src/host/renderers/inertia_renderer.d.ts +5 -0
- package/build/src/host/renderers/inertia_renderer.js +26 -0
- package/build/src/host/validators.d.ts +39 -0
- package/build/src/host/validators.js +13 -0
- package/build/src/keys/jwks_manager.d.ts +6 -0
- package/build/src/keys/jwks_manager.js +11 -0
- package/build/src/mixins/with_audit_log.d.ts +19 -0
- package/build/src/mixins/with_audit_log.js +41 -0
- package/build/src/mixins/with_auth_user.d.ts +18 -0
- package/build/src/mixins/with_auth_user.js +39 -0
- package/build/src/mixins/with_credentials.d.ts +20 -0
- package/build/src/mixins/with_credentials.js +29 -0
- package/build/src/mixins/with_mfa.d.ts +31 -0
- package/build/src/mixins/with_mfa.js +39 -0
- package/build/src/mixins/with_personal_access_token.d.ts +19 -0
- package/build/src/mixins/with_personal_access_token.js +44 -0
- package/build/src/mixins/with_provider_identity.d.ts +20 -0
- package/build/src/mixins/with_provider_identity.js +32 -0
- package/build/src/mixins/with_webauthn_credential.d.ts +37 -0
- package/build/src/mixins/with_webauthn_credential.js +49 -0
- package/build/src/observability/metrics_controller.d.ts +5 -0
- package/build/src/observability/metrics_controller.js +24 -0
- package/build/src/observability/metrics_service.d.ts +2 -0
- package/build/src/observability/metrics_service.js +7 -0
- package/build/src/observability/otel_recorder.d.ts +10 -0
- package/build/src/observability/otel_recorder.js +59 -0
- package/build/src/observability/wire_provider_events.d.ts +12 -0
- package/build/src/observability/wire_provider_events.js +19 -0
- package/build/src/pat/lucid_pat_store.d.ts +6 -0
- package/build/src/pat/lucid_pat_store.js +62 -0
- package/build/src/pat/pat_store.d.ts +31 -0
- package/build/src/pat/pat_store.js +1 -0
- package/build/src/pat/pat_tokens.d.ts +4 -0
- package/build/src/pat/pat_tokens.js +9 -0
- package/build/src/provider/build_provider.d.ts +8 -0
- package/build/src/provider/build_provider.js +101 -0
- package/build/src/provider/interaction_actions.d.ts +21 -0
- package/build/src/provider/interaction_actions.js +32 -0
- package/build/src/provider/oidc_service.d.ts +17 -0
- package/build/src/provider/oidc_service.js +84 -0
- package/build/src/provider/token_exchange.d.ts +15 -0
- package/build/src/provider/token_exchange.js +72 -0
- package/build/src/register_routes.d.ts +16 -0
- package/build/src/register_routes.js +21 -0
- package/build/stubs/config/authkit.stub +29 -0
- package/build/stubs/main.d.ts +1 -0
- package/build/stubs/main.js +2 -0
- package/build/stubs/models/auth_user.stub +13 -0
- package/build/stubs/ui/edge/views/consent.edge +13 -0
- package/build/stubs/ui/edge/views/login.edge +19 -0
- package/build/stubs/ui/react/components/auth_shell.tsx +67 -0
- package/build/stubs/ui/react/pages/account/login.tsx +56 -0
- package/build/stubs/ui/react/pages/account/mfa.tsx +132 -0
- package/build/stubs/ui/react/pages/account/tokens.tsx +88 -0
- package/build/stubs/ui/react/pages/consent.tsx +39 -0
- package/build/stubs/ui/react/pages/forgot.tsx +44 -0
- package/build/stubs/ui/react/pages/login.tsx +171 -0
- package/build/stubs/ui/react/pages/mfa-challenge.tsx +72 -0
- package/build/stubs/ui/react/pages/reset.tsx +58 -0
- package/build/stubs/ui/react/pages/signup.tsx +78 -0
- package/build/stubs/ui/react/pages/verify-email.tsx +24 -0
- package/build/types.d.ts +7 -0
- package/build/types.js +1 -0
- package/package.json +108 -0
- package/stubs/config/authkit.stub +29 -0
- package/stubs/main.ts +2 -0
- package/stubs/models/auth_user.stub +13 -0
- package/stubs/ui/edge/views/consent.edge +13 -0
- package/stubs/ui/edge/views/login.edge +19 -0
- package/stubs/ui/react/components/auth_shell.tsx +67 -0
- package/stubs/ui/react/pages/account/login.tsx +56 -0
- package/stubs/ui/react/pages/account/mfa.tsx +132 -0
- package/stubs/ui/react/pages/account/tokens.tsx +88 -0
- package/stubs/ui/react/pages/consent.tsx +39 -0
- package/stubs/ui/react/pages/forgot.tsx +44 -0
- package/stubs/ui/react/pages/login.tsx +171 -0
- package/stubs/ui/react/pages/mfa-challenge.tsx +72 -0
- package/stubs/ui/react/pages/reset.tsx +58 -0
- package/stubs/ui/react/pages/signup.tsx +78 -0
- package/stubs/ui/react/pages/verify-email.tsx +24 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tipos de eventos de auditoria relevantes para segurança emitidos pelo IdP.
|
|
3
|
+
*/
|
|
4
|
+
export type AuditEventType = 'login.success' | 'login.failure' | 'signup' | 'password_reset.issued' | 'password_reset.consumed' | 'pat.issued' | 'pat.revoked' | 'pat.used' | 'impersonation' | 'mfa.enabled' | 'mfa.disabled' | 'account.locked' | 'passkey.registered' | 'passkey.removed' | 'email_verification.issued' | 'email_verification.consumed';
|
|
5
|
+
/**
|
|
6
|
+
* Evento de auditoria a registrar. O timestamp é definido pelo sink (não aqui).
|
|
7
|
+
*/
|
|
8
|
+
export interface AuditEvent {
|
|
9
|
+
type: AuditEventType;
|
|
10
|
+
accountId?: string | null;
|
|
11
|
+
email?: string | null;
|
|
12
|
+
clientId?: string | null;
|
|
13
|
+
/** Impersonation: quem agiu (o admin). */
|
|
14
|
+
actorId?: string | null;
|
|
15
|
+
ip?: string | null;
|
|
16
|
+
metadata?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Evento de auditoria já persistido (lido de volta pelo console admin). Carrega
|
|
20
|
+
* o id e o timestamp atribuídos pelo sink.
|
|
21
|
+
*/
|
|
22
|
+
export interface StoredAuditEvent extends AuditEvent {
|
|
23
|
+
id: string;
|
|
24
|
+
createdAt: Date | string | null;
|
|
25
|
+
}
|
|
26
|
+
/** Filtros de listagem do log de auditoria (console admin). */
|
|
27
|
+
export interface ListAuditParams {
|
|
28
|
+
/** Página (1-based). Default: 1. */
|
|
29
|
+
page?: number;
|
|
30
|
+
/** Itens por página. Default: 20. */
|
|
31
|
+
limit?: number;
|
|
32
|
+
/** Filtra por tipo de evento exato. */
|
|
33
|
+
type?: string;
|
|
34
|
+
/** Filtra pelo subject (accountId) do evento. */
|
|
35
|
+
subject?: string;
|
|
36
|
+
}
|
|
37
|
+
/** Página de eventos + total absoluto. */
|
|
38
|
+
export interface AuditPage {
|
|
39
|
+
data: StoredAuditEvent[];
|
|
40
|
+
total: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Sink plugável de auditoria. Implementações devem ser best-effort: `record`
|
|
44
|
+
* NUNCA deve lançar para dentro do caminho da request.
|
|
45
|
+
*
|
|
46
|
+
* `list` é OPCIONAL: sinks customizados podem só implementar `record` (write-only).
|
|
47
|
+
* O console admin degrada graciosamente ("consulta não suportada") quando o sink
|
|
48
|
+
* configurado não fornece `list`.
|
|
49
|
+
*/
|
|
50
|
+
export interface AuditSink {
|
|
51
|
+
record(event: AuditEvent): Promise<void>;
|
|
52
|
+
/** Lista eventos paginados (opcional — só sinks que suportam consulta). */
|
|
53
|
+
list?(params: ListAuditParams): Promise<AuditPage>;
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AuditSink } from './audit_sink.js';
|
|
2
|
+
/**
|
|
3
|
+
* Implementação default do {@link AuditSink} sobre um model Lucid composto de
|
|
4
|
+
* `withAuditLog()`. Insere o evento na tabela `audit_logs` e suporta consulta
|
|
5
|
+
* paginada para o console admin.
|
|
6
|
+
*
|
|
7
|
+
* BEST-EFFORT: erros de inserção são logados via `console.error` e engolidos —
|
|
8
|
+
* a auditoria nunca deve lançar para dentro do caminho da request.
|
|
9
|
+
*/
|
|
10
|
+
export declare function lucidAuditSink(Model: any): AuditSink;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implementação default do {@link AuditSink} sobre um model Lucid composto de
|
|
3
|
+
* `withAuditLog()`. Insere o evento na tabela `audit_logs` e suporta consulta
|
|
4
|
+
* paginada para o console admin.
|
|
5
|
+
*
|
|
6
|
+
* BEST-EFFORT: erros de inserção são logados via `console.error` e engolidos —
|
|
7
|
+
* a auditoria nunca deve lançar para dentro do caminho da request.
|
|
8
|
+
*/
|
|
9
|
+
export function lucidAuditSink(Model) {
|
|
10
|
+
return {
|
|
11
|
+
async record(event) {
|
|
12
|
+
try {
|
|
13
|
+
await Model.create({
|
|
14
|
+
type: event.type,
|
|
15
|
+
accountId: event.accountId ?? null,
|
|
16
|
+
email: event.email ?? null,
|
|
17
|
+
clientId: event.clientId ?? null,
|
|
18
|
+
actorId: event.actorId ?? null,
|
|
19
|
+
ip: event.ip ?? null,
|
|
20
|
+
metadata: event.metadata ?? null,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
// eslint-disable-next-line no-console
|
|
25
|
+
console.error('[authkit] audit sink falhou ao registrar evento', event.type, error);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
async list(params) {
|
|
29
|
+
const page = Math.max(1, params.page ?? 1);
|
|
30
|
+
const limit = Math.max(1, params.limit ?? 20);
|
|
31
|
+
const base = () => {
|
|
32
|
+
const q = Model.query();
|
|
33
|
+
if (params.type)
|
|
34
|
+
q.where('type', params.type);
|
|
35
|
+
if (params.subject)
|
|
36
|
+
q.where('accountId', params.subject);
|
|
37
|
+
return q;
|
|
38
|
+
};
|
|
39
|
+
const countResult = await base().count('* as total');
|
|
40
|
+
const total = Number(countResult[0]?.$extras?.total ?? 0);
|
|
41
|
+
const rows = await base()
|
|
42
|
+
.orderBy('createdAt', 'desc')
|
|
43
|
+
.offset((page - 1) * limit)
|
|
44
|
+
.limit(limit);
|
|
45
|
+
const data = rows.map((row) => ({
|
|
46
|
+
id: String(row.id),
|
|
47
|
+
type: row.type,
|
|
48
|
+
accountId: row.accountId ?? null,
|
|
49
|
+
email: row.email ?? null,
|
|
50
|
+
clientId: row.clientId ?? null,
|
|
51
|
+
actorId: row.actorId ?? null,
|
|
52
|
+
ip: row.ip ?? null,
|
|
53
|
+
metadata: row.metadata ?? undefined,
|
|
54
|
+
// createdAt é um luxon DateTime no Lucid; normaliza para ISO string.
|
|
55
|
+
createdAt: row.createdAt?.toISO?.() ?? row.createdAt ?? null,
|
|
56
|
+
}));
|
|
57
|
+
return { data, total };
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
/**
|
|
3
|
+
* Delega a requisição Adonis ao handler do oidc-provider (`service.callback`),
|
|
4
|
+
* que faz seu próprio roteamento e escreve direto na resposta Node.
|
|
5
|
+
*
|
|
6
|
+
* Quando o issuer tem um path (ex.: `/oidc`), o `service.callback` é um app Koa
|
|
7
|
+
* com o provider MONTADO sob esse path via koa-mount (vide OidcService). Por isso
|
|
8
|
+
* NÃO removemos o prefixo de `req.url`: o koa-mount cuida do mount e o provider
|
|
9
|
+
* gera URLs de discovery/redirect já prefixadas (ex.: /oidc/auth, /oidc/jwks).
|
|
10
|
+
*
|
|
11
|
+
* Resolvemos a Promise no `finish`/`close` para impedir que o Adonis tente
|
|
12
|
+
* tratar a resposta novamente.
|
|
13
|
+
*
|
|
14
|
+
* Body bridge: o bodyparser do AdonisJS consome o stream da requisição antes
|
|
15
|
+
* de chegar aqui. O oidc-provider usa `raw-body` para ler o stream — como ele
|
|
16
|
+
* já está esgotado, o provider cai no fallback `ctx.req.body || ctx.request.body`.
|
|
17
|
+
* Populamos `req.body` com o payload já parseado pelo AdonisJS para que o
|
|
18
|
+
* oidc-provider consiga ler os parâmetros do formulário (client_id, code, etc.).
|
|
19
|
+
*/
|
|
20
|
+
export default class OidcCallbackController {
|
|
21
|
+
handle(ctx: HttpContext): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delega a requisição Adonis ao handler do oidc-provider (`service.callback`),
|
|
3
|
+
* que faz seu próprio roteamento e escreve direto na resposta Node.
|
|
4
|
+
*
|
|
5
|
+
* Quando o issuer tem um path (ex.: `/oidc`), o `service.callback` é um app Koa
|
|
6
|
+
* com o provider MONTADO sob esse path via koa-mount (vide OidcService). Por isso
|
|
7
|
+
* NÃO removemos o prefixo de `req.url`: o koa-mount cuida do mount e o provider
|
|
8
|
+
* gera URLs de discovery/redirect já prefixadas (ex.: /oidc/auth, /oidc/jwks).
|
|
9
|
+
*
|
|
10
|
+
* Resolvemos a Promise no `finish`/`close` para impedir que o Adonis tente
|
|
11
|
+
* tratar a resposta novamente.
|
|
12
|
+
*
|
|
13
|
+
* Body bridge: o bodyparser do AdonisJS consome o stream da requisição antes
|
|
14
|
+
* de chegar aqui. O oidc-provider usa `raw-body` para ler o stream — como ele
|
|
15
|
+
* já está esgotado, o provider cai no fallback `ctx.req.body || ctx.request.body`.
|
|
16
|
+
* Populamos `req.body` com o payload já parseado pelo AdonisJS para que o
|
|
17
|
+
* oidc-provider consiga ler os parâmetros do formulário (client_id, code, etc.).
|
|
18
|
+
*/
|
|
19
|
+
export default class OidcCallbackController {
|
|
20
|
+
async handle(ctx) {
|
|
21
|
+
const service = await ctx.containerResolver.make('authkit.server');
|
|
22
|
+
const req = ctx.request.request;
|
|
23
|
+
const res = ctx.response.response;
|
|
24
|
+
// Repassa o body já parseado pelo AdonisJS para o req cru, pois o stream
|
|
25
|
+
// foi consumido pelo bodyparser_middleware antes de chegar neste controller.
|
|
26
|
+
req.body = ctx.request.all();
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
res.on('finish', resolve);
|
|
29
|
+
res.on('close', resolve);
|
|
30
|
+
service.callback(req, res);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import type { ClientConfig, JwksConfig, ObservabilityConfig, TtlConfig } from '@dudousxd/adonis-authkit-core';
|
|
3
|
+
import { adapters, type AdapterFactory, type OidcAdapterClass } from './adapters/factory.js';
|
|
4
|
+
import type { AccountStore, AuthAccount } from './accounts/account_store.js';
|
|
5
|
+
import type { PatStore } from './pat/pat_store.js';
|
|
6
|
+
import type { AuditSink } from './audit/audit_sink.js';
|
|
7
|
+
import type { BrandingConfig } from './host/branding.js';
|
|
8
|
+
import { type AuthMessages, type I18nConfig } from './host/i18n.js';
|
|
9
|
+
export { adapters };
|
|
10
|
+
export type { AuthAccount };
|
|
11
|
+
export type AuthHostRenderer = (ctx: HttpContext, view: string, props: Record<string, unknown>) => unknown;
|
|
12
|
+
export interface AuthSocialConfig {
|
|
13
|
+
providers: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Hooks de e-mail plugáveis (best-effort). Quando ausentes, o host-kit cai no
|
|
17
|
+
* fallback de log em dev (sem enviar e-mail). O envio real fica a cargo do host
|
|
18
|
+
* (ex.: @adonisjs/mail), mantendo a lib agnóstica de transporte.
|
|
19
|
+
*/
|
|
20
|
+
export interface MailHooks {
|
|
21
|
+
/** Disparado após gerar o token de redefinição de senha. */
|
|
22
|
+
onPasswordReset?: (data: {
|
|
23
|
+
email: string;
|
|
24
|
+
resetUrl: string;
|
|
25
|
+
token: string;
|
|
26
|
+
}) => Promise<void>;
|
|
27
|
+
/** Disparado após gerar o token de verificação de e-mail. */
|
|
28
|
+
onEmailVerification?: (data: {
|
|
29
|
+
email: string;
|
|
30
|
+
verifyUrl: string;
|
|
31
|
+
token: string;
|
|
32
|
+
}) => Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
/** Bucket de rate-limit: pontos (requests) permitidos por janela de duração. */
|
|
35
|
+
export interface RateLimitBucket {
|
|
36
|
+
/** Número de requests permitidos na janela. */
|
|
37
|
+
points: number;
|
|
38
|
+
/** Duração da janela (ex.: '1 min', '15 mins', 60). */
|
|
39
|
+
duration: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Rate-limiting para as rotas sensíveis do host-kit.
|
|
43
|
+
* Backed pelo `@adonisjs/limiter` (o host precisa tê-lo configurado: config/limiter.ts).
|
|
44
|
+
* Ligado por default; se o limiter não estiver configurado, o throttle vira no-op
|
|
45
|
+
* (fail-safe, sem quebra). Passe `enabled: false` para montar as rotas SEM throttle.
|
|
46
|
+
*/
|
|
47
|
+
export interface RateLimitConfigInput {
|
|
48
|
+
/** Liga o rate-limit. Default: true. */
|
|
49
|
+
enabled?: boolean;
|
|
50
|
+
/** Bucket das rotas de login/signup/forgot/reset (keyed por IP). Default: 10 req / 1 min. */
|
|
51
|
+
login?: RateLimitBucket;
|
|
52
|
+
/** Bucket da rota de introspecção de PAT (keyed por IP ou bearer). Default: 60 req / 1 min. */
|
|
53
|
+
introspection?: RateLimitBucket;
|
|
54
|
+
/** Nome do store configurado em config/limiter.ts a usar. Default: store padrão do host. */
|
|
55
|
+
store?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface ResolvedRateLimitConfig {
|
|
58
|
+
enabled: boolean;
|
|
59
|
+
login: RateLimitBucket;
|
|
60
|
+
introspection: RateLimitBucket;
|
|
61
|
+
store?: string;
|
|
62
|
+
}
|
|
63
|
+
export declare function resolveRateLimit(input?: RateLimitConfigInput): ResolvedRateLimitConfig;
|
|
64
|
+
/**
|
|
65
|
+
* Bloqueio progressivo de conta (anti-brute-force keyed por EMAIL, complementar ao
|
|
66
|
+
* rate-limit por IP). Backed pelo mesmo `@adonisjs/limiter` do host (peer/opt-in) —
|
|
67
|
+
* SEM migração nem DB. Ligado por default; vira no-op se o limiter não existir.
|
|
68
|
+
*/
|
|
69
|
+
export interface LockoutConfigInput {
|
|
70
|
+
/** Liga o lockout. Default: true (no-op se o limiter não estiver configurado). */
|
|
71
|
+
enabled?: boolean;
|
|
72
|
+
/** Falhas dentro da janela antes de bloquear. Default: 5. */
|
|
73
|
+
maxAttempts?: number;
|
|
74
|
+
/** Janela deslizante (segundos) para contar falhas. Default: 900 (15 min). */
|
|
75
|
+
windowSec?: number;
|
|
76
|
+
/** Duração do 1º bloqueio (segundos). Default: 60. */
|
|
77
|
+
baseLockoutSec?: number;
|
|
78
|
+
/** Teto do backoff progressivo (segundos). Default: 3600 (1 h). */
|
|
79
|
+
maxLockoutSec?: number;
|
|
80
|
+
/** Store do `config/limiter.ts` a usar. Default: store padrão do host. */
|
|
81
|
+
store?: string;
|
|
82
|
+
}
|
|
83
|
+
export interface ResolvedLockoutConfig {
|
|
84
|
+
enabled: boolean;
|
|
85
|
+
maxAttempts: number;
|
|
86
|
+
windowSec: number;
|
|
87
|
+
baseLockoutSec: number;
|
|
88
|
+
maxLockoutSec: number;
|
|
89
|
+
store?: string;
|
|
90
|
+
}
|
|
91
|
+
export declare function resolveLockout(input?: LockoutConfigInput): ResolvedLockoutConfig;
|
|
92
|
+
/**
|
|
93
|
+
* Registro dinâmico de clients (OIDC Dynamic Client Registration — RFC 7591).
|
|
94
|
+
*
|
|
95
|
+
* Quando habilitado, o oidc-provider expõe o endpoint de registro (`/reg`) e os
|
|
96
|
+
* clients criados ali são PERSISTIDOS pelo MESMO adapter usado para os demais
|
|
97
|
+
* artefatos OIDC. Assim, clients dinâmicos coexistem com os `clients` estáticos
|
|
98
|
+
* da config e ficam disponíveis para uma futura UI admin que gerencia clients no DB.
|
|
99
|
+
*/
|
|
100
|
+
export interface DynamicRegistrationConfigInput {
|
|
101
|
+
/** Liga o endpoint de registro dinâmico. Default: false (comportamento atual). */
|
|
102
|
+
enabled: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Exige um Initial Access Token (IAT) como bearer para registrar (RFC 7591 §3).
|
|
105
|
+
* Quando ausente/false, o registro é ABERTO (qualquer um pode registrar um client) —
|
|
106
|
+
* isso raramente é desejável em produção; prefira sempre definir um IAT.
|
|
107
|
+
*/
|
|
108
|
+
initialAccessToken?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Habilita o Registration Management (RFC 7592): ler/atualizar/deletar o client
|
|
111
|
+
* registrado via o `registration_access_token` devolvido no registro. Default: false.
|
|
112
|
+
*/
|
|
113
|
+
management?: boolean;
|
|
114
|
+
}
|
|
115
|
+
export interface ResolvedDynamicRegistrationConfig {
|
|
116
|
+
enabled: boolean;
|
|
117
|
+
initialAccessToken?: string;
|
|
118
|
+
management: boolean;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Console admin opt-in do IdP (B6). Quando habilitado, monta o grupo `/admin/*`
|
|
122
|
+
* (dashboard, usuários/papéis, clients, audit) atrás de um guard que exige sessão
|
|
123
|
+
* de conta E que a conta tenha pelo menos um dos `roles` nas suas roles globais.
|
|
124
|
+
* Default: DESLIGADO — hosts existentes não mudam de comportamento.
|
|
125
|
+
*/
|
|
126
|
+
export interface AdminConfigInput {
|
|
127
|
+
/** Liga o console admin. Default: false. */
|
|
128
|
+
enabled: boolean;
|
|
129
|
+
/** Roles globais que dão acesso ao /admin. Default: ['ADMIN']. */
|
|
130
|
+
roles?: string[];
|
|
131
|
+
}
|
|
132
|
+
export interface ResolvedAdminConfig {
|
|
133
|
+
enabled: boolean;
|
|
134
|
+
roles: string[];
|
|
135
|
+
}
|
|
136
|
+
export declare function resolveAdmin(input?: AdminConfigInput): ResolvedAdminConfig;
|
|
137
|
+
/**
|
|
138
|
+
* Parâmetros do Relying Party (RP) das cerimônias WebAuthn / passkeys. Quando
|
|
139
|
+
* omitidos, são derivados do `issuer`: `rpId` = hostname (sem porta), `origin` =
|
|
140
|
+
* origem (scheme://host[:port]) do issuer, `rpName` = nome do app/branding.
|
|
141
|
+
*/
|
|
142
|
+
export interface WebauthnConfigInput {
|
|
143
|
+
/** Nome do RP mostrado pelo authenticator. Default: branding/app name. */
|
|
144
|
+
rpName?: string;
|
|
145
|
+
/** RP ID — hostname (sem porta) do issuer. Default: hostname do issuer. */
|
|
146
|
+
rpId?: string;
|
|
147
|
+
/** Origin(s) esperada(s) na verificação. Default: origem do issuer. */
|
|
148
|
+
origin?: string | string[];
|
|
149
|
+
}
|
|
150
|
+
export interface ResolvedWebauthnConfig {
|
|
151
|
+
rpName: string;
|
|
152
|
+
rpId: string;
|
|
153
|
+
origin: string | string[];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Resolve os parâmetros do RP de WebAuthn a partir do `issuer` quando omitidos.
|
|
157
|
+
* `rpName` cai no `fallbackName` (branding/mfaIssuer) quando ausente.
|
|
158
|
+
*/
|
|
159
|
+
export declare function resolveWebauthn(issuer: string, fallbackName: string, input?: WebauthnConfigInput): ResolvedWebauthnConfig;
|
|
160
|
+
export interface AuthServerConfigInput {
|
|
161
|
+
issuer: string;
|
|
162
|
+
adapter: AdapterFactory;
|
|
163
|
+
clients: ClientConfig[];
|
|
164
|
+
jwks: JwksConfig;
|
|
165
|
+
ttl?: TtlConfig;
|
|
166
|
+
/** Nome da CLAIM (não do scope) onde os papéis globais são emitidos. Default: 'roles'. */
|
|
167
|
+
globalRolesClaim?: string;
|
|
168
|
+
cookieKeys?: string[];
|
|
169
|
+
observability?: ObservabilityConfig;
|
|
170
|
+
/** Contrato primário de identidade. Deriva findAccount/verifyCredentials do provider. */
|
|
171
|
+
accountStore: AccountStore;
|
|
172
|
+
/** Opcional — necessário só para fluxos de Personal Access Token. */
|
|
173
|
+
patStore?: PatStore;
|
|
174
|
+
/** Caminho base onde o host-kit monta as rotas OIDC. Default: '/oidc'. */
|
|
175
|
+
mountPath?: string;
|
|
176
|
+
/** Renderer de páginas do host (Inertia ou Edge). */
|
|
177
|
+
render?: AuthHostRenderer;
|
|
178
|
+
/** Configuração de branding por cliente. */
|
|
179
|
+
branding?: BrandingConfig;
|
|
180
|
+
/** Internacionalização das telas. Default: pt-BR embutido (zero config). */
|
|
181
|
+
i18n?: I18nConfig;
|
|
182
|
+
/** Configuração de providers sociais. */
|
|
183
|
+
social?: AuthSocialConfig;
|
|
184
|
+
/** Segredo para autenticar requests de introspecção de PAT. */
|
|
185
|
+
patIntrospectionSecret?: string;
|
|
186
|
+
/** Rate-limiting das rotas sensíveis (anti-brute-force). Default: ligado (no-op se o limiter não estiver configurado). */
|
|
187
|
+
rateLimit?: RateLimitConfigInput;
|
|
188
|
+
/** Bloqueio progressivo de conta por email em falhas repetidas. Default: ligado (no-op sem limiter). */
|
|
189
|
+
lockout?: LockoutConfigInput;
|
|
190
|
+
/** Hooks de e-mail (reset de senha / verificação). Opcional — fallback de log em dev. */
|
|
191
|
+
mail?: MailHooks;
|
|
192
|
+
/** Sink de auditoria (best-effort). Opcional — quando ausente, auditoria é no-op. */
|
|
193
|
+
audit?: AuditSink;
|
|
194
|
+
/**
|
|
195
|
+
* Label de issuer TOTP mostrado nos apps autenticadores (MFA). Default: 'AuthKit'.
|
|
196
|
+
* O `lucidAccountStore` lê isso para montar o keyuri/QR.
|
|
197
|
+
*/
|
|
198
|
+
mfaIssuer?: string;
|
|
199
|
+
/**
|
|
200
|
+
* Parâmetros do RP de WebAuthn / passkeys (2º fator alternativo ao TOTP).
|
|
201
|
+
* Opcional — quando omitido, é derivado do `issuer`. As passkeys só ficam
|
|
202
|
+
* disponíveis quando o accountStore + o model de credenciais suportam.
|
|
203
|
+
*/
|
|
204
|
+
webauthn?: WebauthnConfigInput;
|
|
205
|
+
/**
|
|
206
|
+
* Registro dinâmico de clients (RFC 7591/7592). Default: desligado. Quando ligado,
|
|
207
|
+
* os clients registrados são persistidos pelo mesmo adapter OIDC.
|
|
208
|
+
*/
|
|
209
|
+
dynamicRegistration?: DynamicRegistrationConfigInput;
|
|
210
|
+
/**
|
|
211
|
+
* Console admin do IdP (B6). Default: desligado. Quando ligado, o host também
|
|
212
|
+
* deve passar `admin: true` em {@link AuthHostOptions} no registro de rotas
|
|
213
|
+
* (a montagem das rotas acontece antes do config resolver).
|
|
214
|
+
*/
|
|
215
|
+
admin?: AdminConfigInput;
|
|
216
|
+
}
|
|
217
|
+
export interface ResolvedServerConfig {
|
|
218
|
+
issuer: string;
|
|
219
|
+
AdapterClass: OidcAdapterClass;
|
|
220
|
+
clients: ClientConfig[];
|
|
221
|
+
jwks: {
|
|
222
|
+
keys: Record<string, any>[];
|
|
223
|
+
};
|
|
224
|
+
ttl: {
|
|
225
|
+
accessToken: number;
|
|
226
|
+
refreshToken: number;
|
|
227
|
+
idToken: number;
|
|
228
|
+
session: number;
|
|
229
|
+
};
|
|
230
|
+
globalRolesClaim: string;
|
|
231
|
+
cookieKeys: string[];
|
|
232
|
+
observability: ObservabilityConfig;
|
|
233
|
+
findAccount: (sub: string) => Promise<AuthAccount | null>;
|
|
234
|
+
verifyCredentials: (email: string, password: string) => Promise<{
|
|
235
|
+
id: string;
|
|
236
|
+
} | null>;
|
|
237
|
+
accountStore: AccountStore;
|
|
238
|
+
patStore?: PatStore;
|
|
239
|
+
mountPath: string;
|
|
240
|
+
render?: AuthHostRenderer;
|
|
241
|
+
branding?: BrandingConfig;
|
|
242
|
+
social?: AuthSocialConfig;
|
|
243
|
+
patIntrospectionSecret?: string;
|
|
244
|
+
rateLimit: ResolvedRateLimitConfig;
|
|
245
|
+
/** Bloqueio progressivo de conta resolvido (sempre presente; default ligado). */
|
|
246
|
+
lockout: ResolvedLockoutConfig;
|
|
247
|
+
mail?: MailHooks;
|
|
248
|
+
audit?: AuditSink;
|
|
249
|
+
mfaIssuer: string;
|
|
250
|
+
/** RP de WebAuthn resolvido (sempre presente; derivado do issuer por default). */
|
|
251
|
+
webauthn: ResolvedWebauthnConfig;
|
|
252
|
+
dynamicRegistration: ResolvedDynamicRegistrationConfig;
|
|
253
|
+
/** Console admin resolvido (sempre presente; default desligado). */
|
|
254
|
+
admin: ResolvedAdminConfig;
|
|
255
|
+
/** Catálogo de mensagens ativo (locale resolvido), pronto para os renderers. */
|
|
256
|
+
messages: AuthMessages;
|
|
257
|
+
/** Locale ativo (default 'pt-BR'). */
|
|
258
|
+
locale: string;
|
|
259
|
+
}
|
|
260
|
+
export declare function toSeconds(value: string | number | undefined, fallback: number): number;
|
|
261
|
+
export declare function defineConfig(config: AuthServerConfigInput): import("@adonisjs/core/types").ConfigProvider<ResolvedServerConfig>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { configProvider } from '@adonisjs/core';
|
|
2
|
+
import { generateJwks } from './keys/jwks_manager.js';
|
|
3
|
+
import { adapters } from './adapters/factory.js';
|
|
4
|
+
import { resolveMessages } from './host/i18n.js';
|
|
5
|
+
export { adapters };
|
|
6
|
+
const RATE_LIMIT_DEFAULTS = {
|
|
7
|
+
login: { points: 10, duration: '1 min' },
|
|
8
|
+
introspection: { points: 60, duration: '1 min' },
|
|
9
|
+
};
|
|
10
|
+
export function resolveRateLimit(input) {
|
|
11
|
+
const enabled = input?.enabled ?? true;
|
|
12
|
+
return {
|
|
13
|
+
enabled,
|
|
14
|
+
login: input?.login ?? RATE_LIMIT_DEFAULTS.login,
|
|
15
|
+
introspection: input?.introspection ?? RATE_LIMIT_DEFAULTS.introspection,
|
|
16
|
+
store: input?.store,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function resolveLockout(input) {
|
|
20
|
+
return {
|
|
21
|
+
enabled: input?.enabled ?? true,
|
|
22
|
+
maxAttempts: input?.maxAttempts ?? 5,
|
|
23
|
+
windowSec: input?.windowSec ?? 900,
|
|
24
|
+
baseLockoutSec: input?.baseLockoutSec ?? 60,
|
|
25
|
+
maxLockoutSec: input?.maxLockoutSec ?? 3600,
|
|
26
|
+
store: input?.store,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function resolveAdmin(input) {
|
|
30
|
+
return {
|
|
31
|
+
enabled: input?.enabled ?? false,
|
|
32
|
+
roles: input?.roles && input.roles.length > 0 ? input.roles : ['ADMIN'],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve os parâmetros do RP de WebAuthn a partir do `issuer` quando omitidos.
|
|
37
|
+
* `rpName` cai no `fallbackName` (branding/mfaIssuer) quando ausente.
|
|
38
|
+
*/
|
|
39
|
+
export function resolveWebauthn(issuer, fallbackName, input) {
|
|
40
|
+
let host = 'localhost';
|
|
41
|
+
let origin = 'http://localhost';
|
|
42
|
+
try {
|
|
43
|
+
const url = new URL(issuer);
|
|
44
|
+
host = url.hostname;
|
|
45
|
+
origin = url.origin;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// issuer inválido → mantém defaults de localhost (dev).
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
rpName: input?.rpName ?? fallbackName,
|
|
52
|
+
rpId: input?.rpId ?? host,
|
|
53
|
+
origin: input?.origin ?? origin,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const UNITS = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
57
|
+
export function toSeconds(value, fallback) {
|
|
58
|
+
if (value === undefined)
|
|
59
|
+
return fallback;
|
|
60
|
+
if (typeof value === 'number')
|
|
61
|
+
return value;
|
|
62
|
+
const m = /^(\d+)\s*([smhd])$/.exec(value.trim());
|
|
63
|
+
if (!m)
|
|
64
|
+
throw new Error(`TTL inválido: ${value}`);
|
|
65
|
+
return Number(m[1]) * UNITS[m[2]];
|
|
66
|
+
}
|
|
67
|
+
export function defineConfig(config) {
|
|
68
|
+
return configProvider.create(async (app) => {
|
|
69
|
+
const AdapterClass = await config.adapter.resolver(app);
|
|
70
|
+
const jwks = config.jwks.source === 'managed'
|
|
71
|
+
? await generateJwks(config.jwks.algorithm ?? 'RS256')
|
|
72
|
+
: { keys: config.jwks.keys ?? [] };
|
|
73
|
+
return {
|
|
74
|
+
issuer: config.issuer,
|
|
75
|
+
AdapterClass,
|
|
76
|
+
clients: config.clients,
|
|
77
|
+
jwks: jwks,
|
|
78
|
+
ttl: {
|
|
79
|
+
accessToken: toSeconds(config.ttl?.accessToken, 900),
|
|
80
|
+
refreshToken: toSeconds(config.ttl?.refreshToken, 2592000),
|
|
81
|
+
idToken: toSeconds(config.ttl?.idToken, 900),
|
|
82
|
+
session: toSeconds(config.ttl?.session, 604800),
|
|
83
|
+
},
|
|
84
|
+
globalRolesClaim: config.globalRolesClaim ?? 'roles',
|
|
85
|
+
cookieKeys: config.cookieKeys ?? [],
|
|
86
|
+
observability: config.observability ?? {},
|
|
87
|
+
findAccount: (sub) => config.accountStore.findById(sub),
|
|
88
|
+
verifyCredentials: async (email, password) => {
|
|
89
|
+
const acc = await config.accountStore.verifyCredentials(email, password);
|
|
90
|
+
return acc ? { id: acc.id } : null;
|
|
91
|
+
},
|
|
92
|
+
accountStore: config.accountStore,
|
|
93
|
+
patStore: config.patStore,
|
|
94
|
+
mountPath: config.mountPath ?? '/oidc',
|
|
95
|
+
render: config.render,
|
|
96
|
+
branding: config.branding,
|
|
97
|
+
social: config.social,
|
|
98
|
+
patIntrospectionSecret: config.patIntrospectionSecret,
|
|
99
|
+
rateLimit: resolveRateLimit(config.rateLimit),
|
|
100
|
+
lockout: resolveLockout(config.lockout),
|
|
101
|
+
mail: config.mail,
|
|
102
|
+
audit: config.audit,
|
|
103
|
+
mfaIssuer: config.mfaIssuer ?? 'AuthKit',
|
|
104
|
+
webauthn: resolveWebauthn(config.issuer, config.mfaIssuer ?? 'AuthKit', config.webauthn),
|
|
105
|
+
dynamicRegistration: {
|
|
106
|
+
enabled: config.dynamicRegistration?.enabled ?? false,
|
|
107
|
+
initialAccessToken: config.dynamicRegistration?.initialAccessToken,
|
|
108
|
+
management: config.dynamicRegistration?.management ?? false,
|
|
109
|
+
},
|
|
110
|
+
admin: resolveAdmin(config.admin),
|
|
111
|
+
messages: resolveMessages(config.i18n),
|
|
112
|
+
locale: config.i18n?.locale ?? 'pt-BR',
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { AuditSink } from '../audit/audit_sink.js';
|
|
2
|
+
import type { ResolvedLockoutConfig } from '../define_config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Bloqueio progressivo de conta (anti-brute-force keyed por EMAIL, não por IP).
|
|
5
|
+
*
|
|
6
|
+
* Construído sobre o `@adonisjs/limiter` do HOST (peer/opt-in), exatamente como o
|
|
7
|
+
* `rate_limit.ts`: o service do limiter é importado de forma preguiçosa e fail-safe.
|
|
8
|
+
* Se o `@adonisjs/limiter` não estiver instalado/configurado, TODOS os métodos viram
|
|
9
|
+
* no-op (lockout desligado) e `isLocked` devolve `{ locked: false }` — NUNCA lança no
|
|
10
|
+
* caminho da request, e NÃO há migração/DB envolvido (usa o store do limiter do host).
|
|
11
|
+
*
|
|
12
|
+
* Esquema de chaves (todas namespaced + email normalizado):
|
|
13
|
+
* - `authkit_lockout_fail:{email}` — contador de falhas dentro da janela deslizante.
|
|
14
|
+
* - `authkit_lockout:{email}` — chave bloqueada (TTL = duração progressiva do lock).
|
|
15
|
+
* - `authkit_lockout_count:{email}`— quantos locks já ocorreram (alimenta o backoff).
|
|
16
|
+
*/
|
|
17
|
+
/** Service do `@adonisjs/limiter` resolvido preguiçosamente. `any` de propósito (peer/opt-in). */
|
|
18
|
+
type LimiterService = any;
|
|
19
|
+
/**
|
|
20
|
+
* Permite reapontar/limpar o loader do limiter (usado em testes).
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export declare function __setLockoutLimiterLoaderForTests(fn: (() => Promise<LimiterService | null>) | undefined): void;
|
|
24
|
+
/**
|
|
25
|
+
* Backoff progressivo PURO (sem I/O — fácil de testar): a duração do lock cresce
|
|
26
|
+
* com o número de locks já ocorridos para a chave: `base * 2^(lockCount-1)`,
|
|
27
|
+
* limitada a `maxLockoutSec`. `lockCount` é 1-based (1 = primeiro lock).
|
|
28
|
+
*/
|
|
29
|
+
export declare function computeLockoutSec(lockCount: number, cfg: ResolvedLockoutConfig): number;
|
|
30
|
+
/** Resultado de uma checagem de bloqueio. */
|
|
31
|
+
export interface LockState {
|
|
32
|
+
locked: boolean;
|
|
33
|
+
/** Segundos até a conta destravar (presente quando `locked`). */
|
|
34
|
+
retryAfterSec?: number;
|
|
35
|
+
}
|
|
36
|
+
/** Contexto opcional de auditoria para o evento `account.locked`. */
|
|
37
|
+
export interface LockoutAuditContext {
|
|
38
|
+
sink?: AuditSink;
|
|
39
|
+
ip?: string | null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Helper de lockout amarrado a uma config resolvida. Cada método é fail-safe:
|
|
43
|
+
* resolve o limiter preguiçosamente e degrada para no-op quando ele está ausente
|
|
44
|
+
* ou quando `cfg.enabled === false`.
|
|
45
|
+
*/
|
|
46
|
+
export declare class AccountLockout {
|
|
47
|
+
private cfg;
|
|
48
|
+
constructor(cfg: ResolvedLockoutConfig);
|
|
49
|
+
private failKey;
|
|
50
|
+
private lockKey;
|
|
51
|
+
private countKey;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve o limiter (ou `null`). Retorna `null` também quando lockout está
|
|
54
|
+
* desligado por config — assim o caminho de no-op é o mesmo.
|
|
55
|
+
*/
|
|
56
|
+
private limiter;
|
|
57
|
+
/** Store de contagem de falhas (janela deslizante de `windowSec`). */
|
|
58
|
+
private failStore;
|
|
59
|
+
/**
|
|
60
|
+
* Store da contagem de locks. A janela é longa (mantém o histórico de locks por
|
|
61
|
+
* um tempo para o backoff progressivo crescer entre locks sucessivos).
|
|
62
|
+
*/
|
|
63
|
+
private countStore;
|
|
64
|
+
/**
|
|
65
|
+
* Store dedicado a marcar a chave como bloqueada. `requests: 1` + `block(...)`
|
|
66
|
+
* com a duração progressiva; `isBlocked`/`availableIn` consultam o estado.
|
|
67
|
+
*/
|
|
68
|
+
private lockStore;
|
|
69
|
+
/** `true`/retryAfter quando a conta está bloqueada. Fail-safe: `{ locked: false }`. */
|
|
70
|
+
isLocked(email: string): Promise<LockState>;
|
|
71
|
+
/**
|
|
72
|
+
* Registra uma falha de login. Ao cruzar `maxAttempts` dentro da janela, marca
|
|
73
|
+
* a chave como bloqueada com TTL progressivo e incrementa a contagem de locks.
|
|
74
|
+
* Emite `account.locked` UMA vez (na transição), não a cada tentativa bloqueada.
|
|
75
|
+
*/
|
|
76
|
+
recordFailure(email: string, audit?: LockoutAuditContext): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Limpa o estado de falhas após um login bem-sucedido. Remove o contador de
|
|
79
|
+
* falhas e o lock; mantém a contagem de locks (histórico para o backoff) — ela
|
|
80
|
+
* expira naturalmente pela janela longa do `countStore`.
|
|
81
|
+
*/
|
|
82
|
+
clearFailures(email: string): Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
/** Fabrica o helper de lockout a partir da config resolvida. */
|
|
85
|
+
export declare function createAccountLockout(cfg: ResolvedLockoutConfig): AccountLockout;
|
|
86
|
+
export {};
|