@dudousxd/adonis-authkit-server 0.4.0 → 0.6.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.
Files changed (50) hide show
  1. package/README.md +23 -2
  2. package/build/host/views/account/apps.edge +58 -0
  3. package/build/host/views/account/security.edge +53 -0
  4. package/build/host/views/account/tokens.edge +1 -0
  5. package/build/host/views/admin/users.edge +62 -2
  6. package/build/host/views/login.edge +55 -0
  7. package/build/host/views/mfa-challenge.edge +12 -0
  8. package/build/index.d.ts +9 -3
  9. package/build/index.js +5 -2
  10. package/build/src/accounts/account_store.d.ts +80 -2
  11. package/build/src/accounts/account_store.js +12 -0
  12. package/build/src/accounts/lucid_account_store.js +8 -0
  13. package/build/src/accounts/lucid_store/core.d.ts +2 -2
  14. package/build/src/accounts/lucid_store/core.js +33 -0
  15. package/build/src/accounts/lucid_store/mfa.js +4 -1
  16. package/build/src/accounts/lucid_store/status_profile.d.ts +21 -0
  17. package/build/src/accounts/lucid_store/status_profile.js +66 -0
  18. package/build/src/audit/audit_sink.d.ts +1 -1
  19. package/build/src/define_config.d.ts +53 -0
  20. package/build/src/define_config.js +14 -1
  21. package/build/src/doctor/checks.js +32 -32
  22. package/build/src/events/dispatcher.d.ts +45 -0
  23. package/build/src/events/dispatcher.js +92 -0
  24. package/build/src/host/admin_sessions_service.d.ts +8 -0
  25. package/build/src/host/admin_sessions_service.js +19 -0
  26. package/build/src/host/controllers/account_apps_controller.d.ts +15 -0
  27. package/build/src/host/controllers/account_apps_controller.js +61 -0
  28. package/build/src/host/controllers/account_mfa_controller.js +6 -2
  29. package/build/src/host/controllers/account_security_controller.d.ts +9 -0
  30. package/build/src/host/controllers/account_security_controller.js +52 -2
  31. package/build/src/host/controllers/account_session_controller.js +3 -1
  32. package/build/src/host/controllers/admin/admin_users_controller.d.ts +13 -0
  33. package/build/src/host/controllers/admin/admin_users_controller.js +133 -0
  34. package/build/src/host/controllers/interaction_controller.d.ts +32 -0
  35. package/build/src/host/controllers/interaction_controller.js +175 -8
  36. package/build/src/host/default_mailer.d.ts +8 -0
  37. package/build/src/host/default_mailer.js +81 -19
  38. package/build/src/host/email_templates.d.ts +4 -0
  39. package/build/src/host/email_templates.js +5 -2
  40. package/build/src/host/i18n.d.ts +395 -11
  41. package/build/src/host/i18n.js +433 -12
  42. package/build/src/host/login_attempt.d.ts +1 -0
  43. package/build/src/host/login_attempt.js +11 -0
  44. package/build/src/host/register_auth_host.js +18 -1
  45. package/build/src/host/trusted_device.d.ts +61 -0
  46. package/build/src/host/trusted_device.js +65 -0
  47. package/build/src/host/validators.d.ts +35 -0
  48. package/build/src/host/validators.js +14 -0
  49. package/build/src/observability/metrics_controller.js +4 -4
  50. package/package.json +1 -1
@@ -3,17 +3,348 @@
3
3
  *
4
4
  * Todas as strings visíveis ao usuário das views Edge (e as mensagens de
5
5
  * flash/erro produzidas pelos controllers) vivem num catálogo achatado de
6
- * chaves pontilhadas. O default embutido é pt-BR — os apps continuam
7
- * funcionando SEM nenhuma configuração. O host pode sobrescrever chaves
8
- * pontuais ou fornecer locales inteiros (ex.: `en`) via `I18nConfig`.
6
+ * chaves pontilhadas. O default embutido é inglês (`en`) — os apps continuam
7
+ * funcionando SEM nenhuma configuração. O pt-BR é um locale embutido: basta
8
+ * `i18n: { locale: 'pt-BR' }`. O host também pode sobrescrever chaves pontuais
9
+ * ou fornecer locales inteiros (ex.: `fr`) via `I18nConfig`.
9
10
  */
10
11
  /** Locale default do host-kit. */
11
- export const DEFAULT_LOCALE = 'pt-BR';
12
+ export const DEFAULT_LOCALE = 'en';
12
13
  /**
13
- * Catálogo default (pt-BR) — cobre TODAS as strings visíveis ao usuário das
14
+ * Catálogo default (inglês) — cobre TODAS as strings visíveis ao usuário das
14
15
  * views e as mensagens de flash/erro dos controllers. Chaves agrupadas por tela.
15
16
  */
