@dudousxd/adonis-authkit-server 0.5.0 → 0.7.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/README.md +23 -2
- package/build/host/views/account/apps.edge +58 -0
- package/build/host/views/account/security.edge +66 -0
- package/build/host/views/account/tokens.edge +1 -0
- package/build/host/views/admin/users.edge +62 -2
- package/build/host/views/login.edge +55 -0
- package/build/host/views/mfa-challenge.edge +12 -0
- package/build/index.d.ts +8 -2
- package/build/index.js +4 -1
- package/build/src/accounts/account_store.d.ts +80 -2
- package/build/src/accounts/account_store.js +12 -0
- package/build/src/accounts/lucid_account_store.js +8 -0
- package/build/src/accounts/lucid_store/core.d.ts +2 -2
- package/build/src/accounts/lucid_store/core.js +33 -0
- package/build/src/accounts/lucid_store/mfa.js +4 -1
- package/build/src/accounts/lucid_store/status_profile.d.ts +21 -0
- package/build/src/accounts/lucid_store/status_profile.js +66 -0
- package/build/src/audit/audit_sink.d.ts +1 -1
- package/build/src/define_config.d.ts +82 -0
- package/build/src/define_config.js +24 -1
- package/build/src/doctor/checks.js +32 -32
- package/build/src/events/dispatcher.d.ts +45 -0
- package/build/src/events/dispatcher.js +92 -0
- package/build/src/host/admin_sessions_service.d.ts +8 -0
- package/build/src/host/admin_sessions_service.js +19 -0
- package/build/src/host/avatar_storage.d.ts +58 -0
- package/build/src/host/avatar_storage.js +125 -0
- package/build/src/host/controllers/account_apps_controller.d.ts +15 -0
- package/build/src/host/controllers/account_apps_controller.js +61 -0
- package/build/src/host/controllers/account_security_controller.d.ts +9 -0
- package/build/src/host/controllers/account_security_controller.js +84 -2
- package/build/src/host/controllers/account_session_controller.js +3 -1
- package/build/src/host/controllers/admin/admin_users_controller.d.ts +13 -0
- package/build/src/host/controllers/admin/admin_users_controller.js +133 -0
- package/build/src/host/controllers/interaction_controller.d.ts +32 -0
- package/build/src/host/controllers/interaction_controller.js +169 -6
- package/build/src/host/default_mailer.d.ts +8 -0
- package/build/src/host/default_mailer.js +28 -0
- package/build/src/host/i18n.d.ts +98 -0
- package/build/src/host/i18n.js +106 -0
- package/build/src/host/login_attempt.d.ts +1 -0
- package/build/src/host/login_attempt.js +11 -0
- package/build/src/host/register_auth_host.js +18 -1
- package/build/src/host/trusted_device.d.ts +61 -0
- package/build/src/host/trusted_device.js +65 -0
- package/build/src/host/validators.d.ts +35 -0
- package/build/src/host/validators.js +14 -0
- package/package.json +6 -1
package/build/src/host/i18n.js
CHANGED
|
@@ -32,6 +32,10 @@ export const DEFAULT_MESSAGES = {
|
|
|
32
32
|
'login.switch_account': 'Switch account',
|
|
33
33
|
'login.password_label': 'Password',
|
|
34
34
|
'login.submit': 'Log in',
|
|
35
|
+
// Passwordless (login).
|
|
36
|
+
'login.magic_link_button': 'Email me a login link',
|
|
37
|
+
'login.magic_link_sent': 'If the account exists, we sent you a login link.',
|
|
38
|
+
'login.passkey_button': 'Sign in with a passkey',
|
|
35
39
|
// Tela de cadastro (signup).
|
|
36
40
|
'signup.page_title': 'Create account',
|
|
37
41
|
'signup.title': 'Create account',
|
|
@@ -73,6 +77,7 @@ export const DEFAULT_MESSAGES = {
|
|
|
73
77
|
'mfa_challenge.recovery_submit': 'Log in with a recovery code',
|
|
74
78
|
'mfa_challenge.passkey_button': 'Use passkey',
|
|
75
79
|
'mfa_challenge.passkey_error': 'Could not authenticate with the passkey. Please try again.',
|
|
80
|
+
'mfa_challenge.trust_device': 'Trust this device for {days} days',
|
|
76
81
|
// Consent (autorização de cliente OIDC).
|
|
77
82
|
'consent.page_title': 'Authorize',
|
|
78
83
|
'consent.title': 'Authorize access',
|
|
@@ -120,6 +125,34 @@ export const DEFAULT_MESSAGES = {
|
|
|
120
125
|
'account.security.change_email_submit': 'Request email change',
|
|
121
126
|
'account.security.email_change_requested': 'We sent a confirmation link to {email}. Click it to complete the change.',
|
|
122
127
|
'account.security.email_changed': 'Email changed successfully.',
|
|
128
|
+
// Trusted devices (account/security).
|
|
129
|
+
'account.security.trusted_devices_section': 'Trusted devices',
|
|
130
|
+
'account.security.trusted_devices_intro': 'You can stop trusting this browser so two-factor is required here again. To revoke trust on all devices, re-enroll your authenticator.',
|
|
131
|
+
'account.security.trusted_devices_revoke': 'Stop trusting this device',
|
|
132
|
+
'account.security.trusted_devices_revoked': 'This device is no longer trusted. Two-factor will be required here again.',
|
|
133
|
+
// Console de conta — perfil (seção em account/security).
|
|
134
|
+
'account.profile.section': 'Profile',
|
|
135
|
+
'account.profile.intro': 'Update your display name and avatar.',
|
|
136
|
+
'account.profile.name_label': 'Name',
|
|
137
|
+
'account.profile.avatar_label': 'Avatar URL',
|
|
138
|
+
'account.profile.avatar_upload_label': 'Upload avatar',
|
|
139
|
+
'account.profile.avatar_upload_hint': 'JPG, PNG or WebP, up to 5MB.',
|
|
140
|
+
'account.profile.avatar_invalid_type': 'Invalid image type. Use JPG, PNG or WebP.',
|
|
141
|
+
'account.profile.avatar_too_large': 'Image is too large.',
|
|
142
|
+
'account.profile.submit': 'Save profile',
|
|
143
|
+
'account.profile.updated': 'Profile updated successfully.',
|
|
144
|
+
'account.profile.not_supported': 'Profile editing is not available in this installation.',
|
|
145
|
+
// Console de conta — apps com acesso (account/apps).
|
|
146
|
+
'account.apps.page_title': 'Apps with access',
|
|
147
|
+
'account.apps.title': 'Apps with access',
|
|
148
|
+
'account.apps.intro': 'Apps you have authorized to access your account.',
|
|
149
|
+
'account.apps.logout': 'Log out',
|
|
150
|
+
'account.apps.empty': 'No apps have access to your account.',
|
|
151
|
+
'account.apps.tokens': '{accessTokens} access · {refreshTokens} refresh',
|
|
152
|
+
'account.apps.revoke': 'Revoke access',
|
|
153
|
+
'account.apps.revoke_confirm': 'Revoke this app’s access? It will need to be authorized again and its tokens will stop working.',
|
|
154
|
+
'account.apps.revoked': 'Access revoked.',
|
|
155
|
+
'account.apps.not_supported': 'The configured OIDC adapter does not support enumeration — listing apps is unavailable.',
|
|
123
156
|
// Confirmação de troca de e-mail (account/email-confirmed).
|
|
124
157
|
'account.email_confirmed.page_title': 'Email confirmation',
|
|
125
158
|
'account.email_confirmed.ok_title': 'Email changed',
|
|
@@ -172,6 +205,20 @@ export const DEFAULT_MESSAGES = {
|
|
|
172
205
|
'admin.users.roles_placeholder': 'Roles (comma-separated)',
|
|
173
206
|
'admin.users.save_roles': 'Save roles',
|
|
174
207
|
'admin.users.sessions': 'Sessions',
|
|
208
|
+
'admin.users.create_section': 'Create user',
|
|
209
|
+
'admin.users.create_name_placeholder': 'Name (optional)',
|
|
210
|
+
'admin.users.create_email_placeholder': 'Email',
|
|
211
|
+
'admin.users.create_password_placeholder': 'Password (leave blank to send invite)',
|
|
212
|
+
'admin.users.create_submit': 'Create user',
|
|
213
|
+
'admin.users.created': 'User created.',
|
|
214
|
+
'admin.users.reset_password': 'Send password reset',
|
|
215
|
+
'admin.users.reset_sent': 'Password reset email sent.',
|
|
216
|
+
'admin.users.disable': 'Disable',
|
|
217
|
+
'admin.users.enable': 'Enable',
|
|
218
|
+
'admin.users.disabled': 'Account disabled.',
|
|
219
|
+
'admin.users.enabled': 'Account enabled.',
|
|
220
|
+
'admin.users.disabled_badge': 'Disabled',
|
|
221
|
+
'admin.users.disable_confirm': 'Disable this account? The user will not be able to log in.',
|
|
175
222
|
// Console admin — sessões/grants ativos de uma conta.
|
|
176
223
|
'admin.sessions.page_title': 'Active sessions',
|
|
177
224
|
'admin.sessions.title': 'Active sessions',
|
|
@@ -255,6 +302,7 @@ export const DEFAULT_MESSAGES = {
|
|
|
255
302
|
// Mensagens de erro/flash produzidas pelos controllers.
|
|
256
303
|
'errors.invalid_credentials': 'Invalid credentials',
|
|
257
304
|
'errors.invalid_code': 'Invalid code',
|
|
305
|
+
'errors.account_disabled': 'This account has been disabled.',
|
|
258
306
|
'errors.email_taken': 'Email already registered',
|
|
259
307
|
'errors.signup_failed': 'Could not create the account',
|
|
260
308
|
'errors.invalid_or_expired_token': 'Invalid or expired token',
|
|
@@ -277,6 +325,11 @@ export const DEFAULT_MESSAGES = {
|
|
|
277
325
|
'mail.verify.cta': 'Verify email',
|
|
278
326
|
'mail.verify.fallback': 'If you did not create this account, you can ignore this email.',
|
|
279
327
|
'mail.verify.expires': 'This link expires in {minutes} minutes.',
|
|
328
|
+
'mail.magic_link.subject': 'Your login link',
|
|
329
|
+
'mail.magic_link.heading': 'Sign in to your account',
|
|
330
|
+
'mail.magic_link.intro': 'Click the button below to sign in. The link expires shortly and can be used once.',
|
|
331
|
+
'mail.magic_link.cta': 'Sign in',
|
|
332
|
+
'mail.magic_link.fallback': 'If you did not request this, you can ignore this email.',
|
|
280
333
|
'mail.new_login.subject': 'New login to your account',
|
|
281
334
|
'mail.new_login.heading': 'New login detected',
|
|
282
335
|
'mail.new_login.intro': 'We detected a new login to your account.',
|
|
@@ -313,6 +366,10 @@ export const PT_BR_MESSAGES = {
|
|
|
313
366
|
'login.switch_account': 'Trocar de conta',
|
|
314
367
|
'login.password_label': 'Senha',
|
|
315
368
|
'login.submit': 'Entrar',
|
|
369
|
+
// Passwordless (login).
|
|
370
|
+
'login.magic_link_button': 'Me envie um link de login',
|
|
371
|
+
'login.magic_link_sent': 'Se a conta existir, enviamos um link de login.',
|
|
372
|
+
'login.passkey_button': 'Entrar com passkey',
|
|
316
373
|
// Tela de cadastro (signup).
|
|
317
374
|
'signup.page_title': 'Criar conta',
|
|
318
375
|
'signup.title': 'Criar conta',
|
|
@@ -354,6 +411,7 @@ export const PT_BR_MESSAGES = {
|
|
|
354
411
|
'mfa_challenge.recovery_submit': 'Entrar com código de recuperação',
|
|
355
412
|
'mfa_challenge.passkey_button': 'Usar passkey',
|
|
356
413
|
'mfa_challenge.passkey_error': 'Não foi possível autenticar com a passkey. Tente novamente.',
|
|
414
|
+
'mfa_challenge.trust_device': 'Confiar neste dispositivo por {days} dias',
|
|
357
415
|
// Consent (autorização de cliente OIDC).
|
|
358
416
|
'consent.page_title': 'Autorizar',
|
|
359
417
|
'consent.title': 'Autorizar acesso',
|
|
@@ -399,6 +457,34 @@ export const PT_BR_MESSAGES = {
|
|
|
399
457
|
'account.security.change_email_submit': 'Solicitar troca de e-mail',
|
|
400
458
|
'account.security.email_change_requested': 'Enviamos um link de confirmação para {email}. Clique nele para concluir a troca.',
|
|
401
459
|
'account.security.email_changed': 'E-mail alterado com sucesso.',
|
|
460
|
+
// Trusted devices (account/security).
|
|
461
|
+
'account.security.trusted_devices_section': 'Dispositivos confiáveis',
|
|
462
|
+
'account.security.trusted_devices_intro': 'Você pode deixar de confiar neste navegador para que a verificação em duas etapas volte a ser exigida aqui. Para revogar a confiança em todos os dispositivos, refaça o cadastro do seu autenticador.',
|
|
463
|
+
'account.security.trusted_devices_revoke': 'Deixar de confiar neste dispositivo',
|
|
464
|
+
'account.security.trusted_devices_revoked': 'Este dispositivo não é mais confiável. A verificação em duas etapas voltará a ser exigida aqui.',
|
|
465
|
+
// Console de conta — perfil (seção em account/security).
|
|
466
|
+
'account.profile.section': 'Perfil',
|
|
467
|
+
'account.profile.intro': 'Atualize seu nome de exibição e avatar.',
|
|
468
|
+
'account.profile.name_label': 'Nome',
|
|
469
|
+
'account.profile.avatar_label': 'URL do avatar',
|
|
470
|
+
'account.profile.avatar_upload_label': 'Enviar avatar',
|
|
471
|
+
'account.profile.avatar_upload_hint': 'JPG, PNG ou WebP, até 5MB.',
|
|
472
|
+
'account.profile.avatar_invalid_type': 'Tipo de imagem inválido. Use JPG, PNG ou WebP.',
|
|
473
|
+
'account.profile.avatar_too_large': 'A imagem é muito grande.',
|
|
474
|
+
'account.profile.submit': 'Salvar perfil',
|
|
475
|
+
'account.profile.updated': 'Perfil atualizado com sucesso.',
|
|
476
|
+
'account.profile.not_supported': 'A edição de perfil não está disponível nesta instalação.',
|
|
477
|
+
// Console de conta — apps com acesso (account/apps).
|
|
478
|
+
'account.apps.page_title': 'Apps com acesso',
|
|
479
|
+
'account.apps.title': 'Apps com acesso',
|
|
480
|
+
'account.apps.intro': 'Apps que você autorizou a acessar sua conta.',
|
|
481
|
+
'account.apps.logout': 'Sair',
|
|
482
|
+
'account.apps.empty': 'Nenhum app tem acesso à sua conta.',
|
|
483
|
+
'account.apps.tokens': '{accessTokens} access · {refreshTokens} refresh',
|
|
484
|
+
'account.apps.revoke': 'Revogar acesso',
|
|
485
|
+
'account.apps.revoke_confirm': 'Revogar o acesso deste app? Ele precisará ser autorizado novamente e seus tokens deixarão de funcionar.',
|
|
486
|
+
'account.apps.revoked': 'Acesso revogado.',
|
|
487
|
+
'account.apps.not_supported': 'O adapter OIDC configurado não suporta enumeração — a listagem de apps fica indisponível.',
|
|
402
488
|
// Confirmação de troca de e-mail (account/email-confirmed).
|
|
403
489
|
'account.email_confirmed.page_title': 'Confirmação de e-mail',
|
|
404
490
|
'account.email_confirmed.ok_title': 'E-mail alterado',
|
|
@@ -451,6 +537,20 @@ export const PT_BR_MESSAGES = {
|
|
|
451
537
|
'admin.users.roles_placeholder': 'Papéis (separados por vírgula)',
|
|
452
538
|
'admin.users.save_roles': 'Salvar papéis',
|
|
453
539
|
'admin.users.sessions': 'Sessões',
|
|
540
|
+
'admin.users.create_section': 'Criar usuário',
|
|
541
|
+
'admin.users.create_name_placeholder': 'Nome (opcional)',
|
|
542
|
+
'admin.users.create_email_placeholder': 'E-mail',
|
|
543
|
+
'admin.users.create_password_placeholder': 'Senha (deixe em branco para enviar convite)',
|
|
544
|
+
'admin.users.create_submit': 'Criar usuário',
|
|
545
|
+
'admin.users.created': 'Usuário criado.',
|
|
546
|
+
'admin.users.reset_password': 'Enviar redefinição de senha',
|
|
547
|
+
'admin.users.reset_sent': 'E-mail de redefinição de senha enviado.',
|
|
548
|
+
'admin.users.disable': 'Desabilitar',
|
|
549
|
+
'admin.users.enable': 'Reabilitar',
|
|
550
|
+
'admin.users.disabled': 'Conta desabilitada.',
|
|
551
|
+
'admin.users.enabled': 'Conta reabilitada.',
|
|
552
|
+
'admin.users.disabled_badge': 'Desabilitada',
|
|
553
|
+
'admin.users.disable_confirm': 'Desabilitar esta conta? O usuário não conseguirá entrar.',
|
|
454
554
|
// Console admin — sessões/grants ativos de uma conta.
|
|
455
555
|
'admin.sessions.page_title': 'Sessões ativas',
|
|
456
556
|
'admin.sessions.title': 'Sessões ativas',
|
|
@@ -534,6 +634,7 @@ export const PT_BR_MESSAGES = {
|
|
|
534
634
|
// Mensagens de erro/flash produzidas pelos controllers.
|
|
535
635
|
'errors.invalid_credentials': 'Credenciais inválidas',
|
|
536
636
|
'errors.invalid_code': 'Código inválido',
|
|
637
|
+
'errors.account_disabled': 'Esta conta foi desabilitada.',
|
|
537
638
|
'errors.email_taken': 'E-mail já cadastrado',
|
|
538
639
|
'errors.signup_failed': 'Não foi possível criar a conta',
|
|
539
640
|
'errors.invalid_or_expired_token': 'Token inválido ou expirado',
|
|
@@ -556,6 +657,11 @@ export const PT_BR_MESSAGES = {
|
|
|
556
657
|
'mail.verify.cta': 'Verificar e-mail',
|
|
557
658
|
'mail.verify.fallback': 'Se você não criou esta conta, pode ignorar este e-mail.',
|
|
558
659
|
'mail.verify.expires': 'Este link expira em {minutes} minutos.',
|
|
660
|
+
'mail.magic_link.subject': 'Seu link de login',
|
|
661
|
+
'mail.magic_link.heading': 'Entrar na sua conta',
|
|
662
|
+
'mail.magic_link.intro': 'Clique no botão abaixo para entrar. O link expira em breve e pode ser usado uma vez.',
|
|
663
|
+
'mail.magic_link.cta': 'Entrar',
|
|
664
|
+
'mail.magic_link.fallback': 'Se você não solicitou isso, pode ignorar este e-mail.',
|
|
559
665
|
'mail.new_login.subject': 'Novo login na sua conta',
|
|
560
666
|
'mail.new_login.heading': 'Novo login detectado',
|
|
561
667
|
'mail.new_login.intro': 'Detectamos um novo login na sua conta.',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { supportsAccountStatus } from '../accounts/account_store.js';
|
|
1
2
|
import { createAccountLockout } from './account_lockout.js';
|
|
2
3
|
/**
|
|
3
4
|
* Sequência canônica de login por senha + bloqueio progressivo, compartilhada
|
|
@@ -31,6 +32,16 @@ export async function attemptPasswordLogin(cfg, input) {
|
|
|
31
32
|
await lockout.recordFailure(email, { sink: cfg.audit, ip });
|
|
32
33
|
return { ok: false, locked: false };
|
|
33
34
|
}
|
|
35
|
+
// Conta desabilitada: rejeita o login (mesmo com senha correta). A capacidade é
|
|
36
|
+
// opcional — só checada quando o store a implementa. Emite `login.failure` (com
|
|
37
|
+
// motivo `disabled` no metadata) e NÃO registra falha no lockout (não é tentativa
|
|
38
|
+
// de adivinhar senha).
|
|
39
|
+
if (supportsAccountStatus(cfg.accountStore) && (await cfg.accountStore.isDisabled(account.id))) {
|
|
40
|
+
await cfg.audit?.record(input.clientId !== undefined
|
|
41
|
+
? { type: 'login.failure', email, ip, clientId: input.clientId, metadata: { reason: 'disabled' } }
|
|
42
|
+
: { type: 'login.failure', email, ip, metadata: { reason: 'disabled' } });
|
|
43
|
+
return { ok: false, locked: false, disabled: true };
|
|
44
|
+
}
|
|
34
45
|
// Senha correta: limpa o contador de falhas (o lockout protege a etapa de senha).
|
|
35
46
|
await lockout.clearFailures(email);
|
|
36
47
|
return { ok: true, account };
|
|
@@ -53,6 +53,7 @@ const C = {
|
|
|
53
53
|
accountSession: () => import('./controllers/account_session_controller.js'),
|
|
54
54
|
accountTokens: () => import('./controllers/account_tokens_controller.js'),
|
|
55
55
|
accountSecurity: () => import('./controllers/account_security_controller.js'),
|
|
56
|
+
accountApps: () => import('./controllers/account_apps_controller.js'),
|
|
56
57
|
accountMfa: () => import('./controllers/account_mfa_controller.js'),
|
|
57
58
|
adminDashboard: () => import('./controllers/admin/admin_dashboard_controller.js'),
|
|
58
59
|
adminUsers: () => import('./controllers/admin/admin_users_controller.js'),
|
|
@@ -88,6 +89,9 @@ export function registerAuthHost(router, opts) {
|
|
|
88
89
|
// Passkey como 2º fator alternativo no login (begin/finish; challenge na sessão).
|
|
89
90
|
router.post('/auth/interaction/:uid/passkey/options', [C.interaction, 'passkeyOptions']);
|
|
90
91
|
withLogin(router.post('/auth/interaction/:uid/passkey/verify', [C.interaction, 'passkeyVerify']));
|
|
92
|
+
// Magic link (passwordless): POST emite (throttled), GET consome o token do link.
|
|
93
|
+
withLogin(router.post('/auth/interaction/:uid/magic', [C.interaction, 'magicLinkRequest']));
|
|
94
|
+
router.get('/auth/interaction/:uid/magic', [C.interaction, 'magicLinkConsume']);
|
|
91
95
|
router.post('/auth/interaction/:uid/consent', [C.interaction, 'consent']);
|
|
92
96
|
router.get('/auth/interaction/:uid/switch', [C.interaction, 'switchIdentifier']);
|
|
93
97
|
router.get('/auth/interaction/:uid/signup', [C.registration, 'showSignup']);
|
|
@@ -119,10 +123,19 @@ export function registerAuthHost(router, opts) {
|
|
|
119
123
|
router.get('/account/tokens', [C.accountTokens, 'index']);
|
|
120
124
|
router.post('/account/tokens', [C.accountTokens, 'store']);
|
|
121
125
|
router.post('/account/tokens/:id/revoke', [C.accountTokens, 'destroy']);
|
|
122
|
-
// Segurança da conta: trocar senha + solicitar troca de e-mail.
|
|
126
|
+
// Segurança da conta: trocar senha + solicitar troca de e-mail + perfil.
|
|
123
127
|
router.get('/account/security', [C.accountSecurity, 'index']);
|
|
124
128
|
router.post('/account/security/password', [C.accountSecurity, 'changePassword']);
|
|
125
129
|
router.post('/account/security/email', [C.accountSecurity, 'changeEmail']);
|
|
130
|
+
router.post('/account/security/profile', [C.accountSecurity, 'updateProfile']);
|
|
131
|
+
// Trusted devices: limpa o cookie de confiança DESTE navegador.
|
|
132
|
+
router.post('/account/security/trusted-devices/revoke', [
|
|
133
|
+
C.accountSecurity,
|
|
134
|
+
'revokeTrustedDevices',
|
|
135
|
+
]);
|
|
136
|
+
// Apps com acesso (consentimento): lista os grants da conta + revogação por client.
|
|
137
|
+
router.get('/account/apps', [C.accountApps, 'index']);
|
|
138
|
+
router.post('/account/apps/:clientId/revoke', [C.accountApps, 'revoke']);
|
|
126
139
|
// MFA / TOTP (enrollment, confirmação, disable).
|
|
127
140
|
router.get('/account/mfa', [C.accountMfa, 'index']);
|
|
128
141
|
router.post('/account/mfa/enroll', [C.accountMfa, 'enroll']);
|
|
@@ -140,7 +153,11 @@ export function registerAuthHost(router, opts) {
|
|
|
140
153
|
.group(() => {
|
|
141
154
|
router.get('/admin', [C.adminDashboard, 'index']);
|
|
142
155
|
router.get('/admin/users', [C.adminUsers, 'index']);
|
|
156
|
+
router.post('/admin/users', [C.adminUsers, 'store']);
|
|
143
157
|
router.post('/admin/users/:id/roles', [C.adminUsers, 'updateRoles']);
|
|
158
|
+
router.post('/admin/users/:id/reset-password', [C.adminUsers, 'resetPassword']);
|
|
159
|
+
router.post('/admin/users/:id/disable', [C.adminUsers, 'disable']);
|
|
160
|
+
router.post('/admin/users/:id/enable', [C.adminUsers, 'enable']);
|
|
144
161
|
// Sessões/grants ativos da conta + revogação em massa.
|
|
145
162
|
router.get('/admin/users/:id/sessions', [C.adminSessions, 'index']);
|
|
146
163
|
router.post('/admin/users/:id/revoke-sessions', [C.adminSessions, 'revoke']);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Trusted devices" — pular o 2º fator (MFA) neste dispositivo por N dias.
|
|
3
|
+
*
|
|
4
|
+
* Mecanismo SEM novos requisitos de DB: um cookie httpOnly assinado/encriptado
|
|
5
|
+
* com a appKey do host (via `response.encryptedCookie` / `request.encryptedCookie`,
|
|
6
|
+
* que são appKey-backed). O cookie carrega `{ a: accountId, d: deviceId, iat, exp }`.
|
|
7
|
+
*
|
|
8
|
+
* Validação (ver {@link isTrustedDeviceValid}):
|
|
9
|
+
* - `exp` ainda no futuro;
|
|
10
|
+
* - `a` casa com a conta que acabou de passar pela senha;
|
|
11
|
+
* - `iat >= mfaEnabledAt` — re-enrolar o MFA invalida cookies antigos (revogação
|
|
12
|
+
* por re-enrollment, sem estado server-side).
|
|
13
|
+
*
|
|
14
|
+
* Step-up (acr_values pedindo o mfaAcr) SEMPRE ignora o cookie e força o MFA — a
|
|
15
|
+
* decisão fica no controller, antes de checar o cookie.
|
|
16
|
+
*
|
|
17
|
+
* Limitação conhecida (documentada): NÃO há uma lista de revogação por-dispositivo
|
|
18
|
+
* server-side; a revogação disponível é "revogar todos" via re-enrollment do MFA.
|
|
19
|
+
* Uma allowlist/denylist persistida fica como trabalho futuro.
|
|
20
|
+
*/
|
|
21
|
+
/** Nome do cookie de dispositivo confiável. */
|
|
22
|
+
export declare const TRUSTED_DEVICE_COOKIE = "authkit_trusted_device";
|
|
23
|
+
/** Payload guardado (encriptado) no cookie de dispositivo confiável. */
|
|
24
|
+
export interface TrustedDevicePayload {
|
|
25
|
+
/** accountId ao qual a confiança pertence. */
|
|
26
|
+
a: string;
|
|
27
|
+
/** id opaco do dispositivo (para futura revogação por-dispositivo). */
|
|
28
|
+
d: string;
|
|
29
|
+
/** issued-at (epoch ms). */
|
|
30
|
+
iat: number;
|
|
31
|
+
/** expiry (epoch ms). */
|
|
32
|
+
exp: number;
|
|
33
|
+
}
|
|
34
|
+
export interface TrustedDevicesConfigInput {
|
|
35
|
+
/** Liga o mecanismo de trusted devices. Default: true. */
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
/** Validade da confiança em dias. Default: 30. */
|
|
38
|
+
days?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface ResolvedTrustedDevicesConfig {
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
days: number;
|
|
43
|
+
}
|
|
44
|
+
export declare function resolveTrustedDevices(input?: TrustedDevicesConfigInput): ResolvedTrustedDevicesConfig;
|
|
45
|
+
/** Constrói o payload de um novo cookie de confiança para a conta. */
|
|
46
|
+
export declare function buildTrustedDevicePayload(accountId: string, cfg: ResolvedTrustedDevicesConfig, now?: number): TrustedDevicePayload;
|
|
47
|
+
/**
|
|
48
|
+
* `true` se o payload do cookie é uma confiança VÁLIDA para `accountId`:
|
|
49
|
+
* - estrutura íntegra;
|
|
50
|
+
* - pertence à conta certa;
|
|
51
|
+
* - não expirou;
|
|
52
|
+
* - foi emitido em/depois do último (re)enrollment de MFA (`mfaEnabledAt`).
|
|
53
|
+
*
|
|
54
|
+
* `mfaEnabledAt` em epoch ms (ou null quando o store não rastreia — nesse caso a
|
|
55
|
+
* checagem de re-enrollment é pulada, mantendo a validade por expiração apenas).
|
|
56
|
+
*/
|
|
57
|
+
export declare function isTrustedDeviceValid(payload: unknown, opts: {
|
|
58
|
+
accountId: string;
|
|
59
|
+
mfaEnabledAt?: number | null;
|
|
60
|
+
now?: number;
|
|
61
|
+
}): boolean;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* "Trusted devices" — pular o 2º fator (MFA) neste dispositivo por N dias.
|
|
4
|
+
*
|
|
5
|
+
* Mecanismo SEM novos requisitos de DB: um cookie httpOnly assinado/encriptado
|
|
6
|
+
* com a appKey do host (via `response.encryptedCookie` / `request.encryptedCookie`,
|
|
7
|
+
* que são appKey-backed). O cookie carrega `{ a: accountId, d: deviceId, iat, exp }`.
|
|
8
|
+
*
|
|
9
|
+
* Validação (ver {@link isTrustedDeviceValid}):
|
|
10
|
+
* - `exp` ainda no futuro;
|
|
11
|
+
* - `a` casa com a conta que acabou de passar pela senha;
|
|
12
|
+
* - `iat >= mfaEnabledAt` — re-enrolar o MFA invalida cookies antigos (revogação
|
|
13
|
+
* por re-enrollment, sem estado server-side).
|
|
14
|
+
*
|
|
15
|
+
* Step-up (acr_values pedindo o mfaAcr) SEMPRE ignora o cookie e força o MFA — a
|
|
16
|
+
* decisão fica no controller, antes de checar o cookie.
|
|
17
|
+
*
|
|
18
|
+
* Limitação conhecida (documentada): NÃO há uma lista de revogação por-dispositivo
|
|
19
|
+
* server-side; a revogação disponível é "revogar todos" via re-enrollment do MFA.
|
|
20
|
+
* Uma allowlist/denylist persistida fica como trabalho futuro.
|
|
21
|
+
*/
|
|
22
|
+
/** Nome do cookie de dispositivo confiável. */
|
|
23
|
+
export const TRUSTED_DEVICE_COOKIE = 'authkit_trusted_device';
|
|
24
|
+
export function resolveTrustedDevices(input) {
|
|
25
|
+
return {
|
|
26
|
+
enabled: input?.enabled ?? true,
|
|
27
|
+
days: input?.days && input.days > 0 ? input.days : 30,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Constrói o payload de um novo cookie de confiança para a conta. */
|
|
31
|
+
export function buildTrustedDevicePayload(accountId, cfg, now = Date.now()) {
|
|
32
|
+
return {
|
|
33
|
+
a: accountId,
|
|
34
|
+
d: randomBytes(16).toString('hex'),
|
|
35
|
+
iat: now,
|
|
36
|
+
exp: now + cfg.days * 24 * 60 * 60 * 1000,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* `true` se o payload do cookie é uma confiança VÁLIDA para `accountId`:
|
|
41
|
+
* - estrutura íntegra;
|
|
42
|
+
* - pertence à conta certa;
|
|
43
|
+
* - não expirou;
|
|
44
|
+
* - foi emitido em/depois do último (re)enrollment de MFA (`mfaEnabledAt`).
|
|
45
|
+
*
|
|
46
|
+
* `mfaEnabledAt` em epoch ms (ou null quando o store não rastreia — nesse caso a
|
|
47
|
+
* checagem de re-enrollment é pulada, mantendo a validade por expiração apenas).
|
|
48
|
+
*/
|
|
49
|
+
export function isTrustedDeviceValid(payload, opts) {
|
|
50
|
+
const now = opts.now ?? Date.now();
|
|
51
|
+
if (!payload || typeof payload !== 'object')
|
|
52
|
+
return false;
|
|
53
|
+
const p = payload;
|
|
54
|
+
if (typeof p.a !== 'string' || typeof p.iat !== 'number' || typeof p.exp !== 'number') {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (p.a !== opts.accountId)
|
|
58
|
+
return false;
|
|
59
|
+
if (p.exp <= now)
|
|
60
|
+
return false;
|
|
61
|
+
// Re-enrollment do MFA revoga cookies emitidos antes dele.
|
|
62
|
+
if (typeof opts.mfaEnabledAt === 'number' && p.iat < opts.mfaEnabledAt)
|
|
63
|
+
return false;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
@@ -69,3 +69,38 @@ export declare const changeEmailValidator: import("@vinejs/vine").VineValidator<
|
|
|
69
69
|
newEmail: string;
|
|
70
70
|
currentPassword: string;
|
|
71
71
|
}>, Record<string, any> | undefined>;
|
|
72
|
+
/**
|
|
73
|
+
* Edição de perfil no console de conta: nome e avatarUrl, ambos opcionais.
|
|
74
|
+
* Campos vazios são normalizados para string vazia (limpa o valor).
|
|
75
|
+
*/
|
|
76
|
+
export declare const updateProfileValidator: import("@vinejs/vine").VineValidator<import("@vinejs/vine").VineObject<{
|
|
77
|
+
name: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine").VineString>;
|
|
78
|
+
avatarUrl: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine").VineString>;
|
|
79
|
+
}, {
|
|
80
|
+
name?: string | null | undefined;
|
|
81
|
+
avatarUrl?: string | null | undefined;
|
|
82
|
+
}, {
|
|
83
|
+
name?: string | undefined;
|
|
84
|
+
avatarUrl?: string | undefined;
|
|
85
|
+
}, {
|
|
86
|
+
name?: string | undefined;
|
|
87
|
+
avatarUrl?: string | undefined;
|
|
88
|
+
}>, Record<string, any> | undefined>;
|
|
89
|
+
/** Criação de usuário no console admin (email obrigatório; nome/senha opcionais). */
|
|
90
|
+
export declare const adminCreateUserValidator: import("@vinejs/vine").VineValidator<import("@vinejs/vine").VineObject<{
|
|
91
|
+
email: import("@vinejs/vine").VineString;
|
|
92
|
+
name: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine").VineString>;
|
|
93
|
+
password: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine").VineString>;
|
|
94
|
+
}, {
|
|
95
|
+
password?: string | null | undefined;
|
|
96
|
+
name?: string | null | undefined;
|
|
97
|
+
email: string;
|
|
98
|
+
}, {
|
|
99
|
+
password?: string | undefined;
|
|
100
|
+
name?: string | undefined;
|
|
101
|
+
email: string;
|
|
102
|
+
}, {
|
|
103
|
+
password?: string | undefined;
|
|
104
|
+
name?: string | undefined;
|
|
105
|
+
email: string;
|
|
106
|
+
}>, Record<string, any> | undefined>;
|
|
@@ -25,3 +25,17 @@ export const changeEmailValidator = vine.compile(vine.object({
|
|
|
25
25
|
currentPassword: vine.string().minLength(1),
|
|
26
26
|
newEmail: vine.string().trim().email().normalizeEmail(),
|
|
27
27
|
}));
|
|
28
|
+
/**
|
|
29
|
+
* Edição de perfil no console de conta: nome e avatarUrl, ambos opcionais.
|
|
30
|
+
* Campos vazios são normalizados para string vazia (limpa o valor).
|
|
31
|
+
*/
|
|
32
|
+
export const updateProfileValidator = vine.compile(vine.object({
|
|
33
|
+
name: vine.string().trim().maxLength(255).optional(),
|
|
34
|
+
avatarUrl: vine.string().trim().url().maxLength(2048).optional(),
|
|
35
|
+
}));
|
|
36
|
+
/** Criação de usuário no console admin (email obrigatório; nome/senha opcionais). */
|
|
37
|
+
export const adminCreateUserValidator = vine.compile(vine.object({
|
|
38
|
+
email: vine.string().trim().email().normalizeEmail(),
|
|
39
|
+
name: vine.string().trim().maxLength(255).optional(),
|
|
40
|
+
password: vine.string().minLength(8).maxLength(255).optional(),
|
|
41
|
+
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/adonis-authkit-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@adonisjs/ally": "6.3.0",
|
|
56
56
|
"@adonisjs/core": "7.3.3",
|
|
57
|
+
"@adonisjs/drive": "4.0.0",
|
|
57
58
|
"@adonisjs/lucid": "22.4.2",
|
|
58
59
|
"@adonisjs/session": "8.1.0",
|
|
59
60
|
"@adonisjs/shield": "9.0.0",
|
|
@@ -65,6 +66,9 @@
|
|
|
65
66
|
},
|
|
66
67
|
"@adonisjs/ally": {
|
|
67
68
|
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@adonisjs/drive": {
|
|
71
|
+
"optional": true
|
|
68
72
|
}
|
|
69
73
|
},
|
|
70
74
|
"dependencies": {
|
|
@@ -81,6 +85,7 @@
|
|
|
81
85
|
"devDependencies": {
|
|
82
86
|
"@adonisjs/ally": "6.3.0",
|
|
83
87
|
"@adonisjs/core": "7.3.3",
|
|
88
|
+
"@adonisjs/drive": "4.0.0",
|
|
84
89
|
"@adonisjs/lucid": "22.4.2",
|
|
85
90
|
"@adonisjs/session": "8.1.0",
|
|
86
91
|
"@adonisjs/shield": "9.0.0",
|