@fonderie/auth 7.12.0 → 7.14.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.
@@ -101,13 +101,13 @@ interface IDataExportContributor {
101
101
  collect: (userId: string) => Promise<unknown> | unknown;
102
102
  }
103
103
 
104
- const AUTH_CONFIG_KEYS: { sessionDuration: string; verificationCooldown: string; mfa: string; requireVerification: string; }
104
+ const AUTH_CONFIG_KEYS: Record<keyof IAuthRuntimeConfig, string>
105
105
 
106
106
  const MESSAGE_KEYS: { readonly emailRegistration: "email-registration"; readonly emailVerification: "email-verification"; readonly passwordReset: "password-reset"; readonly phoneOtp: "phone-otp"; readonly mfaEnabled: "mfa-enabled"; readonly mfaDisabled: "mfa-disabled"; readonly mfaBackupCodesRegenerated: "mfa-backup-codes-regenerated"; readonly emailChanged: "email-changed"; readonly phoneChanged: "phone-changed"; readonly passwordRevoked: "password-revoked"; readonly oauthRegistration: "oauth-registration"; readonly oauthLinked: "oauth-linked"; readonly oauthUnlinked: "oauth-unlinked"; }
107
107
 
108
108
  type AuthMessageKey = (typeof MESSAGE_KEYS)[keyof typeof MESSAGE_KEYS];
109
109
 
110
- const DEFAULT_TEMPLATES: { "email-registration": { subject: string; html: string; text: string; }; "email-verification": { subject: string; html: string; text: string; }; "password-reset": { subject: string; html: string; text: string; }; "phone-otp": { text: string; }; "mfa-enabled": { subject: string; html: string; text: string; }; "mfa-disabled": { subject: string; html: string; text: string; }; "mfa-backup-codes-regenerated": { subject: string; html: string; text: string; }; "email-changed": { subject: string; html: string; text: string; }; "phone-changed": { subject: string; html: string; text: string; }; "password-revoked": { subject: string; html: string; text: string; }; "oauth-registration": { subject: string; html: string; text: string; }; "oauth-linked": { subject: string; html: string; text: string; }; "oauth-unlinked": { subject: string; html: string; text: string; }; }
110
+ const DEFAULT_TEMPLATES: { "email-registration": IDefaultTemplate; "email-verification": IDefaultTemplate; "password-reset": IDefaultTemplate; "phone-otp": IDefaultTemplate; "mfa-enabled": IDefaultTemplate; "mfa-disabled": IDefaultTemplate; "mfa-backup-codes-regenerated": IDefaultTemplate; "email-changed": IDefaultTemplate; "phone-changed": IDefaultTemplate; "password-revoked": IDefaultTemplate; "oauth-registration": IDefaultTemplate; "oauth-linked": IDefaultTemplate; "oauth-unlinked": IDefaultTemplate; }
111
111
 