16
17
  export const DEFAULT_MESSAGES = {
18
+ // Comum / fallback de marca.
19
+ 'common.app_fallback': 'Auth',
20
+ 'common.brand_eyebrow': 'Auth',
21
+ // Tela de login (interaction OIDC: identifier + password).
22
+ 'login.page_title': 'Login',
23
+ 'login.title': 'Login',
24
+ 'login.identifier_intro': 'Enter your email to continue.',
25
+ 'login.email_label': 'Email',
26
+ 'login.identifier_submit': 'Continue',
27
+ 'login.create_account': 'Create account',
28
+ 'login.forgot_password': 'Forgot password',
29
+ 'login.divider_or': 'or',
30
+ 'login.google': 'Sign in with Google',
31
+ 'login.greeting': 'Hi, {name}',
32
+ 'login.switch_account': 'Switch account',
33
+ 'login.password_label': 'Password',
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',
39
+ // Tela de cadastro (signup).
40
+ 'signup.page_title': 'Create account',
41
+ 'signup.title': 'Create account',
42
+ 'signup.intro': 'Fill in your details to get started.',
43
+ 'signup.name_label': 'Name',
44
+ 'signup.email_label': 'Email',
45
+ 'signup.password_label': 'Password',
46
+ 'signup.submit': 'Create account',
47
+ 'signup.have_account': 'I already have an account',
48
+ // Recuperação de senha (forgot).
49
+ 'forgot.page_title': 'Reset password',
50
+ 'forgot.sent_title': 'Email sent',
51
+ 'forgot.sent_body': 'If the email exists, we will send reset instructions.',
52
+ 'forgot.title': 'Reset password',
53
+ 'forgot.intro': 'We will send you a link to reset your password.',
54
+ 'forgot.email_label': 'Email',
55
+ 'forgot.submit': 'Send link',
56
+ // Redefinição de senha (reset).
57
+ 'reset.page_title': 'Reset password',
58
+ 'reset.done_title': 'Password reset',
59
+ 'reset.done_body': 'You can now log in with your new password.',
60
+ 'reset.title': 'New password',
61
+ 'reset.intro': 'Choose a new password for your account.',
62
+ 'reset.password_label': 'Password',
63
+ 'reset.submit': 'Reset',
64
+ // Verificação de e-mail (verify-email).
65
+ 'verify_email.page_title': 'Verify email',
66
+ 'verify_email.verified_title': 'Email verified',
67
+ 'verify_email.verified_body': 'Your email was confirmed successfully.',
68
+ 'verify_email.invalid_title': 'Invalid link',
69
+ 'verify_email.invalid_body': 'The verification link is invalid or has already been used.',
70
+ // Desafio de MFA no fluxo de login (mfa-challenge).
71
+ 'mfa_challenge.page_title': 'Two-factor verification',
72
+ 'mfa_challenge.title': 'Two-factor verification',
73
+ 'mfa_challenge.intro': 'Open your authenticator app and enter the 6-digit code.',
74
+ 'mfa_challenge.code_label': 'Code',
75
+ 'mfa_challenge.submit': 'Verify',
76
+ 'mfa_challenge.recovery_summary': 'Use a recovery code',
77
+ 'mfa_challenge.recovery_submit': 'Log in with a recovery code',
78
+ 'mfa_challenge.passkey_button': 'Use passkey',
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',
81
+ // Consent (autorização de cliente OIDC).
82
+ 'consent.page_title': 'Authorize',
83
+ 'consent.title': 'Authorize access',
84
+ // `{app}` é interpolado com o nome do app já envolto em <strong> (renderizado
85
+ // raw na view). O nome vem do branding (config-trusted).
86
+ 'consent.body': 'The app <strong>{app}</strong> wants to access your account.',
87
+ 'consent.submit': 'Authorize',
88
+ // Console de conta — login (account/login).
89
+ 'account.login.page_title': 'My account',
90
+ 'account.login.title': 'My account',
91
+ 'account.login.intro': 'Manage your access tokens.',
92
+ 'account.login.email_label': 'Email',
93
+ 'account.login.password_label': 'Password',
94
+ 'account.login.submit': 'Log in',
95
+ // Console de conta — tokens (account/tokens).
96
+ 'account.tokens.page_title': 'Access tokens',
97
+ 'account.tokens.title': 'Access tokens',
98
+ 'account.tokens.logout': 'Log out',
99
+ 'account.tokens.security': 'Security',
100
+ 'account.tokens.created_notice': 'Token created — copy it now, it will not be shown again:',
101
+ 'account.tokens.name_placeholder': 'Token name (e.g. CI deploy)',
102
+ 'account.tokens.create': 'Create',
103
+ 'account.tokens.empty': 'No tokens yet.',
104
+ 'account.tokens.created_at': 'Created on {date}',
105
+ 'account.tokens.last_used': '· last used {date}',
106
+ 'account.tokens.never_used': '· never used',
107
+ 'account.tokens.scopes': 'Scopes: {scopes}',
108
+ 'account.tokens.audience': 'Audience: {audience}',
109
+ 'account.tokens.revoke': 'Revoke',
110
+ // Console de conta — segurança (account/security): senha + e-mail.
111
+ 'account.security.page_title': 'Account security',
112
+ 'account.security.title': 'Account security',
113
+ 'account.security.logout': 'Log out',
114
+ 'account.security.current_email': 'Current email: {email}',
115
+ 'account.security.not_supported': 'Changing password and email is not available in this installation.',
116
+ 'account.security.password_section': 'Change password',
117
+ 'account.security.current_password_label': 'Current password',
118
+ 'account.security.new_password_label': 'New password',
119
+ 'account.security.change_password_submit': 'Change password',
120
+ 'account.security.password_changed': 'Password changed successfully.',
121
+ 'account.security.email_section': 'Change email',
122
+ 'account.security.email_intro': 'We will send a confirmation link to the new address. The change only takes effect after confirmation.',
123
+ 'account.security.new_email_label': 'New email',
124
+ 'account.security.email_password_label': 'Current password',
125
+ 'account.security.change_email_submit': 'Request email change',
126
+ 'account.security.email_change_requested': 'We sent a confirmation link to {email}. Click it to complete the change.',
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.submit': 'Save profile',
139
+ 'account.profile.updated': 'Profile updated successfully.',
140
+ 'account.profile.not_supported': 'Profile editing is not available in this installation.',
141
+ // Console de conta — apps com acesso (account/apps).
142
+ 'account.apps.page_title': 'Apps with access',
143
+ 'account.apps.title': 'Apps with access',
144
+ 'account.apps.intro': 'Apps you have authorized to access your account.',
145
+ 'account.apps.logout': 'Log out',
146
+ 'account.apps.empty': 'No apps have access to your account.',
147
+ 'account.apps.tokens': '{accessTokens} access · {refreshTokens} refresh',
148
+ 'account.apps.revoke': 'Revoke access',
149
+ 'account.apps.revoke_confirm': 'Revoke this app’s access? It will need to be authorized again and its tokens will stop working.',
150
+ 'account.apps.revoked': 'Access revoked.',
151
+ 'account.apps.not_supported': 'The configured OIDC adapter does not support enumeration — listing apps is unavailable.',
152
+ // Confirmação de troca de e-mail (account/email-confirmed).
153
+ 'account.email_confirmed.page_title': 'Email confirmation',
154
+ 'account.email_confirmed.ok_title': 'Email changed',
155
+ 'account.email_confirmed.ok_body': 'Your new email has been confirmed and is now active.',
156
+ 'account.email_confirmed.invalid_title': 'Invalid link',
157
+ 'account.email_confirmed.invalid_body': 'The confirmation link is invalid or has already been used.',
158
+ // Console de conta — MFA (account/mfa).
159
+ 'account.mfa.page_title': 'Two-factor verification',
160
+ 'account.mfa.title': 'Two-factor verification',
161
+ 'account.mfa.logout': 'Log out',
162
+ 'account.mfa.recovery_codes_notice': 'Save your recovery codes — they will not be shown again:',
163
+ 'account.mfa.enroll_intro': 'Scan the QR code with your authenticator app (Google Authenticator, 1Password, etc.).',
164
+ 'account.mfa.qr_alt': 'TOTP QR code',
165
+ 'account.mfa.manual_intro': 'Or enter it manually:',
166
+ 'account.mfa.confirm_code_label': 'Confirmation code',
167
+ 'account.mfa.activate': 'Enable two-factor verification',
168
+ 'account.mfa.enabled_html': 'Two-factor verification is <span class="font-semibold text-emerald-700">enabled</span> on this account.',
169
+ 'account.mfa.disable': 'Disable',
170
+ 'account.mfa.disabled_intro': 'Two-factor verification is disabled. Enable it to protect your account with an authenticator app.',
171
+ 'account.mfa.enable': 'Enable two-factor verification',
172
+ // Console de conta — passkeys (WebAuthn) na tela de MFA.
173
+ 'mfa.passkey.section_title': 'Passkeys',
174
+ 'mfa.passkey.section_intro': 'Use a passkey (biometrics, device PIN, or security key) as a second factor, without typing codes.',
175
+ 'mfa.passkey.add': 'Add passkey',
176
+ 'mfa.passkey.remove': 'Remove',
177
+ 'mfa.passkey.empty': 'No passkeys registered.',
178
+ 'mfa.passkey.unnamed': 'Passkey',
179
+ 'mfa.passkey.created_at': 'Created on {date}',
180
+ 'mfa.passkey.register_error': 'Could not register the passkey. Please try again.',
181
+ 'mfa.passkey.unsupported': 'Your browser does not support passkeys.',
182
+ // Console admin (B6) — navegação compartilhada.
183
+ 'admin.nav.dashboard': 'Dashboard',
184
+ 'admin.nav.users': 'Users',
185
+ 'admin.nav.clients': 'Clients',
186
+ 'admin.nav.audit': 'Audit',
187
+ 'admin.nav.logout': 'Log out',
188
+ // Console admin — dashboard.
189
+ 'admin.dashboard.page_title': 'Admin dashboard',
190
+ 'admin.dashboard.title': 'Admin dashboard',
191
+ 'admin.dashboard.users_count': 'Users',
192
+ 'admin.dashboard.clients_count': 'Clients',
193
+ 'admin.dashboard.audit_count': 'Audit events',
194
+ 'admin.dashboard.recent_title': 'Recent events',
195
+ // Console admin — usuários.
196
+ 'admin.users.page_title': 'Users',
197
+ 'admin.users.title': 'Users',
198
+ 'admin.users.search_placeholder': 'Search by email',
199
+ 'admin.users.search': 'Search',
200
+ 'admin.users.empty': 'No users found.',
201
+ 'admin.users.roles_placeholder': 'Roles (comma-separated)',
202
+ 'admin.users.save_roles': 'Save roles',
203
+ 'admin.users.sessions': 'Sessions',
204
+ 'admin.users.create_section': 'Create user',
205
+ 'admin.users.create_name_placeholder': 'Name (optional)',
206
+ 'admin.users.create_email_placeholder': 'Email',
207
+ 'admin.users.create_password_placeholder': 'Password (leave blank to send invite)',
208
+ 'admin.users.create_submit': 'Create user',
209
+ 'admin.users.created': 'User created.',
210
+ 'admin.users.reset_password': 'Send password reset',
211
+ 'admin.users.reset_sent': 'Password reset email sent.',
212
+ 'admin.users.disable': 'Disable',
213
+ 'admin.users.enable': 'Enable',
214
+ 'admin.users.disabled': 'Account disabled.',
215
+ 'admin.users.enabled': 'Account enabled.',
216
+ 'admin.users.disabled_badge': 'Disabled',
217
+ 'admin.users.disable_confirm': 'Disable this account? The user will not be able to log in.',
218
+ // Console admin — sessões/grants ativos de uma conta.
219
+ 'admin.sessions.page_title': 'Active sessions',
220
+ 'admin.sessions.title': 'Active sessions',
221
+ 'admin.sessions.account': 'Account: {email}',
222
+ 'admin.sessions.back': 'Back to users',
223
+ 'admin.sessions.not_supported': 'The configured OIDC adapter does not support enumeration — session inspection is unavailable.',
224
+ 'admin.sessions.revoked_notice': 'Revoked: {sessions} session(s), {grants} grant(s), {accessTokens} access token(s), {refreshTokens} refresh token(s).',
225
+ 'admin.sessions.sessions_section': 'Sessions (IdP login)',
226
+ 'admin.sessions.sessions_empty': 'No active sessions.',
227
+ 'admin.sessions.session_login_ts': 'Login: {date}',
228
+ 'admin.sessions.session_amr': 'Methods: {amr}',
229
+ 'admin.sessions.grants_section': 'Grants (per-client authorizations)',
230
+ 'admin.sessions.grants_empty': 'No active grants.',
231
+ 'admin.sessions.grant_client': 'Client: {clientId}',
232
+ 'admin.sessions.grant_tokens': '{accessTokens} access · {refreshTokens} refresh',
233
+ 'admin.sessions.revoke_all': 'Revoke all sessions and grants',
234
+ 'admin.sessions.revoke_confirm': 'Revoke all sessions and grants for this account? The user will need to log in again and issued tokens will stop working.',
235
+ // Console admin — clients.
236
+ 'admin.clients.page_title': 'OAuth clients',
237
+ 'admin.clients.title': 'OAuth clients',
238
+ 'admin.clients.empty': 'No clients configured.',
239
+ 'admin.clients.confidential': 'Confidential',
240
+ 'admin.clients.public': 'Public',
241
+ 'admin.clients.grants': 'Grants: {grants}',
242
+ 'admin.clients.redirect_uris': 'Redirects: {uris}',
243
+ 'admin.clients.dynamic_notice': 'Dynamic client registration (RFC 7591) is on — clients registered via /reg are persisted in the adapter and appear in the dynamic section below.',
244
+ 'admin.clients.static_section': 'Static clients (config)',
245
+ 'admin.clients.dynamic_section': 'Dynamic clients (adapter)',
246
+ 'admin.clients.dynamic_empty': 'No dynamic clients persisted.',
247
+ 'admin.clients.dynamic_not_supported': 'The configured OIDC adapter does not support client enumeration — dynamic management is unavailable.',
248
+ 'admin.clients.new': 'New client',
249
+ 'admin.clients.new_title': 'New OIDC client',
250
+ 'admin.clients.edit_title': 'Edit OIDC client',
251
+ 'admin.clients.edit': 'Edit',
252
+ 'admin.clients.delete': 'Delete',
253
+ 'admin.clients.delete_confirm': 'Delete this client? This action cannot be undone.',
254
+ 'admin.clients.regenerate_secret': 'Regenerate secret',
255
+ 'admin.clients.regenerate_confirm': 'Regenerate the secret? The current secret will stop working immediately.',
256
+ 'admin.clients.back': 'Back',
257
+ 'admin.clients.cancel': 'Cancel',
258
+ 'admin.clients.save': 'Save',
259
+ 'admin.clients.create': 'Create client',
260
+ 'admin.clients.secret_once_title': 'Save the client_secret now',
261
+ 'admin.clients.secret_once_notice': 'This is the only time the secret is shown. Copy it now — it cannot be retrieved later.',
262
+ 'admin.clients.field_client_id': 'Client ID',
263
+ 'admin.clients.field_client_id_placeholder': 'leave blank to generate automatically',
264
+ 'admin.clients.field_client_id_help': 'Optional. If empty, a random identifier will be generated.',
265
+ 'admin.clients.field_redirect_uris': 'Redirect URIs',
266
+ 'admin.clients.field_redirect_uris_help': 'One URI per line.',
267
+ 'admin.clients.field_post_logout_uris': 'Post-logout redirect URIs',
268
+ 'admin.clients.field_post_logout_uris_help': 'One URI per line (optional).',
269
+ 'admin.clients.field_grant_types': 'Grant types',
270
+ 'admin.clients.field_auth_method': 'Token endpoint auth method',
271
+ // Console admin — auditoria.
272
+ 'admin.audit.page_title': 'Audit',
273
+ 'admin.audit.title': 'Audit log',
274
+ 'admin.audit.type_placeholder': 'Filter by type',
275
+ 'admin.audit.subject_placeholder': 'Filter by subject (accountId)',
276
+ 'admin.audit.filter': 'Filter',
277
+ 'admin.audit.empty': 'No events found.',
278
+ 'admin.audit.not_supported': 'The configured audit sink does not support querying.',
279
+ // Console admin — paginação compartilhada.
280
+ 'admin.pagination.page': 'Page {page} of {total}',
281
+ 'admin.pagination.prev': 'Previous',
282
+ 'admin.pagination.next': 'Next',
283
+ // Device Authorization Grant (RFC 8628) — telas servidas pelo oidc-provider.
284
+ 'device.input.title': 'Sign in to the device',
285
+ 'device.input.intro': 'Enter the code shown on your device.',
286
+ 'device.input.submit': 'Continue',
287
+ 'device.input.error_invalid': 'The code you entered is incorrect. Please try again.',
288
+ 'device.input.error_aborted': 'The login request was aborted.',
289
+ 'device.input.error_generic': 'An error occurred while processing your request.',
290
+ 'device.confirm.title': 'Confirm device',
291
+ 'device.confirm.body': 'The code below should be displayed on your device. Confirm only if you recognize it.',
292
+ 'device.confirm.submit': 'Continue',
293
+ 'device.confirm.abort': 'Cancel',
294
+ 'device.success.title': 'Login complete',
295
+ 'device.success.body': 'You are signed in. You can return to your device.',
296
+ // Step-up auth (acr_values): cliente exige MFA mas a conta não tem MFA enrolado.
297
+ 'mfa_challenge.required_no_enrollment': 'This client requires two-factor verification. Set up MFA in your account console to continue.',
298
+ // Mensagens de erro/flash produzidas pelos controllers.
299
+ 'errors.invalid_credentials': 'Invalid credentials',
300
+ 'errors.invalid_code': 'Invalid code',
301
+ 'errors.account_disabled': 'This account has been disabled.',
302
+ 'errors.email_taken': 'Email already registered',
303
+ 'errors.signup_failed': 'Could not create the account',
304
+ 'errors.invalid_or_expired_token': 'Invalid or expired token',
305
+ 'errors.account_locked': 'Account temporarily locked due to too many attempts. Try again in {seconds}s.',
306
+ 'errors.session_expired': 'Session expired',
307
+ 'errors.challenge_expired': 'Challenge expired',
308
+ 'errors.passkeys_unavailable': 'Passkeys unavailable',
309
+ 'errors.no_passkey_registered': 'No passkey registered',
310
+ // Assuntos/corpos de e-mail transacional (default_mailer).
311
+ 'mail.common.link_fallback': "If the button does not work, copy and paste this link into your browser:",
312
+ 'mail.reset.subject': 'Reset your password',
313
+ 'mail.reset.heading': 'Reset your password',
314
+ 'mail.reset.intro': 'We received a request to reset the password for your account.',
315
+ 'mail.reset.cta': 'Reset password',
316
+ 'mail.reset.fallback': 'If you did not request this, you can ignore this email.',
317
+ 'mail.reset.expires': 'This link expires in {minutes} minutes.',
318
+ 'mail.verify.subject': 'Verify your email',
319
+ 'mail.verify.heading': 'Verify your email',
320
+ 'mail.verify.intro': 'Confirm your email address to finish setting up your account.',
321
+ 'mail.verify.cta': 'Verify email',
322
+ 'mail.verify.fallback': 'If you did not create this account, you can ignore this email.',
323
+ 'mail.verify.expires': 'This link expires in {minutes} minutes.',
324
+ 'mail.magic_link.subject': 'Your login link',
325
+ 'mail.magic_link.heading': 'Sign in to your account',
326
+ 'mail.magic_link.intro': 'Click the button below to sign in. The link expires shortly and can be used once.',
327
+ 'mail.magic_link.cta': 'Sign in',
328
+ 'mail.magic_link.fallback': 'If you did not request this, you can ignore this email.',
329
+ 'mail.new_login.subject': 'New login to your account',
330
+ 'mail.new_login.heading': 'New login detected',
331
+ 'mail.new_login.intro': 'We detected a new login to your account.',
332
+ 'mail.new_login.when': 'When: {date}',
333
+ 'mail.new_login.ip': 'IP address: {ip}',
334
+ 'mail.new_login.device': 'Device: {device}',
335
+ 'mail.new_login.fallback': 'If this was you, no action is needed. If not, reset your password right away.',
336
+ 'mail.email_change.subject': 'Confirm your new email',
337
+ 'mail.email_change.heading': 'Confirm your new email',
338
+ 'mail.email_change.intro': 'We received a request to change the email address on your account. Confirm the new address below.',
339
+ 'mail.email_change.cta': 'Confirm new email',
340
+ 'mail.email_change.fallback': 'If you did not request this, you can ignore this email.',
341
+ 'mail.email_change.expires': 'This link expires in {minutes} minutes.',
342
+ };
343
+ /**
344
+ * Catálogo embutido pt-BR. Espelha TODAS as chaves do default `en`. Ativado
345
+ * com `i18n: { locale: 'pt-BR' }` sem nenhuma config extra de mensagens.
346
+ */
347
+ export const PT_BR_MESSAGES = {
17
348
  // Comum / fallback de marca.
18
349
  'common.app_fallback': 'Auth',
19
350
  'common.brand_eyebrow': 'Auth',
@@ -31,6 +362,10 @@ export const DEFAULT_MESSAGES = {
31
362
  'login.switch_account': 'Trocar de conta',
32
363
  'login.password_label': 'Senha',
33
364
  'login.submit': 'Entrar',
365
+ // Passwordless (login).
366
+ 'login.magic_link_button': 'Me envie um link de login',
367
+ 'login.magic_link_sent': 'Se a conta existir, enviamos um link de login.',
368
+ 'login.passkey_button': 'Entrar com passkey',
34
369
  // Tela de cadastro (signup).
35
370
  'signup.page_title': 'Criar conta',
36
371
  'signup.title': 'Criar conta',
@@ -72,11 +407,10 @@ export const DEFAULT_MESSAGES = {
72
407
  'mfa_challenge.recovery_submit': 'Entrar com código de recuperação',
73
408
  'mfa_challenge.passkey_button': 'Usar passkey',
74
409
  'mfa_challenge.passkey_error': 'Não foi possível autenticar com a passkey. Tente novamente.',
410
+ 'mfa_challenge.trust_device': 'Confiar neste dispositivo por {days} dias',
75
411
  // Consent (autorização de cliente OIDC).
76
412
  'consent.page_title': 'Autorizar',
77
413
  'consent.title': 'Autorizar acesso',
78
- // `{app}` é interpolado com o nome do app já envolto em <strong> (renderizado
79
- // raw na view). O nome vem do branding (config-trusted).
80
414
  'consent.body': 'O app <strong>{app}</strong> quer acessar sua conta.',
81
415
  'consent.submit': 'Autorizar',
82
416
  // Console de conta — login (account/login).
@@ -119,6 +453,30 @@ export const DEFAULT_MESSAGES = {
119
453
  'account.security.change_email_submit': 'Solicitar troca de e-mail',
120
454
  'account.security.email_change_requested': 'Enviamos um link de confirmação para {email}. Clique nele para concluir a troca.',
121
455
  'account.security.email_changed': 'E-mail alterado com sucesso.',
456
+ // Trusted devices (account/security).
457
+ 'account.security.trusted_devices_section': 'Dispositivos confiáveis',
458
+ '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.',
459
+ 'account.security.trusted_devices_revoke': 'Deixar de confiar neste dispositivo',
460
+ 'account.security.trusted_devices_revoked': 'Este dispositivo não é mais confiável. A verificação em duas etapas voltará a ser exigida aqui.',
461
+ // Console de conta — perfil (seção em account/security).
462
+ 'account.profile.section': 'Perfil',
463
+ 'account.profile.intro': 'Atualize seu nome de exibição e avatar.',
464
+ 'account.profile.name_label': 'Nome',
465
+ 'account.profile.avatar_label': 'URL do avatar',
466
+ 'account.profile.submit': 'Salvar perfil',
467
+ 'account.profile.updated': 'Perfil atualizado com sucesso.',
468
+ 'account.profile.not_supported': 'A edição de perfil não está disponível nesta instalação.',
469
+ // Console de conta — apps com acesso (account/apps).
470
+ 'account.apps.page_title': 'Apps com acesso',
471
+ 'account.apps.title': 'Apps com acesso',
472
+ 'account.apps.intro': 'Apps que você autorizou a acessar sua conta.',
473
+ 'account.apps.logout': 'Sair',
474
+ 'account.apps.empty': 'Nenhum app tem acesso à sua conta.',
475
+ 'account.apps.tokens': '{accessTokens} access · {refreshTokens} refresh',
476
+ 'account.apps.revoke': 'Revogar acesso',
477
+ 'account.apps.revoke_confirm': 'Revogar o acesso deste app? Ele precisará ser autorizado novamente e seus tokens deixarão de funcionar.',
478
+ 'account.apps.revoked': 'Acesso revogado.',
479
+ 'account.apps.not_supported': 'O adapter OIDC configurado não suporta enumeração — a listagem de apps fica indisponível.',
122
480
  // Confirmação de troca de e-mail (account/email-confirmed).
123
481
  'account.email_confirmed.page_title': 'Confirmação de e-mail',
124
482
  'account.email_confirmed.ok_title': 'E-mail alterado',
@@ -171,6 +529,20 @@ export const DEFAULT_MESSAGES = {
171
529
  'admin.users.roles_placeholder': 'Papéis (separados por vírgula)',
172
530
  'admin.users.save_roles': 'Salvar papéis',
173
531
  'admin.users.sessions': 'Sessões',
532
+ 'admin.users.create_section': 'Criar usuário',
533
+ 'admin.users.create_name_placeholder': 'Nome (opcional)',
534
+ 'admin.users.create_email_placeholder': 'E-mail',
535
+ 'admin.users.create_password_placeholder': 'Senha (deixe em branco para enviar convite)',
536
+ 'admin.users.create_submit': 'Criar usuário',
537
+ 'admin.users.created': 'Usuário criado.',
538
+ 'admin.users.reset_password': 'Enviar redefinição de senha',
539
+ 'admin.users.reset_sent': 'E-mail de redefinição de senha enviado.',
540
+ 'admin.users.disable': 'Desabilitar',
541
+ 'admin.users.enable': 'Reabilitar',
542
+ 'admin.users.disabled': 'Conta desabilitada.',
543
+ 'admin.users.enabled': 'Conta reabilitada.',
544
+ 'admin.users.disabled_badge': 'Desabilitada',
545
+ 'admin.users.disable_confirm': 'Desabilitar esta conta? O usuário não conseguirá entrar.',
174
546
  // Console admin — sessões/grants ativos de uma conta.
175
547
  'admin.sessions.page_title': 'Sessões ativas',
176
548
  'admin.sessions.title': 'Sessões ativas',
@@ -254,24 +626,73 @@ export const DEFAULT_MESSAGES = {
254
626
  // Mensagens de erro/flash produzidas pelos controllers.
255
627
  'errors.invalid_credentials': 'Credenciais inválidas',
256
628
  'errors.invalid_code': 'Código inválido',
629
+ 'errors.account_disabled': 'Esta conta foi desabilitada.',
257
630
  'errors.email_taken': 'E-mail já cadastrado',
258
631
  'errors.signup_failed': 'Não foi possível criar a conta',
259
632
  'errors.invalid_or_expired_token': 'Token inválido ou expirado',
260
633
  'errors.account_locked': 'Conta temporariamente bloqueada por excesso de tentativas. Tente novamente em {seconds}s.',
634
+ 'errors.session_expired': 'Sessão expirada',
635
+ 'errors.challenge_expired': 'Desafio expirado',
636
+ 'errors.passkeys_unavailable': 'Passkeys indisponíveis',
637
+ 'errors.no_passkey_registered': 'Nenhuma passkey registrada',
638
+ // Assuntos/corpos de e-mail transacional (default_mailer).
639
+ 'mail.common.link_fallback': 'Se o botão não funcionar, copie e cole este link no navegador:',
640
+ 'mail.reset.subject': 'Redefinição de senha',
641
+ 'mail.reset.heading': 'Redefinição de senha',
642
+ 'mail.reset.intro': 'Recebemos um pedido para redefinir a senha da sua conta.',
643
+ 'mail.reset.cta': 'Redefinir senha',
644
+ 'mail.reset.fallback': 'Se você não solicitou isso, pode ignorar este e-mail.',
645
+ 'mail.reset.expires': 'Este link expira em {minutes} minutos.',
646
+ 'mail.verify.subject': 'Verifique seu e-mail',
647
+ 'mail.verify.heading': 'Verifique seu e-mail',
648
+ 'mail.verify.intro': 'Confirme seu endereço de e-mail para concluir a configuração da conta.',
649
+ 'mail.verify.cta': 'Verificar e-mail',
650
+ 'mail.verify.fallback': 'Se você não criou esta conta, pode ignorar este e-mail.',
651
+ 'mail.verify.expires': 'Este link expira em {minutes} minutos.',
652
+ 'mail.magic_link.subject': 'Seu link de login',
653
+ 'mail.magic_link.heading': 'Entrar na sua conta',
654
+ 'mail.magic_link.intro': 'Clique no botão abaixo para entrar. O link expira em breve e pode ser usado uma vez.',
655
+ 'mail.magic_link.cta': 'Entrar',
656
+ 'mail.magic_link.fallback': 'Se você não solicitou isso, pode ignorar este e-mail.',
657
+ 'mail.new_login.subject': 'Novo login na sua conta',
658
+ 'mail.new_login.heading': 'Novo login detectado',
659
+ 'mail.new_login.intro': 'Detectamos um novo login na sua conta.',
660
+ 'mail.new_login.when': 'Quando: {date}',
661
+ 'mail.new_login.ip': 'Endereço IP: {ip}',
662
+ 'mail.new_login.device': 'Dispositivo: {device}',
663
+ 'mail.new_login.fallback': 'Se foi você, nenhuma ação é necessária. Caso contrário, redefina sua senha imediatamente.',
664
+ 'mail.email_change.subject': 'Confirme seu novo e-mail',
665
+ 'mail.email_change.heading': 'Confirme seu novo e-mail',
666
+ 'mail.email_change.intro': 'Recebemos um pedido para trocar o e-mail da sua conta. Confirme o novo endereço abaixo.',
667
+ 'mail.email_change.cta': 'Confirmar novo e-mail',
668
+ 'mail.email_change.fallback': 'Se você não solicitou isso, pode ignorar este e-mail.',
669
+ 'mail.email_change.expires': 'Este link expira em {minutes} minutos.',
670
+ };
671
+ /**
672
+ * Locales embutidos no host-kit. O `en` é o default; o `pt-BR` está disponível
673
+ * com `i18n: { locale: 'pt-BR' }` sem nenhuma config de mensagens extra. Os
674
+ * overrides/locales do host (via `I18nConfig.messages`) são mesclados por cima.
675
+ */
676
+ export const BUILTIN_MESSAGES = {
677
+ en: DEFAULT_MESSAGES,
678
+ 'pt-BR': PT_BR_MESSAGES,
261
679
  };
262
680
  /**
263
- * Resolve o catálogo ativo: mescla os overrides do locale selecionado SOBRE o
264
- * default pt-BR. Sem config, retorna os defaults intactos. Chaves omitidas pelo
265
- * locale escolhido caem no default pt-BR (fallback de cobertura).
681
+ * Resolve o catálogo ativo. Começa do catálogo embutido do locale selecionado
682
+ * (ou do default `en` quando o locale não é embutido), depois mescla os
683
+ * overrides do host por cima. Sem config, retorna o default `en` intacto.
684
+ * Chaves omitidas caem no default `en` (fallback de cobertura).
266
685
  */
267
686
  export function resolveMessages(i18n) {
268
- const base = { ...DEFAULT_MESSAGES };
269
687
  const locale = i18n?.locale ?? DEFAULT_LOCALE;
688
+ // Base: sempre o default `en` para garantir cobertura total das chaves; o
689
+ // catálogo embutido do locale (ex.: pt-BR) é mesclado por cima.
690
+ const base = { ...DEFAULT_MESSAGES, ...(BUILTIN_MESSAGES[locale] ?? {}) };
270
691
  const overrides = i18n?.messages?.[locale];
271
692
  if (!overrides)
272
693
  return base;
273
694
  // Mescla só valores definidos (o `Partial` permite undefined); chaves omitidas
274
- // seguem caindo no default pt-BR.
695
+ // seguem caindo no catálogo base.
275
696
  for (const [key, value] of Object.entries(overrides)) {
276
697
  if (value !== undefined)
277
698
  base[key] = value;
@@ -20,6 +20,7 @@ export type PasswordLoginResult = {
20
20
  ok: false;
21
21
  locked: boolean;
22
22
  retryAfterSec?: number;
23
+ disabled?: boolean;
23
24
  };
24
25
  /**
25
26
  * Sequência canônica de login por senha + bloqueio progressivo, compartilhada
@@ -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']);