@fonderie/auth 7.21.2 → 7.22.1

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/dist/index.js CHANGED
@@ -74,6 +74,41 @@ import {
74
74
  rateLimit,
75
75
  StoreAdapterStore
76
76
  } from "@fonderie/rate-limit";
77
+
78
+ // src/services/email.ts
79
+ function normalizeEmail(email2) {
80
+ if (typeof email2 !== "string" || email2.length === 0) {
81
+ throw new Error("Invalid email");
82
+ }
83
+ const lower = email2.trim().toLowerCase();
84
+ if (lower.length === 0) {
85
+ throw new Error("Email cannot be empty");
86
+ }
87
+ const atIndex = lower.indexOf("@");
88
+ if (atIndex === -1 || lower.lastIndexOf("@") !== atIndex) {
89
+ throw new Error("Invalid email format");
90
+ }
91
+ const local = lower.substring(0, atIndex);
92
+ const domain = lower.substring(atIndex + 1);
93
+ if (!local || !domain) {
94
+ throw new Error("Invalid email format");
95
+ }
96
+ const plusIndex = local.indexOf("+");
97
+ const normalizedLocal = plusIndex === -1 ? local : local.substring(0, plusIndex);
98
+ if (!normalizedLocal) {
99
+ throw new Error("Invalid email format");
100
+ }
101
+ return `${normalizedLocal}@${domain}`;
102
+ }
103
+ function normalizeEmailSafe(email2) {
104
+ try {
105
+ return normalizeEmail(email2);
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+
111
+ // src/services/rate-limit.ts
77
112
  var min = (n) => n * 60;
78
113
  var DEFAULTS = {
79
114
  // login: 10/15min per IP AND 5/15min per account (credential stuffing
@@ -126,7 +161,9 @@ function buildAuthAccountLimiter(route, store, config) {
126
161
  return rateLimit({
127
162
  store: r.store,
128
163
  rule: r.account.rule,
129
- key: byBodyField(`auth:${route}`, r.account.field)
164
+ // The account bucket is keyed the way accounts are stored, so a rotating
165
+ // '+tag' on the same address does not buy fresh attempts.
166
+ key: byBodyField(`auth:${route}`, r.account.field, r.account.field === "email" ? normalizeEmailSafe : void 0)
130
167
  });
131
168
  }
132
169
 
@@ -1582,39 +1619,6 @@ function checkCooldown(lastSentAt, cooldownMs) {
1582
1619
  // src/controllers/auth.controller.ts
1583
1620
  init_password();
1584
1621
 
1585
- // src/services/email.ts
1586
- function normalizeEmail(email2) {
1587
- if (typeof email2 !== "string" || email2.length === 0) {
1588
- throw new Error("Invalid email");
1589
- }
1590
- const lower = email2.trim().toLowerCase();
1591
- if (lower.length === 0) {
1592
- throw new Error("Email cannot be empty");
1593
- }
1594
- const atIndex = lower.indexOf("@");
1595
- if (atIndex === -1 || lower.lastIndexOf("@") !== atIndex) {
1596
- throw new Error("Invalid email format");
1597
- }
1598
- const local = lower.substring(0, atIndex);
1599
- const domain = lower.substring(atIndex + 1);
1600
- if (!local || !domain) {
1601
- throw new Error("Invalid email format");
1602
- }
1603
- const plusIndex = local.indexOf("+");
1604
- const normalizedLocal = plusIndex === -1 ? local : local.substring(0, plusIndex);
1605
- if (!normalizedLocal) {
1606
- throw new Error("Invalid email format");
1607
- }
1608
- return `${normalizedLocal}@${domain}`;
1609
- }
1610
- function normalizeEmailSafe(email2) {
1611
- try {
1612
- return normalizeEmail(email2);
1613
- } catch {
1614
- return null;
1615
- }
1616
- }
1617
-
1618
1622
  // src/models/password-reset.model.ts
1619
1623
  import { createHash as createHash3 } from "crypto";
1620
1624
  var hashSecret = (value) => createHash3("sha256").update(value).digest("hex");
@@ -3304,7 +3308,7 @@ function oauthController(store, config, bus) {
3304
3308
  const meta = requestMeta(ctx);
3305
3309
  await loginEvents.recordSafe({
3306
3310
  userId: null,
3307
- emailAttempted: claims.email ?? null,
3311
+ emailAttempted: claims.email ? normalizeEmailSafe(claims.email) ?? claims.email : null,
3308
3312
  method: "oauth-apple",
3309
3313
  outcome: "failed",
3310
3314
  failureReason: "token_replayed",
@@ -3352,7 +3356,7 @@ function oauthController(store, config, bus) {
3352
3356
  if (!await consumedTokens.consumeOnce(tokenHash, new Date(expMs))) {
3353
3357
  await loginEvents.recordSafe({
3354
3358
  userId: null,
3355
- emailAttempted: claims.email ?? null,
3359
+ emailAttempted: claims.email ? normalizeEmailSafe(claims.email) ?? claims.email : null,
3356
3360
  method: "oauth-google",
3357
3361
  outcome: "failed",
3358
3362
  failureReason: "token_replayed",
@@ -3710,7 +3714,8 @@ function adminRouteTable(store, bus) {
3710
3714
  // see anything cannot discover who signed up this morning.
3711
3715
  async (ctx) => {
3712
3716
  const params = new URL(ctx.request.url).searchParams;
3713
- const email2 = params.get("email")?.trim().toLowerCase();
3717
+ const raw = params.get("email")?.trim();
3718
+ const email2 = raw ? normalizeEmailSafe(raw) ?? raw.toLowerCase() : void 0;
3714
3719
  if (email2) {
3715
3720
  const user = await users.findByEmail(email2);
3716
3721
  return user ? setApiResponse7(HTTP7.OK, "USER", "User", toAdminUserDTO(user)) : NOT_FOUND();
@@ -3814,7 +3819,7 @@ var AuthModule = class {
3814
3819
  name = "@fonderie/auth";
3815
3820
  // Baked in at build time by tsup.base, so the operator's Modules page can
3816
3821
  // say what is actually deployed rather than 'not reported'.
3817
- version = "7.21.2";
3822
+ version = "7.22.1";
3818
3823
  // Users, sessions, login history, suspend — only through @fonderie/admin.
3819
3824
  describeAdmin() {
3820
3825
  return { routes: describeAuthAdminRoutes(this.store, this.bus) };
@@ -4165,6 +4170,314 @@ Si c'est bien vous, tout est en ordre. Sinon, contactez imm\xE9diatement le supp
4165
4170
  }
4166
4171
  };
4167
4172
 
4173
+ // src/templates.zh-Hans.ts
4174
+ var ZH_HANS_TEMPLATES = {
4175
+ [MESSAGE_KEYS.emailRegistration]: {
4176
+ subject: "\u786E\u8BA4\u60A8\u7684\u8D26\u6237",
4177
+ html: `<h1>\u6B22\u8FCE\u52A0\u5165</h1>
4178
+ <p>{{firstName}}\uFF0C\u60A8\u597D\uFF1A</p>
4179
+ <p>\u611F\u8C22\u60A8\u7684\u6CE8\u518C\u3002\u8BF7\u4F7F\u7528\u4EE5\u4E0B\u9A8C\u8BC1\u7801\u786E\u8BA4\u60A8\u7684\u8D26\u6237\uFF1A</p>
4180
+ <p><span class="pin-code">{{pin}}</span></p>
4181
+ <p class="muted">\u6B64\u9A8C\u8BC1\u7801\u5C06\u5728 24 \u5C0F\u65F6\u540E\u5931\u6548\u3002\u5982\u679C\u60A8\u6CA1\u6709\u521B\u5EFA\u8D26\u6237\uFF0C\u8BF7\u5FFD\u7565\u6B64\u90AE\u4EF6\u3002</p>`,
4182
+ text: `\u6B22\u8FCE\u52A0\u5165
4183
+
4184
+ {{firstName}}\uFF0C\u60A8\u597D\uFF1A
4185
+
4186
+ \u611F\u8C22\u60A8\u7684\u6CE8\u518C\u3002\u8BF7\u4F7F\u7528\u4EE5\u4E0B\u9A8C\u8BC1\u7801\u786E\u8BA4\u60A8\u7684\u8D26\u6237\uFF1A{{pin}}
4187
+
4188
+ \u6B64\u9A8C\u8BC1\u7801\u5C06\u5728 24 \u5C0F\u65F6\u540E\u5931\u6548\u3002\u5982\u679C\u60A8\u6CA1\u6709\u521B\u5EFA\u8D26\u6237\uFF0C\u8BF7\u5FFD\u7565\u6B64\u90AE\u4EF6\u3002`
4189
+ },
4190
+ [MESSAGE_KEYS.emailVerification]: {
4191
+ subject: "\u60A8\u7684\u9A8C\u8BC1\u7801",
4192
+ html: `<h1>\u9A8C\u8BC1\u60A8\u7684\u7535\u5B50\u90AE\u7BB1</h1>
4193
+ <p>{{firstName}}\uFF0C\u60A8\u597D\uFF1A</p>
4194
+ <p>\u8BF7\u4F7F\u7528\u4EE5\u4E0B\u9A8C\u8BC1\u7801\u5B8C\u6210\u8D26\u6237\u8BBE\u7F6E\uFF1A</p>
4195
+ <p><span class="pin-code">{{pin}}</span></p>
4196
+ <p class="muted">\u6B64\u9A8C\u8BC1\u7801\u5C06\u5728 24 \u5C0F\u65F6\u540E\u5931\u6548\u3002\u5982\u679C\u60A8\u6CA1\u6709\u521B\u5EFA\u8D26\u6237\uFF0C\u8BF7\u5FFD\u7565\u6B64\u90AE\u4EF6\u3002</p>`,
4197
+ text: `\u9A8C\u8BC1\u60A8\u7684\u7535\u5B50\u90AE\u7BB1
4198
+
4199
+ {{firstName}}\uFF0C\u60A8\u597D\uFF1A
4200
+
4201
+ \u8BF7\u4F7F\u7528\u4EE5\u4E0B\u9A8C\u8BC1\u7801\u5B8C\u6210\u8D26\u6237\u8BBE\u7F6E\uFF1A{{pin}}
4202
+
4203
+ \u6B64\u9A8C\u8BC1\u7801\u5C06\u5728 24 \u5C0F\u65F6\u540E\u5931\u6548\u3002\u5982\u679C\u60A8\u6CA1\u6709\u521B\u5EFA\u8D26\u6237\uFF0C\u8BF7\u5FFD\u7565\u6B64\u90AE\u4EF6\u3002`
4204
+ },
4205
+ [MESSAGE_KEYS.passwordReset]: {
4206
+ subject: "\u91CD\u7F6E\u60A8\u7684\u5BC6\u7801",
4207
+ html: `<h1>\u91CD\u7F6E\u60A8\u7684\u5BC6\u7801</h1>
4208
+ <p>\u6211\u4EEC\u6536\u5230\u4E86\u91CD\u7F6E\u60A8\u5BC6\u7801\u7684\u8BF7\u6C42\u3002\u8BF7\u4F7F\u7528\u4EE5\u4E0B\u9A8C\u8BC1\u7801\u7EE7\u7EED\uFF1A</p>
4209
+ <p><span class="pin-code">{{pin}}</span></p>
4210
+ <p class="muted">\u6B64\u9A8C\u8BC1\u7801\u5F88\u5FEB\u5C31\u4F1A\u5931\u6548\u3002\u5982\u679C\u60A8\u6CA1\u6709\u7533\u8BF7\u91CD\u7F6E\uFF0C\u65E0\u9700\u4EFB\u4F55\u64CD\u4F5C\uFF0C\u60A8\u7684\u5BC6\u7801\u4FDD\u6301\u4E0D\u53D8\u3002</p>`,
4211
+ text: `\u91CD\u7F6E\u60A8\u7684\u5BC6\u7801
4212
+
4213
+ \u6211\u4EEC\u6536\u5230\u4E86\u91CD\u7F6E\u60A8\u5BC6\u7801\u7684\u8BF7\u6C42\u3002\u8BF7\u4F7F\u7528\u4EE5\u4E0B\u9A8C\u8BC1\u7801\u7EE7\u7EED\uFF1A{{pin}}
4214
+
4215
+ \u6B64\u9A8C\u8BC1\u7801\u5F88\u5FEB\u5C31\u4F1A\u5931\u6548\u3002\u5982\u679C\u60A8\u6CA1\u6709\u7533\u8BF7\u91CD\u7F6E\uFF0C\u65E0\u9700\u4EFB\u4F55\u64CD\u4F5C\uFF0C\u60A8\u7684\u5BC6\u7801\u4FDD\u6301\u4E0D\u53D8\u3002`
4216
+ },
4217
+ [MESSAGE_KEYS.phoneOtp]: {
4218
+ text: "\u60A8\u7684\u9A8C\u8BC1\u7801\u662F {{otp}}\uFF0C10 \u5206\u949F\u5185\u6709\u6548\u3002"
4219
+ },
4220
+ [MESSAGE_KEYS.mfaEnabled]: {
4221
+ subject: "\u53CC\u91CD\u9A8C\u8BC1\u5DF2\u5F00\u542F",
4222
+ html: `<h1>\u53CC\u91CD\u9A8C\u8BC1\u5DF2\u5F00\u542F</h1>
4223
+ <p>\u60A8\u7684\u8D26\u6237\u521A\u521A\u5F00\u542F\u4E86\u53CC\u91CD\u9A8C\u8BC1\u3002\u4ECA\u540E\u767B\u5F55\u65F6\uFF0C\u60A8\u9700\u8981\u8F93\u5165\u8EAB\u4EFD\u9A8C\u8BC1\u5668\u5E94\u7528\u4E2D\u7684\u9A8C\u8BC1\u7801\u3002</p>
4224
+ <p class="muted">\u5982\u679C\u8FD9\u4E0D\u662F\u60A8\u672C\u4EBA\u7684\u64CD\u4F5C\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u7ECF\u80FD\u591F\u8BBF\u95EE\u60A8\u7684\u8D26\u6237\u3002</p>`,
4225
+ text: `\u53CC\u91CD\u9A8C\u8BC1\u5DF2\u5F00\u542F
4226
+
4227
+ \u60A8\u7684\u8D26\u6237\u521A\u521A\u5F00\u542F\u4E86\u53CC\u91CD\u9A8C\u8BC1\u3002\u4ECA\u540E\u767B\u5F55\u65F6\uFF0C\u60A8\u9700\u8981\u8F93\u5165\u8EAB\u4EFD\u9A8C\u8BC1\u5668\u5E94\u7528\u4E2D\u7684\u9A8C\u8BC1\u7801\u3002
4228
+
4229
+ \u5982\u679C\u8FD9\u4E0D\u662F\u60A8\u672C\u4EBA\u7684\u64CD\u4F5C\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u7ECF\u80FD\u591F\u8BBF\u95EE\u60A8\u7684\u8D26\u6237\u3002`
4230
+ },
4231
+ [MESSAGE_KEYS.mfaDisabled]: {
4232
+ subject: "\u53CC\u91CD\u9A8C\u8BC1\u5DF2\u5173\u95ED",
4233
+ html: `<h1>\u53CC\u91CD\u9A8C\u8BC1\u5DF2\u5173\u95ED</h1>
4234
+ <p>\u60A8\u7684\u8D26\u6237\u521A\u521A\u5173\u95ED\u4E86\u53CC\u91CD\u9A8C\u8BC1\u3002\u73B0\u5728\u60A8\u7684\u8D26\u6237\u4EC5\u7531\u5BC6\u7801\u4FDD\u62A4\u3002</p>
4235
+ <p class="muted">\u5982\u679C\u8FD9\u4E0D\u662F\u60A8\u672C\u4EBA\u7684\u64CD\u4F5C\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\u5E76\u91CD\u65B0\u5F00\u542F\u53CC\u91CD\u9A8C\u8BC1\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u7ECF\u80FD\u591F\u8BBF\u95EE\u60A8\u7684\u8D26\u6237\u3002</p>`,
4236
+ text: `\u53CC\u91CD\u9A8C\u8BC1\u5DF2\u5173\u95ED
4237
+
4238
+ \u60A8\u7684\u8D26\u6237\u521A\u521A\u5173\u95ED\u4E86\u53CC\u91CD\u9A8C\u8BC1\u3002\u73B0\u5728\u60A8\u7684\u8D26\u6237\u4EC5\u7531\u5BC6\u7801\u4FDD\u62A4\u3002
4239
+
4240
+ \u5982\u679C\u8FD9\u4E0D\u662F\u60A8\u672C\u4EBA\u7684\u64CD\u4F5C\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\u5E76\u91CD\u65B0\u5F00\u542F\u53CC\u91CD\u9A8C\u8BC1\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u7ECF\u80FD\u591F\u8BBF\u95EE\u60A8\u7684\u8D26\u6237\u3002`
4241
+ },
4242
+ [MESSAGE_KEYS.mfaBackupCodesRegenerated]: {
4243
+ subject: "\u60A8\u7684\u5907\u7528\u9A8C\u8BC1\u7801\u5DF2\u91CD\u65B0\u751F\u6210",
4244
+ html: `<h1>\u5DF2\u751F\u6210\u65B0\u7684\u5907\u7528\u9A8C\u8BC1\u7801</h1>
4245
+ <p>\u60A8\u7684\u8D26\u6237\u521A\u521A\u751F\u6210\u4E86\u4E00\u7EC4\u65B0\u7684\u53CC\u91CD\u9A8C\u8BC1\u5907\u7528\u7801\u3002\u4E4B\u524D\u7684\u5907\u7528\u7801\u5DF2\u5168\u90E8\u5931\u6548\u3002</p>
4246
+ <p class="muted">\u5982\u679C\u8FD9\u4E0D\u662F\u60A8\u672C\u4EBA\u7684\u64CD\u4F5C\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u7ECF\u80FD\u591F\u8BBF\u95EE\u60A8\u7684\u8D26\u6237\u3002</p>`,
4247
+ text: `\u5DF2\u751F\u6210\u65B0\u7684\u5907\u7528\u9A8C\u8BC1\u7801
4248
+
4249
+ \u60A8\u7684\u8D26\u6237\u521A\u521A\u751F\u6210\u4E86\u4E00\u7EC4\u65B0\u7684\u53CC\u91CD\u9A8C\u8BC1\u5907\u7528\u7801\u3002\u4E4B\u524D\u7684\u5907\u7528\u7801\u5DF2\u5168\u90E8\u5931\u6548\u3002
4250
+
4251
+ \u5982\u679C\u8FD9\u4E0D\u662F\u60A8\u672C\u4EBA\u7684\u64CD\u4F5C\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u7ECF\u80FD\u591F\u8BBF\u95EE\u60A8\u7684\u8D26\u6237\u3002`
4252
+ },
4253
+ [MESSAGE_KEYS.emailChanged]: {
4254
+ subject: "\u60A8\u7684\u7535\u5B50\u90AE\u7BB1\u5730\u5740\u5DF2\u66F4\u6539",
4255
+ html: `<h1>\u60A8\u7684\u7535\u5B50\u90AE\u7BB1\u5DF2\u66F4\u6539</h1>
4256
+ <p>\u60A8\u8D26\u6237\u7684\u7535\u5B50\u90AE\u7BB1\u5730\u5740\u521A\u521A\u66F4\u6539\u4E3A <strong>{{newEmail}}</strong>\u3002</p>
4257
+ <p class="muted">\u5982\u679C\u662F\u60A8\u672C\u4EBA\u66F4\u6539\u7684\uFF0C\u65E0\u9700\u4EFB\u4F55\u64CD\u4F5C\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u7ECF\u80FD\u591F\u8BBF\u95EE\u60A8\u7684\u8D26\u6237\u3002</p>`,
4258
+ text: `\u60A8\u7684\u7535\u5B50\u90AE\u7BB1\u5DF2\u66F4\u6539
4259
+
4260
+ \u60A8\u8D26\u6237\u7684\u7535\u5B50\u90AE\u7BB1\u5730\u5740\u521A\u521A\u66F4\u6539\u4E3A {{newEmail}}\u3002
4261
+
4262
+ \u5982\u679C\u662F\u60A8\u672C\u4EBA\u66F4\u6539\u7684\uFF0C\u65E0\u9700\u4EFB\u4F55\u64CD\u4F5C\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u7ECF\u80FD\u591F\u8BBF\u95EE\u60A8\u7684\u8D26\u6237\u3002`
4263
+ },
4264
+ [MESSAGE_KEYS.phoneChanged]: {
4265
+ subject: "\u60A8\u7684\u624B\u673A\u53F7\u7801\u5DF2\u66F4\u6539",
4266
+ html: `<h1>\u60A8\u7684\u624B\u673A\u53F7\u7801\u5DF2\u66F4\u6539</h1>
4267
+ <p>\u60A8\u8D26\u6237\u7684\u624B\u673A\u53F7\u7801\u521A\u521A\u5B8C\u6210\u66F4\u65B0\u3002</p>
4268
+ <p class="muted">\u5982\u679C\u662F\u60A8\u672C\u4EBA\u66F4\u6539\u7684\uFF0C\u65E0\u9700\u4EFB\u4F55\u64CD\u4F5C\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u7ECF\u80FD\u591F\u8BBF\u95EE\u60A8\u7684\u8D26\u6237\u3002</p>`,
4269
+ text: `\u60A8\u7684\u624B\u673A\u53F7\u7801\u5DF2\u66F4\u6539
4270
+
4271
+ \u60A8\u8D26\u6237\u7684\u624B\u673A\u53F7\u7801\u521A\u521A\u5B8C\u6210\u66F4\u65B0\u3002
4272
+
4273
+ \u5982\u679C\u662F\u60A8\u672C\u4EBA\u66F4\u6539\u7684\uFF0C\u65E0\u9700\u4EFB\u4F55\u64CD\u4F5C\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u7ECF\u80FD\u591F\u8BBF\u95EE\u60A8\u7684\u8D26\u6237\u3002`
4274
+ },
4275
+ [MESSAGE_KEYS.passwordRevoked]: {
4276
+ subject: "\u60A8\u8D26\u6237\u7684\u5BC6\u7801\u5DF2\u88AB\u79FB\u9664",
4277
+ html: `<h1>\u60A8\u7684\u5BC6\u7801\u5DF2\u88AB\u79FB\u9664</h1>
4278
+ <p>\u60A8\u521A\u521A\u4F7F\u7528 <strong>{{provider}}</strong> \u767B\u5F55\u3002\u6B64\u8D26\u6237\u4E4B\u524D\u8BBE\u7F6E\u8FC7\u5BC6\u7801\uFF0C\u4F46\u8BE5\u7535\u5B50\u90AE\u7BB1\u5730\u5740\u4ECE\u672A\u7ECF\u8FC7\u786E\u8BA4\uFF0C\u56E0\u6B64\u6211\u4EEC\u65E0\u6CD5\u786E\u5B9A\u5BC6\u7801\u662F\u8C01\u8BBE\u7F6E\u7684\uFF0C\u5DF2\u5C06\u5176\u79FB\u9664\u3002</p>
4279
+ <p>\u4F7F\u7528 {{provider}} \u767B\u5F55\u4E0D\u53D7\u5F71\u54CD\u3002\u5982\u679C\u60A8\u8FD8\u60F3\u8BBE\u7F6E\u5BC6\u7801\uFF0C\u53EF\u4EE5\u5728\u8D26\u6237\u8BBE\u7F6E\u4E2D\u8FDB\u884C\u8BBE\u7F6E\u3002</p>
4280
+ <p class="muted">\u5982\u679C\u60A8\u4ECE\u672A\u5728\u6B64\u8BBE\u7F6E\u8FC7\u5BC6\u7801\uFF0C\u53EF\u80FD\u6709\u5176\u4ED6\u4EBA\u66FE\u8BD5\u56FE\u7528\u60A8\u7684\u5730\u5740\u6CE8\u518C\u3002\u60A8\u65E0\u9700\u4EFB\u4F55\u64CD\u4F5C\uFF0C\u6B64\u8D26\u6237\u5C5E\u4E8E\u60A8\u3002</p>`,
4281
+ text: `\u60A8\u7684\u5BC6\u7801\u5DF2\u88AB\u79FB\u9664
4282
+
4283
+ \u60A8\u521A\u521A\u4F7F\u7528 {{provider}} \u767B\u5F55\u3002\u6B64\u8D26\u6237\u4E4B\u524D\u8BBE\u7F6E\u8FC7\u5BC6\u7801\uFF0C\u4F46\u8BE5\u7535\u5B50\u90AE\u7BB1\u5730\u5740\u4ECE\u672A\u7ECF\u8FC7\u786E\u8BA4\uFF0C
4284
+ \u56E0\u6B64\u6211\u4EEC\u65E0\u6CD5\u786E\u5B9A\u5BC6\u7801\u662F\u8C01\u8BBE\u7F6E\u7684\uFF0C\u5DF2\u5C06\u5176\u79FB\u9664\u3002
4285
+
4286
+ \u4F7F\u7528 {{provider}} \u767B\u5F55\u4E0D\u53D7\u5F71\u54CD\u3002\u5982\u679C\u60A8\u8FD8\u60F3\u8BBE\u7F6E\u5BC6\u7801\uFF0C\u53EF\u4EE5\u5728\u8D26\u6237\u8BBE\u7F6E\u4E2D\u8FDB\u884C\u8BBE\u7F6E\u3002
4287
+
4288
+ \u5982\u679C\u60A8\u4ECE\u672A\u5728\u6B64\u8BBE\u7F6E\u8FC7\u5BC6\u7801\uFF0C\u53EF\u80FD\u6709\u5176\u4ED6\u4EBA\u66FE\u8BD5\u56FE\u7528\u60A8\u7684\u5730\u5740\u6CE8\u518C\u3002\u60A8\u65E0\u9700\u4EFB\u4F55\u64CD\u4F5C\uFF0C\u6B64\u8D26\u6237\u5C5E\u4E8E\u60A8\u3002`
4289
+ },
4290
+ [MESSAGE_KEYS.oauthRegistration]: {
4291
+ subject: "\u6B22\u8FCE\u4F7F\u7528 {{appName}}",
4292
+ html: `<h1>\u6B22\u8FCE\u4F7F\u7528 {{appName}}</h1>
4293
+ <p>\u60A8\u7684\u8D26\u6237\u5DF2\u901A\u8FC7 <strong>{{provider}}</strong> \u521B\u5EFA\u3002\u4ECA\u540E\u968F\u65F6\u53EF\u4EE5\u4F7F\u7528\u540C\u4E00\u4E2A {{provider}} \u8D26\u6237\u767B\u5F55\uFF0C\u65E0\u9700\u8BB0\u4F4F\u5BC6\u7801\u3002</p>
4294
+ <p class="muted">\u5982\u679C\u60A8\u6CA1\u6709\u521B\u5EFA\u6B64\u8D26\u6237\uFF0C\u8BF7\u8054\u7CFB\u5BA2\u670D\u3002</p>`,
4295
+ text: `\u6B22\u8FCE
4296
+
4297
+ \u60A8\u7684\u8D26\u6237\u5DF2\u901A\u8FC7 {{provider}} \u521B\u5EFA\u3002\u4ECA\u540E\u968F\u65F6\u53EF\u4EE5\u4F7F\u7528\u540C\u4E00\u4E2A
4298
+ {{provider}} \u8D26\u6237\u767B\u5F55\uFF0C\u65E0\u9700\u8BB0\u4F4F\u5BC6\u7801\u3002
4299
+
4300
+ \u5982\u679C\u60A8\u6CA1\u6709\u521B\u5EFA\u6B64\u8D26\u6237\uFF0C\u8BF7\u8054\u7CFB\u5BA2\u670D\u3002`
4301
+ },
4302
+ [MESSAGE_KEYS.oauthLinked]: {
4303
+ subject: "\u60A8\u7684\u8D26\u6237\u65B0\u589E\u4E86\u4E00\u79CD\u767B\u5F55\u65B9\u5F0F",
4304
+ html: `<h1>\u5DF2\u6DFB\u52A0 {{provider}} \u767B\u5F55</h1>
4305
+ <p>\u73B0\u5728\u4E5F\u53EF\u4EE5\u4F7F\u7528 <strong>{{provider}}</strong> \u767B\u5F55\u60A8\u7684\u8D26\u6237\u3002</p>
4306
+ <p class="muted">\u5982\u679C\u662F\u60A8\u672C\u4EBA\u6DFB\u52A0\u7684\uFF0C\u65E0\u9700\u4EFB\u4F55\u64CD\u4F5C\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\u5E76\u66F4\u6539\u5BC6\u7801\uFF0C\u53EF\u80FD\u6709\u5176\u4ED6\u4EBA\u80FD\u591F\u4EE5\u60A8\u7684\u8EAB\u4EFD\u767B\u5F55\u3002</p>`,
4307
+ text: `\u5DF2\u6DFB\u52A0 {{provider}} \u767B\u5F55
4308
+
4309
+ \u73B0\u5728\u4E5F\u53EF\u4EE5\u4F7F\u7528 {{provider}} \u767B\u5F55\u60A8\u7684\u8D26\u6237\u3002
4310
+
4311
+ \u5982\u679C\u662F\u60A8\u672C\u4EBA\u6DFB\u52A0\u7684\uFF0C\u65E0\u9700\u4EFB\u4F55\u64CD\u4F5C\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\u5E76\u66F4\u6539\u5BC6\u7801\uFF0C
4312
+ \u53EF\u80FD\u6709\u5176\u4ED6\u4EBA\u80FD\u591F\u4EE5\u60A8\u7684\u8EAB\u4EFD\u767B\u5F55\u3002`
4313
+ },
4314
+ [MESSAGE_KEYS.oauthUnlinked]: {
4315
+ subject: "\u60A8\u7684\u8D26\u6237\u79FB\u9664\u4E86\u4E00\u79CD\u767B\u5F55\u65B9\u5F0F",
4316
+ html: `<h1>\u5DF2\u79FB\u9664 {{provider}} \u767B\u5F55</h1>
4317
+ <p>\u5DF2\u65E0\u6CD5\u518D\u4F7F\u7528 <strong>{{provider}}</strong> \u767B\u5F55\u3002\u60A8\u7684\u7535\u5B50\u90AE\u7BB1\u548C\u5BC6\u7801\u4ECD\u7136\u53EF\u7528\u3002</p>
4318
+ <p class="muted">\u5982\u679C\u662F\u60A8\u672C\u4EBA\u79FB\u9664\u7684\uFF0C\u65E0\u9700\u4EFB\u4F55\u64CD\u4F5C\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\u3002</p>`,
4319
+ text: `\u5DF2\u79FB\u9664 {{provider}} \u767B\u5F55
4320
+
4321
+ \u5DF2\u65E0\u6CD5\u518D\u4F7F\u7528 {{provider}} \u767B\u5F55\u3002\u60A8\u7684\u7535\u5B50\u90AE\u7BB1\u548C\u5BC6\u7801\u4ECD\u7136\u53EF\u7528\u3002
4322
+
4323
+ \u5982\u679C\u662F\u60A8\u672C\u4EBA\u79FB\u9664\u7684\uFF0C\u65E0\u9700\u4EFB\u4F55\u64CD\u4F5C\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8BF7\u7ACB\u5373\u8054\u7CFB\u5BA2\u670D\u3002`
4324
+ }
4325
+ };
4326
+
4327
+ // src/templates.zh-Hant.ts
4328
+ var ZH_HANT_TEMPLATES = {
4329
+ [MESSAGE_KEYS.emailRegistration]: {
4330
+ subject: "\u78BA\u8A8D\u60A8\u7684\u5E33\u6236",
4331
+ html: `<h1>\u6B61\u8FCE\u52A0\u5165</h1>
4332
+ <p>{{firstName}}\uFF0C\u60A8\u597D\uFF1A</p>
4333
+ <p>\u611F\u8B1D\u60A8\u7684\u8A3B\u518A\u3002\u8ACB\u4F7F\u7528\u4EE5\u4E0B\u9A57\u8B49\u78BC\u78BA\u8A8D\u60A8\u7684\u5E33\u6236\uFF1A</p>
4334
+ <p><span class="pin-code">{{pin}}</span></p>
4335
+ <p class="muted">\u6B64\u9A57\u8B49\u78BC\u5C07\u65BC 24 \u5C0F\u6642\u5F8C\u5931\u6548\u3002\u5982\u679C\u60A8\u6C92\u6709\u5EFA\u7ACB\u5E33\u6236\uFF0C\u8ACB\u7565\u904E\u9019\u5C01\u90F5\u4EF6\u3002</p>`,
4336
+ text: `\u6B61\u8FCE\u52A0\u5165
4337
+
4338
+ {{firstName}}\uFF0C\u60A8\u597D\uFF1A
4339
+
4340
+ \u611F\u8B1D\u60A8\u7684\u8A3B\u518A\u3002\u8ACB\u4F7F\u7528\u4EE5\u4E0B\u9A57\u8B49\u78BC\u78BA\u8A8D\u60A8\u7684\u5E33\u6236\uFF1A{{pin}}
4341
+
4342
+ \u6B64\u9A57\u8B49\u78BC\u5C07\u65BC 24 \u5C0F\u6642\u5F8C\u5931\u6548\u3002\u5982\u679C\u60A8\u6C92\u6709\u5EFA\u7ACB\u5E33\u6236\uFF0C\u8ACB\u7565\u904E\u9019\u5C01\u90F5\u4EF6\u3002`
4343
+ },
4344
+ [MESSAGE_KEYS.emailVerification]: {
4345
+ subject: "\u60A8\u7684\u9A57\u8B49\u78BC",
4346
+ html: `<h1>\u9A57\u8B49\u60A8\u7684\u96FB\u5B50\u90F5\u4EF6</h1>
4347
+ <p>{{firstName}}\uFF0C\u60A8\u597D\uFF1A</p>
4348
+ <p>\u8ACB\u4F7F\u7528\u4EE5\u4E0B\u9A57\u8B49\u78BC\u5B8C\u6210\u5E33\u6236\u8A2D\u5B9A\uFF1A</p>
4349
+ <p><span class="pin-code">{{pin}}</span></p>
4350
+ <p class="muted">\u6B64\u9A57\u8B49\u78BC\u5C07\u65BC 24 \u5C0F\u6642\u5F8C\u5931\u6548\u3002\u5982\u679C\u60A8\u6C92\u6709\u5EFA\u7ACB\u5E33\u6236\uFF0C\u8ACB\u7565\u904E\u9019\u5C01\u90F5\u4EF6\u3002</p>`,
4351
+ text: `\u9A57\u8B49\u60A8\u7684\u96FB\u5B50\u90F5\u4EF6
4352
+
4353
+ {{firstName}}\uFF0C\u60A8\u597D\uFF1A
4354
+
4355
+ \u8ACB\u4F7F\u7528\u4EE5\u4E0B\u9A57\u8B49\u78BC\u5B8C\u6210\u5E33\u6236\u8A2D\u5B9A\uFF1A{{pin}}
4356
+
4357
+ \u6B64\u9A57\u8B49\u78BC\u5C07\u65BC 24 \u5C0F\u6642\u5F8C\u5931\u6548\u3002\u5982\u679C\u60A8\u6C92\u6709\u5EFA\u7ACB\u5E33\u6236\uFF0C\u8ACB\u7565\u904E\u9019\u5C01\u90F5\u4EF6\u3002`
4358
+ },
4359
+ [MESSAGE_KEYS.passwordReset]: {
4360
+ subject: "\u91CD\u8A2D\u60A8\u7684\u5BC6\u78BC",
4361
+ html: `<h1>\u91CD\u8A2D\u60A8\u7684\u5BC6\u78BC</h1>
4362
+ <p>\u6211\u5011\u6536\u5230\u91CD\u8A2D\u60A8\u5BC6\u78BC\u7684\u8981\u6C42\u3002\u8ACB\u4F7F\u7528\u4EE5\u4E0B\u9A57\u8B49\u78BC\u7E7C\u7E8C\uFF1A</p>
4363
+ <p><span class="pin-code">{{pin}}</span></p>
4364
+ <p class="muted">\u6B64\u9A57\u8B49\u78BC\u5373\u5C07\u5931\u6548\u3002\u5982\u679C\u60A8\u6C92\u6709\u63D0\u51FA\u91CD\u8A2D\u8981\u6C42\uFF0C\u7121\u9808\u63A1\u53D6\u4EFB\u4F55\u884C\u52D5\uFF0C\u60A8\u7684\u5BC6\u78BC\u7DAD\u6301\u4E0D\u8B8A\u3002</p>`,
4365
+ text: `\u91CD\u8A2D\u60A8\u7684\u5BC6\u78BC
4366
+
4367
+ \u6211\u5011\u6536\u5230\u91CD\u8A2D\u60A8\u5BC6\u78BC\u7684\u8981\u6C42\u3002\u8ACB\u4F7F\u7528\u4EE5\u4E0B\u9A57\u8B49\u78BC\u7E7C\u7E8C\uFF1A{{pin}}
4368
+
4369
+ \u6B64\u9A57\u8B49\u78BC\u5373\u5C07\u5931\u6548\u3002\u5982\u679C\u60A8\u6C92\u6709\u63D0\u51FA\u91CD\u8A2D\u8981\u6C42\uFF0C\u7121\u9808\u63A1\u53D6\u4EFB\u4F55\u884C\u52D5\uFF0C\u60A8\u7684\u5BC6\u78BC\u7DAD\u6301\u4E0D\u8B8A\u3002`
4370
+ },
4371
+ [MESSAGE_KEYS.phoneOtp]: {
4372
+ text: "\u60A8\u7684\u9A57\u8B49\u78BC\u662F {{otp}}\uFF0C10 \u5206\u9418\u5167\u6709\u6548\u3002"
4373
+ },
4374
+ [MESSAGE_KEYS.mfaEnabled]: {
4375
+ subject: "\u96D9\u91CD\u9A57\u8B49\u5DF2\u555F\u7528",
4376
+ html: `<h1>\u96D9\u91CD\u9A57\u8B49\u5DF2\u555F\u7528</h1>
4377
+ <p>\u60A8\u7684\u5E33\u6236\u525B\u525B\u555F\u7528\u4E86\u96D9\u91CD\u9A57\u8B49\u3002\u5F80\u5F8C\u767B\u5165\u6642\uFF0C\u60A8\u9700\u8981\u8F38\u5165\u9A57\u8B49\u5668\u61C9\u7528\u7A0B\u5F0F\u4E2D\u7684\u9A57\u8B49\u78BC\u3002</p>
4378
+ <p class="muted">\u5982\u679C\u9019\u4E0D\u662F\u60A8\u672C\u4EBA\u7684\u64CD\u4F5C\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u80FD\u5B58\u53D6\u60A8\u7684\u5E33\u6236\u3002</p>`,
4379
+ text: `\u96D9\u91CD\u9A57\u8B49\u5DF2\u555F\u7528
4380
+
4381
+ \u60A8\u7684\u5E33\u6236\u525B\u525B\u555F\u7528\u4E86\u96D9\u91CD\u9A57\u8B49\u3002\u5F80\u5F8C\u767B\u5165\u6642\uFF0C\u60A8\u9700\u8981\u8F38\u5165\u9A57\u8B49\u5668\u61C9\u7528\u7A0B\u5F0F\u4E2D\u7684\u9A57\u8B49\u78BC\u3002
4382
+
4383
+ \u5982\u679C\u9019\u4E0D\u662F\u60A8\u672C\u4EBA\u7684\u64CD\u4F5C\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u80FD\u5B58\u53D6\u60A8\u7684\u5E33\u6236\u3002`
4384
+ },
4385
+ [MESSAGE_KEYS.mfaDisabled]: {
4386
+ subject: "\u96D9\u91CD\u9A57\u8B49\u5DF2\u505C\u7528",
4387
+ html: `<h1>\u96D9\u91CD\u9A57\u8B49\u5DF2\u505C\u7528</h1>
4388
+ <p>\u60A8\u7684\u5E33\u6236\u525B\u525B\u505C\u7528\u4E86\u96D9\u91CD\u9A57\u8B49\u3002\u73FE\u5728\u60A8\u7684\u5E33\u6236\u50C5\u53D7\u5BC6\u78BC\u4FDD\u8B77\u3002</p>
4389
+ <p class="muted">\u5982\u679C\u9019\u4E0D\u662F\u60A8\u672C\u4EBA\u7684\u64CD\u4F5C\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\u4E26\u91CD\u65B0\u555F\u7528\u96D9\u91CD\u9A57\u8B49\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u80FD\u5B58\u53D6\u60A8\u7684\u5E33\u6236\u3002</p>`,
4390
+ text: `\u96D9\u91CD\u9A57\u8B49\u5DF2\u505C\u7528
4391
+
4392
+ \u60A8\u7684\u5E33\u6236\u525B\u525B\u505C\u7528\u4E86\u96D9\u91CD\u9A57\u8B49\u3002\u73FE\u5728\u60A8\u7684\u5E33\u6236\u50C5\u53D7\u5BC6\u78BC\u4FDD\u8B77\u3002
4393
+
4394
+ \u5982\u679C\u9019\u4E0D\u662F\u60A8\u672C\u4EBA\u7684\u64CD\u4F5C\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\u4E26\u91CD\u65B0\u555F\u7528\u96D9\u91CD\u9A57\u8B49\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u80FD\u5B58\u53D6\u60A8\u7684\u5E33\u6236\u3002`
4395
+ },
4396
+ [MESSAGE_KEYS.mfaBackupCodesRegenerated]: {
4397
+ subject: "\u60A8\u7684\u5099\u7528\u9A57\u8B49\u78BC\u5DF2\u91CD\u65B0\u7522\u751F",
4398
+ html: `<h1>\u5DF2\u7522\u751F\u65B0\u7684\u5099\u7528\u9A57\u8B49\u78BC</h1>
4399
+ <p>\u60A8\u7684\u5E33\u6236\u525B\u525B\u7522\u751F\u4E86\u4E00\u7D44\u65B0\u7684\u96D9\u91CD\u9A57\u8B49\u5099\u7528\u78BC\u3002\u5148\u524D\u7684\u5099\u7528\u78BC\u5DF2\u5168\u90E8\u5931\u6548\u3002</p>
4400
+ <p class="muted">\u5982\u679C\u9019\u4E0D\u662F\u60A8\u672C\u4EBA\u7684\u64CD\u4F5C\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u80FD\u5B58\u53D6\u60A8\u7684\u5E33\u6236\u3002</p>`,
4401
+ text: `\u5DF2\u7522\u751F\u65B0\u7684\u5099\u7528\u9A57\u8B49\u78BC
4402
+
4403
+ \u60A8\u7684\u5E33\u6236\u525B\u525B\u7522\u751F\u4E86\u4E00\u7D44\u65B0\u7684\u96D9\u91CD\u9A57\u8B49\u5099\u7528\u78BC\u3002\u5148\u524D\u7684\u5099\u7528\u78BC\u5DF2\u5168\u90E8\u5931\u6548\u3002
4404
+
4405
+ \u5982\u679C\u9019\u4E0D\u662F\u60A8\u672C\u4EBA\u7684\u64CD\u4F5C\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u80FD\u5B58\u53D6\u60A8\u7684\u5E33\u6236\u3002`
4406
+ },
4407
+ [MESSAGE_KEYS.emailChanged]: {
4408
+ subject: "\u60A8\u7684\u96FB\u5B50\u90F5\u4EF6\u5730\u5740\u5DF2\u8B8A\u66F4",
4409
+ html: `<h1>\u60A8\u7684\u96FB\u5B50\u90F5\u4EF6\u5DF2\u8B8A\u66F4</h1>
4410
+ <p>\u60A8\u5E33\u6236\u7684\u96FB\u5B50\u90F5\u4EF6\u5730\u5740\u525B\u525B\u8B8A\u66F4\u70BA <strong>{{newEmail}}</strong>\u3002</p>
4411
+ <p class="muted">\u5982\u679C\u662F\u60A8\u672C\u4EBA\u8B8A\u66F4\u7684\uFF0C\u7121\u9808\u63A1\u53D6\u4EFB\u4F55\u884C\u52D5\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u80FD\u5B58\u53D6\u60A8\u7684\u5E33\u6236\u3002</p>`,
4412
+ text: `\u60A8\u7684\u96FB\u5B50\u90F5\u4EF6\u5DF2\u8B8A\u66F4
4413
+
4414
+ \u60A8\u5E33\u6236\u7684\u96FB\u5B50\u90F5\u4EF6\u5730\u5740\u525B\u525B\u8B8A\u66F4\u70BA {{newEmail}}\u3002
4415
+
4416
+ \u5982\u679C\u662F\u60A8\u672C\u4EBA\u8B8A\u66F4\u7684\uFF0C\u7121\u9808\u63A1\u53D6\u4EFB\u4F55\u884C\u52D5\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u80FD\u5B58\u53D6\u60A8\u7684\u5E33\u6236\u3002`
4417
+ },
4418
+ [MESSAGE_KEYS.phoneChanged]: {
4419
+ subject: "\u60A8\u7684\u96FB\u8A71\u865F\u78BC\u5DF2\u8B8A\u66F4",
4420
+ html: `<h1>\u60A8\u7684\u96FB\u8A71\u865F\u78BC\u5DF2\u8B8A\u66F4</h1>
4421
+ <p>\u60A8\u5E33\u6236\u7684\u96FB\u8A71\u865F\u78BC\u525B\u525B\u5B8C\u6210\u66F4\u65B0\u3002</p>
4422
+ <p class="muted">\u5982\u679C\u662F\u60A8\u672C\u4EBA\u8B8A\u66F4\u7684\uFF0C\u7121\u9808\u63A1\u53D6\u4EFB\u4F55\u884C\u52D5\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u80FD\u5B58\u53D6\u60A8\u7684\u5E33\u6236\u3002</p>`,
4423
+ text: `\u60A8\u7684\u96FB\u8A71\u865F\u78BC\u5DF2\u8B8A\u66F4
4424
+
4425
+ \u60A8\u5E33\u6236\u7684\u96FB\u8A71\u865F\u78BC\u525B\u525B\u5B8C\u6210\u66F4\u65B0\u3002
4426
+
4427
+ \u5982\u679C\u662F\u60A8\u672C\u4EBA\u8B8A\u66F4\u7684\uFF0C\u7121\u9808\u63A1\u53D6\u4EFB\u4F55\u884C\u52D5\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\uFF0C\u53EF\u80FD\u6709\u4EBA\u5DF2\u80FD\u5B58\u53D6\u60A8\u7684\u5E33\u6236\u3002`
4428
+ },
4429
+ [MESSAGE_KEYS.passwordRevoked]: {
4430
+ subject: "\u60A8\u5E33\u6236\u7684\u5BC6\u78BC\u5DF2\u88AB\u79FB\u9664",
4431
+ html: `<h1>\u60A8\u7684\u5BC6\u78BC\u5DF2\u88AB\u79FB\u9664</h1>
4432
+ <p>\u60A8\u525B\u525B\u4F7F\u7528 <strong>{{provider}}</strong> \u767B\u5165\u3002\u6B64\u5E33\u6236\u5148\u524D\u8A2D\u5B9A\u904E\u5BC6\u78BC\uFF0C\u4F46\u9019\u500B\u96FB\u5B50\u90F5\u4EF6\u5730\u5740\u5F9E\u672A\u7D93\u904E\u78BA\u8A8D\uFF0C\u56E0\u6B64\u6211\u5011\u7121\u6CD5\u5224\u65B7\u5BC6\u78BC\u662F\u8AB0\u8A2D\u5B9A\u7684\uFF0C\u5DF2\u5C07\u5176\u79FB\u9664\u3002</p>
4433
+ <p>\u4F7F\u7528 {{provider}} \u767B\u5165\u4E0D\u53D7\u5F71\u97FF\u3002\u5982\u679C\u60A8\u4E5F\u60F3\u8A2D\u5B9A\u5BC6\u78BC\uFF0C\u53EF\u4EE5\u5728\u5E33\u6236\u8A2D\u5B9A\u4E2D\u9032\u884C\u8A2D\u5B9A\u3002</p>
4434
+ <p class="muted">\u5982\u679C\u60A8\u5F9E\u672A\u5728\u6B64\u8A2D\u5B9A\u904E\u5BC6\u78BC\uFF0C\u53EF\u80FD\u6709\u5176\u4ED6\u4EBA\u66FE\u8A66\u5716\u7528\u60A8\u7684\u5730\u5740\u8A3B\u518A\u3002\u60A8\u7121\u9808\u63A1\u53D6\u4EFB\u4F55\u884C\u52D5\uFF0C\u6B64\u5E33\u6236\u5C6C\u65BC\u60A8\u3002</p>`,
4435
+ text: `\u60A8\u7684\u5BC6\u78BC\u5DF2\u88AB\u79FB\u9664
4436
+
4437
+ \u60A8\u525B\u525B\u4F7F\u7528 {{provider}} \u767B\u5165\u3002\u6B64\u5E33\u6236\u5148\u524D\u8A2D\u5B9A\u904E\u5BC6\u78BC\uFF0C\u4F46\u9019\u500B\u96FB\u5B50\u90F5\u4EF6\u5730\u5740\u5F9E\u672A\u7D93\u904E\u78BA\u8A8D\uFF0C
4438
+ \u56E0\u6B64\u6211\u5011\u7121\u6CD5\u5224\u65B7\u5BC6\u78BC\u662F\u8AB0\u8A2D\u5B9A\u7684\uFF0C\u5DF2\u5C07\u5176\u79FB\u9664\u3002
4439
+
4440
+ \u4F7F\u7528 {{provider}} \u767B\u5165\u4E0D\u53D7\u5F71\u97FF\u3002\u5982\u679C\u60A8\u4E5F\u60F3\u8A2D\u5B9A\u5BC6\u78BC\uFF0C\u53EF\u4EE5\u5728\u5E33\u6236\u8A2D\u5B9A\u4E2D\u9032\u884C\u8A2D\u5B9A\u3002
4441
+
4442
+ \u5982\u679C\u60A8\u5F9E\u672A\u5728\u6B64\u8A2D\u5B9A\u904E\u5BC6\u78BC\uFF0C\u53EF\u80FD\u6709\u5176\u4ED6\u4EBA\u66FE\u8A66\u5716\u7528\u60A8\u7684\u5730\u5740\u8A3B\u518A\u3002\u60A8\u7121\u9808\u63A1\u53D6\u4EFB\u4F55\u884C\u52D5\uFF0C\u6B64\u5E33\u6236\u5C6C\u65BC\u60A8\u3002`
4443
+ },
4444
+ [MESSAGE_KEYS.oauthRegistration]: {
4445
+ subject: "\u6B61\u8FCE\u4F7F\u7528 {{appName}}",
4446
+ html: `<h1>\u6B61\u8FCE\u4F7F\u7528 {{appName}}</h1>
4447
+ <p>\u60A8\u7684\u5E33\u6236\u5DF2\u900F\u904E <strong>{{provider}}</strong> \u5EFA\u7ACB\u3002\u5F80\u5F8C\u96A8\u6642\u90FD\u80FD\u4F7F\u7528\u540C\u4E00\u500B {{provider}} \u5E33\u6236\u767B\u5165\uFF0C\u4E0D\u5FC5\u8A18\u4F4F\u5BC6\u78BC\u3002</p>
4448
+ <p class="muted">\u5982\u679C\u60A8\u6C92\u6709\u5EFA\u7ACB\u6B64\u5E33\u6236\uFF0C\u8ACB\u806F\u7D61\u5BA2\u670D\u3002</p>`,
4449
+ text: `\u6B61\u8FCE
4450
+
4451
+ \u60A8\u7684\u5E33\u6236\u5DF2\u900F\u904E {{provider}} \u5EFA\u7ACB\u3002\u5F80\u5F8C\u96A8\u6642\u90FD\u80FD\u4F7F\u7528\u540C\u4E00\u500B
4452
+ {{provider}} \u5E33\u6236\u767B\u5165\uFF0C\u4E0D\u5FC5\u8A18\u4F4F\u5BC6\u78BC\u3002
4453
+
4454
+ \u5982\u679C\u60A8\u6C92\u6709\u5EFA\u7ACB\u6B64\u5E33\u6236\uFF0C\u8ACB\u806F\u7D61\u5BA2\u670D\u3002`
4455
+ },
4456
+ [MESSAGE_KEYS.oauthLinked]: {
4457
+ subject: "\u60A8\u7684\u5E33\u6236\u65B0\u589E\u4E86\u4E00\u7A2E\u767B\u5165\u65B9\u5F0F",
4458
+ html: `<h1>\u5DF2\u65B0\u589E {{provider}} \u767B\u5165</h1>
4459
+ <p>\u73FE\u5728\u4E5F\u53EF\u4EE5\u4F7F\u7528 <strong>{{provider}}</strong> \u767B\u5165\u60A8\u7684\u5E33\u6236\u3002</p>
4460
+ <p class="muted">\u5982\u679C\u662F\u60A8\u672C\u4EBA\u65B0\u589E\u7684\uFF0C\u7121\u9808\u63A1\u53D6\u4EFB\u4F55\u884C\u52D5\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\u4E26\u8B8A\u66F4\u5BC6\u78BC\uFF0C\u53EF\u80FD\u6709\u5176\u4ED6\u4EBA\u80FD\u4EE5\u60A8\u7684\u8EAB\u5206\u767B\u5165\u3002</p>`,
4461
+ text: `\u5DF2\u65B0\u589E {{provider}} \u767B\u5165
4462
+
4463
+ \u73FE\u5728\u4E5F\u53EF\u4EE5\u4F7F\u7528 {{provider}} \u767B\u5165\u60A8\u7684\u5E33\u6236\u3002
4464
+
4465
+ \u5982\u679C\u662F\u60A8\u672C\u4EBA\u65B0\u589E\u7684\uFF0C\u7121\u9808\u63A1\u53D6\u4EFB\u4F55\u884C\u52D5\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\u4E26\u8B8A\u66F4\u5BC6\u78BC\uFF0C
4466
+ \u53EF\u80FD\u6709\u5176\u4ED6\u4EBA\u80FD\u4EE5\u60A8\u7684\u8EAB\u5206\u767B\u5165\u3002`
4467
+ },
4468
+ [MESSAGE_KEYS.oauthUnlinked]: {
4469
+ subject: "\u60A8\u7684\u5E33\u6236\u79FB\u9664\u4E86\u4E00\u7A2E\u767B\u5165\u65B9\u5F0F",
4470
+ html: `<h1>\u5DF2\u79FB\u9664 {{provider}} \u767B\u5165</h1>
4471
+ <p>\u5DF2\u7121\u6CD5\u518D\u4F7F\u7528 <strong>{{provider}}</strong> \u767B\u5165\u3002\u60A8\u7684\u96FB\u5B50\u90F5\u4EF6\u548C\u5BC6\u78BC\u4ECD\u53EF\u4F7F\u7528\u3002</p>
4472
+ <p class="muted">\u5982\u679C\u662F\u60A8\u672C\u4EBA\u79FB\u9664\u7684\uFF0C\u7121\u9808\u63A1\u53D6\u4EFB\u4F55\u884C\u52D5\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\u3002</p>`,
4473
+ text: `\u5DF2\u79FB\u9664 {{provider}} \u767B\u5165
4474
+
4475
+ \u5DF2\u7121\u6CD5\u518D\u4F7F\u7528 {{provider}} \u767B\u5165\u3002\u60A8\u7684\u96FB\u5B50\u90F5\u4EF6\u548C\u5BC6\u78BC\u4ECD\u53EF\u4F7F\u7528\u3002
4476
+
4477
+ \u5982\u679C\u662F\u60A8\u672C\u4EBA\u79FB\u9664\u7684\uFF0C\u7121\u9808\u63A1\u53D6\u4EFB\u4F55\u884C\u52D5\u3002\u5982\u679C\u4E0D\u662F\uFF0C\u8ACB\u7ACB\u5373\u806F\u7D61\u5BA2\u670D\u3002`
4478
+ }
4479
+ };
4480
+
4168
4481
  // src/templates.ts
4169
4482
  var EN_TEMPLATES = {
4170
4483
  [MESSAGE_KEYS.emailRegistration]: {
@@ -4322,7 +4635,12 @@ change your password \u2014 someone else may be able to sign in as you.`
4322
4635
  If you did this, you're all set. If not, contact support right away.`
4323
4636
  }
4324
4637
  };
4325
- var DEFAULT_TEMPLATES = withTranslations(EN_TEMPLATES, { fr: FR_TEMPLATES, es: ES_TEMPLATES });
4638
+ var DEFAULT_TEMPLATES = withTranslations(EN_TEMPLATES, {
4639
+ fr: FR_TEMPLATES,
4640
+ es: ES_TEMPLATES,
4641
+ "zh-Hans": ZH_HANS_TEMPLATES,
4642
+ "zh-Hant": ZH_HANT_TEMPLATES
4643
+ });
4326
4644
  var SAMPLE_PAYLOADS = {
4327
4645
  [MESSAGE_KEYS.passwordRevoked]: { provider: "Google" },
4328
4646
  [MESSAGE_KEYS.oauthRegistration]: { provider: "Google", appName: "Fonderie" },
@@ -4357,7 +4675,9 @@ async function importUser(store, user) {
4357
4675
  placeholders.push(`$${vals.length}`);
4358
4676
  };
4359
4677
  if (user.id !== void 0) add("id", user.id);
4360
- add("email", user.email.toLowerCase().trim());
4678
+ const email2 = normalizeEmailSafe(user.email);
4679
+ if (!email2) throw new Error(`[auth] importUser: '${user.email}' is not an email address`);
4680
+ add("email", email2);
4361
4681
  if (user.passwordHash !== void 0) add("password_hash", user.passwordHash);
4362
4682
  if (user.firstName !== void 0) add("first_name", user.firstName);
4363
4683
  if (user.lastName !== void 0) add("last_name", user.lastName);