@fonderie/auth 7.11.1 → 7.13.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/brain/outcomes.md +2 -2
- package/brain/signatures.md +6 -3
- package/dist/index.cjs +373 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -67
- package/dist/index.d.ts +8 -67
- package/dist/index.js +374 -24
- package/dist/index.js.map +1 -1
- package/dist/middlewares/index.cjs +19 -9
- package/dist/middlewares/index.cjs.map +1 -1
- package/dist/middlewares/index.js +19 -9
- package/dist/middlewares/index.js.map +1 -1
- package/package.json +3 -3
package/brain/outcomes.md
CHANGED
|
@@ -138,8 +138,8 @@ Raw SQL ships in `node_modules/@fonderie/auth/dist/migrations/sql/` — read it
|
|
|
138
138
|
|
|
139
139
|
| Method | Path | Middleware chain (auth / validation / handler) |
|
|
140
140
|
|---|---|---|
|
|
141
|
-
| GET | `/_admin/users` | `async (ctx) => { const params = new URL(ctx.request.url).searchParams; const email = params.get('email')?.trim().toLowerCase(); if (email) { const user = await users.findByEmail(email); return user ? setApiResponse(HTTP.OK, 'USER', 'User', toAdminUserDTO(user)) : NOT_FOUND(); } const rawLimit = Number(params.get('limit') ?? 50); const limit = Number.isFinite(rawLimit) ? Math.min(Math.max(Math.trunc(rawLimit), 1), 200) : 50; const cursorParam = params.get('cursor'); const cursor = cursorParam ? decodeLoginCursor(cursorParam) : null; if (cursorParam && !cursor) return setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'Invalid cursor'); const page = await users.list({ limit, ...(cursor ? { cursor } : {}) }); return setApiResponse(HTTP.OK, 'USERS', 'Users', toAdminUserPageDTO(page)); }` |
|
|
142
|
-
| GET | `/_admin/users/:id` | `async (ctx) => { const user = await users.
|
|
141
|
+
| GET | `/_admin/users` | `async (ctx) => { const params = new URL(ctx.request.url).searchParams; const email = params.get('email')?.trim().toLowerCase(); if (email) { const user = await users.findByEmail(email); return user ? setApiResponse(HTTP.OK, 'USER', 'User', toAdminUserDTO(user)) : NOT_FOUND(); } const rawLimit = Number(params.get('limit') ?? 50); const limit = Number.isFinite(rawLimit) ? Math.min(Math.max(Math.trunc(rawLimit), 1), 200) : 50; const cursorParam = params.get('cursor'); const cursor = cursorParam ? decodeLoginCursor(cursorParam) : null; if (cursorParam && !cursor) return setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'Invalid cursor'); const deleted = params.get('deleted') === '1' || params.get('deleted') === 'true'; const page = await users.list({ limit, ...(cursor ? { cursor } : {}), ...(deleted ? { deleted } : {}), }); return setApiResponse(HTTP.OK, 'USERS', 'Users', toAdminUserPageDTO(page)); }` |
|
|
142
|
+
| GET | `/_admin/users/:id` | `async (ctx) => { // Read-only view: a soft-deleted account resolves (deletedAt set), so // an operator arriving from billing sees what happened to it. const user = await users.findByIdIncludingDeleted(idOf(ctx)); return user ? setApiResponse(HTTP.OK, 'USER', 'User', toAdminUserDTO(user)) : NOT_FOUND(); }` |
|
|
143
143
|
| GET | `/_admin/users/:id/login-history` | `async (ctx) => { if (!(await users.findById(idOf(ctx)))) return NOT_FOUND(); const params = new URL(ctx.request.url).searchParams; const rawLimit = Number(params.get('limit') ?? 50); const limit = Number.isFinite(rawLimit) ? Math.min(Math.max(Math.trunc(rawLimit), 1), 200) : 50; const cursorParam = params.get('cursor'); const cursor = cursorParam ? decodeLoginCursor(cursorParam) : null; if (cursorParam && !cursor) return setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'Invalid cursor'); const page = await events.listByUser({ userId: idOf(ctx), limit, ...(cursor ? { cursor } : {}), }); return setApiResponse( HTTP.OK, 'LOGIN_HISTORY', 'Login history', toLoginHistoryPageDTO(page), ); }` |
|
|
144
144
|
| DELETE | `/_admin/users/:id/sessions` | `async (ctx) => { if (!(await users.findById(idOf(ctx)))) return NOT_FOUND(); await sessions.deleteByUser(idOf(ctx)); return setApiResponse(HTTP.OK, 'SESSIONS_REVOKED', 'All sessions revoked'); }` |
|
|
145
145
|
| GET | `/_admin/users/:id/sessions` | `async (ctx) => { if (!(await users.findById(idOf(ctx)))) return NOT_FOUND(); const rows = await sessions.listLiveByUser(idOf(ctx)); return setApiResponse( HTTP.OK, 'SESSIONS', 'Live sessions', rows.map((r) => toSessionDTO(r, null)), ); }` |
|
package/brain/signatures.md
CHANGED
|
@@ -101,13 +101,13 @@ interface IDataExportContributor {
|
|
|
101
101
|
collect: (userId: string) => Promise<unknown> | unknown;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
const AUTH_CONFIG_KEYS:
|
|
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":
|
|
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;
|
|
@@ -213,12 +213,15 @@ function normalizeEmailSafe(email: string): string | null
|
|
|
213
213
|
|
|
214
214
|
function importUser(store: IStoreAdapter, user: IImportUser): Promise<{ id: string; }>
|
|
215
215
|
|
|
216
|
-
function purgeSoftDeletedUsers(store: IStoreAdapter, { olderThanDays }: IPurgeOptions): Promise<number>
|
|
216
|
+
function purgeSoftDeletedUsers(store: IStoreAdapter, { olderThanDays, bus }: IPurgeOptions): Promise<number>
|
|
217
217
|
|
|
218
218
|
function startUserRetention(store: IStoreAdapter, options: IUserRetentionScheduleOptions): { stop: () => void; }
|
|
219
219
|
|
|
220
220
|
interface IPurgeOptions {
|
|
221
221
|
olderThanDays: number;
|
|
222
|
+
bus?: {
|
|
223
|
+
emit(type: string, payload: unknown): Promise<void>;
|
|
224
|
+
} | undefined;
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
interface IUserRetentionScheduleOptions extends IPurgeOptions {
|
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) }),
|
|
@@ -320,6 +322,8 @@ var MESSAGE_KEYS = {
|
|
|
320
322
|
var EVENT_KEYS = {
|
|
321
323
|
userRegistered: "fonderie.user.registered",
|
|
322
324
|
userDeleted: "fonderie.user.deleted",
|
|
325
|
+
// Emitted by purgeSoftDeletedUsers (given a bus) once the row is hard-deleted.
|
|
326
|
+
userPurged: "fonderie.user.purged",
|
|
323
327
|
emailVerified: "fonderie.user.email_verified",
|
|
324
328
|
passwordChanged: "fonderie.user.password_changed"
|
|
325
329
|
};
|
|
@@ -593,7 +597,7 @@ var UserModel = class {
|
|
|
593
597
|
async list(query = {}) {
|
|
594
598
|
const limit = Math.min(query.limit ?? 50, MAX_LIST_LIMIT);
|
|
595
599
|
const params = [];
|
|
596
|
-
const where = ["deleted_at IS NULL"];
|
|
600
|
+
const where = [query.deleted ? "deleted_at IS NOT NULL" : "deleted_at IS NULL"];
|
|
597
601
|
if (query.cursor) {
|
|
598
602
|
params.push(query.cursor.createdAt, query.cursor.id);
|
|
599
603
|
where.push(`(created_at, id) < ($${params.length - 1}::timestamptz, $${params.length}::uuid)`);
|
|
@@ -609,6 +613,16 @@ var UserModel = class {
|
|
|
609
613
|
);
|
|
610
614
|
return { users: rows.slice(0, limit), hasMore: rows.length > limit };
|
|
611
615
|
}
|
|
616
|
+
// For operator views only: a soft-deleted account still resolves, with
|
|
617
|
+
// deletedAt set, so the console can say "deleted on …" instead of "no such
|
|
618
|
+
// user". Nothing that authenticates or acts on an account may use this.
|
|
619
|
+
async findByIdIncludingDeleted(id) {
|
|
620
|
+
const [row] = await this.store.query(
|
|
621
|
+
`SELECT ${USER_COLUMNS} FROM fonderie_users WHERE id = $1`,
|
|
622
|
+
[id]
|
|
623
|
+
);
|
|
624
|
+
return row ?? null;
|
|
625
|
+
}
|
|
612
626
|
async findById(id) {
|
|
613
627
|
const [row] = await this.store.query(
|
|
614
628
|
`SELECT ${USER_COLUMNS} FROM fonderie_users WHERE id = $1 AND deleted_at IS NULL`,
|
|
@@ -630,25 +644,25 @@ var UserModel = class {
|
|
|
630
644
|
);
|
|
631
645
|
return row ?? null;
|
|
632
646
|
}
|
|
633
|
-
async findOrCreateByPhone(phone2, firstName = null, lastName = null) {
|
|
647
|
+
async findOrCreateByPhone(phone2, firstName = null, lastName = null, locale = null) {
|
|
634
648
|
const [row] = await this.store.query(
|
|
635
|
-
`INSERT INTO fonderie_users (phone, first_name, last_name)
|
|
636
|
-
VALUES ($1, $2, $3)
|
|
649
|
+
`INSERT INTO fonderie_users (phone, first_name, last_name, locale)
|
|
650
|
+
VALUES ($1, $2, $3, COALESCE($4, 'en-US'))
|
|
637
651
|
ON CONFLICT (phone) DO UPDATE
|
|
638
652
|
SET first_name = COALESCE(EXCLUDED.first_name, fonderie_users.first_name),
|
|
639
653
|
last_name = COALESCE(EXCLUDED.last_name, fonderie_users.last_name),
|
|
640
654
|
updated_at = now()
|
|
641
655
|
RETURNING id`,
|
|
642
|
-
[phone2, firstName, lastName]
|
|
656
|
+
[phone2, firstName, lastName, locale]
|
|
643
657
|
);
|
|
644
658
|
return row;
|
|
645
659
|
}
|
|
646
|
-
async create(email2, passwordHash, firstName, lastName) {
|
|
660
|
+
async create(email2, passwordHash, firstName, lastName, locale = null) {
|
|
647
661
|
const [row] = await this.store.query(
|
|
648
|
-
`INSERT INTO fonderie_users (email, password_hash, first_name, last_name)
|
|
649
|
-
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'))
|
|
650
664
|
RETURNING id`,
|
|
651
|
-
[email2.toLowerCase().trim(), passwordHash, firstName, lastName]
|
|
665
|
+
[email2.toLowerCase().trim(), passwordHash, firstName, lastName, locale]
|
|
652
666
|
);
|
|
653
667
|
return row ?? null;
|
|
654
668
|
}
|
|
@@ -1607,7 +1621,7 @@ function extractRefreshToken(ctx) {
|
|
|
1607
1621
|
const match = cookie.match(/(?:^|;\s*)refresh_token=([^;]+)/);
|
|
1608
1622
|
return match?.[1] ?? null;
|
|
1609
1623
|
}
|
|
1610
|
-
function authController(store, config, bus) {
|
|
1624
|
+
function authController(store, config, bus, locales = (0, import_core6.defineLocales)()) {
|
|
1611
1625
|
const users = new UserModel(store);
|
|
1612
1626
|
const sessions = new SessionModel(store, config.location);
|
|
1613
1627
|
const loginEvents = new LoginEventModel(store, config.location);
|
|
@@ -1619,6 +1633,7 @@ function authController(store, config, bus) {
|
|
|
1619
1633
|
register: async (ctx) => {
|
|
1620
1634
|
const body = ctx.meta["body"];
|
|
1621
1635
|
const { email: email2, password: password2, phone: phone2, firstName = null, lastName = null } = body ?? {};
|
|
1636
|
+
const locale = (0, import_core6.canonicalLocale)(body?.["locale"]) ?? locales.default;
|
|
1622
1637
|
if (typeof email2 === "string" && typeof password2 === "string") {
|
|
1623
1638
|
const normalizedEmail = normalizeEmailSafe(email2);
|
|
1624
1639
|
if (!normalizedEmail) {
|
|
@@ -1640,7 +1655,8 @@ function authController(store, config, bus) {
|
|
|
1640
1655
|
normalizedEmail,
|
|
1641
1656
|
passwordHash,
|
|
1642
1657
|
firstName,
|
|
1643
|
-
lastName
|
|
1658
|
+
lastName,
|
|
1659
|
+
locale
|
|
1644
1660
|
);
|
|
1645
1661
|
if (!row) {
|
|
1646
1662
|
return (0, import_core6.setApiResponse)(import_core6.HTTP.SERVER_ERROR, "SERVER_ERROR", "Registration failed");
|
|
@@ -1713,7 +1729,8 @@ function authController(store, config, bus) {
|
|
|
1713
1729
|
const { id } = await users.findOrCreateByPhone(
|
|
1714
1730
|
normalizePhone(phone2),
|
|
1715
1731
|
firstName ?? null,
|
|
1716
|
-
lastName ?? null
|
|
1732
|
+
lastName ?? null,
|
|
1733
|
+
locale
|
|
1717
1734
|
);
|
|
1718
1735
|
const otp = (0, import_node_crypto5.randomInt)(1e5, 1e6).toString();
|
|
1719
1736
|
const expiresAt = new Date(Date.now() + OTP_TTL_MS);
|
|
@@ -3124,9 +3141,9 @@ function oauthController(store, config, bus) {
|
|
|
3124
3141
|
}
|
|
3125
3142
|
|
|
3126
3143
|
// src/routes.ts
|
|
3127
|
-
function buildAuthRoutes(store, config, bus) {
|
|
3144
|
+
function buildAuthRoutes(store, config, bus, locales) {
|
|
3128
3145
|
const user = userController(store, config, bus);
|
|
3129
|
-
const auth = authController(store, config, bus);
|
|
3146
|
+
const auth = authController(store, config, bus, locales);
|
|
3130
3147
|
const oauth = oauthController(store, config, bus);
|
|
3131
3148
|
const mfa = mfaController(store, config, config.appName ?? "Fonderie", bus);
|
|
3132
3149
|
const verifyGate = config.requireVerification ? import_middlewares2.requireVerified : (_ctx, next) => next();
|
|
@@ -3426,7 +3443,12 @@ function adminRouteTable(store) {
|
|
|
3426
3443
|
const cursor = cursorParam ? decodeLoginCursor(cursorParam) : null;
|
|
3427
3444
|
if (cursorParam && !cursor)
|
|
3428
3445
|
return (0, import_core12.setApiResponse)(import_core12.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid cursor");
|
|
3429
|
-
const
|
|
3446
|
+
const deleted = params.get("deleted") === "1" || params.get("deleted") === "true";
|
|
3447
|
+
const page = await users.list({
|
|
3448
|
+
limit,
|
|
3449
|
+
...cursor ? { cursor } : {},
|
|
3450
|
+
...deleted ? { deleted } : {}
|
|
3451
|
+
});
|
|
3430
3452
|
return (0, import_core12.setApiResponse)(import_core12.HTTP.OK, "USERS", "Users", toAdminUserPageDTO(page));
|
|
3431
3453
|
}
|
|
3432
3454
|
],
|
|
@@ -3434,7 +3456,7 @@ function adminRouteTable(store) {
|
|
|
3434
3456
|
"GET",
|
|
3435
3457
|
"/_admin/users/:id",
|
|
3436
3458
|
async (ctx) => {
|
|
3437
|
-
const user = await users.
|
|
3459
|
+
const user = await users.findByIdIncludingDeleted(idOf(ctx));
|
|
3438
3460
|
return user ? (0, import_core12.setApiResponse)(import_core12.HTTP.OK, "USER", "User", toAdminUserDTO(user)) : NOT_FOUND();
|
|
3439
3461
|
}
|
|
3440
3462
|
],
|
|
@@ -3513,7 +3535,7 @@ var AuthModule = class {
|
|
|
3513
3535
|
name = "@fonderie/auth";
|
|
3514
3536
|
// Baked in at build time by tsup.base, so the operator's Modules page can
|
|
3515
3537
|
// say what is actually deployed rather than 'not reported'.
|
|
3516
|
-
version = "7.
|
|
3538
|
+
version = "7.13.0";
|
|
3517
3539
|
// Users, sessions, login history, suspend — only through @fonderie/admin.
|
|
3518
3540
|
describeAdmin() {
|
|
3519
3541
|
return { routes: describeAuthAdminRoutes(this.store) };
|
|
@@ -3524,7 +3546,7 @@ var AuthModule = class {
|
|
|
3524
3546
|
}
|
|
3525
3547
|
install(app) {
|
|
3526
3548
|
app.use(withSession(this.store, this.config));
|
|
3527
|
-
const routes = buildAuthRoutes(this.store, this.config, this.bus);
|
|
3549
|
+
const routes = buildAuthRoutes(this.store, this.config, this.bus, app.locales);
|
|
3528
3550
|
for (const [method, path, ...handlers] of routes) {
|
|
3529
3551
|
app.addRoute(method, path, ...handlers);
|
|
3530
3552
|
}
|
|
@@ -3532,7 +3554,328 @@ var AuthModule = class {
|
|
|
3532
3554
|
};
|
|
3533
3555
|
|
|
3534
3556
|
// src/templates.ts
|
|
3535
|
-
var
|
|
3557
|
+
var import_core13 = require("@fonderie/core");
|
|
3558
|
+
|
|
3559
|
+
// src/templates.es.ts
|
|
3560
|
+
var ES_TEMPLATES = {
|
|
3561
|
+
[MESSAGE_KEYS.emailRegistration]: {
|
|
3562
|
+
subject: "Confirma tu cuenta",
|
|
3563
|
+
html: `<h1>Te damos la bienvenida</h1>
|
|
3564
|
+
<p>Hola {{firstName}}:</p>
|
|
3565
|
+
<p>Gracias por registrarte. Confirma tu cuenta con este c\xF3digo:</p>
|
|
3566
|
+
<p><span class="pin-code">{{pin}}</span></p>
|
|
3567
|
+
<p class="muted">Este c\xF3digo caduca en 24 horas. Si no creaste una cuenta, puedes ignorar este correo.</p>`,
|
|
3568
|
+
text: `Te damos la bienvenida
|
|
3569
|
+
|
|
3570
|
+
Hola {{firstName}}:
|
|
3571
|
+
|
|
3572
|
+
Gracias por registrarte. Confirma tu cuenta con este c\xF3digo: {{pin}}
|
|
3573
|
+
|
|
3574
|
+
Este c\xF3digo caduca en 24 horas. Si no creaste una cuenta, puedes ignorar este correo.`
|
|
3575
|
+
},
|
|
3576
|
+
[MESSAGE_KEYS.emailVerification]: {
|
|
3577
|
+
subject: "Tu c\xF3digo de verificaci\xF3n",
|
|
3578
|
+
html: `<h1>Verifica tu correo electr\xF3nico</h1>
|
|
3579
|
+
<p>Hola {{firstName}}:</p>
|
|
3580
|
+
<p>Usa este c\xF3digo para terminar de configurar tu cuenta:</p>
|
|
3581
|
+
<p><span class="pin-code">{{pin}}</span></p>
|
|
3582
|
+
<p class="muted">Este c\xF3digo caduca en 24 horas. Si no creaste una cuenta, puedes ignorar este correo.</p>`,
|
|
3583
|
+
text: `Verifica tu correo electr\xF3nico
|
|
3584
|
+
|
|
3585
|
+
Hola {{firstName}}:
|
|
3586
|
+
|
|
3587
|
+
Usa este c\xF3digo para terminar de configurar tu cuenta: {{pin}}
|
|
3588
|
+
|
|
3589
|
+
Este c\xF3digo caduca en 24 horas. Si no creaste una cuenta, puedes ignorar este correo.`
|
|
3590
|
+
},
|
|
3591
|
+
[MESSAGE_KEYS.passwordReset]: {
|
|
3592
|
+
subject: "Restablece tu contrase\xF1a",
|
|
3593
|
+
html: `<h1>Restablece tu contrase\xF1a</h1>
|
|
3594
|
+
<p>Recibimos una solicitud para restablecer tu contrase\xF1a. Usa este c\xF3digo para continuar:</p>
|
|
3595
|
+
<p><span class="pin-code">{{pin}}</span></p>
|
|
3596
|
+
<p class="muted">Este c\xF3digo caduca pronto. Si no lo solicitaste, no tienes que hacer nada — tu contrase\xF1a sigue siendo la misma.</p>`,
|
|
3597
|
+
text: `Restablece tu contrase\xF1a
|
|
3598
|
+
|
|
3599
|
+
Recibimos una solicitud para restablecer tu contrase\xF1a. Usa este c\xF3digo para continuar: {{pin}}
|
|
3600
|
+
|
|
3601
|
+
Este c\xF3digo caduca pronto. Si no lo solicitaste, no tienes que hacer nada \u2014 tu contrase\xF1a sigue siendo la misma.`
|
|
3602
|
+
},
|
|
3603
|
+
// SMS — text only.
|
|
3604
|
+
[MESSAGE_KEYS.phoneOtp]: {
|
|
3605
|
+
text: "{{otp}} es tu c\xF3digo de verificaci\xF3n. Caduca en 10 minutos."
|
|
3606
|
+
},
|
|
3607
|
+
[MESSAGE_KEYS.mfaEnabled]: {
|
|
3608
|
+
subject: "La verificaci\xF3n en dos pasos est\xE1 activada",
|
|
3609
|
+
html: `<h1>Verificaci\xF3n en dos pasos activada</h1>
|
|
3610
|
+
<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>
|
|
3611
|
+
<p class="muted">Si no fuiste t\xFA, contacta con soporte de inmediato — alguien podr\xEDa tener acceso a tu cuenta.</p>`,
|
|
3612
|
+
text: `Verificaci\xF3n en dos pasos activada
|
|
3613
|
+
|
|
3614
|
+
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.
|
|
3615
|
+
|
|
3616
|
+
Si no fuiste t\xFA, contacta con soporte de inmediato \u2014 alguien podr\xEDa tener acceso a tu cuenta.`
|
|
3617
|
+
},
|
|
3618
|
+
[MESSAGE_KEYS.mfaDisabled]: {
|
|
3619
|
+
subject: "La verificaci\xF3n en dos pasos est\xE1 desactivada",
|
|
3620
|
+
html: `<h1>Verificaci\xF3n en dos pasos desactivada</h1>
|
|
3621
|
+
<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>
|
|
3622
|
+
<p class="muted">Si no fuiste t\xFA, contacta con soporte de inmediato y vuelve a activar la verificaci\xF3n en dos pasos — alguien podr\xEDa tener acceso a tu cuenta.</p>`,
|
|
3623
|
+
text: `Verificaci\xF3n en dos pasos desactivada
|
|
3624
|
+
|
|
3625
|
+
Se acaba de desactivar la verificaci\xF3n en dos pasos en tu cuenta. Ahora tu cuenta est\xE1 protegida solo por tu contrase\xF1a.
|
|
3626
|
+
|
|
3627
|
+
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.`
|
|
3628
|
+
},
|
|
3629
|
+
[MESSAGE_KEYS.mfaBackupCodesRegenerated]: {
|
|
3630
|
+
subject: "Se regeneraron tus c\xF3digos de respaldo",
|
|
3631
|
+
html: `<h1>Nuevos c\xF3digos de respaldo generados</h1>
|
|
3632
|
+
<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>
|
|
3633
|
+
<p class="muted">Si no fuiste t\xFA, contacta con soporte de inmediato — alguien podr\xEDa tener acceso a tu cuenta.</p>`,
|
|
3634
|
+
text: `Nuevos c\xF3digos de respaldo generados
|
|
3635
|
+
|
|
3636
|
+
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.
|
|
3637
|
+
|
|
3638
|
+
Si no fuiste t\xFA, contacta con soporte de inmediato \u2014 alguien podr\xEDa tener acceso a tu cuenta.`
|
|
3639
|
+
},
|
|
3640
|
+
[MESSAGE_KEYS.emailChanged]: {
|
|
3641
|
+
subject: "Se cambi\xF3 tu direcci\xF3n de correo electr\xF3nico",
|
|
3642
|
+
html: `<h1>Se cambi\xF3 tu correo electr\xF3nico</h1>
|
|
3643
|
+
<p>La direcci\xF3n de correo electr\xF3nico de tu cuenta se acaba de cambiar a <strong>{{newEmail}}</strong>.</p>
|
|
3644
|
+
<p class="muted">Si hiciste este cambio, todo est\xE1 en orden. Si no, contacta con soporte de inmediato — alguien podr\xEDa tener acceso a tu cuenta.</p>`,
|
|
3645
|
+
text: `Se cambi\xF3 tu correo electr\xF3nico
|
|
3646
|
+
|
|
3647
|
+
La direcci\xF3n de correo electr\xF3nico de tu cuenta se acaba de cambiar a {{newEmail}}.
|
|
3648
|
+
|
|
3649
|
+
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.`
|
|
3650
|
+
},
|
|
3651
|
+
[MESSAGE_KEYS.phoneChanged]: {
|
|
3652
|
+
subject: "Se cambi\xF3 tu n\xFAmero de tel\xE9fono",
|
|
3653
|
+
html: `<h1>Se cambi\xF3 tu n\xFAmero de tel\xE9fono</h1>
|
|
3654
|
+
<p>El n\xFAmero de tel\xE9fono de tu cuenta se acaba de actualizar.</p>
|
|
3655
|
+
<p class="muted">Si hiciste este cambio, todo est\xE1 en orden. Si no, contacta con soporte de inmediato — alguien podr\xEDa tener acceso a tu cuenta.</p>`,
|
|
3656
|
+
text: `Se cambi\xF3 tu n\xFAmero de tel\xE9fono
|
|
3657
|
+
|
|
3658
|
+
El n\xFAmero de tel\xE9fono de tu cuenta se acaba de actualizar.
|
|
3659
|
+
|
|
3660
|
+
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.`
|
|
3661
|
+
},
|
|
3662
|
+
[MESSAGE_KEYS.passwordRevoked]: {
|
|
3663
|
+
subject: "Se elimin\xF3 la contrase\xF1a de tu cuenta",
|
|
3664
|
+
html: `<h1>Se elimin\xF3 tu contrase\xF1a</h1>
|
|
3665
|
+
<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 — as\xED que no pod\xEDamos saber qui\xE9n la estableci\xF3, y se ha eliminado.</p>
|
|
3666
|
+
<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>
|
|
3667
|
+
<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 — la cuenta es tuya.</p>`,
|
|
3668
|
+
text: `Se elimin\xF3 tu contrase\xF1a
|
|
3669
|
+
|
|
3670
|
+
Acabas de iniciar sesi\xF3n con {{provider}}. Esta cuenta ten\xEDa una contrase\xF1a, pero
|
|
3671
|
+
esta direcci\xF3n de correo nunca se hab\xEDa confirmado \u2014 as\xED que no pod\xEDamos saber
|
|
3672
|
+
qui\xE9n la estableci\xF3, y se ha eliminado.
|
|
3673
|
+
|
|
3674
|
+
Iniciar sesi\xF3n con {{provider}} funciona como antes. Si tambi\xE9n quieres una
|
|
3675
|
+
contrase\xF1a, puedes crearla en la configuraci\xF3n de tu cuenta.
|
|
3676
|
+
|
|
3677
|
+
Si nunca estableciste una contrase\xF1a aqu\xED, es posible que alguien intentara
|
|
3678
|
+
registrarse con tu direcci\xF3n. No tienes que hacer nada m\xE1s \u2014 la cuenta es tuya.`
|
|
3679
|
+
},
|
|
3680
|
+
[MESSAGE_KEYS.oauthRegistration]: {
|
|
3681
|
+
subject: "Te damos la bienvenida a {{appName}}",
|
|
3682
|
+
html: `<h1>Te damos la bienvenida a {{appName}}</h1>
|
|
3683
|
+
<p>Tu cuenta se cre\xF3 con <strong>{{provider}}</strong>. Inicia sesi\xF3n cuando quieras con la misma cuenta de {{provider}} — no hay ninguna contrase\xF1a que recordar.</p>
|
|
3684
|
+
<p class="muted">Si no creaste esta cuenta, contacta con soporte.</p>`,
|
|
3685
|
+
text: `Te damos la bienvenida
|
|
3686
|
+
|
|
3687
|
+
Tu cuenta se cre\xF3 con {{provider}}. Inicia sesi\xF3n cuando quieras con la misma
|
|
3688
|
+
cuenta de {{provider}} \u2014 no hay ninguna contrase\xF1a que recordar.
|
|
3689
|
+
|
|
3690
|
+
Si no creaste esta cuenta, contacta con soporte.`
|
|
3691
|
+
},
|
|
3692
|
+
[MESSAGE_KEYS.oauthLinked]: {
|
|
3693
|
+
subject: "Se a\xF1adi\xF3 un nuevo m\xE9todo de inicio de sesi\xF3n a tu cuenta",
|
|
3694
|
+
html: `<h1>Se a\xF1adi\xF3 el inicio de sesi\xF3n con {{provider}}</h1>
|
|
3695
|
+
<p>Ahora tambi\xE9n puedes iniciar sesi\xF3n en tu cuenta con <strong>{{provider}}</strong>.</p>
|
|
3696
|
+
<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 — otra persona podr\xEDa iniciar sesi\xF3n como t\xFA.</p>`,
|
|
3697
|
+
text: `Se a\xF1adi\xF3 el inicio de sesi\xF3n con {{provider}}
|
|
3698
|
+
|
|
3699
|
+
Ahora tambi\xE9n puedes iniciar sesi\xF3n en tu cuenta con {{provider}}.
|
|
3700
|
+
|
|
3701
|
+
Si fuiste t\xFA, no tienes que hacer nada m\xE1s. Si no, contacta con soporte de
|
|
3702
|
+
inmediato y cambia tu contrase\xF1a \u2014 otra persona podr\xEDa iniciar sesi\xF3n como t\xFA.`
|
|
3703
|
+
},
|
|
3704
|
+
[MESSAGE_KEYS.oauthUnlinked]: {
|
|
3705
|
+
subject: "Se elimin\xF3 un m\xE9todo de inicio de sesi\xF3n de tu cuenta",
|
|
3706
|
+
html: `<h1>Se elimin\xF3 el inicio de sesi\xF3n con {{provider}}</h1>
|
|
3707
|
+
<p><strong>{{provider}}</strong> ya no se puede usar para iniciar sesi\xF3n. Tu correo electr\xF3nico y tu contrase\xF1a siguen funcionando.</p>
|
|
3708
|
+
<p class="muted">Si fuiste t\xFA, todo est\xE1 en orden. Si no, contacta con soporte de inmediato.</p>`,
|
|
3709
|
+
text: `Se elimin\xF3 el inicio de sesi\xF3n con {{provider}}
|
|
3710
|
+
|
|
3711
|
+
{{provider}} ya no se puede usar para iniciar sesi\xF3n. Tu correo electr\xF3nico y tu contrase\xF1a siguen funcionando.
|
|
3712
|
+
|
|
3713
|
+
Si fuiste t\xFA, todo est\xE1 en orden. Si no, contacta con soporte de inmediato.`
|
|
3714
|
+
}
|
|
3715
|
+
};
|
|
3716
|
+
|
|
3717
|
+
// src/templates.fr.ts
|
|
3718
|
+
var FR_TEMPLATES = {
|
|
3719
|
+
[MESSAGE_KEYS.emailRegistration]: {
|
|
3720
|
+
subject: "Confirmez votre compte",
|
|
3721
|
+
html: `<h1>Bienvenue</h1>
|
|
3722
|
+
<p>Bonjour {{firstName}},</p>
|
|
3723
|
+
<p>Merci pour votre inscription. Confirmez votre compte avec ce code :</p>
|
|
3724
|
+
<p><span class="pin-code">{{pin}}</span></p>
|
|
3725
|
+
<p class="muted">Ce code expire dans 24 heures. Si vous n’avez pas cr\xE9\xE9 de compte, vous pouvez ignorer cet e-mail.</p>`,
|
|
3726
|
+
text: `Bienvenue
|
|
3727
|
+
|
|
3728
|
+
Bonjour {{firstName}},
|
|
3729
|
+
|
|
3730
|
+
Merci pour votre inscription. Confirmez votre compte avec ce code : {{pin}}
|
|
3731
|
+
|
|
3732
|
+
Ce code expire dans 24 heures. Si vous n'avez pas cr\xE9\xE9 de compte, vous pouvez ignorer cet e-mail.`
|
|
3733
|
+
},
|
|
3734
|
+
[MESSAGE_KEYS.emailVerification]: {
|
|
3735
|
+
subject: "Votre code de v\xE9rification",
|
|
3736
|
+
html: `<h1>V\xE9rifiez votre adresse e-mail</h1>
|
|
3737
|
+
<p>Bonjour {{firstName}},</p>
|
|
3738
|
+
<p>Utilisez ce code pour terminer la cr\xE9ation de votre compte :</p>
|
|
3739
|
+
<p><span class="pin-code">{{pin}}</span></p>
|
|
3740
|
+
<p class="muted">Ce code expire dans 24 heures. Si vous n’avez pas cr\xE9\xE9 de compte, vous pouvez ignorer cet e-mail.</p>`,
|
|
3741
|
+
text: `V\xE9rifiez votre adresse e-mail
|
|
3742
|
+
|
|
3743
|
+
Bonjour {{firstName}},
|
|
3744
|
+
|
|
3745
|
+
Utilisez ce code pour terminer la cr\xE9ation de votre compte : {{pin}}
|
|
3746
|
+
|
|
3747
|
+
Ce code expire dans 24 heures. Si vous n'avez pas cr\xE9\xE9 de compte, vous pouvez ignorer cet e-mail.`
|
|
3748
|
+
},
|
|
3749
|
+
[MESSAGE_KEYS.passwordReset]: {
|
|
3750
|
+
subject: "R\xE9initialisez votre mot de passe",
|
|
3751
|
+
html: `<h1>R\xE9initialisez votre mot de passe</h1>
|
|
3752
|
+
<p>Nous avons re\xE7u une demande de r\xE9initialisation de votre mot de passe. Utilisez ce code pour continuer :</p>
|
|
3753
|
+
<p><span class="pin-code">{{pin}}</span></p>
|
|
3754
|
+
<p class="muted">Ce code expire bient\xF4t. Si vous n’avez rien demand\xE9, aucune action n’est n\xE9cessaire — votre mot de passe reste inchang\xE9.</p>`,
|
|
3755
|
+
text: `R\xE9initialisez votre mot de passe
|
|
3756
|
+
|
|
3757
|
+
Nous avons re\xE7u une demande de r\xE9initialisation de votre mot de passe. Utilisez ce code pour continuer : {{pin}}
|
|
3758
|
+
|
|
3759
|
+
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.`
|
|
3760
|
+
},
|
|
3761
|
+
// SMS — text only.
|
|
3762
|
+
[MESSAGE_KEYS.phoneOtp]: {
|
|
3763
|
+
text: "{{otp}} est votre code de v\xE9rification. Il expire dans 10 minutes."
|
|
3764
|
+
},
|
|
3765
|
+
[MESSAGE_KEYS.mfaEnabled]: {
|
|
3766
|
+
subject: "La double authentification est activ\xE9e",
|
|
3767
|
+
html: `<h1>Double authentification activ\xE9e</h1>
|
|
3768
|
+
<p>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.</p>
|
|
3769
|
+
<p class="muted">Si ce n’est pas vous, contactez imm\xE9diatement le support — quelqu’un pourrait avoir acc\xE8s \xE0 votre compte.</p>`,
|
|
3770
|
+
text: `Double authentification activ\xE9e
|
|
3771
|
+
|
|
3772
|
+
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.
|
|
3773
|
+
|
|
3774
|
+
Si ce n'est pas vous, contactez imm\xE9diatement le support \u2014 quelqu'un pourrait avoir acc\xE8s \xE0 votre compte.`
|
|
3775
|
+
},
|
|
3776
|
+
[MESSAGE_KEYS.mfaDisabled]: {
|
|
3777
|
+
subject: "La double authentification est d\xE9sactiv\xE9e",
|
|
3778
|
+
html: `<h1>Double authentification d\xE9sactiv\xE9e</h1>
|
|
3779
|
+
<p>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.</p>
|
|
3780
|
+
<p class="muted">Si ce n’est pas vous, contactez imm\xE9diatement le support et r\xE9activez la double authentification — quelqu’un pourrait avoir acc\xE8s \xE0 votre compte.</p>`,
|
|
3781
|
+
text: `Double authentification d\xE9sactiv\xE9e
|
|
3782
|
+
|
|
3783
|
+
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.
|
|
3784
|
+
|
|
3785
|
+
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.`
|
|
3786
|
+
},
|
|
3787
|
+
[MESSAGE_KEYS.mfaBackupCodesRegenerated]: {
|
|
3788
|
+
subject: "Vos codes de secours ont \xE9t\xE9 r\xE9g\xE9n\xE9r\xE9s",
|
|
3789
|
+
html: `<h1>Nouveaux codes de secours g\xE9n\xE9r\xE9s</h1>
|
|
3790
|
+
<p>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.</p>
|
|
3791
|
+
<p class="muted">Si ce n’est pas vous, contactez imm\xE9diatement le support — quelqu’un pourrait avoir acc\xE8s \xE0 votre compte.</p>`,
|
|
3792
|
+
text: `Nouveaux codes de secours g\xE9n\xE9r\xE9s
|
|
3793
|
+
|
|
3794
|
+
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.
|
|
3795
|
+
|
|
3796
|
+
Si ce n'est pas vous, contactez imm\xE9diatement le support \u2014 quelqu'un pourrait avoir acc\xE8s \xE0 votre compte.`
|
|
3797
|
+
},
|
|
3798
|
+
[MESSAGE_KEYS.emailChanged]: {
|
|
3799
|
+
subject: "Votre adresse e-mail a \xE9t\xE9 modifi\xE9e",
|
|
3800
|
+
html: `<h1>Votre adresse e-mail a \xE9t\xE9 modifi\xE9e</h1>
|
|
3801
|
+
<p>L’adresse e-mail de votre compte vient d’\xEAtre remplac\xE9e par <strong>{{newEmail}}</strong>.</p>
|
|
3802
|
+
<p class="muted">Si vous \xEAtes \xE0 l’origine de ce changement, tout est en ordre. Sinon, contactez imm\xE9diatement le support — quelqu’un pourrait avoir acc\xE8s \xE0 votre compte.</p>`,
|
|
3803
|
+
text: `Votre adresse e-mail a \xE9t\xE9 modifi\xE9e
|
|
3804
|
+
|
|
3805
|
+
L'adresse e-mail de votre compte vient d'\xEAtre remplac\xE9e par {{newEmail}}.
|
|
3806
|
+
|
|
3807
|
+
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.`
|
|
3808
|
+
},
|
|
3809
|
+
[MESSAGE_KEYS.phoneChanged]: {
|
|
3810
|
+
subject: "Votre num\xE9ro de t\xE9l\xE9phone a \xE9t\xE9 modifi\xE9",
|
|
3811
|
+
html: `<h1>Votre num\xE9ro de t\xE9l\xE9phone a \xE9t\xE9 modifi\xE9</h1>
|
|
3812
|
+
<p>Le num\xE9ro de t\xE9l\xE9phone de votre compte vient d’\xEAtre mis \xE0 jour.</p>
|
|
3813
|
+
<p class="muted">Si vous \xEAtes \xE0 l’origine de ce changement, tout est en ordre. Sinon, contactez imm\xE9diatement le support — quelqu’un pourrait avoir acc\xE8s \xE0 votre compte.</p>`,
|
|
3814
|
+
text: `Votre num\xE9ro de t\xE9l\xE9phone a \xE9t\xE9 modifi\xE9
|
|
3815
|
+
|
|
3816
|
+
Le num\xE9ro de t\xE9l\xE9phone de votre compte vient d'\xEAtre mis \xE0 jour.
|
|
3817
|
+
|
|
3818
|
+
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.`
|
|
3819
|
+
},
|
|
3820
|
+
[MESSAGE_KEYS.passwordRevoked]: {
|
|
3821
|
+
subject: "Le mot de passe de votre compte a \xE9t\xE9 supprim\xE9",
|
|
3822
|
+
html: `<h1>Votre mot de passe a \xE9t\xE9 supprim\xE9</h1>
|
|
3823
|
+
<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’avait jamais \xE9t\xE9 confirm\xE9e — nous ne pouvions donc pas savoir qui l’avait d\xE9fini, et il a \xE9t\xE9 supprim\xE9.</p>
|
|
3824
|
+
<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>
|
|
3825
|
+
<p class="muted">Si vous n’avez jamais d\xE9fini de mot de passe ici, quelqu’un a peut-\xEAtre tent\xE9 de s’inscrire avec votre adresse. Vous n’avez rien d’autre \xE0 faire — le compte vous appartient.</p>`,
|
|
3826
|
+
text: `Votre mot de passe a \xE9t\xE9 supprim\xE9
|
|
3827
|
+
|
|
3828
|
+
Vous venez de vous connecter avec {{provider}}. Un mot de passe avait \xE9t\xE9 d\xE9fini
|
|
3829
|
+
sur ce compte, mais cette adresse e-mail n'avait jamais \xE9t\xE9 confirm\xE9e \u2014 nous ne
|
|
3830
|
+
pouvions donc pas savoir qui l'avait d\xE9fini, et il a \xE9t\xE9 supprim\xE9.
|
|
3831
|
+
|
|
3832
|
+
La connexion avec {{provider}} fonctionne comme avant. Si vous souhaitez aussi un
|
|
3833
|
+
mot de passe, d\xE9finissez-en un dans les param\xE8tres de votre compte.
|
|
3834
|
+
|
|
3835
|
+
Si vous n'avez jamais d\xE9fini de mot de passe ici, quelqu'un a peut-\xEAtre tent\xE9 de
|
|
3836
|
+
s'inscrire avec votre adresse. Vous n'avez rien d'autre \xE0 faire \u2014 le compte vous
|
|
3837
|
+
appartient.`
|
|
3838
|
+
},
|
|
3839
|
+
[MESSAGE_KEYS.oauthRegistration]: {
|
|
3840
|
+
subject: "Bienvenue sur {{appName}}",
|
|
3841
|
+
html: `<h1>Bienvenue sur {{appName}}</h1>
|
|
3842
|
+
<p>Votre compte a \xE9t\xE9 cr\xE9\xE9 avec <strong>{{provider}}</strong>. Connectez-vous \xE0 tout moment avec ce m\xEAme compte {{provider}} — aucun mot de passe \xE0 retenir.</p>
|
|
3843
|
+
<p class="muted">Si vous n’avez pas cr\xE9\xE9 ce compte, contactez le support.</p>`,
|
|
3844
|
+
text: `Bienvenue
|
|
3845
|
+
|
|
3846
|
+
Votre compte a \xE9t\xE9 cr\xE9\xE9 avec {{provider}}. Connectez-vous \xE0 tout moment avec ce
|
|
3847
|
+
m\xEAme compte {{provider}} \u2014 aucun mot de passe \xE0 retenir.
|
|
3848
|
+
|
|
3849
|
+
Si vous n'avez pas cr\xE9\xE9 ce compte, contactez le support.`
|
|
3850
|
+
},
|
|
3851
|
+
[MESSAGE_KEYS.oauthLinked]: {
|
|
3852
|
+
subject: "Une nouvelle m\xE9thode de connexion a \xE9t\xE9 ajout\xE9e \xE0 votre compte",
|
|
3853
|
+
html: `<h1>Connexion avec {{provider}} ajout\xE9e</h1>
|
|
3854
|
+
<p>Vous pouvez d\xE9sormais aussi vous connecter \xE0 votre compte avec <strong>{{provider}}</strong>.</p>
|
|
3855
|
+
<p class="muted">Si c’est bien vous, vous n’avez rien d’autre \xE0 faire. Sinon, contactez imm\xE9diatement le support et changez votre mot de passe — quelqu’un d’autre pourrait se connecter \xE0 votre place.</p>`,
|
|
3856
|
+
text: `Connexion avec {{provider}} ajout\xE9e
|
|
3857
|
+
|
|
3858
|
+
Vous pouvez d\xE9sormais aussi vous connecter \xE0 votre compte avec {{provider}}.
|
|
3859
|
+
|
|
3860
|
+
Si c'est bien vous, vous n'avez rien d'autre \xE0 faire. Sinon, contactez
|
|
3861
|
+
imm\xE9diatement le support et changez votre mot de passe \u2014 quelqu'un d'autre
|
|
3862
|
+
pourrait se connecter \xE0 votre place.`
|
|
3863
|
+
},
|
|
3864
|
+
[MESSAGE_KEYS.oauthUnlinked]: {
|
|
3865
|
+
subject: "Une m\xE9thode de connexion a \xE9t\xE9 retir\xE9e de votre compte",
|
|
3866
|
+
html: `<h1>Connexion avec {{provider}} retir\xE9e</h1>
|
|
3867
|
+
<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>
|
|
3868
|
+
<p class="muted">Si c’est bien vous, tout est en ordre. Sinon, contactez imm\xE9diatement le support.</p>`,
|
|
3869
|
+
text: `Connexion avec {{provider}} retir\xE9e
|
|
3870
|
+
|
|
3871
|
+
{{provider}} ne peut plus \xEAtre utilis\xE9 pour vous connecter. Votre adresse e-mail et votre mot de passe fonctionnent toujours.
|
|
3872
|
+
|
|
3873
|
+
Si c'est bien vous, tout est en ordre. Sinon, contactez imm\xE9diatement le support.`
|
|
3874
|
+
}
|
|
3875
|
+
};
|
|
3876
|
+
|
|
3877
|
+
// src/templates.ts
|
|
3878
|
+
var EN_TEMPLATES = {
|
|
3536
3879
|
[MESSAGE_KEYS.emailRegistration]: {
|
|
3537
3880
|
subject: "Confirm your account",
|
|
3538
3881
|
html: `<h1>Welcome aboard</h1>
|
|
@@ -3688,6 +4031,7 @@ change your password \u2014 someone else may be able to sign in as you.`
|
|
|
3688
4031
|
If you did this, you're all set. If not, contact support right away.`
|
|
3689
4032
|
}
|
|
3690
4033
|
};
|
|
4034
|
+
var DEFAULT_TEMPLATES = (0, import_core13.withTranslations)(EN_TEMPLATES, { fr: FR_TEMPLATES, es: ES_TEMPLATES });
|
|
3691
4035
|
var SAMPLE_PAYLOADS = {
|
|
3692
4036
|
[MESSAGE_KEYS.passwordRevoked]: { provider: "Google" },
|
|
3693
4037
|
[MESSAGE_KEYS.oauthRegistration]: { provider: "Google", appName: "Fonderie" },
|
|
@@ -3744,7 +4088,7 @@ async function importUser(store, user) {
|
|
|
3744
4088
|
}
|
|
3745
4089
|
|
|
3746
4090
|
// src/services/retention.ts
|
|
3747
|
-
async function purgeSoftDeletedUsers(store, { olderThanDays }) {
|
|
4091
|
+
async function purgeSoftDeletedUsers(store, { olderThanDays, bus }) {
|
|
3748
4092
|
if (!Number.isFinite(olderThanDays) || olderThanDays < 0) {
|
|
3749
4093
|
throw new Error("[auth] purgeSoftDeletedUsers: olderThanDays must be a non-negative number");
|
|
3750
4094
|
}
|
|
@@ -3755,6 +4099,9 @@ async function purgeSoftDeletedUsers(store, { olderThanDays }) {
|
|
|
3755
4099
|
RETURNING id`,
|
|
3756
4100
|
[olderThanDays]
|
|
3757
4101
|
);
|
|
4102
|
+
if (bus) {
|
|
4103
|
+
for (const { id } of rows) await bus.emit("fonderie.user.purged", { userId: id });
|
|
4104
|
+
}
|
|
3758
4105
|
return rows.length;
|
|
3759
4106
|
}
|
|
3760
4107
|
function startUserRetention(store, options) {
|
|
@@ -3763,7 +4110,10 @@ function startUserRetention(store, options) {
|
|
|
3763
4110
|
const run = async () => {
|
|
3764
4111
|
if (stopped) return;
|
|
3765
4112
|
try {
|
|
3766
|
-
const deleted = await purgeSoftDeletedUsers(store, {
|
|
4113
|
+
const deleted = await purgeSoftDeletedUsers(store, {
|
|
4114
|
+
olderThanDays: options.olderThanDays,
|
|
4115
|
+
...options.bus ? { bus: options.bus } : {}
|
|
4116
|
+
});
|
|
3767
4117
|
options.onPurge?.(deleted);
|
|
3768
4118
|
} catch (err) {
|
|
3769
4119
|
console.error("[auth] scheduled user retention purge failed:", err);
|