112
112
  interface IUserDTO {
113
113
  id: string;
package/dist/index.cjs CHANGED
@@ -193,14 +193,16 @@ var email = import_zod.z.string().trim().pipe(import_zod.z.email());
193
193
  var password = import_zod.z.string().min(8, "password must be at least 8 characters").max(128);
194
194
  var phone = import_zod.z.string().refine((v) => /^\+?[1-9]\d{6,14}$/.test(v.replace(/[\s\-()]/g, "")), "Invalid phone number");
195
195
  var sixDigitPin = import_zod.z.string().trim().regex(/^\d{6}$/, "must be a 6-digit code");
196
+ var signupLocale = import_zod.z.string().max(35).nullish();
196
197
  var registerSchema = import_zod.z.union([
197
198
  import_zod.z.object({
198
199
  email,
199
200
  password,
200
201
  firstName: import_zod.z.string().max(100).nullish(),
201
- lastName: import_zod.z.string().max(100).nullish()
202
+ lastName: import_zod.z.string().max(100).nullish(),
203
+ locale: signupLocale
202
204
  }),
203
- import_zod.z.object({ phone })
205
+ import_zod.z.object({ phone, locale: signupLocale })
204
206
  ]);
205
207
  var loginSchema = import_zod.z.union([
206
208
  import_zod.z.object({ email, password: import_zod.z.string().min(1).max(128) }),
@@ -642,25 +644,25 @@ var UserModel = class {
642
644
  );
643
645
  return row ?? null;
644
646
  }
645
- async findOrCreateByPhone(phone2, firstName = null, lastName = null) {
647
+ async findOrCreateByPhone(phone2, firstName = null, lastName = null, locale = null) {
646
648
  const [row] = await this.store.query(
647
- `INSERT INTO fonderie_users (phone, first_name, last_name)
648
- VALUES ($1, $2, $3)
649
+ `INSERT INTO fonderie_users (phone, first_name, last_name, locale)
650
+ VALUES ($1, $2, $3, COALESCE($4, 'en-US'))
649
651
  ON CONFLICT (phone) DO UPDATE
650
652
  SET first_name = COALESCE(EXCLUDED.first_name, fonderie_users.first_name),
651
653
  last_name = COALESCE(EXCLUDED.last_name, fonderie_users.last_name),
652
654
  updated_at = now()
653
655
  RETURNING id`,
654
- [phone2, firstName, lastName]
656
+ [phone2, firstName, lastName, locale]
655
657
  );
656
658
  return row;
657
659
  }
658
- async create(email2, passwordHash, firstName, lastName) {
660
+ async create(email2, passwordHash, firstName, lastName, locale = null) {
659
661
  const [row] = await this.store.query(
660
- `INSERT INTO fonderie_users (email, password_hash, first_name, last_name)
661
- VALUES ($1, $2, $3, $4)
662
+ `INSERT INTO fonderie_users (email, password_hash, first_name, last_name, locale)
663
+ VALUES ($1, $2, $3, $4, COALESCE($5, 'en-US'))
662
664
  RETURNING id`,
663
- [email2.toLowerCase().trim(), passwordHash, firstName, lastName]
665
+ [email2.toLowerCase().trim(), passwordHash, firstName, lastName, locale]
664
666
  );
665
667
  return row ?? null;
666
668
  }
@@ -1619,7 +1621,7 @@ function extractRefreshToken(ctx) {
1619
1621
  const match = cookie.match(/(?:^|;\s*)refresh_token=([^;]+)/);
1620
1622
  return match?.[1] ?? null;
1621
1623
  }
1622
- function authController(store, config, bus) {
1624
+ function authController(store, config, bus, locales = (0, import_core6.defineLocales)()) {
1623
1625
  const users = new UserModel(store);
1624
1626
  const sessions = new SessionModel(store, config.location);
1625
1627
  const loginEvents = new LoginEventModel(store, config.location);
@@ -1631,6 +1633,7 @@ function authController(store, config, bus) {
1631
1633
  register: async (ctx) => {
1632
1634
  const body = ctx.meta["body"];
1633
1635
  const { email: email2, password: password2, phone: phone2, firstName = null, lastName = null } = body ?? {};
1636
+ const locale = (0, import_core6.canonicalLocale)(body?.["locale"]) ?? locales.default;
1634
1637
  if (typeof email2 === "string" && typeof password2 === "string") {
1635
1638
  const normalizedEmail = normalizeEmailSafe(email2);
1636
1639
  if (!normalizedEmail) {
@@ -1652,7 +1655,8 @@ function authController(store, config, bus) {
1652
1655
  normalizedEmail,
1653
1656
  passwordHash,
1654
1657
  firstName,
1655
- lastName
1658
+ lastName,
1659
+ locale
1656
1660
  );
1657
1661
  if (!row) {
1658
1662
  return (0, import_core6.setApiResponse)(import_core6.HTTP.SERVER_ERROR, "SERVER_ERROR", "Registration failed");
@@ -1725,7 +1729,8 @@ function authController(store, config, bus) {
1725
1729
  const { id } = await users.findOrCreateByPhone(
1726
1730
  normalizePhone(phone2),
1727
1731
  firstName ?? null,
1728
- lastName ?? null
1732
+ lastName ?? null,
1733
+ locale
1729
1734
  );
1730
1735
  const otp = (0, import_node_crypto5.randomInt)(1e5, 1e6).toString();
1731
1736
  const expiresAt = new Date(Date.now() + OTP_TTL_MS);
@@ -3136,9 +3141,9 @@ function oauthController(store, config, bus) {
3136
3141
  }
3137
3142
 
3138
3143
  // src/routes.ts
3139
- function buildAuthRoutes(store, config, bus) {
3144
+ function buildAuthRoutes(store, config, bus, locales) {
3140
3145
  const user = userController(store, config, bus);
3141
- const auth = authController(store, config, bus);
3146
+ const auth = authController(store, config, bus, locales);
3142
3147
  const oauth = oauthController(store, config, bus);
3143
3148
  const mfa = mfaController(store, config, config.appName ?? "Fonderie", bus);
3144
3149
  const verifyGate = config.requireVerification ? import_middlewares2.requireVerified : (_ctx, next) => next();
@@ -3252,20 +3257,27 @@ function collectAuthConfigProblems(config) {
3252
3257
  problems.push({
3253
3258
  module: MODULE,
3254
3259
  severity: "error",
3255
- message: `jwtSecret must be at least ${import_core11.MIN_SECRET_LENGTH} characters (got ${secret.length})`
3260
+ message: `jwtSecret must be at least ${import_core11.MIN_SECRET_LENGTH} characters (got ${secret.length})`,
3261
+ domain: "auth",
3262
+ reason: "JWT_SECRET_TOO_SHORT",
3263
+ metadata: { min: import_core11.MIN_SECRET_LENGTH, got: secret.length }
3256
3264
  });
3257
3265
  } else if (secretProblem === "placeholder") {
3258
3266
  problems.push({
3259
3267
  module: MODULE,
3260
3268
  severity: "error",
3261
- message: "jwtSecret looks like a placeholder or dev-default value"
3269
+ message: "jwtSecret looks like a placeholder or dev-default value",
3270
+ domain: "auth",
3271
+ reason: "JWT_SECRET_PLACEHOLDER"
3262
3272
  });
3263
3273
  }
3264
3274
  if (config.secureCookies === false) {
3265
3275
  problems.push({
3266
3276
  module: MODULE,
3267
3277
  severity: process.env["NODE_ENV"] === "production" ? "error" : "warning",
3268
- message: "secureCookies is false \u2014 auth cookies may be sent over non-HTTPS connections in production"
3278
+ message: "secureCookies is false \u2014 auth cookies may be sent over non-HTTPS connections in production",
3279
+ domain: "auth",
3280
+ reason: "INSECURE_COOKIES"
3269
3281
  });
3270
3282
  }
3271
3283
  if (config.google) {
@@ -3274,13 +3286,17 @@ function collectAuthConfigProblems(config) {
3274
3286
  problems.push({
3275
3287
  module: MODULE,
3276
3288
  severity: "error",
3277
- message: "google OAuth is configured but clientId, clientSecret, or redirectUri is missing"
3289
+ message: "google OAuth is configured but clientId, clientSecret, or redirectUri is missing",
3290
+ domain: "auth",
3291
+ reason: "GOOGLE_INCOMPLETE"
3278
3292
  });
3279
3293
  } else if (import_core11.PLACEHOLDER_SECRET.test(clientSecret)) {
3280
3294
  problems.push({
3281
3295
  module: MODULE,
3282
3296
  severity: "error",
3283
- message: "google.clientSecret looks like a placeholder or dev-default value"
3297
+ message: "google.clientSecret looks like a placeholder or dev-default value",
3298
+ domain: "auth",
3299
+ reason: "GOOGLE_SECRET_PLACEHOLDER"
3284
3300
  });
3285
3301
  }
3286
3302
  }
@@ -3290,20 +3306,26 @@ function collectAuthConfigProblems(config) {
3290
3306
  problems.push({
3291
3307
  module: MODULE,
3292
3308
  severity: "error",
3293
- message: "apple OAuth is configured but clientId, teamId, keyId, privateKey, or redirectUri is missing"
3309
+ message: "apple OAuth is configured but clientId, teamId, keyId, privateKey, or redirectUri is missing",
3310
+ domain: "auth",
3311
+ reason: "APPLE_INCOMPLETE"
3294
3312
  });
3295
3313
  } else if (!/-----BEGIN [A-Z ]*PRIVATE KEY-----/.test(a.privateKey)) {
3296
3314
  problems.push({
3297
3315
  module: MODULE,
3298
3316
  severity: "error",
3299
- message: "apple.privateKey does not look like a PEM .p8 key (expected `-----BEGIN PRIVATE KEY-----`)"
3317
+ message: "apple.privateKey does not look like a PEM .p8 key (expected `-----BEGIN PRIVATE KEY-----`)",
3318
+ domain: "auth",
3319
+ reason: "APPLE_KEY_NOT_PEM"
3300
3320
  });
3301
3321
  }
3302
3322
  if (a.nativeClientIds?.some((id) => !id || id.trim() === "" || id.includes("*"))) {
3303
3323
  problems.push({
3304
3324
  module: MODULE,
3305
3325
  severity: "error",
3306
- message: "apple.nativeClientIds must be exact bundle ids \u2014 empty or wildcard entries would accept identity tokens minted for other apps"
3326
+ message: "apple.nativeClientIds must be exact bundle ids \u2014 empty or wildcard entries would accept identity tokens minted for other apps",
3327
+ domain: "auth",
3328
+ reason: "APPLE_NATIVE_IDS_INVALID"
3307
3329
  });
3308
3330
  }
3309
3331
  }
@@ -3311,14 +3333,18 @@ function collectAuthConfigProblems(config) {
3311
3333
  problems.push({
3312
3334
  module: MODULE,
3313
3335
  severity: process.env["NODE_ENV"] === "production" ? "error" : "warning",
3314
- message: "mfa is enabled without mfaSecretKey \u2014 TOTP secrets are stored plaintext at rest; set a 32-byte key (openssl rand -hex 32)"
3336
+ message: "mfa is enabled without mfaSecretKey \u2014 TOTP secrets are stored plaintext at rest; set a 32-byte key (openssl rand -hex 32)",
3337
+ domain: "auth",
3338
+ reason: "MFA_KEY_MISSING"
3315
3339
  });
3316
3340
  }
3317
3341
  if (config.mfaSecretKey && !/^[0-9a-fA-F]{64}$/.test(config.mfaSecretKey)) {
3318
3342
  problems.push({
3319
3343
  module: MODULE,
3320
3344
  severity: "error",
3321
- message: "mfaSecretKey must be 64 hex characters (32 bytes, e.g. `openssl rand -hex 32`)"
3345
+ message: "mfaSecretKey must be 64 hex characters (32 bytes, e.g. `openssl rand -hex 32`)",
3346
+ domain: "auth",
3347
+ reason: "MFA_KEY_INVALID"
3322
3348
  });
3323
3349
  }
3324
3350
  return problems;
@@ -3530,7 +3556,7 @@ var AuthModule = class {
3530
3556
  name = "@fonderie/auth";
3531
3557
  // Baked in at build time by tsup.base, so the operator's Modules page can
3532
3558
  // say what is actually deployed rather than 'not reported'.
3533
- version = "7.12.0";
3559
+ version = "7.14.0";
3534
3560
  // Users, sessions, login history, suspend — only through @fonderie/admin.
3535
3561
  describeAdmin() {
3536
3562
  return { routes: describeAuthAdminRoutes(this.store) };
@@ -3541,7 +3567,7 @@ var AuthModule = class {
3541
3567
  }
3542
3568
  install(app) {
3543
3569
  app.use(withSession(this.store, this.config));
3544
- const routes = buildAuthRoutes(this.store, this.config, this.bus);
3570
+ const routes = buildAuthRoutes(this.store, this.config, this.bus, app.locales);
3545
3571
  for (const [method, path, ...handlers] of routes) {
3546
3572
  app.addRoute(method, path, ...handlers);
3547
3573
  }
@@ -3549,7 +3575,328 @@ var AuthModule = class {
3549
3575
  };
3550
3576
 
3551
3577
  // src/templates.ts
3552
- var DEFAULT_TEMPLATES = {
3578
+ var import_core13 = require("@fonderie/core");
3579
+
3580
+ // src/templates.es.ts
3581
+ var ES_TEMPLATES = {
3582
+ [MESSAGE_KEYS.emailRegistration]: {
3583
+ subject: "Confirma tu cuenta",
3584
+ html: `<h1>Te damos la bienvenida</h1>
3585
+ <p>Hola {{firstName}}:</p>
3586
+ <p>Gracias por registrarte. Confirma tu cuenta con este c\xF3digo:</p>
3587
+ <p><span class="pin-code">{{pin}}</span></p>
3588
+ <p class="muted">Este c\xF3digo caduca en 24 horas. Si no creaste una cuenta, puedes ignorar este correo.</p>`,
3589
+ text: `Te damos la bienvenida
3590
+
3591
+ Hola {{firstName}}:
3592
+
3593
+ Gracias por registrarte. Confirma tu cuenta con este c\xF3digo: {{pin}}
3594
+
3595
+ Este c\xF3digo caduca en 24 horas. Si no creaste una cuenta, puedes ignorar este correo.`
3596
+ },
3597
+ [MESSAGE_KEYS.emailVerification]: {
3598
+ subject: "Tu c\xF3digo de verificaci\xF3n",
3599
+ html: `<h1>Verifica tu correo electr\xF3nico</h1>
3600
+ <p>Hola {{firstName}}:</p>
3601
+ <p>Usa este c\xF3digo para terminar de configurar tu cuenta:</p>
3602
+ <p><span class="pin-code">{{pin}}</span></p>
3603
+ <p class="muted">Este c\xF3digo caduca en 24 horas. Si no creaste una cuenta, puedes ignorar este correo.</p>`,
3604
+ text: `Verifica tu correo electr\xF3nico
3605
+
3606
+ Hola {{firstName}}:
3607
+
3608
+ Usa este c\xF3digo para terminar de configurar tu cuenta: {{pin}}
3609
+
3610
+ Este c\xF3digo caduca en 24 horas. Si no creaste una cuenta, puedes ignorar este correo.`
3611
+ },
3612
+ [MESSAGE_KEYS.passwordReset]: {
3613
+ subject: "Restablece tu contrase\xF1a",
3614
+ html: `<h1>Restablece tu contrase\xF1a</h1>
3615
+ <p>Recibimos una solicitud para restablecer tu contrase\xF1a. Usa este c\xF3digo para continuar:</p>
3616
+ <p><span class="pin-code">{{pin}}</span></p>
3617
+ <p class="muted">Este c\xF3digo caduca pronto. Si no lo solicitaste, no tienes que hacer nada &mdash; tu contrase\xF1a sigue siendo la misma.</p>`,
3618
+ text: `Restablece tu contrase\xF1a
3619
+
3620
+ Recibimos una solicitud para restablecer tu contrase\xF1a. Usa este c\xF3digo para continuar: {{pin}}
3621
+
3622
+ Este c\xF3digo caduca pronto. Si no lo solicitaste, no tienes que hacer nada \u2014 tu contrase\xF1a sigue siendo la misma.`
3623
+ },
3624
+ // SMS — text only.
3625
+ [MESSAGE_KEYS.phoneOtp]: {
3626
+ text: "{{otp}} es tu c\xF3digo de verificaci\xF3n. Caduca en 10 minutos."
3627
+ },
3628
+ [MESSAGE_KEYS.mfaEnabled]: {
3629
+ subject: "La verificaci\xF3n en dos pasos est\xE1 activada",
3630
+ html: `<h1>Verificaci\xF3n en dos pasos activada</h1>
3631
+ <p>Se acaba de activar la verificaci\xF3n en dos pasos en tu cuenta. A partir de ahora, introducir\xE1s un c\xF3digo de tu aplicaci\xF3n de autenticaci\xF3n al iniciar sesi\xF3n.</p>
3632
+ <p class="muted">Si no fuiste t\xFA, contacta con soporte de inmediato &mdash; alguien podr\xEDa tener acceso a tu cuenta.</p>`,
3633
+ text: `Verificaci\xF3n en dos pasos activada
3634
+
3635
+ Se acaba de activar la verificaci\xF3n en dos pasos en tu cuenta. A partir de ahora, introducir\xE1s un c\xF3digo de tu aplicaci\xF3n de autenticaci\xF3n al iniciar sesi\xF3n.
3636
+
3637
+ Si no fuiste t\xFA, contacta con soporte de inmediato \u2014 alguien podr\xEDa tener acceso a tu cuenta.`
3638
+ },
3639
+ [MESSAGE_KEYS.mfaDisabled]: {
3640
+ subject: "La verificaci\xF3n en dos pasos est\xE1 desactivada",
3641
+ html: `<h1>Verificaci\xF3n en dos pasos desactivada</h1>
3642
+ <p>Se acaba de desactivar la verificaci\xF3n en dos pasos en tu cuenta. Ahora tu cuenta est\xE1 protegida solo por tu contrase\xF1a.</p>
3643
+ <p class="muted">Si no fuiste t\xFA, contacta con soporte de inmediato y vuelve a activar la verificaci\xF3n en dos pasos &mdash; alguien podr\xEDa tener acceso a tu cuenta.</p>`,
3644
+ text: `Verificaci\xF3n en dos pasos desactivada
3645
+
3646
+ Se acaba de desactivar la verificaci\xF3n en dos pasos en tu cuenta. Ahora tu cuenta est\xE1 protegida solo por tu contrase\xF1a.
3647
+
3648
+ Si no fuiste t\xFA, contacta con soporte de inmediato y vuelve a activar la verificaci\xF3n en dos pasos \u2014 alguien podr\xEDa tener acceso a tu cuenta.`
3649
+ },
3650
+ [MESSAGE_KEYS.mfaBackupCodesRegenerated]: {
3651
+ subject: "Se regeneraron tus c\xF3digos de respaldo",
3652
+ html: `<h1>Nuevos c\xF3digos de respaldo generados</h1>
3653
+ <p>Se acaba de generar un nuevo conjunto de c\xF3digos de respaldo de verificaci\xF3n en dos pasos para tu cuenta. Tus c\xF3digos anteriores ya no funcionan.</p>
3654
+ <p class="muted">Si no fuiste t\xFA, contacta con soporte de inmediato &mdash; alguien podr\xEDa tener acceso a tu cuenta.</p>`,
3655
+ text: `Nuevos c\xF3digos de respaldo generados
3656
+
3657
+ Se acaba de generar un nuevo conjunto de c\xF3digos de respaldo de verificaci\xF3n en dos pasos para tu cuenta. Tus c\xF3digos anteriores ya no funcionan.
3658
+
3659
+ Si no fuiste t\xFA, contacta con soporte de inmediato \u2014 alguien podr\xEDa tener acceso a tu cuenta.`
3660
+ },
3661
+ [MESSAGE_KEYS.emailChanged]: {
3662
+ subject: "Se cambi\xF3 tu direcci\xF3n de correo electr\xF3nico",
3663
+ html: `<h1>Se cambi\xF3 tu correo electr\xF3nico</h1>
3664
+ <p>La direcci\xF3n de correo electr\xF3nico de tu cuenta se acaba de cambiar a <strong>{{newEmail}}</strong>.</p>
3665
+ <p class="muted">Si hiciste este cambio, todo est\xE1 en orden. Si no, contacta con soporte de inmediato &mdash; alguien podr\xEDa tener acceso a tu cuenta.</p>`,
3666
+ text: `Se cambi\xF3 tu correo electr\xF3nico
3667
+
3668
+ La direcci\xF3n de correo electr\xF3nico de tu cuenta se acaba de cambiar a {{newEmail}}.
3669
+
3670
+ Si hiciste este cambio, todo est\xE1 en orden. Si no, contacta con soporte de inmediato \u2014 alguien podr\xEDa tener acceso a tu cuenta.`
3671
+ },
3672
+ [MESSAGE_KEYS.phoneChanged]: {
3673
+ subject: "Se cambi\xF3 tu n\xFAmero de tel\xE9fono",
3674
+ html: `<h1>Se cambi\xF3 tu n\xFAmero de tel\xE9fono</h1>
3675
+ <p>El n\xFAmero de tel\xE9fono de tu cuenta se acaba de actualizar.</p>
3676
+ <p class="muted">Si hiciste este cambio, todo est\xE1 en orden. Si no, contacta con soporte de inmediato &mdash; alguien podr\xEDa tener acceso a tu cuenta.</p>`,
3677
+ text: `Se cambi\xF3 tu n\xFAmero de tel\xE9fono
3678
+
3679
+ El n\xFAmero de tel\xE9fono de tu cuenta se acaba de actualizar.
3680
+
3681
+ Si hiciste este cambio, todo est\xE1 en orden. Si no, contacta con soporte de inmediato \u2014 alguien podr\xEDa tener acceso a tu cuenta.`
3682
+ },
3683
+ [MESSAGE_KEYS.passwordRevoked]: {
3684
+ subject: "Se elimin\xF3 la contrase\xF1a de tu cuenta",
3685
+ html: `<h1>Se elimin\xF3 tu contrase\xF1a</h1>
3686
+ <p>Acabas de iniciar sesi\xF3n con <strong>{{provider}}</strong>. Esta cuenta ten\xEDa una contrase\xF1a, pero esta direcci\xF3n de correo nunca se hab\xEDa confirmado &mdash; as\xED que no pod\xEDamos saber qui\xE9n la estableci\xF3, y se ha eliminado.</p>
3687
+ <p>Iniciar sesi\xF3n con {{provider}} funciona como antes. Si tambi\xE9n quieres una contrase\xF1a, puedes crearla en la configuraci\xF3n de tu cuenta.</p>
3688
+ <p class="muted">Si nunca estableciste una contrase\xF1a aqu\xED, es posible que alguien intentara registrarse con tu direcci\xF3n. No tienes que hacer nada m\xE1s &mdash; la cuenta es tuya.</p>`,
3689
+ text: `Se elimin\xF3 tu contrase\xF1a
3690
+
3691
+ Acabas de iniciar sesi\xF3n con {{provider}}. Esta cuenta ten\xEDa una contrase\xF1a, pero
3692
+ esta direcci\xF3n de correo nunca se hab\xEDa confirmado \u2014 as\xED que no pod\xEDamos saber
3693
+ qui\xE9n la estableci\xF3, y se ha eliminado.
3694
+
3695
+ Iniciar sesi\xF3n con {{provider}} funciona como antes. Si tambi\xE9n quieres una
3696
+ contrase\xF1a, puedes crearla en la configuraci\xF3n de tu cuenta.
3697
+
3698
+ Si nunca estableciste una contrase\xF1a aqu\xED, es posible que alguien intentara
3699
+ registrarse con tu direcci\xF3n. No tienes que hacer nada m\xE1s \u2014 la cuenta es tuya.`
3700
+ },
3701
+ [MESSAGE_KEYS.oauthRegistration]: {
3702
+ subject: "Te damos la bienvenida a {{appName}}",
3703
+ html: `<h1>Te damos la bienvenida a {{appName}}</h1>
3704
+ <p>Tu cuenta se cre\xF3 con <strong>{{provider}}</strong>. Inicia sesi\xF3n cuando quieras con la misma cuenta de {{provider}} &mdash; no hay ninguna contrase\xF1a que recordar.</p>
3705
+ <p class="muted">Si no creaste esta cuenta, contacta con soporte.</p>`,
3706
+ text: `Te damos la bienvenida
3707
+
3708
+ Tu cuenta se cre\xF3 con {{provider}}. Inicia sesi\xF3n cuando quieras con la misma
3709
+ cuenta de {{provider}} \u2014 no hay ninguna contrase\xF1a que recordar.
3710
+
3711
+ Si no creaste esta cuenta, contacta con soporte.`
3712
+ },
3713
+ [MESSAGE_KEYS.oauthLinked]: {
3714
+ subject: "Se a\xF1adi\xF3 un nuevo m\xE9todo de inicio de sesi\xF3n a tu cuenta",
3715
+ html: `<h1>Se a\xF1adi\xF3 el inicio de sesi\xF3n con {{provider}}</h1>
3716
+ <p>Ahora tambi\xE9n puedes iniciar sesi\xF3n en tu cuenta con <strong>{{provider}}</strong>.</p>
3717
+ <p class="muted">Si fuiste t\xFA, no tienes que hacer nada m\xE1s. Si no, contacta con soporte de inmediato y cambia tu contrase\xF1a &mdash; otra persona podr\xEDa iniciar sesi\xF3n como t\xFA.</p>`,
3718
+ text: `Se a\xF1adi\xF3 el inicio de sesi\xF3n con {{provider}}
3719
+
3720
+ Ahora tambi\xE9n puedes iniciar sesi\xF3n en tu cuenta con {{provider}}.
3721
+
3722
+ Si fuiste t\xFA, no tienes que hacer nada m\xE1s. Si no, contacta con soporte de
3723
+ inmediato y cambia tu contrase\xF1a \u2014 otra persona podr\xEDa iniciar sesi\xF3n como t\xFA.`
3724
+ },
3725
+ [MESSAGE_KEYS.oauthUnlinked]: {
3726
+ subject: "Se elimin\xF3 un m\xE9todo de inicio de sesi\xF3n de tu cuenta",
3727
+ html: `<h1>Se elimin\xF3 el inicio de sesi\xF3n con {{provider}}</h1>
3728
+ <p><strong>{{provider}}</strong> ya no se puede usar para iniciar sesi\xF3n. Tu correo electr\xF3nico y tu contrase\xF1a siguen funcionando.</p>
3729
+ <p class="muted">Si fuiste t\xFA, todo est\xE1 en orden. Si no, contacta con soporte de inmediato.</p>`,
3730
+ text: `Se elimin\xF3 el inicio de sesi\xF3n con {{provider}}
3731
+
3732
+ {{provider}} ya no se puede usar para iniciar sesi\xF3n. Tu correo electr\xF3nico y tu contrase\xF1a siguen funcionando.
3733
+
3734
+ Si fuiste t\xFA, todo est\xE1 en orden. Si no, contacta con soporte de inmediato.`
3735
+ }
3736
+ };
3737
+
3738
+ // src/templates.fr.ts
3739
+ var FR_TEMPLATES = {
3740
+ [MESSAGE_KEYS.emailRegistration]: {
3741
+ subject: "Confirmez votre compte",
3742
+ html: `<h1>Bienvenue</h1>
3743
+ <p>Bonjour {{firstName}},</p>
3744
+ <p>Merci pour votre inscription. Confirmez votre compte avec ce code :</p>
3745
+ <p><span class="pin-code">{{pin}}</span></p>
3746
+ <p class="muted">Ce code expire dans 24 heures. Si vous n&rsquo;avez pas cr\xE9\xE9 de compte, vous pouvez ignorer cet e-mail.</p>`,
3747
+ text: `Bienvenue
3748
+
3749
+ Bonjour {{firstName}},
3750
+
3751
+ Merci pour votre inscription. Confirmez votre compte avec ce code : {{pin}}
3752
+
3753
+ Ce code expire dans 24 heures. Si vous n'avez pas cr\xE9\xE9 de compte, vous pouvez ignorer cet e-mail.`
3754
+ },
3755
+ [MESSAGE_KEYS.emailVerification]: {
3756
+ subject: "Votre code de v\xE9rification",
3757
+ html: `<h1>V\xE9rifiez votre adresse e-mail</h1>
3758
+ <p>Bonjour {{firstName}},</p>
3759
+ <p>Utilisez ce code pour terminer la cr\xE9ation de votre compte :</p>
3760
+ <p><span class="pin-code">{{pin}}</span></p>
3761
+ <p class="muted">Ce code expire dans 24 heures. Si vous n&rsquo;avez pas cr\xE9\xE9 de compte, vous pouvez ignorer cet e-mail.</p>`,
3762
+ text: `V\xE9rifiez votre adresse e-mail
3763
+
3764
+ Bonjour {{firstName}},
3765
+
3766
+ Utilisez ce code pour terminer la cr\xE9ation de votre compte : {{pin}}
3767
+
3768
+ Ce code expire dans 24 heures. Si vous n'avez pas cr\xE9\xE9 de compte, vous pouvez ignorer cet e-mail.`
3769
+ },
3770
+ [MESSAGE_KEYS.passwordReset]: {
3771
+ subject: "R\xE9initialisez votre mot de passe",
3772
+ html: `<h1>R\xE9initialisez votre mot de passe</h1>
3773
+ <p>Nous avons re\xE7u une demande de r\xE9initialisation de votre mot de passe. Utilisez ce code pour continuer :</p>
3774
+ <p><span class="pin-code">{{pin}}</span></p>
3775
+ <p class="muted">Ce code expire bient\xF4t. Si vous n&rsquo;avez rien demand\xE9, aucune action n&rsquo;est n\xE9cessaire &mdash; votre mot de passe reste inchang\xE9.</p>`,
3776
+ text: `R\xE9initialisez votre mot de passe
3777
+
3778
+ Nous avons re\xE7u une demande de r\xE9initialisation de votre mot de passe. Utilisez ce code pour continuer : {{pin}}
3779
+
3780
+ Ce code expire bient\xF4t. Si vous n'avez rien demand\xE9, aucune action n'est n\xE9cessaire \u2014 votre mot de passe reste inchang\xE9.`
3781
+ },
3782
+ // SMS — text only.
3783
+ [MESSAGE_KEYS.phoneOtp]: {
3784
+ text: "{{otp}} est votre code de v\xE9rification. Il expire dans 10 minutes."
3785
+ },
3786
+ [MESSAGE_KEYS.mfaEnabled]: {
3787
+ subject: "La double authentification est activ\xE9e",
3788
+ html: `<h1>Double authentification activ\xE9e</h1>
3789
+ <p>La double authentification vient d&rsquo;\xEAtre activ\xE9e sur votre compte. D\xE9sormais, vous saisirez un code de votre application d&rsquo;authentification \xE0 chaque connexion.</p>
3790
+ <p class="muted">Si ce n&rsquo;est pas vous, contactez imm\xE9diatement le support &mdash; quelqu&rsquo;un pourrait avoir acc\xE8s \xE0 votre compte.</p>`,
3791
+ text: `Double authentification activ\xE9e
3792
+
3793
+ La double authentification vient d'\xEAtre activ\xE9e sur votre compte. D\xE9sormais, vous saisirez un code de votre application d'authentification \xE0 chaque connexion.
3794
+
3795
+ Si ce n'est pas vous, contactez imm\xE9diatement le support \u2014 quelqu'un pourrait avoir acc\xE8s \xE0 votre compte.`
3796
+ },
3797
+ [MESSAGE_KEYS.mfaDisabled]: {
3798
+ subject: "La double authentification est d\xE9sactiv\xE9e",
3799
+ html: `<h1>Double authentification d\xE9sactiv\xE9e</h1>
3800
+ <p>La double authentification vient d&rsquo;\xEAtre d\xE9sactiv\xE9e sur votre compte. Votre compte n&rsquo;est d\xE9sormais prot\xE9g\xE9 que par votre mot de passe.</p>
3801
+ <p class="muted">Si ce n&rsquo;est pas vous, contactez imm\xE9diatement le support et r\xE9activez la double authentification &mdash; quelqu&rsquo;un pourrait avoir acc\xE8s \xE0 votre compte.</p>`,
3802
+ text: `Double authentification d\xE9sactiv\xE9e
3803
+
3804
+ La double authentification vient d'\xEAtre d\xE9sactiv\xE9e sur votre compte. Votre compte n'est d\xE9sormais prot\xE9g\xE9 que par votre mot de passe.
3805
+
3806
+ Si ce n'est pas vous, contactez imm\xE9diatement le support et r\xE9activez la double authentification \u2014 quelqu'un pourrait avoir acc\xE8s \xE0 votre compte.`
3807
+ },
3808
+ [MESSAGE_KEYS.mfaBackupCodesRegenerated]: {
3809
+ subject: "Vos codes de secours ont \xE9t\xE9 r\xE9g\xE9n\xE9r\xE9s",
3810
+ html: `<h1>Nouveaux codes de secours g\xE9n\xE9r\xE9s</h1>
3811
+ <p>Une nouvelle s\xE9rie de codes de secours pour la double authentification vient d&rsquo;\xEAtre g\xE9n\xE9r\xE9e pour votre compte. Vos anciens codes ne fonctionnent plus.</p>
3812
+ <p class="muted">Si ce n&rsquo;est pas vous, contactez imm\xE9diatement le support &mdash; quelqu&rsquo;un pourrait avoir acc\xE8s \xE0 votre compte.</p>`,
3813
+ text: `Nouveaux codes de secours g\xE9n\xE9r\xE9s
3814
+
3815
+ Une nouvelle s\xE9rie de codes de secours pour la double authentification vient d'\xEAtre g\xE9n\xE9r\xE9e pour votre compte. Vos anciens codes ne fonctionnent plus.
3816
+
3817
+ Si ce n'est pas vous, contactez imm\xE9diatement le support \u2014 quelqu'un pourrait avoir acc\xE8s \xE0 votre compte.`
3818
+ },
3819
+ [MESSAGE_KEYS.emailChanged]: {
3820
+ subject: "Votre adresse e-mail a \xE9t\xE9 modifi\xE9e",
3821
+ html: `<h1>Votre adresse e-mail a \xE9t\xE9 modifi\xE9e</h1>
3822
+ <p>L&rsquo;adresse e-mail de votre compte vient d&rsquo;\xEAtre remplac\xE9e par <strong>{{newEmail}}</strong>.</p>
3823
+ <p class="muted">Si vous \xEAtes \xE0 l&rsquo;origine de ce changement, tout est en ordre. Sinon, contactez imm\xE9diatement le support &mdash; quelqu&rsquo;un pourrait avoir acc\xE8s \xE0 votre compte.</p>`,
3824
+ text: `Votre adresse e-mail a \xE9t\xE9 modifi\xE9e
3825
+
3826
+ L'adresse e-mail de votre compte vient d'\xEAtre remplac\xE9e par {{newEmail}}.
3827
+
3828
+ Si vous \xEAtes \xE0 l'origine de ce changement, tout est en ordre. Sinon, contactez imm\xE9diatement le support \u2014 quelqu'un pourrait avoir acc\xE8s \xE0 votre compte.`
3829
+ },
3830
+ [MESSAGE_KEYS.phoneChanged]: {
3831
+ subject: "Votre num\xE9ro de t\xE9l\xE9phone a \xE9t\xE9 modifi\xE9",
3832
+ html: `<h1>Votre num\xE9ro de t\xE9l\xE9phone a \xE9t\xE9 modifi\xE9</h1>
3833
+ <p>Le num\xE9ro de t\xE9l\xE9phone de votre compte vient d&rsquo;\xEAtre mis \xE0 jour.</p>
3834
+ <p class="muted">Si vous \xEAtes \xE0 l&rsquo;origine de ce changement, tout est en ordre. Sinon, contactez imm\xE9diatement le support &mdash; quelqu&rsquo;un pourrait avoir acc\xE8s \xE0 votre compte.</p>`,
3835
+ text: `Votre num\xE9ro de t\xE9l\xE9phone a \xE9t\xE9 modifi\xE9
3836
+
3837
+ Le num\xE9ro de t\xE9l\xE9phone de votre compte vient d'\xEAtre mis \xE0 jour.
3838
+
3839
+ Si vous \xEAtes \xE0 l'origine de ce changement, tout est en ordre. Sinon, contactez imm\xE9diatement le support \u2014 quelqu'un pourrait avoir acc\xE8s \xE0 votre compte.`
3840
+ },
3841
+ [MESSAGE_KEYS.passwordRevoked]: {
3842
+ subject: "Le mot de passe de votre compte a \xE9t\xE9 supprim\xE9",
3843
+ html: `<h1>Votre mot de passe a \xE9t\xE9 supprim\xE9</h1>
3844
+ <p>Vous venez de vous connecter avec <strong>{{provider}}</strong>. Un mot de passe avait \xE9t\xE9 d\xE9fini sur ce compte, mais cette adresse e-mail n&rsquo;avait jamais \xE9t\xE9 confirm\xE9e &mdash; nous ne pouvions donc pas savoir qui l&rsquo;avait d\xE9fini, et il a \xE9t\xE9 supprim\xE9.</p>
3845
+ <p>La connexion avec {{provider}} fonctionne comme avant. Si vous souhaitez aussi un mot de passe, d\xE9finissez-en un dans les param\xE8tres de votre compte.</p>
3846
+ <p class="muted">Si vous n&rsquo;avez jamais d\xE9fini de mot de passe ici, quelqu&rsquo;un a peut-\xEAtre tent\xE9 de s&rsquo;inscrire avec votre adresse. Vous n&rsquo;avez rien d&rsquo;autre \xE0 faire &mdash; le compte vous appartient.</p>`,
3847
+ text: `Votre mot de passe a \xE9t\xE9 supprim\xE9
3848
+
3849
+ Vous venez de vous connecter avec {{provider}}. Un mot de passe avait \xE9t\xE9 d\xE9fini
3850
+ sur ce compte, mais cette adresse e-mail n'avait jamais \xE9t\xE9 confirm\xE9e \u2014 nous ne
3851
+ pouvions donc pas savoir qui l'avait d\xE9fini, et il a \xE9t\xE9 supprim\xE9.
3852
+
3853
+ La connexion avec {{provider}} fonctionne comme avant. Si vous souhaitez aussi un
3854
+ mot de passe, d\xE9finissez-en un dans les param\xE8tres de votre compte.
3855
+
3856
+ Si vous n'avez jamais d\xE9fini de mot de passe ici, quelqu'un a peut-\xEAtre tent\xE9 de
3857
+ s'inscrire avec votre adresse. Vous n'avez rien d'autre \xE0 faire \u2014 le compte vous
3858
+ appartient.`
3859
+ },
3860
+ [MESSAGE_KEYS.oauthRegistration]: {
3861
+ subject: "Bienvenue sur {{appName}}",
3862
+ html: `<h1>Bienvenue sur {{appName}}</h1>
3863
+ <p>Votre compte a \xE9t\xE9 cr\xE9\xE9 avec <strong>{{provider}}</strong>. Connectez-vous \xE0 tout moment avec ce m\xEAme compte {{provider}} &mdash; aucun mot de passe \xE0 retenir.</p>
3864
+ <p class="muted">Si vous n&rsquo;avez pas cr\xE9\xE9 ce compte, contactez le support.</p>`,
3865
+ text: `Bienvenue
3866
+
3867
+ Votre compte a \xE9t\xE9 cr\xE9\xE9 avec {{provider}}. Connectez-vous \xE0 tout moment avec ce
3868
+ m\xEAme compte {{provider}} \u2014 aucun mot de passe \xE0 retenir.
3869
+
3870
+ Si vous n'avez pas cr\xE9\xE9 ce compte, contactez le support.`
3871
+ },
3872
+ [MESSAGE_KEYS.oauthLinked]: {
3873
+ subject: "Une nouvelle m\xE9thode de connexion a \xE9t\xE9 ajout\xE9e \xE0 votre compte",
3874
+ html: `<h1>Connexion avec {{provider}} ajout\xE9e</h1>
3875
+ <p>Vous pouvez d\xE9sormais aussi vous connecter \xE0 votre compte avec <strong>{{provider}}</strong>.</p>
3876
+ <p class="muted">Si c&rsquo;est bien vous, vous n&rsquo;avez rien d&rsquo;autre \xE0 faire. Sinon, contactez imm\xE9diatement le support et changez votre mot de passe &mdash; quelqu&rsquo;un d&rsquo;autre pourrait se connecter \xE0 votre place.</p>`,
3877
+ text: `Connexion avec {{provider}} ajout\xE9e
3878
+
3879
+ Vous pouvez d\xE9sormais aussi vous connecter \xE0 votre compte avec {{provider}}.
3880
+
3881
+ Si c'est bien vous, vous n'avez rien d'autre \xE0 faire. Sinon, contactez
3882
+ imm\xE9diatement le support et changez votre mot de passe \u2014 quelqu'un d'autre
3883
+ pourrait se connecter \xE0 votre place.`
3884
+ },
3885
+ [MESSAGE_KEYS.oauthUnlinked]: {
3886
+ subject: "Une m\xE9thode de connexion a \xE9t\xE9 retir\xE9e de votre compte",
3887
+ html: `<h1>Connexion avec {{provider}} retir\xE9e</h1>
3888
+ <p><strong>{{provider}}</strong> ne peut plus \xEAtre utilis\xE9 pour vous connecter. Votre adresse e-mail et votre mot de passe fonctionnent toujours.</p>
3889
+ <p class="muted">Si c&rsquo;est bien vous, tout est en ordre. Sinon, contactez imm\xE9diatement le support.</p>`,
3890
+ text: `Connexion avec {{provider}} retir\xE9e
3891
+
3892
+ {{provider}} ne peut plus \xEAtre utilis\xE9 pour vous connecter. Votre adresse e-mail et votre mot de passe fonctionnent toujours.
3893
+
3894
+ Si c'est bien vous, tout est en ordre. Sinon, contactez imm\xE9diatement le support.`
3895
+ }
3896
+ };
3897
+
3898
+ // src/templates.ts
3899
+ var EN_TEMPLATES = {
3553
3900
  [MESSAGE_KEYS.emailRegistration]: {
3554
3901
  subject: "Confirm your account",
3555
3902
  html: `<h1>Welcome aboard</h1>
@@ -3705,6 +4052,7 @@ change your password \u2014 someone else may be able to sign in as you.`
3705
4052
  If you did this, you're all set. If not, contact support right away.`
3706
4053
  }
3707
4054
  };
4055
+ var DEFAULT_TEMPLATES = (0, import_core13.withTranslations)(EN_TEMPLATES, { fr: FR_TEMPLATES, es: ES_TEMPLATES });
3708
4056
  var SAMPLE_PAYLOADS = {
3709
4057
  [MESSAGE_KEYS.passwordRevoked]: { provider: "Google" },
3710
4058
  [MESSAGE_KEYS.oauthRegistration]: { provider: "Google", appName: "Fonderie" },