@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.
Files changed (48) 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 +66 -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 +8 -2
  9. package/build/index.js +4 -1
  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 +82 -0
  20. package/build/src/define_config.js +24 -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/avatar_storage.d.ts +58 -0
  27. package/build/src/host/avatar_storage.js +125 -0
  28. package/build/src/host/controllers/account_apps_controller.d.ts +15 -0
  29. package/build/src/host/controllers/account_apps_controller.js +61 -0
  30. package/build/src/host/controllers/account_security_controller.d.ts +9 -0
  31. package/build/src/host/controllers/account_security_controller.js +84 -2
  32. package/build/src/host/controllers/account_session_controller.js +3 -1
  33. package/build/src/host/controllers/admin/admin_users_controller.d.ts +13 -0
  34. package/build/src/host/controllers/admin/admin_users_controller.js +133 -0
  35. package/build/src/host/controllers/interaction_controller.d.ts +32 -0
  36. package/build/src/host/controllers/interaction_controller.js +169 -6
  37. package/build/src/host/default_mailer.d.ts +8 -0
  38. package/build/src/host/default_mailer.js +28 -0
  39. package/build/src/host/i18n.d.ts +98 -0
  40. package/build/src/host/i18n.js +106 -0
  41. package/build/src/host/login_attempt.d.ts +1 -0
  42. package/build/src/host/login_attempt.js +11 -0
  43. package/build/src/host/register_auth_host.js +18 -1
  44. package/build/src/host/trusted_device.d.ts +61 -0
  45. package/build/src/host/trusted_device.js +65 -0
  46. package/build/src/host/validators.d.ts +35 -0
  47. package/build/src/host/validators.js +14 -0
  48. package/package.json +6 -1
@@ -3,7 +3,9 @@ import { brandFor, isFirstParty } from '../branding.js';
3
3
  import { translate } from '../i18n.js';
4
4
  import { attemptPasswordLogin } from '../login_attempt.js';
5
5
  import { notifyLoginSuccess } from '../login_notify.js';
6
- import { supportsPasskeys } from '../../accounts/account_store.js';
6
+ import { supportsPasskeys, supportsMagicLink } from '../../accounts/account_store.js';
7
+ import { sendMagicLinkEmail } from '../default_mailer.js';
8
+ import { TRUSTED_DEVICE_COOKIE, buildTrustedDevicePayload, isTrustedDeviceValid, } from '../trusted_device.js';
7
9
  const SESSION_KEY = 'authkit_login_email';
8
10
  /** accountId aguardando o 2º fator depois da senha verificada. */
9
11
  const MFA_PENDING_KEY = 'authkit_mfa_pending';
