@dudousxd/adonis-authkit-server 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/commands/commands.json +28 -0
- package/build/commands/doctor.d.ts +10 -0
- package/build/commands/doctor.js +66 -0
- package/build/commands/rotate_keys.d.ts +10 -0
- package/build/commands/rotate_keys.js +53 -0
- package/build/host/views/account/email-confirmed.edge +15 -0
- package/build/host/views/account/security.edge +83 -0
- package/build/host/views/account/tokens.edge +7 -4
- package/build/host/views/admin/sessions.edge +89 -0
- package/build/host/views/admin/users.edge +1 -0
- package/build/host/views/mfa-challenge.edge +29 -23
- package/build/index.d.ts +5 -4
- package/build/index.js +3 -3
- package/build/src/accounts/account_store.d.ts +46 -1
- package/build/src/accounts/account_store.js +4 -0
- package/build/src/accounts/lucid_store/core.d.ts +5 -4
- package/build/src/accounts/lucid_store/core.js +67 -2
- package/build/src/adapters/adapter_contract.d.ts +17 -0
- package/build/src/adapters/database_adapter.d.ts +9 -5
- package/build/src/adapters/database_adapter.js +13 -6
- package/build/src/adapters/redis_adapter.d.ts +11 -5
- package/build/src/adapters/redis_adapter.js +16 -7
- package/build/src/audit/audit_sink.d.ts +1 -1
- package/build/src/define_config.d.ts +102 -0
- package/build/src/define_config.js +46 -3
- package/build/src/doctor/checks.d.ts +51 -0
- package/build/src/doctor/checks.js +231 -0
- package/build/src/host/admin_clients_service.js +12 -5
- package/build/src/host/admin_sessions_service.d.ts +63 -0
- package/build/src/host/admin_sessions_service.js +127 -0
- package/build/src/host/controllers/account_mfa_controller.js +6 -2
- package/build/src/host/controllers/account_security_controller.d.ts +16 -0
- package/build/src/host/controllers/account_security_controller.js +119 -0
- package/build/src/host/controllers/account_session_controller.js +2 -1
- package/build/src/host/controllers/admin/admin_sessions_controller.d.ts +14 -0
- package/build/src/host/controllers/admin/admin_sessions_controller.js +64 -0
- package/build/src/host/controllers/interaction_controller.d.ts +11 -0
- package/build/src/host/controllers/interaction_controller.js +55 -12
- package/build/src/host/default_mailer.d.ts +17 -0
- package/build/src/host/default_mailer.js +94 -9
- package/build/src/host/email_templates.d.ts +4 -0
- package/build/src/host/email_templates.js +5 -2
- package/build/src/host/i18n.d.ts +358 -11
- package/build/src/host/i18n.js +393 -12
- package/build/src/host/login_notify.d.ts +20 -0
- package/build/src/host/login_notify.js +71 -0
- package/build/src/host/register_auth_host.js +12 -0
- package/build/src/host/validators.d.ts +32 -0
- package/build/src/host/validators.js +14 -0
- package/build/src/keys/keystore.d.ts +43 -0
- package/build/src/keys/keystore.js +74 -0
- package/build/src/observability/metrics_controller.js +4 -4
- package/build/src/provider/build_provider.js +23 -0
- package/build/src/provider/device_sources.d.ts +6 -0
- package/build/src/provider/device_sources.js +65 -0
- package/build/src/provider/interaction_actions.d.ts +6 -1
- package/build/src/provider/interaction_actions.js +9 -2
- package/package.json +2 -2
package/build/src/host/i18n.js
CHANGED
|
@@ -3,17 +3,299 @@
|
|
|
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 é
|
|
7
|
-
* funcionando SEM nenhuma configuração. O
|
|
8
|
-
*
|
|
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 = '
|
|
12
|
+
export const DEFAULT_LOCALE = 'en';
|
|
12
13
|
/**
|
|
13
|
-
* Catálogo default (
|
|
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
|
+
// Tela de cadastro (signup).
|
|
36
|
+
'signup.page_title': 'Create account',
|
|
37
|
+
'signup.title': 'Create account',
|
|
38
|
+
'signup.intro': 'Fill in your details to get started.',
|
|
39
|
+
'signup.name_label': 'Name',
|
|
40
|
+
'signup.email_label': 'Email',
|
|
41
|
+
'signup.password_label': 'Password',
|
|
42
|
+
'signup.submit': 'Create account',
|
|
43
|
+
'signup.have_account': 'I already have an account',
|
|
44
|
+
// Recuperação de senha (forgot).
|
|
45
|
+
'forgot.page_title': 'Reset password',
|
|
46
|
+
'forgot.sent_title': 'Email sent',
|
|
47
|
+
'forgot.sent_body': 'If the email exists, we will send reset instructions.',
|
|
48
|
+
'forgot.title': 'Reset password',
|
|
49
|
+
'forgot.intro': 'We will send you a link to reset your password.',
|
|
50
|
+
'forgot.email_label': 'Email',
|
|
51
|
+
'forgot.submit': 'Send link',
|
|
52
|
+
// Redefinição de senha (reset).
|
|
53
|
+
'reset.page_title': 'Reset password',
|
|
54
|
+
'reset.done_title': 'Password reset',
|
|
55
|
+
'reset.done_body': 'You can now log in with your new password.',
|
|
56
|
+
'reset.title': 'New password',
|
|
57
|
+
'reset.intro': 'Choose a new password for your account.',
|
|
58
|
+
'reset.password_label': 'Password',
|
|
59
|
+
'reset.submit': 'Reset',
|
|
60
|
+
// Verificação de e-mail (verify-email).
|
|
61
|
+
'verify_email.page_title': 'Verify email',
|
|
62
|
+
'verify_email.verified_title': 'Email verified',
|
|
63
|
+
'verify_email.verified_body': 'Your email was confirmed successfully.',
|
|
64
|
+
'verify_email.invalid_title': 'Invalid link',
|
|
65
|
+
'verify_email.invalid_body': 'The verification link is invalid or has already been used.',
|
|
66
|
+
// Desafio de MFA no fluxo de login (mfa-challenge).
|
|
67
|
+
'mfa_challenge.page_title': 'Two-factor verification',
|
|
68
|
+
'mfa_challenge.title': 'Two-factor verification',
|
|
69
|
+
'mfa_challenge.intro': 'Open your authenticator app and enter the 6-digit code.',
|
|
70
|
+
'mfa_challenge.code_label': 'Code',
|
|
71
|
+
'mfa_challenge.submit': 'Verify',
|
|
72
|
+
'mfa_challenge.recovery_summary': 'Use a recovery code',
|
|
73
|
+
'mfa_challenge.recovery_submit': 'Log in with a recovery code',
|
|
74
|
+
'mfa_challenge.passkey_button': 'Use passkey',
|
|
75
|
+
'mfa_challenge.passkey_error': 'Could not authenticate with the passkey. Please try again.',
|
|
76
|
+
// Consent (autorização de cliente OIDC).
|
|
77
|
+
'consent.page_title': 'Authorize',
|
|
78
|
+
'consent.title': 'Authorize access',
|
|
79
|
+
// `{app}` é interpolado com o nome do app já envolto em <strong> (renderizado
|
|
80
|
+
// raw na view). O nome vem do branding (config-trusted).
|
|
81
|
+
'consent.body': 'The app <strong>{app}</strong> wants to access your account.',
|
|
82
|
+
'consent.submit': 'Authorize',
|
|
83
|
+
// Console de conta — login (account/login).
|
|
84
|
+
'account.login.page_title': 'My account',
|
|
85
|
+
'account.login.title': 'My account',
|
|
86
|
+
'account.login.intro': 'Manage your access tokens.',
|
|
87
|
+
'account.login.email_label': 'Email',
|
|
88
|
+
'account.login.password_label': 'Password',
|
|
89
|
+
'account.login.submit': 'Log in',
|
|
90
|
+
// Console de conta — tokens (account/tokens).
|
|
91
|
+
'account.tokens.page_title': 'Access tokens',
|
|
92
|
+
'account.tokens.title': 'Access tokens',
|
|
93
|
+
'account.tokens.logout': 'Log out',
|
|
94
|
+
'account.tokens.security': 'Security',
|
|
95
|
+
'account.tokens.created_notice': 'Token created — copy it now, it will not be shown again:',
|
|
96
|
+
'account.tokens.name_placeholder': 'Token name (e.g. CI deploy)',
|
|
97
|
+
'account.tokens.create': 'Create',
|
|
98
|
+
'account.tokens.empty': 'No tokens yet.',
|
|
99
|
+
'account.tokens.created_at': 'Created on {date}',
|
|
100
|
+
'account.tokens.last_used': '· last used {date}',
|
|
101
|
+
'account.tokens.never_used': '· never used',
|
|
102
|
+
'account.tokens.scopes': 'Scopes: {scopes}',
|
|
103
|
+
'account.tokens.audience': 'Audience: {audience}',
|
|
104
|
+
'account.tokens.revoke': 'Revoke',
|
|
105
|
+
// Console de conta — segurança (account/security): senha + e-mail.
|
|
106
|
+
'account.security.page_title': 'Account security',
|
|
107
|
+
'account.security.title': 'Account security',
|
|
108
|
+
'account.security.logout': 'Log out',
|
|
109
|
+
'account.security.current_email': 'Current email: {email}',
|
|
110
|
+
'account.security.not_supported': 'Changing password and email is not available in this installation.',
|
|
111
|
+
'account.security.password_section': 'Change password',
|
|
112
|
+
'account.security.current_password_label': 'Current password',
|
|
113
|
+
'account.security.new_password_label': 'New password',
|
|
114
|
+
'account.security.change_password_submit': 'Change password',
|
|
115
|
+
'account.security.password_changed': 'Password changed successfully.',
|
|
116
|
+
'account.security.email_section': 'Change email',
|
|
117
|
+
'account.security.email_intro': 'We will send a confirmation link to the new address. The change only takes effect after confirmation.',
|
|
118
|
+
'account.security.new_email_label': 'New email',
|
|
119
|
+
'account.security.email_password_label': 'Current password',
|
|
120
|
+
'account.security.change_email_submit': 'Request email change',
|
|
121
|
+
'account.security.email_change_requested': 'We sent a confirmation link to {email}. Click it to complete the change.',
|
|
122
|
+
'account.security.email_changed': 'Email changed successfully.',
|
|
123
|
+
// Confirmação de troca de e-mail (account/email-confirmed).
|
|
124
|
+
'account.email_confirmed.page_title': 'Email confirmation',
|
|
125
|
+
'account.email_confirmed.ok_title': 'Email changed',
|
|
126
|
+
'account.email_confirmed.ok_body': 'Your new email has been confirmed and is now active.',
|
|
127
|
+
'account.email_confirmed.invalid_title': 'Invalid link',
|
|
128
|
+
'account.email_confirmed.invalid_body': 'The confirmation link is invalid or has already been used.',
|
|
129
|
+
// Console de conta — MFA (account/mfa).
|
|
130
|
+
'account.mfa.page_title': 'Two-factor verification',
|
|
131
|
+
'account.mfa.title': 'Two-factor verification',
|
|
132
|
+
'account.mfa.logout': 'Log out',
|
|
133
|
+
'account.mfa.recovery_codes_notice': 'Save your recovery codes — they will not be shown again:',
|
|
134
|
+
'account.mfa.enroll_intro': 'Scan the QR code with your authenticator app (Google Authenticator, 1Password, etc.).',
|
|
135
|
+
'account.mfa.qr_alt': 'TOTP QR code',
|
|
136
|
+
'account.mfa.manual_intro': 'Or enter it manually:',
|
|
137
|
+
'account.mfa.confirm_code_label': 'Confirmation code',
|
|
138
|
+
'account.mfa.activate': 'Enable two-factor verification',
|
|
139
|
+
'account.mfa.enabled_html': 'Two-factor verification is <span class="font-semibold text-emerald-700">enabled</span> on this account.',
|
|
140
|
+
'account.mfa.disable': 'Disable',
|
|
141
|
+
'account.mfa.disabled_intro': 'Two-factor verification is disabled. Enable it to protect your account with an authenticator app.',
|
|
142
|
+
'account.mfa.enable': 'Enable two-factor verification',
|
|
143
|
+
// Console de conta — passkeys (WebAuthn) na tela de MFA.
|
|
144
|
+
'mfa.passkey.section_title': 'Passkeys',
|
|
145
|
+
'mfa.passkey.section_intro': 'Use a passkey (biometrics, device PIN, or security key) as a second factor, without typing codes.',
|
|
146
|
+
'mfa.passkey.add': 'Add passkey',
|
|
147
|
+
'mfa.passkey.remove': 'Remove',
|
|
148
|
+
'mfa.passkey.empty': 'No passkeys registered.',
|
|
149
|
+
'mfa.passkey.unnamed': 'Passkey',
|
|
150
|
+
'mfa.passkey.created_at': 'Created on {date}',
|
|
151
|
+
'mfa.passkey.register_error': 'Could not register the passkey. Please try again.',
|
|
152
|
+
'mfa.passkey.unsupported': 'Your browser does not support passkeys.',
|
|
153
|
+
// Console admin (B6) — navegação compartilhada.
|
|
154
|
+
'admin.nav.dashboard': 'Dashboard',
|
|
155
|
+
'admin.nav.users': 'Users',
|
|
156
|
+
'admin.nav.clients': 'Clients',
|
|
157
|
+
'admin.nav.audit': 'Audit',
|
|
158
|
+
'admin.nav.logout': 'Log out',
|
|
159
|
+
// Console admin — dashboard.
|
|
160
|
+
'admin.dashboard.page_title': 'Admin dashboard',
|
|
161
|
+
'admin.dashboard.title': 'Admin dashboard',
|
|
162
|
+
'admin.dashboard.users_count': 'Users',
|
|
163
|
+
'admin.dashboard.clients_count': 'Clients',
|
|
164
|
+
'admin.dashboard.audit_count': 'Audit events',
|
|
165
|
+
'admin.dashboard.recent_title': 'Recent events',
|
|
166
|
+
// Console admin — usuários.
|
|
167
|
+
'admin.users.page_title': 'Users',
|
|
168
|
+
'admin.users.title': 'Users',
|
|
169
|
+
'admin.users.search_placeholder': 'Search by email',
|
|
170
|
+
'admin.users.search': 'Search',
|
|
171
|
+
'admin.users.empty': 'No users found.',
|
|
172
|
+
'admin.users.roles_placeholder': 'Roles (comma-separated)',
|
|
173
|
+
'admin.users.save_roles': 'Save roles',
|
|
174
|
+
'admin.users.sessions': 'Sessions',
|
|
175
|
+
// Console admin — sessões/grants ativos de uma conta.
|
|
176
|
+
'admin.sessions.page_title': 'Active sessions',
|
|
177
|
+
'admin.sessions.title': 'Active sessions',
|
|
178
|
+
'admin.sessions.account': 'Account: {email}',
|
|
179
|
+
'admin.sessions.back': 'Back to users',
|
|
180
|
+
'admin.sessions.not_supported': 'The configured OIDC adapter does not support enumeration — session inspection is unavailable.',
|
|
181
|
+
'admin.sessions.revoked_notice': 'Revoked: {sessions} session(s), {grants} grant(s), {accessTokens} access token(s), {refreshTokens} refresh token(s).',
|
|
182
|
+
'admin.sessions.sessions_section': 'Sessions (IdP login)',
|
|
183
|
+
'admin.sessions.sessions_empty': 'No active sessions.',
|
|
184
|
+
'admin.sessions.session_login_ts': 'Login: {date}',
|
|
185
|
+
'admin.sessions.session_amr': 'Methods: {amr}',
|
|
186
|
+
'admin.sessions.grants_section': 'Grants (per-client authorizations)',
|
|
187
|
+
'admin.sessions.grants_empty': 'No active grants.',
|
|
188
|
+
'admin.sessions.grant_client': 'Client: {clientId}',
|
|
189
|
+
'admin.sessions.grant_tokens': '{accessTokens} access · {refreshTokens} refresh',
|
|
190
|
+
'admin.sessions.revoke_all': 'Revoke all sessions and grants',
|
|
191
|
+
'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.',
|
|
192
|
+
// Console admin — clients.
|
|
193
|
+
'admin.clients.page_title': 'OAuth clients',
|
|
194
|
+
'admin.clients.title': 'OAuth clients',
|
|
195
|
+
'admin.clients.empty': 'No clients configured.',
|
|
196
|
+
'admin.clients.confidential': 'Confidential',
|
|
197
|
+
'admin.clients.public': 'Public',
|
|
198
|
+
'admin.clients.grants': 'Grants: {grants}',
|
|
199
|
+
'admin.clients.redirect_uris': 'Redirects: {uris}',
|
|
200
|
+
'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.',
|
|
201
|
+
'admin.clients.static_section': 'Static clients (config)',
|
|
202
|
+
'admin.clients.dynamic_section': 'Dynamic clients (adapter)',
|
|
203
|
+
'admin.clients.dynamic_empty': 'No dynamic clients persisted.',
|
|
204
|
+
'admin.clients.dynamic_not_supported': 'The configured OIDC adapter does not support client enumeration — dynamic management is unavailable.',
|
|
205
|
+
'admin.clients.new': 'New client',
|
|
206
|
+
'admin.clients.new_title': 'New OIDC client',
|
|
207
|
+
'admin.clients.edit_title': 'Edit OIDC client',
|
|
208
|
+
'admin.clients.edit': 'Edit',
|
|
209
|
+
'admin.clients.delete': 'Delete',
|
|
210
|
+
'admin.clients.delete_confirm': 'Delete this client? This action cannot be undone.',
|
|
211
|
+
'admin.clients.regenerate_secret': 'Regenerate secret',
|
|
212
|
+
'admin.clients.regenerate_confirm': 'Regenerate the secret? The current secret will stop working immediately.',
|
|
213
|
+
'admin.clients.back': 'Back',
|
|
214
|
+
'admin.clients.cancel': 'Cancel',
|
|
215
|
+
'admin.clients.save': 'Save',
|
|
216
|
+
'admin.clients.create': 'Create client',
|
|
217
|
+
'admin.clients.secret_once_title': 'Save the client_secret now',
|
|
218
|
+
'admin.clients.secret_once_notice': 'This is the only time the secret is shown. Copy it now — it cannot be retrieved later.',
|
|
219
|
+
'admin.clients.field_client_id': 'Client ID',
|
|
220
|
+
'admin.clients.field_client_id_placeholder': 'leave blank to generate automatically',
|
|
221
|
+
'admin.clients.field_client_id_help': 'Optional. If empty, a random identifier will be generated.',
|
|
222
|
+
'admin.clients.field_redirect_uris': 'Redirect URIs',
|
|
223
|
+
'admin.clients.field_redirect_uris_help': 'One URI per line.',
|
|
224
|
+
'admin.clients.field_post_logout_uris': 'Post-logout redirect URIs',
|
|
225
|
+
'admin.clients.field_post_logout_uris_help': 'One URI per line (optional).',
|
|
226
|
+
'admin.clients.field_grant_types': 'Grant types',
|
|
227
|
+
'admin.clients.field_auth_method': 'Token endpoint auth method',
|
|
228
|
+
// Console admin — auditoria.
|
|
229
|
+
'admin.audit.page_title': 'Audit',
|
|
230
|
+
'admin.audit.title': 'Audit log',
|
|
231
|
+
'admin.audit.type_placeholder': 'Filter by type',
|
|
232
|
+
'admin.audit.subject_placeholder': 'Filter by subject (accountId)',
|
|
233
|
+
'admin.audit.filter': 'Filter',
|
|
234
|
+
'admin.audit.empty': 'No events found.',
|
|
235
|
+
'admin.audit.not_supported': 'The configured audit sink does not support querying.',
|
|
236
|
+
// Console admin — paginação compartilhada.
|
|
237
|
+
'admin.pagination.page': 'Page {page} of {total}',
|
|
238
|
+
'admin.pagination.prev': 'Previous',
|
|
239
|
+
'admin.pagination.next': 'Next',
|
|
240
|
+
// Device Authorization Grant (RFC 8628) — telas servidas pelo oidc-provider.
|
|
241
|
+
'device.input.title': 'Sign in to the device',
|
|
242
|
+
'device.input.intro': 'Enter the code shown on your device.',
|
|
243
|
+
'device.input.submit': 'Continue',
|
|
244
|
+
'device.input.error_invalid': 'The code you entered is incorrect. Please try again.',
|
|
245
|
+
'device.input.error_aborted': 'The login request was aborted.',
|
|
246
|
+
'device.input.error_generic': 'An error occurred while processing your request.',
|
|
247
|
+
'device.confirm.title': 'Confirm device',
|
|
248
|
+
'device.confirm.body': 'The code below should be displayed on your device. Confirm only if you recognize it.',
|
|
249
|
+
'device.confirm.submit': 'Continue',
|
|
250
|
+
'device.confirm.abort': 'Cancel',
|
|
251
|
+
'device.success.title': 'Login complete',
|
|
252
|
+
'device.success.body': 'You are signed in. You can return to your device.',
|
|
253
|
+
// Step-up auth (acr_values): cliente exige MFA mas a conta não tem MFA enrolado.
|
|
254
|
+
'mfa_challenge.required_no_enrollment': 'This client requires two-factor verification. Set up MFA in your account console to continue.',
|
|
255
|
+
// Mensagens de erro/flash produzidas pelos controllers.
|
|
256
|
+
'errors.invalid_credentials': 'Invalid credentials',
|
|
257
|
+
'errors.invalid_code': 'Invalid code',
|
|
258
|
+
'errors.email_taken': 'Email already registered',
|
|
259
|
+
'errors.signup_failed': 'Could not create the account',
|
|
260
|
+
'errors.invalid_or_expired_token': 'Invalid or expired token',
|
|
261
|
+
'errors.account_locked': 'Account temporarily locked due to too many attempts. Try again in {seconds}s.',
|
|
262
|
+
'errors.session_expired': 'Session expired',
|
|
263
|
+
'errors.challenge_expired': 'Challenge expired',
|
|
264
|
+
'errors.passkeys_unavailable': 'Passkeys unavailable',
|
|
265
|
+
'errors.no_passkey_registered': 'No passkey registered',
|
|
266
|
+
// Assuntos/corpos de e-mail transacional (default_mailer).
|
|
267
|
+
'mail.common.link_fallback': "If the button does not work, copy and paste this link into your browser:",
|
|
268
|
+
'mail.reset.subject': 'Reset your password',
|
|
269
|
+
'mail.reset.heading': 'Reset your password',
|
|
270
|
+
'mail.reset.intro': 'We received a request to reset the password for your account.',
|
|
271
|
+
'mail.reset.cta': 'Reset password',
|
|
272
|
+
'mail.reset.fallback': 'If you did not request this, you can ignore this email.',
|
|
273
|
+
'mail.reset.expires': 'This link expires in {minutes} minutes.',
|
|
274
|
+
'mail.verify.subject': 'Verify your email',
|
|
275
|
+
'mail.verify.heading': 'Verify your email',
|
|
276
|
+
'mail.verify.intro': 'Confirm your email address to finish setting up your account.',
|
|
277
|
+
'mail.verify.cta': 'Verify email',
|
|
278
|
+
'mail.verify.fallback': 'If you did not create this account, you can ignore this email.',
|
|
279
|
+
'mail.verify.expires': 'This link expires in {minutes} minutes.',
|
|
280
|
+
'mail.new_login.subject': 'New login to your account',
|
|
281
|
+
'mail.new_login.heading': 'New login detected',
|
|
282
|
+
'mail.new_login.intro': 'We detected a new login to your account.',
|
|
283
|
+
'mail.new_login.when': 'When: {date}',
|
|
284
|
+
'mail.new_login.ip': 'IP address: {ip}',
|
|
285
|
+
'mail.new_login.device': 'Device: {device}',
|
|
286
|
+
'mail.new_login.fallback': 'If this was you, no action is needed. If not, reset your password right away.',
|
|
287
|
+
'mail.email_change.subject': 'Confirm your new email',
|
|
288
|
+
'mail.email_change.heading': 'Confirm your new email',
|
|
289
|
+
'mail.email_change.intro': 'We received a request to change the email address on your account. Confirm the new address below.',
|
|
290
|
+
'mail.email_change.cta': 'Confirm new email',
|
|
291
|
+
'mail.email_change.fallback': 'If you did not request this, you can ignore this email.',
|
|
292
|
+
'mail.email_change.expires': 'This link expires in {minutes} minutes.',
|
|
293
|
+
};
|
|
294
|
+
/**
|
|
295
|
+
* Catálogo embutido pt-BR. Espelha TODAS as chaves do default `en`. Ativado
|
|
296
|
+
* com `i18n: { locale: 'pt-BR' }` sem nenhuma config extra de mensagens.
|
|
297
|
+
*/
|
|
298
|
+
export const PT_BR_MESSAGES = {
|
|
17
299
|
// Comum / fallback de marca.
|
|
18
300
|
'common.app_fallback': 'Auth',
|
|
19
301
|
'common.brand_eyebrow': 'Auth',
|
|
@@ -75,8 +357,6 @@ export const DEFAULT_MESSAGES = {
|
|
|
75
357
|
// Consent (autorização de cliente OIDC).
|
|
76
358
|
'consent.page_title': 'Autorizar',
|
|
77
359
|
'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
360
|
'consent.body': 'O app <strong>{app}</strong> quer acessar sua conta.',
|
|
81
361
|
'consent.submit': 'Autorizar',
|
|
82
362
|
// Console de conta — login (account/login).
|
|
@@ -90,6 +370,7 @@ export const DEFAULT_MESSAGES = {
|
|
|
90
370
|
'account.tokens.page_title': 'Tokens de acesso',
|
|
91
371
|
'account.tokens.title': 'Tokens de acesso',
|
|
92
372
|
'account.tokens.logout': 'Sair',
|
|
373
|
+
'account.tokens.security': 'Segurança',
|
|
93
374
|
'account.tokens.created_notice': 'Token criado — copie agora, não será mostrado de novo:',
|
|
94
375
|
'account.tokens.name_placeholder': 'Nome do token (ex.: CI deploy)',
|
|
95
376
|
'account.tokens.create': 'Criar',
|
|
@@ -100,6 +381,30 @@ export const DEFAULT_MESSAGES = {
|
|
|
100
381
|
'account.tokens.scopes': 'Escopos: {scopes}',
|
|
101
382
|
'account.tokens.audience': 'Audiência: {audience}',
|
|
102
383
|
'account.tokens.revoke': 'Revogar',
|
|
384
|
+
// Console de conta — segurança (account/security): senha + e-mail.
|
|
385
|
+
'account.security.page_title': 'Segurança da conta',
|
|
386
|
+
'account.security.title': 'Segurança da conta',
|
|
387
|
+
'account.security.logout': 'Sair',
|
|
388
|
+
'account.security.current_email': 'E-mail atual: {email}',
|
|
389
|
+
'account.security.not_supported': 'A troca de senha e e-mail não está disponível nesta instalação.',
|
|
390
|
+
'account.security.password_section': 'Trocar senha',
|
|
391
|
+
'account.security.current_password_label': 'Senha atual',
|
|
392
|
+
'account.security.new_password_label': 'Nova senha',
|
|
393
|
+
'account.security.change_password_submit': 'Trocar senha',
|
|
394
|
+
'account.security.password_changed': 'Senha alterada com sucesso.',
|
|
395
|
+
'account.security.email_section': 'Trocar e-mail',
|
|
396
|
+
'account.security.email_intro': 'Enviaremos um link de confirmação para o novo endereço. A troca só é aplicada após a confirmação.',
|
|
397
|
+
'account.security.new_email_label': 'Novo e-mail',
|
|
398
|
+
'account.security.email_password_label': 'Senha atual',
|
|
399
|
+
'account.security.change_email_submit': 'Solicitar troca de e-mail',
|
|
400
|
+
'account.security.email_change_requested': 'Enviamos um link de confirmação para {email}. Clique nele para concluir a troca.',
|
|
401
|
+
'account.security.email_changed': 'E-mail alterado com sucesso.',
|
|
402
|
+
// Confirmação de troca de e-mail (account/email-confirmed).
|
|
403
|
+
'account.email_confirmed.page_title': 'Confirmação de e-mail',
|
|
404
|
+
'account.email_confirmed.ok_title': 'E-mail alterado',
|
|
405
|
+
'account.email_confirmed.ok_body': 'Seu novo e-mail foi confirmado e já está ativo.',
|
|
406
|
+
'account.email_confirmed.invalid_title': 'Link inválido',
|
|
407
|
+
'account.email_confirmed.invalid_body': 'O link de confirmação é inválido ou já foi utilizado.',
|
|
103
408
|
// Console de conta — MFA (account/mfa).
|
|
104
409
|
'account.mfa.page_title': 'Verificação em duas etapas',
|
|
105
410
|
'account.mfa.title': 'Verificação em duas etapas',
|
|
@@ -145,6 +450,24 @@ export const DEFAULT_MESSAGES = {
|
|
|
145
450
|
'admin.users.empty': 'Nenhum usuário encontrado.',
|
|
146
451
|
'admin.users.roles_placeholder': 'Papéis (separados por vírgula)',
|
|
147
452
|
'admin.users.save_roles': 'Salvar papéis',
|
|
453
|
+
'admin.users.sessions': 'Sessões',
|
|
454
|
+
// Console admin — sessões/grants ativos de uma conta.
|
|
455
|
+
'admin.sessions.page_title': 'Sessões ativas',
|
|
456
|
+
'admin.sessions.title': 'Sessões ativas',
|
|
457
|
+
'admin.sessions.account': 'Conta: {email}',
|
|
458
|
+
'admin.sessions.back': 'Voltar para usuários',
|
|
459
|
+
'admin.sessions.not_supported': 'O adapter OIDC configurado não suporta enumeração — a inspeção de sessões fica indisponível.',
|
|
460
|
+
'admin.sessions.revoked_notice': 'Revogado: {sessions} sessão(ões), {grants} grant(s), {accessTokens} access token(s), {refreshTokens} refresh token(s).',
|
|
461
|
+
'admin.sessions.sessions_section': 'Sessões (login no IdP)',
|
|
462
|
+
'admin.sessions.sessions_empty': 'Nenhuma sessão ativa.',
|
|
463
|
+
'admin.sessions.session_login_ts': 'Login: {date}',
|
|
464
|
+
'admin.sessions.session_amr': 'Métodos: {amr}',
|
|
465
|
+
'admin.sessions.grants_section': 'Grants (autorizações por client)',
|
|
466
|
+
'admin.sessions.grants_empty': 'Nenhum grant ativo.',
|
|
467
|
+
'admin.sessions.grant_client': 'Client: {clientId}',
|
|
468
|
+
'admin.sessions.grant_tokens': '{accessTokens} access · {refreshTokens} refresh',
|
|
469
|
+
'admin.sessions.revoke_all': 'Revogar todas as sessões e grants',
|
|
470
|
+
'admin.sessions.revoke_confirm': 'Revogar todas as sessões e grants desta conta? O usuário precisará entrar novamente e os tokens emitidos deixarão de funcionar.',
|
|
148
471
|
// Console admin — clients.
|
|
149
472
|
'admin.clients.page_title': 'Clients OAuth',
|
|
150
473
|
'admin.clients.title': 'Clients OAuth',
|
|
@@ -193,6 +516,21 @@ export const DEFAULT_MESSAGES = {
|
|
|
193
516
|
'admin.pagination.page': 'Página {page} de {total}',
|
|
194
517
|
'admin.pagination.prev': 'Anterior',
|
|
195
518
|
'admin.pagination.next': 'Próxima',
|
|
519
|
+
// Device Authorization Grant (RFC 8628) — telas servidas pelo oidc-provider.
|
|
520
|
+
'device.input.title': 'Entrar no dispositivo',
|
|
521
|
+
'device.input.intro': 'Digite o código exibido no seu dispositivo.',
|
|
522
|
+
'device.input.submit': 'Continuar',
|
|
523
|
+
'device.input.error_invalid': 'O código informado está incorreto. Tente novamente.',
|
|
524
|
+
'device.input.error_aborted': 'A solicitação de login foi interrompida.',
|
|
525
|
+
'device.input.error_generic': 'Ocorreu um erro ao processar sua solicitação.',
|
|
526
|
+
'device.confirm.title': 'Confirmar dispositivo',
|
|
527
|
+
'device.confirm.body': 'O código abaixo deve estar sendo exibido no seu dispositivo. Confirme apenas se reconhecê-lo.',
|
|
528
|
+
'device.confirm.submit': 'Continuar',
|
|
529
|
+
'device.confirm.abort': 'Cancelar',
|
|
530
|
+
'device.success.title': 'Login concluído',
|
|
531
|
+
'device.success.body': 'Login realizado com sucesso. Você já pode voltar ao dispositivo.',
|
|
532
|
+
// Step-up auth (acr_values): cliente exige MFA mas a conta não tem MFA enrolado.
|
|
533
|
+
'mfa_challenge.required_no_enrollment': 'Este cliente exige verificação em duas etapas. Configure o MFA no console da sua conta para continuar.',
|
|
196
534
|
// Mensagens de erro/flash produzidas pelos controllers.
|
|
197
535
|
'errors.invalid_credentials': 'Credenciais inválidas',
|
|
198
536
|
'errors.invalid_code': 'Código inválido',
|
|
@@ -200,20 +538,63 @@ export const DEFAULT_MESSAGES = {
|
|
|
200
538
|
'errors.signup_failed': 'Não foi possível criar a conta',
|
|
201
539
|
'errors.invalid_or_expired_token': 'Token inválido ou expirado',
|
|
202
540
|
'errors.account_locked': 'Conta temporariamente bloqueada por excesso de tentativas. Tente novamente em {seconds}s.',
|
|
541
|
+
'errors.session_expired': 'Sessão expirada',
|
|
542
|
+
'errors.challenge_expired': 'Desafio expirado',
|
|
543
|
+
'errors.passkeys_unavailable': 'Passkeys indisponíveis',
|
|
544
|
+
'errors.no_passkey_registered': 'Nenhuma passkey registrada',
|
|
545
|
+
// Assuntos/corpos de e-mail transacional (default_mailer).
|
|
546
|
+
'mail.common.link_fallback': 'Se o botão não funcionar, copie e cole este link no navegador:',
|
|
547
|
+
'mail.reset.subject': 'Redefinição de senha',
|
|
548
|
+
'mail.reset.heading': 'Redefinição de senha',
|
|
549
|
+
'mail.reset.intro': 'Recebemos um pedido para redefinir a senha da sua conta.',
|
|
550
|
+
'mail.reset.cta': 'Redefinir senha',
|
|
551
|
+
'mail.reset.fallback': 'Se você não solicitou isso, pode ignorar este e-mail.',
|
|
552
|
+
'mail.reset.expires': 'Este link expira em {minutes} minutos.',
|
|
553
|
+
'mail.verify.subject': 'Verifique seu e-mail',
|
|
554
|
+
'mail.verify.heading': 'Verifique seu e-mail',
|
|
555
|
+
'mail.verify.intro': 'Confirme seu endereço de e-mail para concluir a configuração da conta.',
|
|
556
|
+
'mail.verify.cta': 'Verificar e-mail',
|
|
557
|
+
'mail.verify.fallback': 'Se você não criou esta conta, pode ignorar este e-mail.',
|
|
558
|
+
'mail.verify.expires': 'Este link expira em {minutes} minutos.',
|
|
559
|
+
'mail.new_login.subject': 'Novo login na sua conta',
|
|
560
|
+
'mail.new_login.heading': 'Novo login detectado',
|
|
561
|
+
'mail.new_login.intro': 'Detectamos um novo login na sua conta.',
|
|
562
|
+
'mail.new_login.when': 'Quando: {date}',
|
|
563
|
+
'mail.new_login.ip': 'Endereço IP: {ip}',
|
|
564
|
+
'mail.new_login.device': 'Dispositivo: {device}',
|
|
565
|
+
'mail.new_login.fallback': 'Se foi você, nenhuma ação é necessária. Caso contrário, redefina sua senha imediatamente.',
|
|
566
|
+
'mail.email_change.subject': 'Confirme seu novo e-mail',
|
|
567
|
+
'mail.email_change.heading': 'Confirme seu novo e-mail',
|
|
568
|
+
'mail.email_change.intro': 'Recebemos um pedido para trocar o e-mail da sua conta. Confirme o novo endereço abaixo.',
|
|
569
|
+
'mail.email_change.cta': 'Confirmar novo e-mail',
|
|
570
|
+
'mail.email_change.fallback': 'Se você não solicitou isso, pode ignorar este e-mail.',
|
|
571
|
+
'mail.email_change.expires': 'Este link expira em {minutes} minutos.',
|
|
572
|
+
};
|
|
573
|
+
/**
|
|
574
|
+
* Locales embutidos no host-kit. O `en` é o default; o `pt-BR` está disponível
|
|
575
|
+
* com `i18n: { locale: 'pt-BR' }` sem nenhuma config de mensagens extra. Os
|
|
576
|
+
* overrides/locales do host (via `I18nConfig.messages`) são mesclados por cima.
|
|
577
|
+
*/
|
|
578
|
+
export const BUILTIN_MESSAGES = {
|
|
579
|
+
en: DEFAULT_MESSAGES,
|
|
580
|
+
'pt-BR': PT_BR_MESSAGES,
|
|
203
581
|
};
|
|
204
582
|
/**
|
|
205
|
-
* Resolve o catálogo ativo
|
|
206
|
-
* default
|
|
207
|
-
*
|
|
583
|
+
* Resolve o catálogo ativo. Começa do catálogo embutido do locale selecionado
|
|
584
|
+
* (ou do default `en` quando o locale não é embutido), depois mescla os
|
|
585
|
+
* overrides do host por cima. Sem config, retorna o default `en` intacto.
|
|
586
|
+
* Chaves omitidas caem no default `en` (fallback de cobertura).
|
|
208
587
|
*/
|
|
209
588
|
export function resolveMessages(i18n) {
|
|
210
|
-
const base = { ...DEFAULT_MESSAGES };
|
|
211
589
|
const locale = i18n?.locale ?? DEFAULT_LOCALE;
|
|
590
|
+
// Base: sempre o default `en` para garantir cobertura total das chaves; o
|
|
591
|
+
// catálogo embutido do locale (ex.: pt-BR) é mesclado por cima.
|
|
592
|
+
const base = { ...DEFAULT_MESSAGES, ...(BUILTIN_MESSAGES[locale] ?? {}) };
|
|
212
593
|
const overrides = i18n?.messages?.[locale];
|
|
213
594
|
if (!overrides)
|
|
214
595
|
return base;
|
|
215
596
|
// Mescla só valores definidos (o `Partial` permite undefined); chaves omitidas
|
|
216
|
-
// seguem caindo no
|
|
597
|
+
// seguem caindo no catálogo base.
|
|
217
598
|
for (const [key, value] of Object.entries(overrides)) {
|
|
218
599
|
if (value !== undefined)
|
|
219
600
|
base[key] = value;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import type { ResolvedServerConfig } from '../define_config.js';
|
|
3
|
+
/** Dados de um login bem-sucedido a auditar/notificar. */
|
|
4
|
+
export interface LoginSuccessInput {
|
|
5
|
+
accountId: string;
|
|
6
|
+
email?: string | null;
|
|
7
|
+
ip?: string | null;
|
|
8
|
+
clientId?: string | null;
|
|
9
|
+
/** Metadata extra a anexar ao evento login.success (ex.: { mfa: 'totp' }). */
|
|
10
|
+
metadata?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Centraliza o pós-login bem-sucedido: registra o evento `login.success` e dispara
|
|
14
|
+
* (best-effort) o alerta de NOVO acesso quando o IP nunca foi visto para a conta.
|
|
15
|
+
*
|
|
16
|
+
* É fire-and-forget e FAIL-SAFE: a notificação roda DEPOIS do audit e qualquer erro
|
|
17
|
+
* é engolido — NUNCA bloqueia nem lança no caminho do login. Substitui as chamadas
|
|
18
|
+
* `cfg.audit?.record({ type: 'login.success', ... })` espalhadas pelos controllers.
|
|
19
|
+
*/
|
|
20
|
+
export declare function notifyLoginSuccess(ctx: HttpContext, cfg: ResolvedServerConfig, input: LoginSuccessInput): Promise<void>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { sendNewLoginEmail } from './default_mailer.js';
|
|
2
|
+
/**
|
|
3
|
+
* Centraliza o pós-login bem-sucedido: registra o evento `login.success` e dispara
|
|
4
|
+
* (best-effort) o alerta de NOVO acesso quando o IP nunca foi visto para a conta.
|
|
5
|
+
*
|
|
6
|
+
* É fire-and-forget e FAIL-SAFE: a notificação roda DEPOIS do audit e qualquer erro
|
|
7
|
+
* é engolido — NUNCA bloqueia nem lança no caminho do login. Substitui as chamadas
|
|
8
|
+
* `cfg.audit?.record({ type: 'login.success', ... })` espalhadas pelos controllers.
|
|
9
|
+
*/
|
|
10
|
+
export async function notifyLoginSuccess(ctx, cfg, input) {
|
|
11
|
+
const { accountId, email, ip, clientId, metadata } = input;
|
|
12
|
+
// 1) Audit do login.success (mesmo formato de antes).
|
|
13
|
+
await cfg.audit?.record({
|
|
14
|
+
type: 'login.success',
|
|
15
|
+
accountId,
|
|
16
|
+
email: email ?? null,
|
|
17
|
+
ip: ip ?? null,
|
|
18
|
+
clientId: clientId ?? null,
|
|
19
|
+
metadata,
|
|
20
|
+
});
|
|
21
|
+
// 2) Alerta de novo acesso (opt-out via notifications.newLoginEmail: false).
|
|
22
|
+
if (!cfg.notifications.newLoginEmail)
|
|
23
|
+
return;
|
|
24
|
+
// Fire-and-forget: nunca propaga erro pro caminho do login.
|
|
25
|
+
void (async () => {
|
|
26
|
+
// Resolve o e-mail quando o caller não o forneceu (ex.: fluxo de MFA só tem o
|
|
27
|
+
// accountId em escopo). Best-effort.
|
|
28
|
+
let resolvedEmail = email ?? null;
|
|
29
|
+
if (!resolvedEmail) {
|
|
30
|
+
resolvedEmail = (await cfg.accountStore.findById(accountId))?.email ?? null;
|
|
31
|
+
}
|
|
32
|
+
await maybeNotifyNewLogin(ctx, cfg, { accountId, email: resolvedEmail, ip: ip ?? null });
|
|
33
|
+
})().catch((error) => {
|
|
34
|
+
ctx.logger.error({ err: error, accountId }, 'authkit: falha no alerta de novo acesso');
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Verifica se o IP é novo para a conta (consultando o audit sink por
|
|
39
|
+
* `login.success` do subject) e, se for, envia o e-mail de alerta + audita
|
|
40
|
+
* `login.new_ip_notified`. Degrada para no-op quando: sem IP, sem e-mail, sem
|
|
41
|
+
* sink consultável (`list`), ou já houve um login.success deste IP antes.
|
|
42
|
+
*/
|
|
43
|
+
async function maybeNotifyNewLogin(ctx, cfg, data) {
|
|
44
|
+
const { accountId, email, ip } = data;
|
|
45
|
+
if (!ip || !email)
|
|
46
|
+
return;
|
|
47
|
+
// Sem consulta do histórico não dá pra decidir se o IP é novo → no-op.
|
|
48
|
+
if (typeof cfg.audit?.list !== 'function')
|
|
49
|
+
return;
|
|
50
|
+
// Lê o histórico de login.success do subject. O evento ATUAL já foi gravado por
|
|
51
|
+
// notifyLoginSuccess, então um IP visto antes aparece com count >= 2 para o IP.
|
|
52
|
+
// Buscamos uma página ampla e contamos as ocorrências deste IP.
|
|
53
|
+
const page = await cfg.audit.list({
|
|
54
|
+
type: 'login.success',
|
|
55
|
+
subject: accountId,
|
|
56
|
+
page: 1,
|
|
57
|
+
limit: 200,
|
|
58
|
+
});
|
|
59
|
+
const sameIpCount = page.data.filter((e) => e.ip === ip).length;
|
|
60
|
+
// > 1 significa que já havia um login.success deste IP antes do atual → não é novo.
|
|
61
|
+
if (sameIpCount > 1)
|
|
62
|
+
return;
|
|
63
|
+
const when = new Date().toISOString();
|
|
64
|
+
await sendNewLoginEmail(ctx, { email, ip, when });
|
|
65
|
+
await cfg.audit?.record({
|
|
66
|
+
type: 'login.new_ip_notified',
|
|
67
|
+
accountId,
|
|
68
|
+
email,
|
|
69
|
+
ip,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -52,9 +52,11 @@ const C = {
|
|
|
52
52
|
patIntrospection: () => import('./controllers/pat_introspection_controller.js'),
|
|
53
53
|
accountSession: () => import('./controllers/account_session_controller.js'),
|
|
54
54
|
accountTokens: () => import('./controllers/account_tokens_controller.js'),
|
|
55
|
+
accountSecurity: () => import('./controllers/account_security_controller.js'),
|
|
55
56
|
accountMfa: () => import('./controllers/account_mfa_controller.js'),
|
|
56
57
|
adminDashboard: () => import('./controllers/admin/admin_dashboard_controller.js'),
|
|
57
58
|
adminUsers: () => import('./controllers/admin/admin_users_controller.js'),
|
|
59
|
+
adminSessions: () => import('./controllers/admin/admin_sessions_controller.js'),
|
|
58
60
|
adminClients: () => import('./controllers/admin/admin_clients_controller.js'),
|
|
59
61
|
adminAudit: () => import('./controllers/admin/admin_audit_controller.js'),
|
|
60
62
|
};
|
|
@@ -108,12 +110,19 @@ export function registerAuthHost(router, opts) {
|
|
|
108
110
|
router.get('/account/login', [C.accountSession, 'show']);
|
|
109
111
|
router.post('/account/login', [C.accountSession, 'login']);
|
|
110
112
|
router.post('/account/logout', [C.accountSession, 'logout']);
|
|
113
|
+
// Confirmação de troca de e-mail (standalone, GET-only — consome o token do link;
|
|
114
|
+
// pode ser aberta em outro dispositivo, então NÃO exige sessão).
|
|
115
|
+
router.get('/account/email/confirm', [C.accountSecurity, 'confirmEmail']);
|
|
111
116
|
// Rotas de tokens protegidas por AccountAuthMiddleware (redireciona para /account/login se não autenticado).
|
|
112
117
|
router
|
|
113
118
|
.group(() => {
|
|
114
119
|
router.get('/account/tokens', [C.accountTokens, 'index']);
|
|
115
120
|
router.post('/account/tokens', [C.accountTokens, 'store']);
|
|
116
121
|
router.post('/account/tokens/:id/revoke', [C.accountTokens, 'destroy']);
|
|
122
|
+
// Segurança da conta: trocar senha + solicitar troca de e-mail.
|
|
123
|
+
router.get('/account/security', [C.accountSecurity, 'index']);
|
|
124
|
+
router.post('/account/security/password', [C.accountSecurity, 'changePassword']);
|
|
125
|
+
router.post('/account/security/email', [C.accountSecurity, 'changeEmail']);
|
|
117
126
|
// MFA / TOTP (enrollment, confirmação, disable).
|
|
118
127
|
router.get('/account/mfa', [C.accountMfa, 'index']);
|
|
119
128
|
router.post('/account/mfa/enroll', [C.accountMfa, 'enroll']);
|
|
@@ -132,6 +141,9 @@ export function registerAuthHost(router, opts) {
|
|
|
132
141
|
router.get('/admin', [C.adminDashboard, 'index']);
|
|
133
142
|
router.get('/admin/users', [C.adminUsers, 'index']);
|
|
134
143
|
router.post('/admin/users/:id/roles', [C.adminUsers, 'updateRoles']);
|
|
144
|
+
// Sessões/grants ativos da conta + revogação em massa.
|
|
145
|
+
router.get('/admin/users/:id/sessions', [C.adminSessions, 'index']);
|
|
146
|
+
router.post('/admin/users/:id/revoke-sessions', [C.adminSessions, 'revoke']);
|
|
135
147
|
router.get('/admin/clients', [C.adminClients, 'index']);
|
|
136
148
|
// CRUD de clients OIDC (adapter-backed). `/new` ANTES de `:id` p/ não casar
|
|
137
149
|
// "new" como id; todas as escritas são POST (com _csrf na view).
|
|
@@ -37,3 +37,35 @@ export declare const resetPasswordValidator: import("@vinejs/vine").VineValidato
|
|
|
37
37
|
password: string;
|
|
38
38
|
token: string;
|
|
39
39
|
}>, Record<string, any> | undefined>;
|
|
40
|
+
/**
|
|
41
|
+
* Troca de senha no console de conta. A regra da nova senha espelha o
|
|
42
|
+
* signupValidator (min 8, max 255); a senha atual é confirmada à parte via
|
|
43
|
+
* verifyCredentials.
|
|
44
|
+
*/
|
|
45
|
+
export declare const changePasswordValidator: import("@vinejs/vine").VineValidator<import("@vinejs/vine").VineObject<{
|
|
46
|
+
currentPassword: import("@vinejs/vine").VineString;
|
|
47
|
+
newPassword: import("@vinejs/vine").VineString;
|
|
48
|
+
}, {
|
|
49
|
+
currentPassword: string;
|
|
50
|
+
newPassword: string;
|
|
51
|
+
}, {
|
|
52
|
+
currentPassword: string;
|
|
53
|
+
newPassword: string;
|
|
54
|
+
}, {
|
|
55
|
+
currentPassword: string;
|
|
56
|
+
newPassword: string;
|
|
57
|
+
}>, Record<string, any> | undefined>;
|
|
58
|
+
/** Troca de e-mail no console de conta: senha atual + o novo e-mail. */
|
|
59
|
+
export declare const changeEmailValidator: import("@vinejs/vine").VineValidator<import("@vinejs/vine").VineObject<{
|
|
60
|
+
currentPassword: import("@vinejs/vine").VineString;
|
|
61
|
+
newEmail: import("@vinejs/vine").VineString;
|
|
62
|
+
}, {
|
|
63
|
+
newEmail: string;
|
|
64
|
+
currentPassword: string;
|
|
65
|
+
}, {
|
|
66
|
+
newEmail: string;
|
|
67
|
+
currentPassword: string;
|
|
68
|
+
}, {
|
|
69
|
+
newEmail: string;
|
|
70
|
+
currentPassword: string;
|
|
71
|
+
}>, Record<string, any> | undefined>;
|