strapi-plugin-firebase-authentication 1.1.12 → 1.2.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/dist/_chunks/{App-BqjE8BHb.js → App-B7d4qS3T.js} +112 -29
- package/dist/_chunks/{App-BY1gNGKH.mjs → App-CQ9ehArz.mjs} +112 -29
- package/dist/_chunks/{api-D_4cdJU5.mjs → api-BM2UtpvM.mjs} +1 -1
- package/dist/_chunks/{api-DQCdqlCd.js → api-DYP-1kdx.js} +1 -1
- package/dist/_chunks/{index-D8pv1Q6h.mjs → index-0tTyhxbb.mjs} +148 -34
- package/dist/_chunks/{index-DlPxMuSK.js → index-B5EwGI_y.js} +2 -2
- package/dist/_chunks/{index-DtGfwf9S.mjs → index-CMFutRyI.mjs} +2 -2
- package/dist/_chunks/{index-C4t4JZZ_.js → index-Cwp9xkG4.js} +148 -34
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/src/components/user-management/ResendVerification/ResendVerification.d.ts +9 -0
- package/dist/admin/src/components/user-management/index.d.ts +1 -0
- package/dist/admin/src/pages/Settings/api.d.ts +4 -0
- package/dist/admin/src/pages/utils/api.d.ts +2 -1
- package/dist/server/index.js +691 -10
- package/dist/server/index.mjs +691 -10
- package/dist/server/src/config/index.d.ts +1 -1
- package/dist/server/src/content-types/index.d.ts +16 -0
- package/dist/server/src/controllers/firebaseController.d.ts +15 -0
- package/dist/server/src/controllers/index.d.ts +3 -0
- package/dist/server/src/controllers/userController.d.ts +1 -0
- package/dist/server/src/index.d.ts +37 -1
- package/dist/server/src/services/emailService.d.ts +10 -0
- package/dist/server/src/services/firebaseService.d.ts +16 -0
- package/dist/server/src/services/index.d.ts +17 -0
- package/dist/server/src/services/settingsService.d.ts +2 -0
- package/dist/server/src/services/tokenService.d.ts +21 -0
- package/dist/server/src/services/userService.d.ts +5 -0
- package/dist/server/src/templates/defaults/email-verification.d.ts +2 -0
- package/dist/server/src/templates/defaults/index.d.ts +1 -0
- package/dist/server/src/templates/types.d.ts +3 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -230,7 +230,7 @@ const register = ({ strapi: strapi2 }) => {
|
|
|
230
230
|
const config$1 = {
|
|
231
231
|
default: ({ env: env2 }) => ({
|
|
232
232
|
firebaseJsonEncryptionKey: env2("FIREBASE_JSON_ENCRYPTION_KEY", "your-key-here"),
|
|
233
|
-
emailRequired:
|
|
233
|
+
emailRequired: env2.bool("FIREBASE_EMAIL_REQUIRED", false),
|
|
234
234
|
emailPattern: "{randomString}@phone-user.firebase.local"
|
|
235
235
|
}),
|
|
236
236
|
validator(config2) {
|
|
@@ -321,6 +321,15 @@ const attributes$1 = {
|
|
|
321
321
|
minimum: 1,
|
|
322
322
|
maximum: 72,
|
|
323
323
|
description: "How long the magic link remains valid (in hours)"
|
|
324
|
+
},
|
|
325
|
+
emailVerificationUrl: {
|
|
326
|
+
type: "string",
|
|
327
|
+
"default": "http://localhost:3000/verify-email",
|
|
328
|
+
description: "URL where users will be redirected to verify their email"
|
|
329
|
+
},
|
|
330
|
+
emailVerificationEmailSubject: {
|
|
331
|
+
type: "string",
|
|
332
|
+
"default": "Verify Your Email"
|
|
324
333
|
}
|
|
325
334
|
};
|
|
326
335
|
const firebaseAuthenticationConfiguration = {
|
|
@@ -371,6 +380,13 @@ const attributes = {
|
|
|
371
380
|
},
|
|
372
381
|
resetTokenExpiresAt: {
|
|
373
382
|
type: "datetime"
|
|
383
|
+
},
|
|
384
|
+
verificationTokenHash: {
|
|
385
|
+
type: "string",
|
|
386
|
+
"private": true
|
|
387
|
+
},
|
|
388
|
+
verificationTokenExpiresAt: {
|
|
389
|
+
type: "datetime"
|
|
374
390
|
}
|
|
375
391
|
};
|
|
376
392
|
const firebaseUserData = {
|
|
@@ -615,6 +631,61 @@ const firebaseController = {
|
|
|
615
631
|
ctx.status = 500;
|
|
616
632
|
ctx.body = { error: "An error occurred while resetting your password" };
|
|
617
633
|
}
|
|
634
|
+
},
|
|
635
|
+
/**
|
|
636
|
+
* Send email verification - public endpoint
|
|
637
|
+
* POST /api/firebase-authentication/sendVerificationEmail
|
|
638
|
+
* Public endpoint - no authentication required
|
|
639
|
+
*/
|
|
640
|
+
async sendVerificationEmail(ctx) {
|
|
641
|
+
strapi.log.debug("sendVerificationEmail endpoint called");
|
|
642
|
+
try {
|
|
643
|
+
const { email: email2 } = ctx.request.body || {};
|
|
644
|
+
ctx.body = await strapi.plugin(pluginName).service("firebaseService").sendVerificationEmail(email2);
|
|
645
|
+
} catch (error2) {
|
|
646
|
+
strapi.log.error("sendVerificationEmail controller error:", error2);
|
|
647
|
+
if (error2.name === "ValidationError") {
|
|
648
|
+
ctx.status = 400;
|
|
649
|
+
} else {
|
|
650
|
+
ctx.status = 500;
|
|
651
|
+
}
|
|
652
|
+
ctx.body = { error: error2.message };
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
/**
|
|
656
|
+
* Verify email using custom JWT token
|
|
657
|
+
* POST /api/firebase-authentication/verifyEmail
|
|
658
|
+
* Public endpoint - token provides authentication
|
|
659
|
+
*
|
|
660
|
+
* @param ctx - Koa context with { token } in body
|
|
661
|
+
* @returns { success: true, message: "Email verified successfully" }
|
|
662
|
+
*/
|
|
663
|
+
async verifyEmail(ctx) {
|
|
664
|
+
strapi.log.debug("verifyEmail endpoint called");
|
|
665
|
+
try {
|
|
666
|
+
const { token } = ctx.request.body || {};
|
|
667
|
+
if (!token) {
|
|
668
|
+
ctx.status = 400;
|
|
669
|
+
ctx.body = { error: "Token is required" };
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
const result = await strapi.plugin(pluginName).service("firebaseService").verifyEmail(token);
|
|
673
|
+
ctx.body = result;
|
|
674
|
+
} catch (error2) {
|
|
675
|
+
strapi.log.error("verifyEmail controller error:", error2);
|
|
676
|
+
if (error2.name === "ValidationError") {
|
|
677
|
+
ctx.status = 400;
|
|
678
|
+
ctx.body = { error: error2.message };
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (error2.name === "NotFoundError") {
|
|
682
|
+
ctx.status = 404;
|
|
683
|
+
ctx.body = { error: error2.message };
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
ctx.status = 500;
|
|
687
|
+
ctx.body = { error: "An error occurred while verifying your email" };
|
|
688
|
+
}
|
|
618
689
|
}
|
|
619
690
|
};
|
|
620
691
|
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
|
|
@@ -28982,6 +29053,17 @@ const userController = {
|
|
|
28982
29053
|
} catch (error2) {
|
|
28983
29054
|
throw new ApplicationError$2(error2.message || "Failed to send password reset email");
|
|
28984
29055
|
}
|
|
29056
|
+
},
|
|
29057
|
+
sendVerificationEmail: async (ctx) => {
|
|
29058
|
+
const userId = ctx.params.id;
|
|
29059
|
+
if (!userId) {
|
|
29060
|
+
throw new ValidationError$1("User ID is required");
|
|
29061
|
+
}
|
|
29062
|
+
try {
|
|
29063
|
+
ctx.body = await strapi.plugin("firebase-authentication").service("userService").sendVerificationEmail(userId);
|
|
29064
|
+
} catch (error2) {
|
|
29065
|
+
throw new ApplicationError$2(error2.message || "Failed to send verification email");
|
|
29066
|
+
}
|
|
28985
29067
|
}
|
|
28986
29068
|
};
|
|
28987
29069
|
const settingsController = {
|
|
@@ -29084,7 +29166,9 @@ const settingsController = {
|
|
|
29084
29166
|
enableMagicLink = false,
|
|
29085
29167
|
magicLinkUrl = "http://localhost:1338/verify-magic-link.html",
|
|
29086
29168
|
magicLinkEmailSubject = "Sign in to Your Application",
|
|
29087
|
-
magicLinkExpiryHours = 1
|
|
29169
|
+
magicLinkExpiryHours = 1,
|
|
29170
|
+
emailVerificationUrl = "http://localhost:3000/verify-email",
|
|
29171
|
+
emailVerificationEmailSubject = "Verify Your Email"
|
|
29088
29172
|
} = requestBody;
|
|
29089
29173
|
const existingConfig = await strapi.db.query("plugin::firebase-authentication.firebase-authentication-configuration").findOne({ where: {} });
|
|
29090
29174
|
let result;
|
|
@@ -29098,7 +29182,9 @@ const settingsController = {
|
|
|
29098
29182
|
enableMagicLink,
|
|
29099
29183
|
magicLinkUrl,
|
|
29100
29184
|
magicLinkEmailSubject,
|
|
29101
|
-
magicLinkExpiryHours
|
|
29185
|
+
magicLinkExpiryHours,
|
|
29186
|
+
emailVerificationUrl,
|
|
29187
|
+
emailVerificationEmailSubject
|
|
29102
29188
|
}
|
|
29103
29189
|
});
|
|
29104
29190
|
} else {
|
|
@@ -29112,7 +29198,9 @@ const settingsController = {
|
|
|
29112
29198
|
enableMagicLink,
|
|
29113
29199
|
magicLinkUrl,
|
|
29114
29200
|
magicLinkEmailSubject,
|
|
29115
|
-
magicLinkExpiryHours
|
|
29201
|
+
magicLinkExpiryHours,
|
|
29202
|
+
emailVerificationUrl,
|
|
29203
|
+
emailVerificationEmailSubject
|
|
29116
29204
|
}
|
|
29117
29205
|
});
|
|
29118
29206
|
}
|
|
@@ -29124,7 +29212,9 @@ const settingsController = {
|
|
|
29124
29212
|
enableMagicLink: result.enableMagicLink,
|
|
29125
29213
|
magicLinkUrl: result.magicLinkUrl,
|
|
29126
29214
|
magicLinkEmailSubject: result.magicLinkEmailSubject,
|
|
29127
|
-
magicLinkExpiryHours: result.magicLinkExpiryHours
|
|
29215
|
+
magicLinkExpiryHours: result.magicLinkExpiryHours,
|
|
29216
|
+
emailVerificationUrl: result.emailVerificationUrl,
|
|
29217
|
+
emailVerificationEmailSubject: result.emailVerificationEmailSubject
|
|
29128
29218
|
};
|
|
29129
29219
|
} catch (error2) {
|
|
29130
29220
|
throw new ApplicationError$2("Error saving password configuration", {
|
|
@@ -29217,6 +29307,14 @@ const admin = {
|
|
|
29217
29307
|
policies: ["admin::isAuthenticatedAdmin"]
|
|
29218
29308
|
}
|
|
29219
29309
|
},
|
|
29310
|
+
{
|
|
29311
|
+
method: "PUT",
|
|
29312
|
+
path: "/users/sendVerificationEmail/:id",
|
|
29313
|
+
handler: "userController.sendVerificationEmail",
|
|
29314
|
+
config: {
|
|
29315
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
29316
|
+
}
|
|
29317
|
+
},
|
|
29220
29318
|
{
|
|
29221
29319
|
method: "GET",
|
|
29222
29320
|
path: "/users/:id",
|
|
@@ -29315,6 +29413,26 @@ const contentApi = {
|
|
|
29315
29413
|
// Public endpoint - token provides authentication
|
|
29316
29414
|
policies: []
|
|
29317
29415
|
}
|
|
29416
|
+
},
|
|
29417
|
+
{
|
|
29418
|
+
method: "POST",
|
|
29419
|
+
path: "/sendVerificationEmail",
|
|
29420
|
+
handler: "firebaseController.sendVerificationEmail",
|
|
29421
|
+
config: {
|
|
29422
|
+
auth: false,
|
|
29423
|
+
// Public endpoint - sends email verification link
|
|
29424
|
+
policies: []
|
|
29425
|
+
}
|
|
29426
|
+
},
|
|
29427
|
+
{
|
|
29428
|
+
method: "POST",
|
|
29429
|
+
path: "/verifyEmail",
|
|
29430
|
+
handler: "firebaseController.verifyEmail",
|
|
29431
|
+
config: {
|
|
29432
|
+
auth: false,
|
|
29433
|
+
// Public endpoint - token provides authentication
|
|
29434
|
+
policies: []
|
|
29435
|
+
}
|
|
29318
29436
|
}
|
|
29319
29437
|
]
|
|
29320
29438
|
};
|
|
@@ -29435,7 +29553,10 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29435
29553
|
enableMagicLink: configObject.enableMagicLink || false,
|
|
29436
29554
|
magicLinkUrl: configObject.magicLinkUrl || "http://localhost:1338/verify-magic-link.html",
|
|
29437
29555
|
magicLinkEmailSubject: configObject.magicLinkEmailSubject || "Sign in to Your Application",
|
|
29438
|
-
magicLinkExpiryHours: configObject.magicLinkExpiryHours || 1
|
|
29556
|
+
magicLinkExpiryHours: configObject.magicLinkExpiryHours || 1,
|
|
29557
|
+
// Include email verification configuration fields
|
|
29558
|
+
emailVerificationUrl: configObject.emailVerificationUrl || "http://localhost:3000/verify-email",
|
|
29559
|
+
emailVerificationEmailSubject: configObject.emailVerificationEmailSubject || "Verify Your Email"
|
|
29439
29560
|
};
|
|
29440
29561
|
} catch (error2) {
|
|
29441
29562
|
strapi2.log.error(`Firebase config error: ${error2.message}`);
|
|
@@ -29478,7 +29599,9 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29478
29599
|
enableMagicLink = false,
|
|
29479
29600
|
magicLinkUrl = "http://localhost:1338/verify-magic-link.html",
|
|
29480
29601
|
magicLinkEmailSubject = "Sign in to Your Application",
|
|
29481
|
-
magicLinkExpiryHours = 1
|
|
29602
|
+
magicLinkExpiryHours = 1,
|
|
29603
|
+
emailVerificationUrl = "http://localhost:3000/verify-email",
|
|
29604
|
+
emailVerificationEmailSubject = "Verify Your Email"
|
|
29482
29605
|
} = requestBody;
|
|
29483
29606
|
if (!requestBody) throw new ValidationError3(ERROR_MESSAGES.MISSING_DATA);
|
|
29484
29607
|
try {
|
|
@@ -29514,7 +29637,9 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29514
29637
|
enableMagicLink,
|
|
29515
29638
|
magicLinkUrl,
|
|
29516
29639
|
magicLinkEmailSubject,
|
|
29517
|
-
magicLinkExpiryHours
|
|
29640
|
+
magicLinkExpiryHours,
|
|
29641
|
+
emailVerificationUrl,
|
|
29642
|
+
emailVerificationEmailSubject
|
|
29518
29643
|
}
|
|
29519
29644
|
});
|
|
29520
29645
|
} else {
|
|
@@ -29530,11 +29655,20 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29530
29655
|
enableMagicLink,
|
|
29531
29656
|
magicLinkUrl,
|
|
29532
29657
|
magicLinkEmailSubject,
|
|
29533
|
-
magicLinkExpiryHours
|
|
29658
|
+
magicLinkExpiryHours,
|
|
29659
|
+
emailVerificationUrl,
|
|
29660
|
+
emailVerificationEmailSubject
|
|
29534
29661
|
}
|
|
29535
29662
|
});
|
|
29536
29663
|
}
|
|
29537
29664
|
await strapi2.plugin("firebase-authentication").service("settingsService").init();
|
|
29665
|
+
setImmediate(async () => {
|
|
29666
|
+
try {
|
|
29667
|
+
await strapi2.plugin("firebase-authentication").service("autoLinkService").linkAllUsers(strapi2);
|
|
29668
|
+
} catch (error2) {
|
|
29669
|
+
strapi2.log.error(`Auto-linking after config save failed: ${error2.message}`);
|
|
29670
|
+
}
|
|
29671
|
+
});
|
|
29538
29672
|
const configData = res.firebaseConfigJson || res.firebase_config_json;
|
|
29539
29673
|
if (!configData) {
|
|
29540
29674
|
strapi2.log.error("Firebase config data missing from database response");
|
|
@@ -29558,6 +29692,8 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29558
29692
|
res.magicLinkUrl = res.magicLinkUrl || magicLinkUrl;
|
|
29559
29693
|
res.magicLinkEmailSubject = res.magicLinkEmailSubject || magicLinkEmailSubject;
|
|
29560
29694
|
res.magicLinkExpiryHours = res.magicLinkExpiryHours || magicLinkExpiryHours;
|
|
29695
|
+
res.emailVerificationUrl = res.emailVerificationUrl || emailVerificationUrl;
|
|
29696
|
+
res.emailVerificationEmailSubject = res.emailVerificationEmailSubject || emailVerificationEmailSubject;
|
|
29561
29697
|
return res;
|
|
29562
29698
|
} catch (error2) {
|
|
29563
29699
|
strapi2.log.error("=== FIREBASE CONFIG SAVE ERROR ===");
|
|
@@ -30217,6 +30353,40 @@ const userService = ({ strapi: strapi2 }) => {
|
|
|
30217
30353
|
}
|
|
30218
30354
|
throw new ApplicationError$2(e.message?.toString() || "Failed to reset password");
|
|
30219
30355
|
}
|
|
30356
|
+
},
|
|
30357
|
+
/**
|
|
30358
|
+
* Send email verification email (admin-initiated)
|
|
30359
|
+
* @param entityId - Firebase UID of the user
|
|
30360
|
+
*/
|
|
30361
|
+
sendVerificationEmail: async (entityId) => {
|
|
30362
|
+
try {
|
|
30363
|
+
ensureFirebaseInitialized();
|
|
30364
|
+
const user = await strapi2.firebase.auth().getUser(entityId);
|
|
30365
|
+
if (!user.email) {
|
|
30366
|
+
throw new ApplicationError$2("User does not have an email address");
|
|
30367
|
+
}
|
|
30368
|
+
if (user.emailVerified) {
|
|
30369
|
+
return { success: true, message: "Email is already verified" };
|
|
30370
|
+
}
|
|
30371
|
+
const config2 = await strapi2.db.query("plugin::firebase-authentication.firebase-authentication-configuration").findOne({ where: {} });
|
|
30372
|
+
const emailVerificationUrl = config2?.emailVerificationUrl;
|
|
30373
|
+
if (!emailVerificationUrl) {
|
|
30374
|
+
throw new ApplicationError$2("Email verification URL is not configured");
|
|
30375
|
+
}
|
|
30376
|
+
const firebaseUserData2 = await strapi2.plugin("firebase-authentication").service("firebaseUserDataService").getByFirebaseUID(entityId);
|
|
30377
|
+
if (!firebaseUserData2) {
|
|
30378
|
+
throw new ApplicationError$2("User is not linked to Firebase authentication");
|
|
30379
|
+
}
|
|
30380
|
+
const tokenService2 = strapi2.plugin("firebase-authentication").service("tokenService");
|
|
30381
|
+
const token = await tokenService2.generateVerificationToken(firebaseUserData2.documentId, user.email);
|
|
30382
|
+
const verificationLink = `${emailVerificationUrl}?token=${token}`;
|
|
30383
|
+
strapi2.log.debug(`Generated email verification link for user ${user.email}`);
|
|
30384
|
+
const emailService2 = strapi2.plugin("firebase-authentication").service("emailService");
|
|
30385
|
+
return await emailService2.sendVerificationEmail(user, verificationLink);
|
|
30386
|
+
} catch (e) {
|
|
30387
|
+
strapi2.log.error(`sendVerificationEmail error: ${e.message}`);
|
|
30388
|
+
throw new ApplicationError$2(e.message?.toString() || "Failed to send verification email");
|
|
30389
|
+
}
|
|
30220
30390
|
}
|
|
30221
30391
|
};
|
|
30222
30392
|
};
|
|
@@ -30896,6 +31066,159 @@ const firebaseService = ({ strapi: strapi2 }) => ({
|
|
|
30896
31066
|
verificationUrl: magicLinkUrl
|
|
30897
31067
|
};
|
|
30898
31068
|
}
|
|
31069
|
+
},
|
|
31070
|
+
/**
|
|
31071
|
+
* Send email verification - public endpoint
|
|
31072
|
+
* Generates a verification token and sends an email to the user
|
|
31073
|
+
* Security: Always returns generic success message to prevent email enumeration
|
|
31074
|
+
*/
|
|
31075
|
+
async sendVerificationEmail(email2) {
|
|
31076
|
+
strapi2.log.info(`[sendVerificationEmail] Starting email verification for: ${email2}`);
|
|
31077
|
+
if (!email2) {
|
|
31078
|
+
throw new ValidationError$1("Email is required");
|
|
31079
|
+
}
|
|
31080
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
31081
|
+
if (!emailRegex.test(email2)) {
|
|
31082
|
+
throw new ValidationError$1("Invalid email format");
|
|
31083
|
+
}
|
|
31084
|
+
const config2 = await strapi2.plugin("firebase-authentication").service("settingsService").getFirebaseConfigJson();
|
|
31085
|
+
const verificationUrl = config2?.emailVerificationUrl;
|
|
31086
|
+
if (!verificationUrl) {
|
|
31087
|
+
throw new ApplicationError$2("Email verification URL is not configured");
|
|
31088
|
+
}
|
|
31089
|
+
if (process.env.NODE_ENV === "production" && !verificationUrl.startsWith("https://")) {
|
|
31090
|
+
throw new ApplicationError$2("Email verification URL must use HTTPS in production");
|
|
31091
|
+
}
|
|
31092
|
+
try {
|
|
31093
|
+
new URL(verificationUrl);
|
|
31094
|
+
} catch (error2) {
|
|
31095
|
+
throw new ApplicationError$2("Email verification URL is not a valid URL format");
|
|
31096
|
+
}
|
|
31097
|
+
try {
|
|
31098
|
+
let firebaseUser;
|
|
31099
|
+
try {
|
|
31100
|
+
firebaseUser = await strapi2.firebase.auth().getUserByEmail(email2);
|
|
31101
|
+
} catch (fbError) {
|
|
31102
|
+
strapi2.log.debug("User not found in Firebase");
|
|
31103
|
+
}
|
|
31104
|
+
if (!firebaseUser) {
|
|
31105
|
+
strapi2.log.warn(`⚠️ [sendVerificationEmail] User not found in Firebase for email: ${email2}`);
|
|
31106
|
+
return { message: "If an account with that email exists, a verification link has been sent." };
|
|
31107
|
+
}
|
|
31108
|
+
if (firebaseUser.emailVerified) {
|
|
31109
|
+
strapi2.log.info(`[sendVerificationEmail] User ${email2} is already verified`);
|
|
31110
|
+
return { message: "Email is already verified." };
|
|
31111
|
+
}
|
|
31112
|
+
const firebaseData = await strapi2.plugin("firebase-authentication").service("firebaseUserDataService").getByFirebaseUID(firebaseUser.uid);
|
|
31113
|
+
if (!firebaseData) {
|
|
31114
|
+
strapi2.log.warn(`⚠️ [sendVerificationEmail] No firebase-user-data record for: ${email2}`);
|
|
31115
|
+
return { message: "If an account with that email exists, a verification link has been sent." };
|
|
31116
|
+
}
|
|
31117
|
+
strapi2.log.info(
|
|
31118
|
+
`✅ [sendVerificationEmail] User found: ${JSON.stringify({
|
|
31119
|
+
firebaseUID: firebaseUser.uid,
|
|
31120
|
+
email: firebaseUser.email,
|
|
31121
|
+
emailVerified: firebaseUser.emailVerified
|
|
31122
|
+
})}`
|
|
31123
|
+
);
|
|
31124
|
+
const tokenService2 = strapi2.plugin("firebase-authentication").service("tokenService");
|
|
31125
|
+
const token = await tokenService2.generateVerificationToken(firebaseData.documentId, email2);
|
|
31126
|
+
const verificationLink = `${verificationUrl}?token=${token}`;
|
|
31127
|
+
strapi2.log.info(`✅ [sendVerificationEmail] Verification link generated for ${email2}`);
|
|
31128
|
+
strapi2.log.info(`[sendVerificationEmail] Attempting to send verification email to: ${email2}`);
|
|
31129
|
+
await strapi2.plugin("firebase-authentication").service("emailService").sendVerificationEmail(firebaseUser, verificationLink);
|
|
31130
|
+
strapi2.log.info(`✅ [sendVerificationEmail] Verification email sent successfully to: ${email2}`);
|
|
31131
|
+
return {
|
|
31132
|
+
message: "If an account with that email exists, a verification link has been sent."
|
|
31133
|
+
};
|
|
31134
|
+
} catch (error2) {
|
|
31135
|
+
strapi2.log.error(
|
|
31136
|
+
`❌ [sendVerificationEmail] ERROR: ${JSON.stringify({
|
|
31137
|
+
email: email2,
|
|
31138
|
+
message: error2.message,
|
|
31139
|
+
code: error2.code,
|
|
31140
|
+
name: error2.name,
|
|
31141
|
+
stack: error2.stack
|
|
31142
|
+
})}`
|
|
31143
|
+
);
|
|
31144
|
+
return {
|
|
31145
|
+
message: "If an account with that email exists, a verification link has been sent."
|
|
31146
|
+
};
|
|
31147
|
+
}
|
|
31148
|
+
},
|
|
31149
|
+
/**
|
|
31150
|
+
* Verify email with token - public endpoint
|
|
31151
|
+
* Validates the token and marks the user's email as verified in Firebase
|
|
31152
|
+
*/
|
|
31153
|
+
async verifyEmail(token) {
|
|
31154
|
+
strapi2.log.info(`[verifyEmail] Starting email verification with token`);
|
|
31155
|
+
if (!token) {
|
|
31156
|
+
throw new ValidationError$1("Verification token is required");
|
|
31157
|
+
}
|
|
31158
|
+
const tokenService2 = strapi2.plugin("firebase-authentication").service("tokenService");
|
|
31159
|
+
const validationResult = await tokenService2.validateVerificationToken(token);
|
|
31160
|
+
if (!validationResult.valid) {
|
|
31161
|
+
strapi2.log.warn(`[verifyEmail] Token validation failed: ${validationResult.error}`);
|
|
31162
|
+
throw new ValidationError$1(validationResult.error || "Invalid verification link");
|
|
31163
|
+
}
|
|
31164
|
+
const { firebaseUID, firebaseUserDataDocumentId, email: tokenEmail } = validationResult;
|
|
31165
|
+
try {
|
|
31166
|
+
const firebaseUser = await strapi2.firebase.auth().getUser(firebaseUID);
|
|
31167
|
+
if (tokenEmail && firebaseUser.email !== tokenEmail) {
|
|
31168
|
+
strapi2.log.warn(
|
|
31169
|
+
`[verifyEmail] Email changed: token email ${tokenEmail} != current email ${firebaseUser.email}`
|
|
31170
|
+
);
|
|
31171
|
+
await tokenService2.invalidateVerificationToken(firebaseUserDataDocumentId);
|
|
31172
|
+
throw new ValidationError$1(
|
|
31173
|
+
"Email address has changed since verification was requested. Please request a new verification link."
|
|
31174
|
+
);
|
|
31175
|
+
}
|
|
31176
|
+
if (firebaseUser.emailVerified) {
|
|
31177
|
+
strapi2.log.info(`[verifyEmail] User ${firebaseUser.email} is already verified`);
|
|
31178
|
+
await tokenService2.invalidateVerificationToken(firebaseUserDataDocumentId);
|
|
31179
|
+
return {
|
|
31180
|
+
success: true,
|
|
31181
|
+
message: "Email is already verified."
|
|
31182
|
+
};
|
|
31183
|
+
}
|
|
31184
|
+
await strapi2.firebase.auth().updateUser(firebaseUID, {
|
|
31185
|
+
emailVerified: true
|
|
31186
|
+
});
|
|
31187
|
+
try {
|
|
31188
|
+
const firebaseUserDataService2 = strapi2.plugin("firebase-authentication").service("firebaseUserDataService");
|
|
31189
|
+
const firebaseUserData2 = await firebaseUserDataService2.getByFirebaseUID(firebaseUID);
|
|
31190
|
+
if (firebaseUserData2?.user?.documentId) {
|
|
31191
|
+
await strapi2.db.query("plugin::users-permissions.user").update({
|
|
31192
|
+
where: { documentId: firebaseUserData2.user.documentId },
|
|
31193
|
+
data: { confirmed: true }
|
|
31194
|
+
});
|
|
31195
|
+
strapi2.log.info(`✅ [verifyEmail] Strapi user confirmed for: ${firebaseUserData2.user.documentId}`);
|
|
31196
|
+
}
|
|
31197
|
+
} catch (strapiUpdateError) {
|
|
31198
|
+
strapi2.log.warn(
|
|
31199
|
+
`[verifyEmail] Failed to update Strapi user confirmed status: ${strapiUpdateError.message}`
|
|
31200
|
+
);
|
|
31201
|
+
}
|
|
31202
|
+
strapi2.log.info(`✅ [verifyEmail] Email verified successfully for: ${firebaseUser.email}`);
|
|
31203
|
+
await tokenService2.invalidateVerificationToken(firebaseUserDataDocumentId);
|
|
31204
|
+
return {
|
|
31205
|
+
success: true,
|
|
31206
|
+
message: "Email verified successfully."
|
|
31207
|
+
};
|
|
31208
|
+
} catch (error2) {
|
|
31209
|
+
strapi2.log.error(
|
|
31210
|
+
`❌ [verifyEmail] ERROR: ${JSON.stringify({
|
|
31211
|
+
firebaseUID,
|
|
31212
|
+
message: error2.message,
|
|
31213
|
+
code: error2.code,
|
|
31214
|
+
name: error2.name
|
|
31215
|
+
})}`
|
|
31216
|
+
);
|
|
31217
|
+
if (error2 instanceof ValidationError$1) {
|
|
31218
|
+
throw error2;
|
|
31219
|
+
}
|
|
31220
|
+
throw new ApplicationError$2("Failed to verify email. Please try again.");
|
|
31221
|
+
}
|
|
30899
31222
|
}
|
|
30900
31223
|
});
|
|
30901
31224
|
const passwordResetTemplate = {
|
|
@@ -31275,13 +31598,144 @@ The <%= appName %> Team
|
|
|
31275
31598
|
Need help? Contact us at <%= supportEmail %>
|
|
31276
31599
|
<% } %>
|
|
31277
31600
|
|
|
31601
|
+
© <%= year %> <%= appName %>. All rights reserved.
|
|
31602
|
+
`.trim()
|
|
31603
|
+
};
|
|
31604
|
+
const emailVerificationTemplate = {
|
|
31605
|
+
subject: "Verify Your Email - <%= appName %>",
|
|
31606
|
+
html: `
|
|
31607
|
+
<!DOCTYPE html>
|
|
31608
|
+
<html lang="en">
|
|
31609
|
+
<head>
|
|
31610
|
+
<meta charset="UTF-8">
|
|
31611
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
31612
|
+
<title>Verify Your Email</title>
|
|
31613
|
+
</head>
|
|
31614
|
+
<body style="margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f4f4f4;">
|
|
31615
|
+
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;">
|
|
31616
|
+
<tr>
|
|
31617
|
+
<td align="center" style="padding: 40px 0;">
|
|
31618
|
+
<table role="presentation" style="width: 600px; max-width: 100%; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
|
|
31619
|
+
<!-- Header -->
|
|
31620
|
+
<tr>
|
|
31621
|
+
<td style="padding: 40px 40px 20px 40px; text-align: center;">
|
|
31622
|
+
<h1 style="margin: 0; font-size: 28px; color: #333333; font-weight: 600;">
|
|
31623
|
+
Verify Your Email Address
|
|
31624
|
+
</h1>
|
|
31625
|
+
</td>
|
|
31626
|
+
</tr>
|
|
31627
|
+
|
|
31628
|
+
<!-- Content -->
|
|
31629
|
+
<tr>
|
|
31630
|
+
<td style="padding: 20px 40px;">
|
|
31631
|
+
<p style="margin: 0 0 20px 0; font-size: 16px; line-height: 1.6; color: #555555;">
|
|
31632
|
+
Hello<% if (user.firstName) { %> <%= user.firstName %><% } %>,
|
|
31633
|
+
</p>
|
|
31634
|
+
|
|
31635
|
+
<p style="margin: 0 0 20px 0; font-size: 16px; line-height: 1.6; color: #555555;">
|
|
31636
|
+
Thank you for signing up with <strong><%= appName %></strong>! Please verify your email address <strong><%= user.email %></strong> to complete your registration.
|
|
31637
|
+
</p>
|
|
31638
|
+
|
|
31639
|
+
<p style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #555555;">
|
|
31640
|
+
Click the button below to verify your email:
|
|
31641
|
+
</p>
|
|
31642
|
+
|
|
31643
|
+
<!-- CTA Button -->
|
|
31644
|
+
<table role="presentation" style="width: 100%; border-collapse: collapse;">
|
|
31645
|
+
<tr>
|
|
31646
|
+
<td align="center" style="padding: 0 0 30px 0;">
|
|
31647
|
+
<a href="<%= verificationLink %>"
|
|
31648
|
+
style="display: inline-block; padding: 14px 32px; background-color: #28a745; color: #ffffff; text-decoration: none; font-size: 16px; font-weight: 600; border-radius: 5px; transition: background-color 0.3s;">
|
|
31649
|
+
Verify Email Address
|
|
31650
|
+
</a>
|
|
31651
|
+
</td>
|
|
31652
|
+
</tr>
|
|
31653
|
+
</table>
|
|
31654
|
+
|
|
31655
|
+
<p style="margin: 0 0 20px 0; font-size: 14px; line-height: 1.6; color: #777777;">
|
|
31656
|
+
Or copy and paste this link into your browser:
|
|
31657
|
+
</p>
|
|
31658
|
+
|
|
31659
|
+
<p style="margin: 0 0 30px 0; font-size: 14px; line-height: 1.6; word-break: break-all; color: #28a745;">
|
|
31660
|
+
<%= verificationLink %>
|
|
31661
|
+
</p>
|
|
31662
|
+
|
|
31663
|
+
<!-- Security Notice -->
|
|
31664
|
+
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #fff3cd; border-radius: 4px; margin: 0 0 20px 0;">
|
|
31665
|
+
<tr>
|
|
31666
|
+
<td style="padding: 12px 16px;">
|
|
31667
|
+
<p style="margin: 0; font-size: 14px; line-height: 1.6; color: #856404;">
|
|
31668
|
+
<strong>⚠️ Important:</strong> This link will expire in <strong><%= expiresIn %></strong>.
|
|
31669
|
+
If you didn't create an account with us, you can safely ignore this email.
|
|
31670
|
+
</p>
|
|
31671
|
+
</td>
|
|
31672
|
+
</tr>
|
|
31673
|
+
</table>
|
|
31674
|
+
|
|
31675
|
+
<p style="margin: 0 0 20px 0; font-size: 16px; line-height: 1.6; color: #555555;">
|
|
31676
|
+
For security reasons, please do not share this link with anyone.
|
|
31677
|
+
</p>
|
|
31678
|
+
</td>
|
|
31679
|
+
</tr>
|
|
31680
|
+
|
|
31681
|
+
<!-- Footer -->
|
|
31682
|
+
<tr>
|
|
31683
|
+
<td style="padding: 30px 40px 40px 40px; border-top: 1px solid #eeeeee;">
|
|
31684
|
+
<p style="margin: 0 0 10px 0; font-size: 14px; line-height: 1.6; color: #999999; text-align: center;">
|
|
31685
|
+
Best regards,<br>
|
|
31686
|
+
The <%= appName %> Team
|
|
31687
|
+
</p>
|
|
31688
|
+
|
|
31689
|
+
<% if (supportEmail) { %>
|
|
31690
|
+
<p style="margin: 0 0 10px 0; font-size: 12px; line-height: 1.6; color: #999999; text-align: center;">
|
|
31691
|
+
Need help? Contact us at <a href="mailto:<%= supportEmail %>" style="color: #28a745; text-decoration: none;"><%= supportEmail %></a>
|
|
31692
|
+
</p>
|
|
31693
|
+
<% } %>
|
|
31694
|
+
|
|
31695
|
+
<p style="margin: 0; font-size: 12px; line-height: 1.6; color: #999999; text-align: center;">
|
|
31696
|
+
© <%= year %> <%= appName %>. All rights reserved.
|
|
31697
|
+
</p>
|
|
31698
|
+
</td>
|
|
31699
|
+
</tr>
|
|
31700
|
+
</table>
|
|
31701
|
+
</td>
|
|
31702
|
+
</tr>
|
|
31703
|
+
</table>
|
|
31704
|
+
</body>
|
|
31705
|
+
</html>
|
|
31706
|
+
`.trim(),
|
|
31707
|
+
text: `
|
|
31708
|
+
Verify Your Email Address
|
|
31709
|
+
|
|
31710
|
+
Hello<% if (user.firstName) { %> <%= user.firstName %><% } %>,
|
|
31711
|
+
|
|
31712
|
+
Thank you for signing up with <%= appName %>! Please verify your email address <%= user.email %> to complete your registration.
|
|
31713
|
+
|
|
31714
|
+
To verify your email, please visit the following link:
|
|
31715
|
+
|
|
31716
|
+
<%= verificationLink %>
|
|
31717
|
+
|
|
31718
|
+
This link will expire in <%= expiresIn %>.
|
|
31719
|
+
|
|
31720
|
+
If you didn't create an account with us, you can safely ignore this email.
|
|
31721
|
+
|
|
31722
|
+
For security reasons, please do not share this link with anyone.
|
|
31723
|
+
|
|
31724
|
+
Best regards,
|
|
31725
|
+
The <%= appName %> Team
|
|
31726
|
+
|
|
31727
|
+
<% if (supportEmail) { %>
|
|
31728
|
+
Need help? Contact us at <%= supportEmail %>
|
|
31729
|
+
<% } %>
|
|
31730
|
+
|
|
31278
31731
|
© <%= year %> <%= appName %>. All rights reserved.
|
|
31279
31732
|
`.trim()
|
|
31280
31733
|
};
|
|
31281
31734
|
const defaultTemplates = {
|
|
31282
31735
|
passwordReset: passwordResetTemplate,
|
|
31283
31736
|
magicLink: magicLinkTemplate,
|
|
31284
|
-
passwordChanged: passwordChangedTemplate
|
|
31737
|
+
passwordChanged: passwordChangedTemplate,
|
|
31738
|
+
emailVerification: emailVerificationTemplate
|
|
31285
31739
|
};
|
|
31286
31740
|
class TemplateService {
|
|
31287
31741
|
/**
|
|
@@ -31693,6 +32147,114 @@ class EmailService {
|
|
|
31693
32147
|
message: "Password changed but confirmation email could not be sent"
|
|
31694
32148
|
};
|
|
31695
32149
|
}
|
|
32150
|
+
/**
|
|
32151
|
+
* Send email verification email with three-tier fallback system
|
|
32152
|
+
* Tier 1: Strapi Email Plugin (if configured)
|
|
32153
|
+
* Tier 2: Custom Hook Function (if provided in config)
|
|
32154
|
+
* Tier 3: Development Console Logging (dev mode only)
|
|
32155
|
+
*/
|
|
32156
|
+
async sendVerificationEmail(user, verificationLink) {
|
|
32157
|
+
if (!user.email) {
|
|
32158
|
+
throw new ValidationError$1("User does not have an email address");
|
|
32159
|
+
}
|
|
32160
|
+
const variables = {
|
|
32161
|
+
user: {
|
|
32162
|
+
email: user.email,
|
|
32163
|
+
firstName: user.firstName || user.displayName?.split(" ")[0],
|
|
32164
|
+
lastName: user.lastName,
|
|
32165
|
+
displayName: user.displayName,
|
|
32166
|
+
phoneNumber: user.phoneNumber,
|
|
32167
|
+
uid: user.uid
|
|
32168
|
+
},
|
|
32169
|
+
verificationLink,
|
|
32170
|
+
expiresIn: "1 hour"
|
|
32171
|
+
};
|
|
32172
|
+
const settingsService2 = strapi.plugin("firebase-authentication").service("settingsService");
|
|
32173
|
+
const dbConfig = await settingsService2.getFirebaseConfigJson();
|
|
32174
|
+
const customSubject = dbConfig?.emailVerificationEmailSubject;
|
|
32175
|
+
const pluginConfig = strapi.config.get("plugin::firebase-authentication");
|
|
32176
|
+
const appConfig = pluginConfig?.app || {};
|
|
32177
|
+
const completeVariables = {
|
|
32178
|
+
...variables,
|
|
32179
|
+
appName: appConfig?.name || "Your Application",
|
|
32180
|
+
appUrl: appConfig?.url || process.env.PUBLIC_URL || "http://localhost:3000",
|
|
32181
|
+
supportEmail: appConfig?.supportEmail,
|
|
32182
|
+
year: (/* @__PURE__ */ new Date()).getFullYear(),
|
|
32183
|
+
expiresIn: variables.expiresIn
|
|
32184
|
+
};
|
|
32185
|
+
const templateService2 = strapi.plugin("firebase-authentication").service("templateService");
|
|
32186
|
+
const template = await templateService2.getTemplate("emailVerification");
|
|
32187
|
+
const subjectTemplate = customSubject || template.subject;
|
|
32188
|
+
const compiledSubject = _$1.template(subjectTemplate)(completeVariables);
|
|
32189
|
+
try {
|
|
32190
|
+
const compiledHtml = template.html ? _$1.template(template.html)(completeVariables) : void 0;
|
|
32191
|
+
const compiledText = template.text ? _$1.template(template.text)(completeVariables) : void 0;
|
|
32192
|
+
const emailPlugin = strapi.plugin("email");
|
|
32193
|
+
if (!emailPlugin) {
|
|
32194
|
+
throw new Error("Email plugin not found");
|
|
32195
|
+
}
|
|
32196
|
+
const emailService2 = emailPlugin.service("email");
|
|
32197
|
+
await emailService2.send({
|
|
32198
|
+
to: user.email,
|
|
32199
|
+
subject: compiledSubject,
|
|
32200
|
+
html: compiledHtml,
|
|
32201
|
+
text: compiledText
|
|
32202
|
+
});
|
|
32203
|
+
strapi.log.info(`✅ Email verification sent via Strapi email plugin to ${user.email}`);
|
|
32204
|
+
return {
|
|
32205
|
+
success: true,
|
|
32206
|
+
message: `Verification email sent to ${user.email}`
|
|
32207
|
+
};
|
|
32208
|
+
} catch (tier1Error) {
|
|
32209
|
+
strapi.log.debug(`Strapi email plugin failed: ${tier1Error.message}. Trying fallback options...`);
|
|
32210
|
+
}
|
|
32211
|
+
const customSender = pluginConfig?.sendVerificationEmail;
|
|
32212
|
+
if (customSender && typeof customSender === "function") {
|
|
32213
|
+
try {
|
|
32214
|
+
const compiledHtml = template.html ? _$1.template(template.html)(completeVariables) : void 0;
|
|
32215
|
+
const compiledText = template.text ? _$1.template(template.text)(completeVariables) : void 0;
|
|
32216
|
+
await customSender({
|
|
32217
|
+
to: user.email,
|
|
32218
|
+
subject: compiledSubject,
|
|
32219
|
+
html: compiledHtml,
|
|
32220
|
+
text: compiledText,
|
|
32221
|
+
verificationLink,
|
|
32222
|
+
variables: completeVariables
|
|
32223
|
+
});
|
|
32224
|
+
strapi.log.info(`✅ Email verification sent via custom hook to ${user.email}`);
|
|
32225
|
+
return {
|
|
32226
|
+
success: true,
|
|
32227
|
+
message: `Verification email sent to ${user.email}`
|
|
32228
|
+
};
|
|
32229
|
+
} catch (tier2Error) {
|
|
32230
|
+
strapi.log.error(`Custom hook failed: ${tier2Error.message}. Continuing to next fallback...`);
|
|
32231
|
+
}
|
|
32232
|
+
}
|
|
32233
|
+
if (process.env.NODE_ENV !== "production") {
|
|
32234
|
+
try {
|
|
32235
|
+
strapi.log.info("\n" + "=".repeat(80));
|
|
32236
|
+
strapi.log.info("EMAIL VERIFICATION (Development Mode)");
|
|
32237
|
+
strapi.log.info("=".repeat(80));
|
|
32238
|
+
strapi.log.info(`To: ${user.email}`);
|
|
32239
|
+
strapi.log.info(`Subject: ${compiledSubject}`);
|
|
32240
|
+
strapi.log.info(`Verification Link: ${verificationLink}`);
|
|
32241
|
+
strapi.log.info(`Expires In: 1 hour`);
|
|
32242
|
+
strapi.log.info("=".repeat(80));
|
|
32243
|
+
strapi.log.info("Note: Email not sent - no email service configured");
|
|
32244
|
+
strapi.log.info("Copy the link above and open in your browser to verify");
|
|
32245
|
+
strapi.log.info("=".repeat(80) + "\n");
|
|
32246
|
+
return {
|
|
32247
|
+
success: true,
|
|
32248
|
+
message: "Verification link logged to console (development mode)"
|
|
32249
|
+
};
|
|
32250
|
+
} catch (tier3Error) {
|
|
32251
|
+
strapi.log.error(`Development fallback failed: ${tier3Error.message}`);
|
|
32252
|
+
}
|
|
32253
|
+
}
|
|
32254
|
+
throw new ApplicationError$2(
|
|
32255
|
+
"Email service is not configured. Please configure Strapi email plugin or provide custom sendVerificationEmail function in plugin config."
|
|
32256
|
+
);
|
|
32257
|
+
}
|
|
31696
32258
|
}
|
|
31697
32259
|
const emailService = ({ strapi: strapi2 }) => new EmailService();
|
|
31698
32260
|
const firebaseUserDataService = ({ strapi: strapi2 }) => ({
|
|
@@ -35146,6 +35708,125 @@ const tokenService = ({ strapi: strapi2 }) => {
|
|
|
35146
35708
|
}
|
|
35147
35709
|
});
|
|
35148
35710
|
strapi2.log.debug(`Invalidated reset token for user ${firebaseUserDataDocumentId}`);
|
|
35711
|
+
},
|
|
35712
|
+
// ==================== EMAIL VERIFICATION TOKENS ====================
|
|
35713
|
+
/**
|
|
35714
|
+
* Generate an email verification token for a user
|
|
35715
|
+
* @param firebaseUserDataDocumentId - The documentId of the firebase-user-data record
|
|
35716
|
+
* @param email - The email address at time of request (for change detection)
|
|
35717
|
+
* @returns The JWT token to include in the verification URL
|
|
35718
|
+
*/
|
|
35719
|
+
async generateVerificationToken(firebaseUserDataDocumentId, email2) {
|
|
35720
|
+
const signingKey = getSigningKey();
|
|
35721
|
+
const jti = crypto__default.default.randomBytes(32).toString("hex");
|
|
35722
|
+
const payload = {
|
|
35723
|
+
sub: firebaseUserDataDocumentId,
|
|
35724
|
+
purpose: "email-verification",
|
|
35725
|
+
email: email2,
|
|
35726
|
+
jti
|
|
35727
|
+
};
|
|
35728
|
+
const token = jwt.sign(payload, signingKey, {
|
|
35729
|
+
expiresIn: "1h"
|
|
35730
|
+
});
|
|
35731
|
+
const tokenHash = crypto__default.default.createHash("sha256").update(jti).digest("hex");
|
|
35732
|
+
const expiresAt = new Date(Date.now() + 60 * 60 * 1e3);
|
|
35733
|
+
await strapi2.db.query(FIREBASE_USER_DATA_CONTENT_TYPE).update({
|
|
35734
|
+
where: { documentId: firebaseUserDataDocumentId },
|
|
35735
|
+
data: {
|
|
35736
|
+
verificationTokenHash: tokenHash,
|
|
35737
|
+
verificationTokenExpiresAt: expiresAt.toISOString()
|
|
35738
|
+
}
|
|
35739
|
+
});
|
|
35740
|
+
strapi2.log.debug(`Generated verification token for user ${firebaseUserDataDocumentId}`);
|
|
35741
|
+
return token;
|
|
35742
|
+
},
|
|
35743
|
+
/**
|
|
35744
|
+
* Validate an email verification token
|
|
35745
|
+
* @param token - The JWT token from the verification URL
|
|
35746
|
+
* @returns Validation result with user info and email if valid
|
|
35747
|
+
*/
|
|
35748
|
+
async validateVerificationToken(token) {
|
|
35749
|
+
const signingKey = getSigningKey();
|
|
35750
|
+
try {
|
|
35751
|
+
const decoded = jwt.verify(token, signingKey);
|
|
35752
|
+
if (decoded.purpose !== "email-verification") {
|
|
35753
|
+
return {
|
|
35754
|
+
valid: false,
|
|
35755
|
+
firebaseUserDataDocumentId: "",
|
|
35756
|
+
firebaseUID: "",
|
|
35757
|
+
error: "Invalid token purpose"
|
|
35758
|
+
};
|
|
35759
|
+
}
|
|
35760
|
+
const tokenHash = crypto__default.default.createHash("sha256").update(decoded.jti).digest("hex");
|
|
35761
|
+
const firebaseUserData2 = await strapi2.db.query(FIREBASE_USER_DATA_CONTENT_TYPE).findOne({
|
|
35762
|
+
where: { documentId: decoded.sub }
|
|
35763
|
+
});
|
|
35764
|
+
if (!firebaseUserData2) {
|
|
35765
|
+
return {
|
|
35766
|
+
valid: false,
|
|
35767
|
+
firebaseUserDataDocumentId: "",
|
|
35768
|
+
firebaseUID: "",
|
|
35769
|
+
error: "User not found"
|
|
35770
|
+
};
|
|
35771
|
+
}
|
|
35772
|
+
if (firebaseUserData2.verificationTokenHash !== tokenHash) {
|
|
35773
|
+
return {
|
|
35774
|
+
valid: false,
|
|
35775
|
+
firebaseUserDataDocumentId: "",
|
|
35776
|
+
firebaseUID: "",
|
|
35777
|
+
error: "Verification link has already been used or is invalid"
|
|
35778
|
+
};
|
|
35779
|
+
}
|
|
35780
|
+
if (firebaseUserData2.verificationTokenExpiresAt) {
|
|
35781
|
+
const expiresAt = new Date(firebaseUserData2.verificationTokenExpiresAt);
|
|
35782
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
35783
|
+
return {
|
|
35784
|
+
valid: false,
|
|
35785
|
+
firebaseUserDataDocumentId: "",
|
|
35786
|
+
firebaseUID: "",
|
|
35787
|
+
error: "Verification link has expired"
|
|
35788
|
+
};
|
|
35789
|
+
}
|
|
35790
|
+
}
|
|
35791
|
+
return {
|
|
35792
|
+
valid: true,
|
|
35793
|
+
firebaseUserDataDocumentId: firebaseUserData2.documentId,
|
|
35794
|
+
firebaseUID: firebaseUserData2.firebaseUserID,
|
|
35795
|
+
email: decoded.email
|
|
35796
|
+
};
|
|
35797
|
+
} catch (error2) {
|
|
35798
|
+
if (error2.name === "TokenExpiredError") {
|
|
35799
|
+
return {
|
|
35800
|
+
valid: false,
|
|
35801
|
+
firebaseUserDataDocumentId: "",
|
|
35802
|
+
firebaseUID: "",
|
|
35803
|
+
error: "Verification link has expired"
|
|
35804
|
+
};
|
|
35805
|
+
}
|
|
35806
|
+
if (error2.name === "JsonWebTokenError") {
|
|
35807
|
+
return {
|
|
35808
|
+
valid: false,
|
|
35809
|
+
firebaseUserDataDocumentId: "",
|
|
35810
|
+
firebaseUID: "",
|
|
35811
|
+
error: "Invalid verification link"
|
|
35812
|
+
};
|
|
35813
|
+
}
|
|
35814
|
+
throw error2;
|
|
35815
|
+
}
|
|
35816
|
+
},
|
|
35817
|
+
/**
|
|
35818
|
+
* Invalidate a verification token after use
|
|
35819
|
+
* @param firebaseUserDataDocumentId - The documentId of the firebase-user-data record
|
|
35820
|
+
*/
|
|
35821
|
+
async invalidateVerificationToken(firebaseUserDataDocumentId) {
|
|
35822
|
+
await strapi2.db.query(FIREBASE_USER_DATA_CONTENT_TYPE).update({
|
|
35823
|
+
where: { documentId: firebaseUserDataDocumentId },
|
|
35824
|
+
data: {
|
|
35825
|
+
verificationTokenHash: null,
|
|
35826
|
+
verificationTokenExpiresAt: null
|
|
35827
|
+
}
|
|
35828
|
+
});
|
|
35829
|
+
strapi2.log.debug(`Invalidated verification token for user ${firebaseUserDataDocumentId}`);
|
|
35149
35830
|
}
|
|
35150
35831
|
};
|
|
35151
35832
|
};
|