@@ -45,6 +47,10 @@ export default class AuthInteractionController {
45
47
  // Step 2: password — look up user for personalisation (enumeration-safe: always show step 2)
46
48
  const acc = await cfg.accountStore.findByEmail(email);
47
49
  const account = acc ? { fullName: acc.name ?? null, globalRoles: acc.globalRoles ?? [] } : null;
50
+ // Passwordless: magic link disponível se ligado E o store suporta. Passkey-first
51
+ // disponível se ligado, o store suporta E a conta tem ao menos uma passkey.
52
+ const magicLinkAvailable = cfg.passwordless.magicLink && supportsMagicLink(cfg.accountStore);
53
+ const passkeyFirstAvailable = cfg.passwordless.passkeyFirst && !!acc && (await this.hasPasskeys(cfg, acc.id));
48
54
  return render(ctx, 'login', {
49
55
  uid: details.uid,
50
56
  csrfToken: ctx.request.csrfToken,
@@ -52,6 +58,8 @@ export default class AuthInteractionController {
52
58
  email,
53
59
  account,
54
60
  brand,
61
+ magicLinkAvailable,
62
+ passkeyFirstAvailable,
55
63
  });
56
64
  }
57
65
  /**
@@ -102,7 +110,9 @@ export default class AuthInteractionController {
102
110
  ? translate(cfg.messages, 'errors.account_locked', {
103
111
  seconds: result.retryAfterSec ?? 0,
104
112
  })
105
- : translate(cfg.messages, 'errors.invalid_credentials'),
113
+ : result.disabled
114
+ ? translate(cfg.messages, 'errors.account_disabled')
115
+ : translate(cfg.messages, 'errors.invalid_credentials'),
106
116
  brand,
107
117
  });
108
118
  }
@@ -126,6 +136,18 @@ export default class AuthInteractionController {
126
136
  noEnrollment: true,
127
137
  });
128
138
  }
139
+ // Trusted device: se o mecanismo está ligado, a conta JÁ tem MFA enrolado e
140
+ // o request NÃO é um step-up (que sempre força o MFA), um cookie de confiança
141
+ // válido para ESTA conta pula o 2º fator. amr fica `['pwd']` (sem acr de MFA).
142
+ if (cfg.trustedDevices.enabled && mfa.enabled && !mfaRequired) {
143
+ const trusted = await this.checkTrustedDevice(ctx, acc.id, mfa.enabledAt ?? null);
144
+ if (trusted) {
145
+ await service.interactions.completeLogin(ctx, acc.id, { amr: ['pwd'] });
146
+ await notifyLoginSuccess(ctx, cfg, { accountId: acc.id, email, ip, clientId });
147
+ ctx.session.forget(SESSION_KEY);
148
+ return;
149
+ }
150
+ }
129
151
  ctx.session.put(MFA_PENDING_KEY, acc.id);
130
152
  // Passkey disponível como alternativa ao TOTP se o store suporta E a conta
131
153
  // tem ao menos uma credencial registrada.
@@ -135,6 +157,8 @@ export default class AuthInteractionController {
135
157
  csrfToken: ctx.request.csrfToken,
136
158
  brand,
137
159
  passkeyAvailable,
160
+ trustedDevicesEnabled: cfg.trustedDevices.enabled,
161
+ trustedDeviceDays: cfg.trustedDevices.days,
138
162
  });
139
163
  }
140
164
  // Sem MFA: finaliza a interaction (escreve o 303 de volta para o client).
@@ -185,9 +209,13 @@ export default class AuthInteractionController {
185
209
  error: translate(cfg.messages, 'errors.invalid_code'),
186
210
  brand,
187
211
  passkeyAvailable: await this.hasPasskeys(cfg, accountId),
212
+ trustedDevicesEnabled: cfg.trustedDevices.enabled,
213
+ trustedDeviceDays: cfg.trustedDevices.days,
188
214
  });
189
215
  }
190
- // Sucesso no 2º fator: finaliza a interaction para o accountId pendente.
216
+ // Sucesso no 2º fator: opcionalmente confia neste dispositivo (checkbox).
217
+ await this.maybeTrustDevice(ctx, cfg, accountId);
218
+ // Finaliza a interaction para o accountId pendente.
191
219
  ctx.session.forget(MFA_PENDING_KEY);
192
220
  ctx.session.forget(SESSION_KEY);
193
221
  await notifyLoginSuccess(ctx, cfg, {
@@ -230,6 +258,135 @@ export default class AuthInteractionController {
230
258
  const list = await cfg.accountStore.listPasskeys(accountId);
231
259
  return Array.isArray(list) && list.length > 0;
232
260
  }
261
+ /**
262
+ * Lê o cookie de dispositivo confiável (encriptado, appKey-backed) e valida que
263
+ * pertence a `accountId`, não expirou e é posterior ao último (re)enrollment de
264
+ * MFA. Step-up NÃO chama isto (força sempre o MFA). Best-effort: qualquer erro de
265
+ * leitura → não confiável.
266
+ */
267
+ async checkTrustedDevice(ctx, accountId, mfaEnabledAt) {
268
+ try {
269
+ const payload = ctx.request.encryptedCookie(TRUSTED_DEVICE_COOKIE);
270
+ return isTrustedDeviceValid(payload, { accountId, mfaEnabledAt });
271
+ }
272
+ catch {
273
+ return false;
274
+ }
275
+ }
276
+ /**
277
+ * Se o checkbox "confiar neste dispositivo" foi marcado E o mecanismo está ligado,
278
+ * grava o cookie encriptado de confiança para a conta (skip MFA por N dias).
279
+ */
280
+ async maybeTrustDevice(ctx, cfg, accountId) {
281
+ if (!cfg.trustedDevices.enabled)
282
+ return;
283
+ const checked = ctx.request.input('trustDevice');
284
+ // Checkbox HTML: presente ('on'/'true'/'1') = marcado.
285
+ const on = checked === 'on' || checked === 'true' || checked === '1' || checked === true;
286
+ if (!on)
287
+ return;
288
+ const payload = buildTrustedDevicePayload(accountId, cfg.trustedDevices);
289
+ ctx.response.encryptedCookie(TRUSTED_DEVICE_COOKIE, payload, {
290
+ httpOnly: true,
291
+ sameSite: 'lax',
292
+ maxAge: cfg.trustedDevices.days * 24 * 60 * 60,
293
+ });
294
+ }
295
+ /**
296
+ * accountId para uma cerimônia de passkey no login. Prioriza o accountId pendente
297
+ * do MFA (passkey como 2º fator). Quando ausente e o passkey-first está ligado,
298
+ * resolve a conta pelo e-mail guardado na sessão (passkey ANTES da senha) — só se
299
+ * a conta existe E tem ao menos uma passkey.
300
+ */
301
+ async resolvePasskeyAccountId(ctx, cfg) {
302
+ const pending = ctx.session.get(MFA_PENDING_KEY);
303
+ if (pending)
304
+ return pending;
305
+ if (!cfg.passwordless?.passkeyFirst)
306
+ return undefined;
307
+ const email = ctx.session.get(SESSION_KEY);
308
+ if (!email)
309
+ return undefined;
310
+ const acc = await cfg.accountStore.findByEmail(email);
311
+ if (!acc)
312
+ return undefined;
313
+ return (await this.hasPasskeys(cfg, acc.id)) ? acc.id : undefined;
314
+ }
315
+ /**
316
+ * POST /auth/interaction/:uid/magic
317
+ * Magic link: lê o e-mail da sessão (passwordless.magicLink ligado), emite um
318
+ * token de uso único e dispara o e-mail. SEMPRE renderiza "link enviado",
319
+ * independentemente de a conta existir (anti-enumeração). Throttled como o login.
320
+ */
321
+ async magicLinkRequest(ctx) {
322
+ const service = await ctx.containerResolver.make('authkit.server');
323
+ const cfg = service.config;
324
+ const render = cfg.render;
325
+ const details = await service.interactions.details(ctx);
326
+ const brand = brandFor(cfg.branding, details.params.client_id, details.params.audience);
327
+ const email = ctx.session.get(SESSION_KEY);
328
+ const uid = ctx.request.param('uid');
329
+ if (cfg.passwordless.magicLink && supportsMagicLink(cfg.accountStore) && email) {
330
+ const issued = await cfg.accountStore.issueMagicLinkToken(email);
331
+ if (issued) {
332
+ await cfg.audit?.record({
333
+ type: 'login.magic_link_sent',
334
+ accountId: issued.account.id,
335
+ email,
336
+ ip: ctx.request.ip?.() ?? null,
337
+ clientId: details.params.client_id ?? null,
338
+ });
339
+ const origin = `${ctx.request.protocol()}://${ctx.request.host()}`;
340
+ const magicUrl = `${origin}/auth/interaction/${uid}/magic?token=${encodeURIComponent(issued.token)}`;
341
+ if (cfg.mail?.onMagicLink) {
342
+ await cfg.mail.onMagicLink({ email, magicUrl, token: issued.token });
343
+ }
344
+ else {
345
+ await sendMagicLinkEmail(ctx, { email, magicUrl });
346
+ }
347
+ }
348
+ }
349
+ // Resposta uniforme (não vaza existência de conta).
350
+ return render(ctx, 'login', {
351
+ uid,
352
+ csrfToken: ctx.request.csrfToken,
353
+ step: 'password',
354
+ email,
355
+ account: null,
356
+ brand,
357
+ magicLinkSent: true,
358
+ });
359
+ }
360
+ /**
361
+ * GET /auth/interaction/:uid/magic?token=...
362
+ * Consome o magic link. Em sucesso finaliza o login (amr `['email']`). Token
363
+ * inválido/expirado volta ao início do login.
364
+ */
365
+ async magicLinkConsume(ctx) {
366
+ const service = await ctx.containerResolver.make('authkit.server');
367
+ const cfg = service.config;
368
+ const uid = ctx.request.param('uid');
369
+ const ip = ctx.request.ip?.() ?? null;
370
+ const clientId = (await service.interactions.details(ctx)).params.client_id;
371
+ const token = ctx.request.qs().token ?? '';
372
+ if (!cfg.passwordless.magicLink || !supportsMagicLink(cfg.accountStore) || !token) {
373
+ return ctx.response.redirect(`/auth/interaction/${uid}`);
374
+ }
375
+ const acc = await cfg.accountStore.consumeMagicLinkToken(token);
376
+ if (!acc) {
377
+ await cfg.audit?.record({ type: 'login.failure', ip, clientId, metadata: { stage: 'magic_link' } });
378
+ return ctx.response.redirect(`/auth/interaction/${uid}`);
379
+ }
380
+ await notifyLoginSuccess(ctx, cfg, {
381
+ accountId: acc.id,
382
+ email: acc.email,
383
+ ip,
384
+ clientId: clientId ?? null,
385
+ metadata: { method: 'magic_link' },
386
+ });
387
+ ctx.session.forget(SESSION_KEY);
388
+ await service.interactions.completeLogin(ctx, acc.id, { amr: ['email'] });
389
+ }
233
390
  /**
234
391
  * POST /auth/interaction/:uid/passkey/options
235
392
  * Gera as opções de autenticação por passkey para o accountId pendente do MFA,
@@ -238,7 +395,7 @@ export default class AuthInteractionController {
238
395
  async passkeyOptions(ctx) {
239
396
  const service = await ctx.containerResolver.make('authkit.server');
240
397
  const cfg = service.config;
241
- const accountId = ctx.session.get(MFA_PENDING_KEY);
398
+ const accountId = await this.resolvePasskeyAccountId(ctx, cfg);
242
399
  if (!accountId) {
243
400
  return ctx.response.badRequest({
244
401
  message: translate(cfg.messages, 'errors.session_expired'),
@@ -266,7 +423,7 @@ export default class AuthInteractionController {
266
423
  const render = cfg.render;
267
424
  const details = await service.interactions.details(ctx);
268
425
  const brand = brandFor(cfg.branding, details.params.client_id, details.params.audience);
269
- const accountId = ctx.session.get(MFA_PENDING_KEY);
426
+ const accountId = await this.resolvePasskeyAccountId(ctx, cfg);
270
427
  const challenge = ctx.session.get(PASSKEY_AUTH_CHALLENGE_KEY);
271
428
  const ip = ctx.request.ip?.() ?? null;
272
429
  const clientId = details.params.client_id ?? null;
@@ -301,8 +458,12 @@ export default class AuthInteractionController {
301
458
  error: translate(cfg.messages, 'mfa_challenge.passkey_error'),
302
459
  brand,
303
460
  passkeyAvailable: await this.hasPasskeys(cfg, accountId),
461
+ trustedDevicesEnabled: cfg.trustedDevices.enabled,
462
+ trustedDeviceDays: cfg.trustedDevices.days,
304
463
  });
305
464
  }
465
+ // Passkey OK: opcionalmente confia neste dispositivo (checkbox no challenge).
466
+ await this.maybeTrustDevice(ctx, cfg, accountId);
306
467
  ctx.session.forget(MFA_PENDING_KEY);
307
468
  ctx.session.forget(SESSION_KEY);
308
469
  await notifyLoginSuccess(ctx, cfg, {
@@ -311,7 +472,9 @@ export default class AuthInteractionController {
311
472
  clientId,
312
473
  metadata: { mfa: 'webauthn' },
313
474
  });
314
- await service.interactions.completeLogin(ctx, accountId, this.stepUpExtra(cfg, details, 'webauthn'));
475
+ // Step-up carimba acr/amr quando solicitado; senão a passkey conta como o fator
476
+ // forte do login (amr `['webauthn']`) — vale tanto p/ MFA quanto passkey-first.
477
+ await service.interactions.completeLogin(ctx, accountId, this.stepUpExtra(cfg, details, 'webauthn') ?? { amr: ['webauthn'] });
315
478
  }
316
479
  /**
317
480
  * GET /auth/interaction/:uid/switch
@@ -45,6 +45,14 @@ export declare function sendNewLoginEmail(ctx: HttpContext, data: {
45
45
  ip: string;
46
46
  when: string;
47
47
  }): Promise<void>;
48
+ /**
49
+ * Envia o e-mail de magic link (login passwordless) pelo mailer default do host.
50
+ * Best-effort: no fallback (sem mail) loga o link; nunca lança.
51
+ */
52
+ export declare function sendMagicLinkEmail(ctx: HttpContext, data: {
53
+ email: string;
54
+ magicUrl: string;
55
+ }): Promise<void>;
48
56
  /**
49
57
  * Envia o e-mail de verificação pelo mailer default do host.
50
58
  * Best-effort: no fallback (sem mail) loga o link; nunca lança.
@@ -197,6 +197,34 @@ export async function sendNewLoginEmail(ctx, data) {
197
197
  ctx.logger.error({ err: error, email: data.email }, 'authkit: falha ao enviar alerta de novo acesso');
198
198
  }
199
199
  }
200
+ /**
201
+ * Envia o e-mail de magic link (login passwordless) pelo mailer default do host.
202
+ * Best-effort: no fallback (sem mail) loga o link; nunca lança.
203
+ */
204
+ export async function sendMagicLinkEmail(ctx, data) {
205
+ try {
206
+ const brand = resolveBrand(ctx);
207
+ const { messages: t, locale } = resolveMailMessages(ctx);
208
+ const content = renderTransactionalEmail({
209
+ brand,
210
+ locale,
211
+ linkFallback: translate(t, 'mail.common.link_fallback'),
212
+ subject: translate(t, 'mail.magic_link.subject'),
213
+ heading: translate(t, 'mail.magic_link.heading'),
214
+ intro: translate(t, 'mail.magic_link.intro'),
215
+ ctaLabel: translate(t, 'mail.magic_link.cta'),
216
+ ctaUrl: data.magicUrl,
217
+ footnote: translate(t, 'mail.magic_link.fallback'),
218
+ });
219
+ const sent = await sendEmail(ctx, data.email, content);
220
+ if (!sent) {
221
+ ctx.logger.info({ magicUrl: data.magicUrl, email: data.email }, 'authkit: magic link de login (dev — @adonisjs/mail ausente)');
222
+ }
223
+ }
224
+ catch (error) {
225
+ ctx.logger.error({ err: error, email: data.email }, 'authkit: falha ao enviar magic link de login');
226
+ }
227
+ }
200
228
  /**
201
229
  * Envia o e-mail de verificação pelo mailer default do host.
202
230
  * Best-effort: no fallback (sem mail) loga o link; nunca lança.
@@ -44,6 +44,9 @@ export declare const DEFAULT_MESSAGES: {
44
44
  'login.switch_account': string;
45
45
  'login.password_label': string;
46
46
  'login.submit': string;
47
+ 'login.magic_link_button': string;
48
+ 'login.magic_link_sent': string;
49
+ 'login.passkey_button': string;
47
50
  'signup.page_title': string;
48
51
  'signup.title': string;
49
52
  'signup.intro': string;
@@ -80,6 +83,7 @@ export declare const DEFAULT_MESSAGES: {
80
83
  'mfa_challenge.recovery_submit': string;
81
84
  'mfa_challenge.passkey_button': string;
82
85
  'mfa_challenge.passkey_error': string;
86
+ 'mfa_challenge.trust_device': string;
83
87
  'consent.page_title': string;
84
88
  'consent.title': string;
85
89
  'consent.body': string;
@@ -121,6 +125,31 @@ export declare const DEFAULT_MESSAGES: {
121
125
  'account.security.change_email_submit': string;
122
126
  'account.security.email_change_requested': string;
123
127
  'account.security.email_changed': string;
128
+ 'account.security.trusted_devices_section': string;
129
+ 'account.security.trusted_devices_intro': string;
130
+ 'account.security.trusted_devices_revoke': string;
131
+ 'account.security.trusted_devices_revoked': string;
132
+ 'account.profile.section': string;
133
+ 'account.profile.intro': string;
134
+ 'account.profile.name_label': string;
135
+ 'account.profile.avatar_label': string;
136
+ 'account.profile.avatar_upload_label': string;
137
+ 'account.profile.avatar_upload_hint': string;
138
+ 'account.profile.avatar_invalid_type': string;
139
+ 'account.profile.avatar_too_large': string;
140
+ 'account.profile.submit': string;
141
+ 'account.profile.updated': string;
142
+ 'account.profile.not_supported': string;
143
+ 'account.apps.page_title': string;
144
+ 'account.apps.title': string;
145
+ 'account.apps.intro': string;
146
+ 'account.apps.logout': string;
147
+ 'account.apps.empty': string;
148
+ 'account.apps.tokens': string;
149
+ 'account.apps.revoke': string;
150
+ 'account.apps.revoke_confirm': string;
151
+ 'account.apps.revoked': string;
152
+ 'account.apps.not_supported': string;
124
153
  'account.email_confirmed.page_title': string;
125
154
  'account.email_confirmed.ok_title': string;
126
155
  'account.email_confirmed.ok_body': string;
@@ -167,6 +196,20 @@ export declare const DEFAULT_MESSAGES: {
167
196
  'admin.users.roles_placeholder': string;
168
197
  'admin.users.save_roles': string;
169
198
  'admin.users.sessions': string;
199
+ 'admin.users.create_section': string;
200
+ 'admin.users.create_name_placeholder': string;
201
+ 'admin.users.create_email_placeholder': string;
202
+ 'admin.users.create_password_placeholder': string;
203
+ 'admin.users.create_submit': string;
204
+ 'admin.users.created': string;
205
+ 'admin.users.reset_password': string;
206
+ 'admin.users.reset_sent': string;
207
+ 'admin.users.disable': string;
208
+ 'admin.users.enable': string;
209
+ 'admin.users.disabled': string;
210
+ 'admin.users.enabled': string;
211
+ 'admin.users.disabled_badge': string;
212
+ 'admin.users.disable_confirm': string;
170
213
  'admin.sessions.page_title': string;
171
214
  'admin.sessions.title': string;
172
215
  'admin.sessions.account': string;
@@ -243,6 +286,7 @@ export declare const DEFAULT_MESSAGES: {
243
286
  'mfa_challenge.required_no_enrollment': string;
244
287
  'errors.invalid_credentials': string;
245
288
  'errors.invalid_code': string;
289
+ 'errors.account_disabled': string;
246
290
  'errors.email_taken': string;
247
291
  'errors.signup_failed': string;
248
292
  'errors.invalid_or_expired_token': string;
@@ -264,6 +308,11 @@ export declare const DEFAULT_MESSAGES: {
264
308
  'mail.verify.cta': string;
265
309
  'mail.verify.fallback': string;
266
310
  'mail.verify.expires': string;
311
+ 'mail.magic_link.subject': string;
312
+ 'mail.magic_link.heading': string;
313
+ 'mail.magic_link.intro': string;
314
+ 'mail.magic_link.cta': string;
315
+ 'mail.magic_link.fallback': string;
267
316
  'mail.new_login.subject': string;
268
317
  'mail.new_login.heading': string;
269
318
  'mail.new_login.intro': string;
@@ -298,6 +347,9 @@ export declare const PT_BR_MESSAGES: {
298
347
  'login.switch_account': string;
299
348
  'login.password_label': string;
300
349
  'login.submit': string;
350
+ 'login.magic_link_button': string;
351
+ 'login.magic_link_sent': string;
352
+ 'login.passkey_button': string;
301
353
  'signup.page_title': string;
302
354
  'signup.title': string;
303
355
  'signup.intro': string;
@@ -334,6 +386,7 @@ export declare const PT_BR_MESSAGES: {
334
386
  'mfa_challenge.recovery_submit': string;
335
387
  'mfa_challenge.passkey_button': string;
336
388
  'mfa_challenge.passkey_error': string;
389
+ 'mfa_challenge.trust_device': string;
337
390
  'consent.page_title': string;
338
391
  'consent.title': string;
339
392
  'consent.body': string;
@@ -375,6 +428,31 @@ export declare const PT_BR_MESSAGES: {
375
428
  'account.security.change_email_submit': string;
376
429
  'account.security.email_change_requested': string;
377
430
  'account.security.email_changed': string;
431
+ 'account.security.trusted_devices_section': string;
432
+ 'account.security.trusted_devices_intro': string;
433
+ 'account.security.trusted_devices_revoke': string;
434
+ 'account.security.trusted_devices_revoked': string;
435
+ 'account.profile.section': string;
436
+ 'account.profile.intro': string;
437
+ 'account.profile.name_label': string;
438
+ 'account.profile.avatar_label': string;
439
+ 'account.profile.avatar_upload_label': string;
440
+ 'account.profile.avatar_upload_hint': string;
441
+ 'account.profile.avatar_invalid_type': string;
442
+ 'account.profile.avatar_too_large': string;
443
+ 'account.profile.submit': string;
444
+ 'account.profile.updated': string;
445
+ 'account.profile.not_supported': string;
446
+ 'account.apps.page_title': string;
447
+ 'account.apps.title': string;
448
+ 'account.apps.intro': string;
449
+ 'account.apps.logout': string;
450
+ 'account.apps.empty': string;
451
+ 'account.apps.tokens': string;
452
+ 'account.apps.revoke': string;
453
+ 'account.apps.revoke_confirm': string;
454
+ 'account.apps.revoked': string;
455
+ 'account.apps.not_supported': string;
378
456
  'account.email_confirmed.page_title': string;
379
457
  'account.email_confirmed.ok_title': string;
380
458
  'account.email_confirmed.ok_body': string;
@@ -421,6 +499,20 @@ export declare const PT_BR_MESSAGES: {
421
499
  'admin.users.roles_placeholder': string;
422
500
  'admin.users.save_roles': string;
423
501
  'admin.users.sessions': string;
502
+ 'admin.users.create_section': string;
503
+ 'admin.users.create_name_placeholder': string;
504
+ 'admin.users.create_email_placeholder': string;
505
+ 'admin.users.create_password_placeholder': string;
506
+ 'admin.users.create_submit': string;
507
+ 'admin.users.created': string;
508
+ 'admin.users.reset_password': string;
509
+ 'admin.users.reset_sent': string;
510
+ 'admin.users.disable': string;
511
+ 'admin.users.enable': string;
512
+ 'admin.users.disabled': string;
513
+ 'admin.users.enabled': string;
514
+ 'admin.users.disabled_badge': string;
515
+ 'admin.users.disable_confirm': string;
424
516
  'admin.sessions.page_title': string;
425
517
  'admin.sessions.title': string;
426
518
  'admin.sessions.account': string;
@@ -497,6 +589,7 @@ export declare const PT_BR_MESSAGES: {
497
589
  'mfa_challenge.required_no_enrollment': string;
498
590
  'errors.invalid_credentials': string;
499
591
  'errors.invalid_code': string;
592
+ 'errors.account_disabled': string;
500
593
  'errors.email_taken': string;
501
594
  'errors.signup_failed': string;
502
595
  'errors.invalid_or_expired_token': string;
@@ -518,6 +611,11 @@ export declare const PT_BR_MESSAGES: {
518
611
  'mail.verify.cta': string;
519
612
  'mail.verify.fallback': string;
520
613
  'mail.verify.expires': string;
614
+ 'mail.magic_link.subject': string;
615
+ 'mail.magic_link.heading': string;
616
+ 'mail.magic_link.intro': string;
617
+ 'mail.magic_link.cta': string;
618
+ 'mail.magic_link.fallback': string;
521
619
  'mail.new_login.subject': string;
522
620
  'mail.new_login.heading': string;
523
621
  'mail.new_login.intro': string;