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.mjs
CHANGED
|
@@ -198,7 +198,7 @@ const register = ({ strapi: strapi2 }) => {
|
|
|
198
198
|
const config$1 = {
|
|
199
199
|
default: ({ env: env2 }) => ({
|
|
200
200
|
firebaseJsonEncryptionKey: env2("FIREBASE_JSON_ENCRYPTION_KEY", "your-key-here"),
|
|
201
|
-
emailRequired:
|
|
201
|
+
emailRequired: env2.bool("FIREBASE_EMAIL_REQUIRED", false),
|
|
202
202
|
emailPattern: "{randomString}@phone-user.firebase.local"
|
|
203
203
|
}),
|
|
204
204
|
validator(config2) {
|
|
@@ -289,6 +289,15 @@ const attributes$1 = {
|
|
|
289
289
|
minimum: 1,
|
|
290
290
|
maximum: 72,
|
|
291
291
|
description: "How long the magic link remains valid (in hours)"
|
|
292
|
+
},
|
|
293
|
+
emailVerificationUrl: {
|
|
294
|
+
type: "string",
|
|
295
|
+
"default": "http://localhost:3000/verify-email",
|
|
296
|
+
description: "URL where users will be redirected to verify their email"
|
|
297
|
+
},
|
|
298
|
+
emailVerificationEmailSubject: {
|
|
299
|
+
type: "string",
|
|
300
|
+
"default": "Verify Your Email"
|
|
292
301
|
}
|
|
293
302
|
};
|
|
294
303
|
const firebaseAuthenticationConfiguration = {
|
|
@@ -339,6 +348,13 @@ const attributes = {
|
|
|
339
348
|
},
|
|
340
349
|
resetTokenExpiresAt: {
|
|
341
350
|
type: "datetime"
|
|
351
|
+
},
|
|
352
|
+
verificationTokenHash: {
|
|
353
|
+
type: "string",
|
|
354
|
+
"private": true
|
|
355
|
+
},
|
|
356
|
+
verificationTokenExpiresAt: {
|
|
357
|
+
type: "datetime"
|
|
342
358
|
}
|
|
343
359
|
};
|
|
344
360
|
const firebaseUserData = {
|
|
@@ -583,6 +599,61 @@ const firebaseController = {
|
|
|
583
599
|
ctx.status = 500;
|
|
584
600
|
ctx.body = { error: "An error occurred while resetting your password" };
|
|
585
601
|
}
|
|
602
|
+
},
|
|
603
|
+
/**
|
|
604
|
+
* Send email verification - public endpoint
|
|
605
|
+
* POST /api/firebase-authentication/sendVerificationEmail
|
|
606
|
+
* Public endpoint - no authentication required
|
|
607
|
+
*/
|
|
608
|
+
async sendVerificationEmail(ctx) {
|
|
609
|
+
strapi.log.debug("sendVerificationEmail endpoint called");
|
|
610
|
+
try {
|
|
611
|
+
const { email: email2 } = ctx.request.body || {};
|
|
612
|
+
ctx.body = await strapi.plugin(pluginName).service("firebaseService").sendVerificationEmail(email2);
|
|
613
|
+
} catch (error2) {
|
|
614
|
+
strapi.log.error("sendVerificationEmail controller error:", error2);
|
|
615
|
+
if (error2.name === "ValidationError") {
|
|
616
|
+
ctx.status = 400;
|
|
617
|
+
} else {
|
|
618
|
+
ctx.status = 500;
|
|
619
|
+
}
|
|
620
|
+
ctx.body = { error: error2.message };
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
/**
|
|
624
|
+
* Verify email using custom JWT token
|
|
625
|
+
* POST /api/firebase-authentication/verifyEmail
|
|
626
|
+
* Public endpoint - token provides authentication
|
|
627
|
+
*
|
|
628
|
+
* @param ctx - Koa context with { token } in body
|
|
629
|
+
* @returns { success: true, message: "Email verified successfully" }
|
|
630
|
+
*/
|
|
631
|
+
async verifyEmail(ctx) {
|
|
632
|
+
strapi.log.debug("verifyEmail endpoint called");
|
|
633
|
+
try {
|
|
634
|
+
const { token } = ctx.request.body || {};
|
|
635
|
+
if (!token) {
|
|
636
|
+
ctx.status = 400;
|
|
637
|
+
ctx.body = { error: "Token is required" };
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
const result = await strapi.plugin(pluginName).service("firebaseService").verifyEmail(token);
|
|
641
|
+
ctx.body = result;
|
|
642
|
+
} catch (error2) {
|
|
643
|
+
strapi.log.error("verifyEmail controller error:", error2);
|
|
644
|
+
if (error2.name === "ValidationError") {
|
|
645
|
+
ctx.status = 400;
|
|
646
|
+
ctx.body = { error: error2.message };
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (error2.name === "NotFoundError") {
|
|
650
|
+
ctx.status = 404;
|
|
651
|
+
ctx.body = { error: error2.message };
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
ctx.status = 500;
|
|
655
|
+
ctx.body = { error: "An error occurred while verifying your email" };
|
|
656
|
+
}
|
|
586
657
|
}
|
|
587
658
|
};
|
|
588
659
|
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
|
|
@@ -28950,6 +29021,17 @@ const userController = {
|
|
|
28950
29021
|
} catch (error2) {
|
|
28951
29022
|
throw new ApplicationError$2(error2.message || "Failed to send password reset email");
|
|
28952
29023
|
}
|
|
29024
|
+
},
|
|
29025
|
+
sendVerificationEmail: async (ctx) => {
|
|
29026
|
+
const userId = ctx.params.id;
|
|
29027
|
+
if (!userId) {
|
|
29028
|
+
throw new ValidationError$1("User ID is required");
|
|
29029
|
+
}
|
|
29030
|
+
try {
|
|
29031
|
+
ctx.body = await strapi.plugin("firebase-authentication").service("userService").sendVerificationEmail(userId);
|
|
29032
|
+
} catch (error2) {
|
|
29033
|
+
throw new ApplicationError$2(error2.message || "Failed to send verification email");
|
|
29034
|
+
}
|
|
28953
29035
|
}
|
|
28954
29036
|
};
|
|
28955
29037
|
const settingsController = {
|
|
@@ -29052,7 +29134,9 @@ const settingsController = {
|
|
|
29052
29134
|
enableMagicLink = false,
|
|
29053
29135
|
magicLinkUrl = "http://localhost:1338/verify-magic-link.html",
|
|
29054
29136
|
magicLinkEmailSubject = "Sign in to Your Application",
|
|
29055
|
-
magicLinkExpiryHours = 1
|
|
29137
|
+
magicLinkExpiryHours = 1,
|
|
29138
|
+
emailVerificationUrl = "http://localhost:3000/verify-email",
|
|
29139
|
+
emailVerificationEmailSubject = "Verify Your Email"
|
|
29056
29140
|
} = requestBody;
|
|
29057
29141
|
const existingConfig = await strapi.db.query("plugin::firebase-authentication.firebase-authentication-configuration").findOne({ where: {} });
|
|
29058
29142
|
let result;
|
|
@@ -29066,7 +29150,9 @@ const settingsController = {
|
|
|
29066
29150
|
enableMagicLink,
|
|
29067
29151
|
magicLinkUrl,
|
|
29068
29152
|
magicLinkEmailSubject,
|
|
29069
|
-
magicLinkExpiryHours
|
|
29153
|
+
magicLinkExpiryHours,
|
|
29154
|
+
emailVerificationUrl,
|
|
29155
|
+
emailVerificationEmailSubject
|
|
29070
29156
|
}
|
|
29071
29157
|
});
|
|
29072
29158
|
} else {
|
|
@@ -29080,7 +29166,9 @@ const settingsController = {
|
|
|
29080
29166
|
enableMagicLink,
|
|
29081
29167
|
magicLinkUrl,
|
|
29082
29168
|
magicLinkEmailSubject,
|
|
29083
|
-
magicLinkExpiryHours
|
|
29169
|
+
magicLinkExpiryHours,
|
|
29170
|
+
emailVerificationUrl,
|
|
29171
|
+
emailVerificationEmailSubject
|
|
29084
29172
|
}
|
|
29085
29173
|
});
|
|
29086
29174
|
}
|
|
@@ -29092,7 +29180,9 @@ const settingsController = {
|
|
|
29092
29180
|
enableMagicLink: result.enableMagicLink,
|
|
29093
29181
|
magicLinkUrl: result.magicLinkUrl,
|
|
29094
29182
|
magicLinkEmailSubject: result.magicLinkEmailSubject,
|
|
29095
|
-
magicLinkExpiryHours: result.magicLinkExpiryHours
|
|
29183
|
+
magicLinkExpiryHours: result.magicLinkExpiryHours,
|
|
29184
|
+
emailVerificationUrl: result.emailVerificationUrl,
|
|
29185
|
+
emailVerificationEmailSubject: result.emailVerificationEmailSubject
|
|
29096
29186
|
};
|
|
29097
29187
|
} catch (error2) {
|
|
29098
29188
|
throw new ApplicationError$2("Error saving password configuration", {
|
|
@@ -29185,6 +29275,14 @@ const admin = {
|
|
|
29185
29275
|
policies: ["admin::isAuthenticatedAdmin"]
|
|
29186
29276
|
}
|
|
29187
29277
|
},
|
|
29278
|
+
{
|
|
29279
|
+
method: "PUT",
|
|
29280
|
+
path: "/users/sendVerificationEmail/:id",
|
|
29281
|
+
handler: "userController.sendVerificationEmail",
|
|
29282
|
+
config: {
|
|
29283
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
29284
|
+
}
|
|
29285
|
+
},
|
|
29188
29286
|
{
|
|
29189
29287
|
method: "GET",
|
|
29190
29288
|
path: "/users/:id",
|
|
@@ -29283,6 +29381,26 @@ const contentApi = {
|
|
|
29283
29381
|
// Public endpoint - token provides authentication
|
|
29284
29382
|
policies: []
|
|
29285
29383
|
}
|
|
29384
|
+
},
|
|
29385
|
+
{
|
|
29386
|
+
method: "POST",
|
|
29387
|
+
path: "/sendVerificationEmail",
|
|
29388
|
+
handler: "firebaseController.sendVerificationEmail",
|
|
29389
|
+
config: {
|
|
29390
|
+
auth: false,
|
|
29391
|
+
// Public endpoint - sends email verification link
|
|
29392
|
+
policies: []
|
|
29393
|
+
}
|
|
29394
|
+
},
|
|
29395
|
+
{
|
|
29396
|
+
method: "POST",
|
|
29397
|
+
path: "/verifyEmail",
|
|
29398
|
+
handler: "firebaseController.verifyEmail",
|
|
29399
|
+
config: {
|
|
29400
|
+
auth: false,
|
|
29401
|
+
// Public endpoint - token provides authentication
|
|
29402
|
+
policies: []
|
|
29403
|
+
}
|
|
29286
29404
|
}
|
|
29287
29405
|
]
|
|
29288
29406
|
};
|
|
@@ -29403,7 +29521,10 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29403
29521
|
enableMagicLink: configObject.enableMagicLink || false,
|
|
29404
29522
|
magicLinkUrl: configObject.magicLinkUrl || "http://localhost:1338/verify-magic-link.html",
|
|
29405
29523
|
magicLinkEmailSubject: configObject.magicLinkEmailSubject || "Sign in to Your Application",
|
|
29406
|
-
magicLinkExpiryHours: configObject.magicLinkExpiryHours || 1
|
|
29524
|
+
magicLinkExpiryHours: configObject.magicLinkExpiryHours || 1,
|
|
29525
|
+
// Include email verification configuration fields
|
|
29526
|
+
emailVerificationUrl: configObject.emailVerificationUrl || "http://localhost:3000/verify-email",
|
|
29527
|
+
emailVerificationEmailSubject: configObject.emailVerificationEmailSubject || "Verify Your Email"
|
|
29407
29528
|
};
|
|
29408
29529
|
} catch (error2) {
|
|
29409
29530
|
strapi2.log.error(`Firebase config error: ${error2.message}`);
|
|
@@ -29446,7 +29567,9 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29446
29567
|
enableMagicLink = false,
|
|
29447
29568
|
magicLinkUrl = "http://localhost:1338/verify-magic-link.html",
|
|
29448
29569
|
magicLinkEmailSubject = "Sign in to Your Application",
|
|
29449
|
-
magicLinkExpiryHours = 1
|
|
29570
|
+
magicLinkExpiryHours = 1,
|
|
29571
|
+
emailVerificationUrl = "http://localhost:3000/verify-email",
|
|
29572
|
+
emailVerificationEmailSubject = "Verify Your Email"
|
|
29450
29573
|
} = requestBody;
|
|
29451
29574
|
if (!requestBody) throw new ValidationError3(ERROR_MESSAGES.MISSING_DATA);
|
|
29452
29575
|
try {
|
|
@@ -29482,7 +29605,9 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29482
29605
|
enableMagicLink,
|
|
29483
29606
|
magicLinkUrl,
|
|
29484
29607
|
magicLinkEmailSubject,
|
|
29485
|
-
magicLinkExpiryHours
|
|
29608
|
+
magicLinkExpiryHours,
|
|
29609
|
+
emailVerificationUrl,
|
|
29610
|
+
emailVerificationEmailSubject
|
|
29486
29611
|
}
|
|
29487
29612
|
});
|
|
29488
29613
|
} else {
|
|
@@ -29498,11 +29623,20 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29498
29623
|
enableMagicLink,
|
|
29499
29624
|
magicLinkUrl,
|
|
29500
29625
|
magicLinkEmailSubject,
|
|
29501
|
-
magicLinkExpiryHours
|
|
29626
|
+
magicLinkExpiryHours,
|
|
29627
|
+
emailVerificationUrl,
|
|
29628
|
+
emailVerificationEmailSubject
|
|
29502
29629
|
}
|
|
29503
29630
|
});
|
|
29504
29631
|
}
|
|
29505
29632
|
await strapi2.plugin("firebase-authentication").service("settingsService").init();
|
|
29633
|
+
setImmediate(async () => {
|
|
29634
|
+
try {
|
|
29635
|
+
await strapi2.plugin("firebase-authentication").service("autoLinkService").linkAllUsers(strapi2);
|
|
29636
|
+
} catch (error2) {
|
|
29637
|
+
strapi2.log.error(`Auto-linking after config save failed: ${error2.message}`);
|
|
29638
|
+
}
|
|
29639
|
+
});
|
|
29506
29640
|
const configData = res.firebaseConfigJson || res.firebase_config_json;
|
|
29507
29641
|
if (!configData) {
|
|
29508
29642
|
strapi2.log.error("Firebase config data missing from database response");
|
|
@@ -29526,6 +29660,8 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29526
29660
|
res.magicLinkUrl = res.magicLinkUrl || magicLinkUrl;
|
|
29527
29661
|
res.magicLinkEmailSubject = res.magicLinkEmailSubject || magicLinkEmailSubject;
|
|
29528
29662
|
res.magicLinkExpiryHours = res.magicLinkExpiryHours || magicLinkExpiryHours;
|
|
29663
|
+
res.emailVerificationUrl = res.emailVerificationUrl || emailVerificationUrl;
|
|
29664
|
+
res.emailVerificationEmailSubject = res.emailVerificationEmailSubject || emailVerificationEmailSubject;
|
|
29529
29665
|
return res;
|
|
29530
29666
|
} catch (error2) {
|
|
29531
29667
|
strapi2.log.error("=== FIREBASE CONFIG SAVE ERROR ===");
|
|
@@ -30185,6 +30321,40 @@ const userService = ({ strapi: strapi2 }) => {
|
|
|
30185
30321
|
}
|
|
30186
30322
|
throw new ApplicationError$2(e.message?.toString() || "Failed to reset password");
|
|
30187
30323
|
}
|
|
30324
|
+
},
|
|
30325
|
+
/**
|
|
30326
|
+
* Send email verification email (admin-initiated)
|
|
30327
|
+
* @param entityId - Firebase UID of the user
|
|
30328
|
+
*/
|
|
30329
|
+
sendVerificationEmail: async (entityId) => {
|
|
30330
|
+
try {
|
|
30331
|
+
ensureFirebaseInitialized();
|
|
30332
|
+
const user = await strapi2.firebase.auth().getUser(entityId);
|
|
30333
|
+
if (!user.email) {
|
|
30334
|
+
throw new ApplicationError$2("User does not have an email address");
|
|
30335
|
+
}
|
|
30336
|
+
if (user.emailVerified) {
|
|
30337
|
+
return { success: true, message: "Email is already verified" };
|
|
30338
|
+
}
|
|
30339
|
+
const config2 = await strapi2.db.query("plugin::firebase-authentication.firebase-authentication-configuration").findOne({ where: {} });
|
|
30340
|
+
const emailVerificationUrl = config2?.emailVerificationUrl;
|
|
30341
|
+
if (!emailVerificationUrl) {
|
|
30342
|
+
throw new ApplicationError$2("Email verification URL is not configured");
|
|
30343
|
+
}
|
|
30344
|
+
const firebaseUserData2 = await strapi2.plugin("firebase-authentication").service("firebaseUserDataService").getByFirebaseUID(entityId);
|
|
30345
|
+
if (!firebaseUserData2) {
|
|
30346
|
+
throw new ApplicationError$2("User is not linked to Firebase authentication");
|
|
30347
|
+
}
|
|
30348
|
+
const tokenService2 = strapi2.plugin("firebase-authentication").service("tokenService");
|
|
30349
|
+
const token = await tokenService2.generateVerificationToken(firebaseUserData2.documentId, user.email);
|
|
30350
|
+
const verificationLink = `${emailVerificationUrl}?token=${token}`;
|
|
30351
|
+
strapi2.log.debug(`Generated email verification link for user ${user.email}`);
|
|
30352
|
+
const emailService2 = strapi2.plugin("firebase-authentication").service("emailService");
|
|
30353
|
+
return await emailService2.sendVerificationEmail(user, verificationLink);
|
|
30354
|
+
} catch (e) {
|
|
30355
|
+
strapi2.log.error(`sendVerificationEmail error: ${e.message}`);
|
|
30356
|
+
throw new ApplicationError$2(e.message?.toString() || "Failed to send verification email");
|
|
30357
|
+
}
|
|
30188
30358
|
}
|
|
30189
30359
|
};
|
|
30190
30360
|
};
|
|
@@ -30864,6 +31034,159 @@ const firebaseService = ({ strapi: strapi2 }) => ({
|
|
|
30864
31034
|
verificationUrl: magicLinkUrl
|
|
30865
31035
|
};
|
|
30866
31036
|
}
|
|
31037
|
+
},
|
|
31038
|
+
/**
|
|
31039
|
+
* Send email verification - public endpoint
|
|
31040
|
+
* Generates a verification token and sends an email to the user
|
|
31041
|
+
* Security: Always returns generic success message to prevent email enumeration
|
|
31042
|
+
*/
|
|
31043
|
+
async sendVerificationEmail(email2) {
|
|
31044
|
+
strapi2.log.info(`[sendVerificationEmail] Starting email verification for: ${email2}`);
|
|
31045
|
+
if (!email2) {
|
|
31046
|
+
throw new ValidationError$1("Email is required");
|
|
31047
|
+
}
|
|
31048
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
31049
|
+
if (!emailRegex.test(email2)) {
|
|
31050
|
+
throw new ValidationError$1("Invalid email format");
|
|
31051
|
+
}
|
|
31052
|
+
const config2 = await strapi2.plugin("firebase-authentication").service("settingsService").getFirebaseConfigJson();
|
|
31053
|
+
const verificationUrl = config2?.emailVerificationUrl;
|
|
31054
|
+
if (!verificationUrl) {
|
|
31055
|
+
throw new ApplicationError$2("Email verification URL is not configured");
|
|
31056
|
+
}
|
|
31057
|
+
if (process.env.NODE_ENV === "production" && !verificationUrl.startsWith("https://")) {
|
|
31058
|
+
throw new ApplicationError$2("Email verification URL must use HTTPS in production");
|
|
31059
|
+
}
|
|
31060
|
+
try {
|
|
31061
|
+
new URL(verificationUrl);
|
|
31062
|
+
} catch (error2) {
|
|
31063
|
+
throw new ApplicationError$2("Email verification URL is not a valid URL format");
|
|
31064
|
+
}
|
|
31065
|
+
try {
|
|
31066
|
+
let firebaseUser;
|
|
31067
|
+
try {
|
|
31068
|
+
firebaseUser = await strapi2.firebase.auth().getUserByEmail(email2);
|
|
31069
|
+
} catch (fbError) {
|
|
31070
|
+
strapi2.log.debug("User not found in Firebase");
|
|
31071
|
+
}
|
|
31072
|
+
if (!firebaseUser) {
|
|
31073
|
+
strapi2.log.warn(`⚠️ [sendVerificationEmail] User not found in Firebase for email: ${email2}`);
|
|
31074
|
+
return { message: "If an account with that email exists, a verification link has been sent." };
|
|
31075
|
+
}
|
|
31076
|
+
if (firebaseUser.emailVerified) {
|
|
31077
|
+
strapi2.log.info(`[sendVerificationEmail] User ${email2} is already verified`);
|
|
31078
|
+
return { message: "Email is already verified." };
|
|
31079
|
+
}
|
|
31080
|
+
const firebaseData = await strapi2.plugin("firebase-authentication").service("firebaseUserDataService").getByFirebaseUID(firebaseUser.uid);
|
|
31081
|
+
if (!firebaseData) {
|
|
31082
|
+
strapi2.log.warn(`⚠️ [sendVerificationEmail] No firebase-user-data record for: ${email2}`);
|
|
31083
|
+
return { message: "If an account with that email exists, a verification link has been sent." };
|
|
31084
|
+
}
|
|
31085
|
+
strapi2.log.info(
|
|
31086
|
+
`✅ [sendVerificationEmail] User found: ${JSON.stringify({
|
|
31087
|
+
firebaseUID: firebaseUser.uid,
|
|
31088
|
+
email: firebaseUser.email,
|
|
31089
|
+
emailVerified: firebaseUser.emailVerified
|
|
31090
|
+
})}`
|
|
31091
|
+
);
|
|
31092
|
+
const tokenService2 = strapi2.plugin("firebase-authentication").service("tokenService");
|
|
31093
|
+
const token = await tokenService2.generateVerificationToken(firebaseData.documentId, email2);
|
|
31094
|
+
const verificationLink = `${verificationUrl}?token=${token}`;
|
|
31095
|
+
strapi2.log.info(`✅ [sendVerificationEmail] Verification link generated for ${email2}`);
|
|
31096
|
+
strapi2.log.info(`[sendVerificationEmail] Attempting to send verification email to: ${email2}`);
|
|
31097
|
+
await strapi2.plugin("firebase-authentication").service("emailService").sendVerificationEmail(firebaseUser, verificationLink);
|
|
31098
|
+
strapi2.log.info(`✅ [sendVerificationEmail] Verification email sent successfully to: ${email2}`);
|
|
31099
|
+
return {
|
|
31100
|
+
message: "If an account with that email exists, a verification link has been sent."
|
|
31101
|
+
};
|
|
31102
|
+
} catch (error2) {
|
|
31103
|
+
strapi2.log.error(
|
|
31104
|
+
`❌ [sendVerificationEmail] ERROR: ${JSON.stringify({
|
|
31105
|
+
email: email2,
|
|
31106
|
+
message: error2.message,
|
|
31107
|
+
code: error2.code,
|
|
31108
|
+
name: error2.name,
|
|
31109
|
+
stack: error2.stack
|
|
31110
|
+
})}`
|
|
31111
|
+
);
|
|
31112
|
+
return {
|
|
31113
|
+
message: "If an account with that email exists, a verification link has been sent."
|
|
31114
|
+
};
|
|
31115
|
+
}
|
|
31116
|
+
},
|
|
31117
|
+
/**
|
|
31118
|
+
* Verify email with token - public endpoint
|
|
31119
|
+
* Validates the token and marks the user's email as verified in Firebase
|
|
31120
|
+
*/
|
|
31121
|
+
async verifyEmail(token) {
|
|
31122
|
+
strapi2.log.info(`[verifyEmail] Starting email verification with token`);
|
|
31123
|
+
if (!token) {
|
|
31124
|
+
throw new ValidationError$1("Verification token is required");
|
|
31125
|
+
}
|
|
31126
|
+
const tokenService2 = strapi2.plugin("firebase-authentication").service("tokenService");
|
|
31127
|
+
const validationResult = await tokenService2.validateVerificationToken(token);
|
|
31128
|
+
if (!validationResult.valid) {
|
|
31129
|
+
strapi2.log.warn(`[verifyEmail] Token validation failed: ${validationResult.error}`);
|
|
31130
|
+
throw new ValidationError$1(validationResult.error || "Invalid verification link");
|
|
31131
|
+
}
|
|
31132
|
+
const { firebaseUID, firebaseUserDataDocumentId, email: tokenEmail } = validationResult;
|
|
31133
|
+
try {
|
|
31134
|
+
const firebaseUser = await strapi2.firebase.auth().getUser(firebaseUID);
|
|
31135
|
+
if (tokenEmail && firebaseUser.email !== tokenEmail) {
|
|
31136
|
+
strapi2.log.warn(
|
|
31137
|
+
`[verifyEmail] Email changed: token email ${tokenEmail} != current email ${firebaseUser.email}`
|
|
31138
|
+
);
|
|
31139
|
+
await tokenService2.invalidateVerificationToken(firebaseUserDataDocumentId);
|
|
31140
|
+
throw new ValidationError$1(
|
|
31141
|
+
"Email address has changed since verification was requested. Please request a new verification link."
|
|
31142
|
+
);
|
|
31143
|
+
}
|
|
31144
|
+
if (firebaseUser.emailVerified) {
|
|
31145
|
+
strapi2.log.info(`[verifyEmail] User ${firebaseUser.email} is already verified`);
|
|
31146
|
+
await tokenService2.invalidateVerificationToken(firebaseUserDataDocumentId);
|
|
31147
|
+
return {
|
|
31148
|
+
success: true,
|
|
31149
|
+
message: "Email is already verified."
|
|
31150
|
+
};
|
|
31151
|
+
}
|
|
31152
|
+
await strapi2.firebase.auth().updateUser(firebaseUID, {
|
|
31153
|
+
emailVerified: true
|
|
31154
|
+
});
|
|
31155
|
+
try {
|
|
31156
|
+
const firebaseUserDataService2 = strapi2.plugin("firebase-authentication").service("firebaseUserDataService");
|
|
31157
|
+
const firebaseUserData2 = await firebaseUserDataService2.getByFirebaseUID(firebaseUID);
|
|
31158
|
+
if (firebaseUserData2?.user?.documentId) {
|
|
31159
|
+
await strapi2.db.query("plugin::users-permissions.user").update({
|
|
31160
|
+
where: { documentId: firebaseUserData2.user.documentId },
|
|
31161
|
+
data: { confirmed: true }
|
|
31162
|
+
});
|
|
31163
|
+
strapi2.log.info(`✅ [verifyEmail] Strapi user confirmed for: ${firebaseUserData2.user.documentId}`);
|
|
31164
|
+
}
|
|
31165
|
+
} catch (strapiUpdateError) {
|
|
31166
|
+
strapi2.log.warn(
|
|
31167
|
+
`[verifyEmail] Failed to update Strapi user confirmed status: ${strapiUpdateError.message}`
|
|
31168
|
+
);
|
|
31169
|
+
}
|
|
31170
|
+
strapi2.log.info(`✅ [verifyEmail] Email verified successfully for: ${firebaseUser.email}`);
|
|
31171
|
+
await tokenService2.invalidateVerificationToken(firebaseUserDataDocumentId);
|
|
31172
|
+
return {
|
|
31173
|
+
success: true,
|
|
31174
|
+
message: "Email verified successfully."
|
|
31175
|
+
};
|
|
31176
|
+
} catch (error2) {
|
|
31177
|
+
strapi2.log.error(
|
|
31178
|
+
`❌ [verifyEmail] ERROR: ${JSON.stringify({
|
|
31179
|
+
firebaseUID,
|
|
31180
|
+
message: error2.message,
|
|
31181
|
+
code: error2.code,
|
|
31182
|
+
name: error2.name
|
|
31183
|
+
})}`
|
|
31184
|
+
);
|
|
31185
|
+
if (error2 instanceof ValidationError$1) {
|
|
31186
|
+
throw error2;
|
|
31187
|
+
}
|
|
31188
|
+
throw new ApplicationError$2("Failed to verify email. Please try again.");
|
|
31189
|
+
}
|
|
30867
31190
|
}
|
|
30868
31191
|
});
|
|
30869
31192
|
const passwordResetTemplate = {
|
|
@@ -31243,13 +31566,144 @@ The <%= appName %> Team
|
|
|
31243
31566
|
Need help? Contact us at <%= supportEmail %>
|
|
31244
31567
|
<% } %>
|
|
31245
31568
|
|
|
31569
|
+
© <%= year %> <%= appName %>. All rights reserved.
|
|
31570
|
+
`.trim()
|
|
31571
|
+
};
|
|
31572
|
+
const emailVerificationTemplate = {
|
|
31573
|
+
subject: "Verify Your Email - <%= appName %>",
|
|
31574
|
+
html: `
|
|
31575
|
+
<!DOCTYPE html>
|
|
31576
|
+
<html lang="en">
|
|
31577
|
+
<head>
|
|
31578
|
+
<meta charset="UTF-8">
|
|
31579
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
31580
|
+
<title>Verify Your Email</title>
|
|
31581
|
+
</head>
|
|
31582
|
+
<body style="margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f4f4f4;">
|
|
31583
|
+
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;">
|
|
31584
|
+
<tr>
|
|
31585
|
+
<td align="center" style="padding: 40px 0;">
|
|
31586
|
+
<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);">
|
|
31587
|
+
<!-- Header -->
|
|
31588
|
+
<tr>
|
|
31589
|
+
<td style="padding: 40px 40px 20px 40px; text-align: center;">
|
|
31590
|
+
<h1 style="margin: 0; font-size: 28px; color: #333333; font-weight: 600;">
|
|
31591
|
+
Verify Your Email Address
|
|
31592
|
+
</h1>
|
|
31593
|
+
</td>
|
|
31594
|
+
</tr>
|
|
31595
|
+
|
|
31596
|
+
<!-- Content -->
|
|
31597
|
+
<tr>
|
|
31598
|
+
<td style="padding: 20px 40px;">
|
|
31599
|
+
<p style="margin: 0 0 20px 0; font-size: 16px; line-height: 1.6; color: #555555;">
|
|
31600
|
+
Hello<% if (user.firstName) { %> <%= user.firstName %><% } %>,
|
|
31601
|
+
</p>
|
|
31602
|
+
|
|
31603
|
+
<p style="margin: 0 0 20px 0; font-size: 16px; line-height: 1.6; color: #555555;">
|
|
31604
|
+
Thank you for signing up with <strong><%= appName %></strong>! Please verify your email address <strong><%= user.email %></strong> to complete your registration.
|
|
31605
|
+
</p>
|
|
31606
|
+
|
|
31607
|
+
<p style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #555555;">
|
|
31608
|
+
Click the button below to verify your email:
|
|
31609
|
+
</p>
|
|
31610
|
+
|
|
31611
|
+
<!-- CTA Button -->
|
|
31612
|
+
<table role="presentation" style="width: 100%; border-collapse: collapse;">
|
|
31613
|
+
<tr>
|
|
31614
|
+
<td align="center" style="padding: 0 0 30px 0;">
|
|
31615
|
+
<a href="<%= verificationLink %>"
|
|
31616
|
+
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;">
|
|
31617
|
+
Verify Email Address
|
|
31618
|
+
</a>
|
|
31619
|
+
</td>
|
|
31620
|
+
</tr>
|
|
31621
|
+
</table>
|
|
31622
|
+
|
|
31623
|
+
<p style="margin: 0 0 20px 0; font-size: 14px; line-height: 1.6; color: #777777;">
|
|
31624
|
+
Or copy and paste this link into your browser:
|
|
31625
|
+
</p>
|
|
31626
|
+
|
|
31627
|
+
<p style="margin: 0 0 30px 0; font-size: 14px; line-height: 1.6; word-break: break-all; color: #28a745;">
|
|
31628
|
+
<%= verificationLink %>
|
|
31629
|
+
</p>
|
|
31630
|
+
|
|
31631
|
+
<!-- Security Notice -->
|
|
31632
|
+
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #fff3cd; border-radius: 4px; margin: 0 0 20px 0;">
|
|
31633
|
+
<tr>
|
|
31634
|
+
<td style="padding: 12px 16px;">
|
|
31635
|
+
<p style="margin: 0; font-size: 14px; line-height: 1.6; color: #856404;">
|
|
31636
|
+
<strong>⚠️ Important:</strong> This link will expire in <strong><%= expiresIn %></strong>.
|
|
31637
|
+
If you didn't create an account with us, you can safely ignore this email.
|
|
31638
|
+
</p>
|
|
31639
|
+
</td>
|
|
31640
|
+
</tr>
|
|
31641
|
+
</table>
|
|
31642
|
+
|
|
31643
|
+
<p style="margin: 0 0 20px 0; font-size: 16px; line-height: 1.6; color: #555555;">
|
|
31644
|
+
For security reasons, please do not share this link with anyone.
|
|
31645
|
+
</p>
|
|
31646
|
+
</td>
|
|
31647
|
+
</tr>
|
|
31648
|
+
|
|
31649
|
+
<!-- Footer -->
|
|
31650
|
+
<tr>
|
|
31651
|
+
<td style="padding: 30px 40px 40px 40px; border-top: 1px solid #eeeeee;">
|
|
31652
|
+
<p style="margin: 0 0 10px 0; font-size: 14px; line-height: 1.6; color: #999999; text-align: center;">
|
|
31653
|
+
Best regards,<br>
|
|
31654
|
+
The <%= appName %> Team
|
|
31655
|
+
</p>
|
|
31656
|
+
|
|
31657
|
+
<% if (supportEmail) { %>
|
|
31658
|
+
<p style="margin: 0 0 10px 0; font-size: 12px; line-height: 1.6; color: #999999; text-align: center;">
|
|
31659
|
+
Need help? Contact us at <a href="mailto:<%= supportEmail %>" style="color: #28a745; text-decoration: none;"><%= supportEmail %></a>
|
|
31660
|
+
</p>
|
|
31661
|
+
<% } %>
|
|
31662
|
+
|
|
31663
|
+
<p style="margin: 0; font-size: 12px; line-height: 1.6; color: #999999; text-align: center;">
|
|
31664
|
+
© <%= year %> <%= appName %>. All rights reserved.
|
|
31665
|
+
</p>
|
|
31666
|
+
</td>
|
|
31667
|
+
</tr>
|
|
31668
|
+
</table>
|
|
31669
|
+
</td>
|
|
31670
|
+
</tr>
|
|
31671
|
+
</table>
|
|
31672
|
+
</body>
|
|
31673
|
+
</html>
|
|
31674
|
+
`.trim(),
|
|
31675
|
+
text: `
|
|
31676
|
+
Verify Your Email Address
|
|
31677
|
+
|
|
31678
|
+
Hello<% if (user.firstName) { %> <%= user.firstName %><% } %>,
|
|
31679
|
+
|
|
31680
|
+
Thank you for signing up with <%= appName %>! Please verify your email address <%= user.email %> to complete your registration.
|
|
31681
|
+
|
|
31682
|
+
To verify your email, please visit the following link:
|
|
31683
|
+
|
|
31684
|
+
<%= verificationLink %>
|
|
31685
|
+
|
|
31686
|
+
This link will expire in <%= expiresIn %>.
|
|
31687
|
+
|
|
31688
|
+
If you didn't create an account with us, you can safely ignore this email.
|
|
31689
|
+
|
|
31690
|
+
For security reasons, please do not share this link with anyone.
|
|
31691
|
+
|
|
31692
|
+
Best regards,
|
|
31693
|
+
The <%= appName %> Team
|
|
31694
|
+
|
|
31695
|
+
<% if (supportEmail) { %>
|
|
31696
|
+
Need help? Contact us at <%= supportEmail %>
|
|
31697
|
+
<% } %>
|
|
31698
|
+
|
|
31246
31699
|
© <%= year %> <%= appName %>. All rights reserved.
|
|
31247
31700
|
`.trim()
|
|
31248
31701
|
};
|
|
31249
31702
|
const defaultTemplates = {
|
|
31250
31703
|
passwordReset: passwordResetTemplate,
|
|
31251
31704
|
magicLink: magicLinkTemplate,
|
|
31252
|
-
passwordChanged: passwordChangedTemplate
|
|
31705
|
+
passwordChanged: passwordChangedTemplate,
|
|
31706
|
+
emailVerification: emailVerificationTemplate
|
|
31253
31707
|
};
|
|
31254
31708
|
class TemplateService {
|
|
31255
31709
|
/**
|
|
@@ -31661,6 +32115,114 @@ class EmailService {
|
|
|
31661
32115
|
message: "Password changed but confirmation email could not be sent"
|
|
31662
32116
|
};
|
|
31663
32117
|
}
|
|
32118
|
+
/**
|
|
32119
|
+
* Send email verification email with three-tier fallback system
|
|
32120
|
+
* Tier 1: Strapi Email Plugin (if configured)
|
|
32121
|
+
* Tier 2: Custom Hook Function (if provided in config)
|
|
32122
|
+
* Tier 3: Development Console Logging (dev mode only)
|
|
32123
|
+
*/
|
|
32124
|
+
async sendVerificationEmail(user, verificationLink) {
|
|
32125
|
+
if (!user.email) {
|
|
32126
|
+
throw new ValidationError$1("User does not have an email address");
|
|
32127
|
+
}
|
|
32128
|
+
const variables = {
|
|
32129
|
+
user: {
|
|
32130
|
+
email: user.email,
|
|
32131
|
+
firstName: user.firstName || user.displayName?.split(" ")[0],
|
|
32132
|
+
lastName: user.lastName,
|
|
32133
|
+
displayName: user.displayName,
|
|
32134
|
+
phoneNumber: user.phoneNumber,
|
|
32135
|
+
uid: user.uid
|
|
32136
|
+
},
|
|
32137
|
+
verificationLink,
|
|
32138
|
+
expiresIn: "1 hour"
|
|
32139
|
+
};
|
|
32140
|
+
const settingsService2 = strapi.plugin("firebase-authentication").service("settingsService");
|
|
32141
|
+
const dbConfig = await settingsService2.getFirebaseConfigJson();
|
|
32142
|
+
const customSubject = dbConfig?.emailVerificationEmailSubject;
|
|
32143
|
+
const pluginConfig = strapi.config.get("plugin::firebase-authentication");
|
|
32144
|
+
const appConfig = pluginConfig?.app || {};
|
|
32145
|
+
const completeVariables = {
|
|
32146
|
+
...variables,
|
|
32147
|
+
appName: appConfig?.name || "Your Application",
|
|
32148
|
+
appUrl: appConfig?.url || process.env.PUBLIC_URL || "http://localhost:3000",
|
|
32149
|
+
supportEmail: appConfig?.supportEmail,
|
|
32150
|
+
year: (/* @__PURE__ */ new Date()).getFullYear(),
|
|
32151
|
+
expiresIn: variables.expiresIn
|
|
32152
|
+
};
|
|
32153
|
+
const templateService2 = strapi.plugin("firebase-authentication").service("templateService");
|
|
32154
|
+
const template = await templateService2.getTemplate("emailVerification");
|
|
32155
|
+
const subjectTemplate = customSubject || template.subject;
|
|
32156
|
+
const compiledSubject = _$1.template(subjectTemplate)(completeVariables);
|
|
32157
|
+
try {
|
|
32158
|
+
const compiledHtml = template.html ? _$1.template(template.html)(completeVariables) : void 0;
|
|
32159
|
+
const compiledText = template.text ? _$1.template(template.text)(completeVariables) : void 0;
|
|
32160
|
+
const emailPlugin = strapi.plugin("email");
|
|
32161
|
+
if (!emailPlugin) {
|
|
32162
|
+
throw new Error("Email plugin not found");
|
|
32163
|
+
}
|
|
32164
|
+
const emailService2 = emailPlugin.service("email");
|
|
32165
|
+
await emailService2.send({
|
|
32166
|
+
to: user.email,
|
|
32167
|
+
subject: compiledSubject,
|
|
32168
|
+
html: compiledHtml,
|
|
32169
|
+
text: compiledText
|
|
32170
|
+
});
|
|
32171
|
+
strapi.log.info(`✅ Email verification sent via Strapi email plugin to ${user.email}`);
|
|
32172
|
+
return {
|
|
32173
|
+
success: true,
|
|
32174
|
+
message: `Verification email sent to ${user.email}`
|
|
32175
|
+
};
|
|
32176
|
+
} catch (tier1Error) {
|
|
32177
|
+
strapi.log.debug(`Strapi email plugin failed: ${tier1Error.message}. Trying fallback options...`);
|
|
32178
|
+
}
|
|
32179
|
+
const customSender = pluginConfig?.sendVerificationEmail;
|
|
32180
|
+
if (customSender && typeof customSender === "function") {
|
|
32181
|
+
try {
|
|
32182
|
+
const compiledHtml = template.html ? _$1.template(template.html)(completeVariables) : void 0;
|
|
32183
|
+
const compiledText = template.text ? _$1.template(template.text)(completeVariables) : void 0;
|
|
32184
|
+
await customSender({
|
|
32185
|
+
to: user.email,
|
|
32186
|
+
subject: compiledSubject,
|
|
32187
|
+
html: compiledHtml,
|
|
32188
|
+
text: compiledText,
|
|
32189
|
+
verificationLink,
|
|
32190
|
+
variables: completeVariables
|
|
32191
|
+
});
|
|
32192
|
+
strapi.log.info(`✅ Email verification sent via custom hook to ${user.email}`);
|
|
32193
|
+
return {
|
|
32194
|
+
success: true,
|
|
32195
|
+
message: `Verification email sent to ${user.email}`
|
|
32196
|
+
};
|
|
32197
|
+
} catch (tier2Error) {
|
|
32198
|
+
strapi.log.error(`Custom hook failed: ${tier2Error.message}. Continuing to next fallback...`);
|
|
32199
|
+
}
|
|
32200
|
+
}
|
|
32201
|
+
if (process.env.NODE_ENV !== "production") {
|
|
32202
|
+
try {
|
|
32203
|
+
strapi.log.info("\n" + "=".repeat(80));
|
|
32204
|
+
strapi.log.info("EMAIL VERIFICATION (Development Mode)");
|
|
32205
|
+
strapi.log.info("=".repeat(80));
|
|
32206
|
+
strapi.log.info(`To: ${user.email}`);
|
|
32207
|
+
strapi.log.info(`Subject: ${compiledSubject}`);
|
|
32208
|
+
strapi.log.info(`Verification Link: ${verificationLink}`);
|
|
32209
|
+
strapi.log.info(`Expires In: 1 hour`);
|
|
32210
|
+
strapi.log.info("=".repeat(80));
|
|
32211
|
+
strapi.log.info("Note: Email not sent - no email service configured");
|
|
32212
|
+
strapi.log.info("Copy the link above and open in your browser to verify");
|
|
32213
|
+
strapi.log.info("=".repeat(80) + "\n");
|
|
32214
|
+
return {
|
|
32215
|
+
success: true,
|
|
32216
|
+
message: "Verification link logged to console (development mode)"
|
|
32217
|
+
};
|
|
32218
|
+
} catch (tier3Error) {
|
|
32219
|
+
strapi.log.error(`Development fallback failed: ${tier3Error.message}`);
|
|
32220
|
+
}
|
|
32221
|
+
}
|
|
32222
|
+
throw new ApplicationError$2(
|
|
32223
|
+
"Email service is not configured. Please configure Strapi email plugin or provide custom sendVerificationEmail function in plugin config."
|
|
32224
|
+
);
|
|
32225
|
+
}
|
|
31664
32226
|
}
|
|
31665
32227
|
const emailService = ({ strapi: strapi2 }) => new EmailService();
|
|
31666
32228
|
const firebaseUserDataService = ({ strapi: strapi2 }) => ({
|
|
@@ -35114,6 +35676,125 @@ const tokenService = ({ strapi: strapi2 }) => {
|
|
|
35114
35676
|
}
|
|
35115
35677
|
});
|
|
35116
35678
|
strapi2.log.debug(`Invalidated reset token for user ${firebaseUserDataDocumentId}`);
|
|
35679
|
+
},
|
|
35680
|
+
// ==================== EMAIL VERIFICATION TOKENS ====================
|
|
35681
|
+
/**
|
|
35682
|
+
* Generate an email verification token for a user
|
|
35683
|
+
* @param firebaseUserDataDocumentId - The documentId of the firebase-user-data record
|
|
35684
|
+
* @param email - The email address at time of request (for change detection)
|
|
35685
|
+
* @returns The JWT token to include in the verification URL
|
|
35686
|
+
*/
|
|
35687
|
+
async generateVerificationToken(firebaseUserDataDocumentId, email2) {
|
|
35688
|
+
const signingKey = getSigningKey();
|
|
35689
|
+
const jti = crypto$1.randomBytes(32).toString("hex");
|
|
35690
|
+
const payload = {
|
|
35691
|
+
sub: firebaseUserDataDocumentId,
|
|
35692
|
+
purpose: "email-verification",
|
|
35693
|
+
email: email2,
|
|
35694
|
+
jti
|
|
35695
|
+
};
|
|
35696
|
+
const token = jwt.sign(payload, signingKey, {
|
|
35697
|
+
expiresIn: "1h"
|
|
35698
|
+
});
|
|
35699
|
+
const tokenHash = crypto$1.createHash("sha256").update(jti).digest("hex");
|
|
35700
|
+
const expiresAt = new Date(Date.now() + 60 * 60 * 1e3);
|
|
35701
|
+
await strapi2.db.query(FIREBASE_USER_DATA_CONTENT_TYPE).update({
|
|
35702
|
+
where: { documentId: firebaseUserDataDocumentId },
|
|
35703
|
+
data: {
|
|
35704
|
+
verificationTokenHash: tokenHash,
|
|
35705
|
+
verificationTokenExpiresAt: expiresAt.toISOString()
|
|
35706
|
+
}
|
|
35707
|
+
});
|
|
35708
|
+
strapi2.log.debug(`Generated verification token for user ${firebaseUserDataDocumentId}`);
|
|
35709
|
+
return token;
|
|
35710
|
+
},
|
|
35711
|
+
/**
|
|
35712
|
+
* Validate an email verification token
|
|
35713
|
+
* @param token - The JWT token from the verification URL
|
|
35714
|
+
* @returns Validation result with user info and email if valid
|
|
35715
|
+
*/
|
|
35716
|
+
async validateVerificationToken(token) {
|
|
35717
|
+
const signingKey = getSigningKey();
|
|
35718
|
+
try {
|
|
35719
|
+
const decoded = jwt.verify(token, signingKey);
|
|
35720
|
+
if (decoded.purpose !== "email-verification") {
|
|
35721
|
+
return {
|
|
35722
|
+
valid: false,
|
|
35723
|
+
firebaseUserDataDocumentId: "",
|
|
35724
|
+
firebaseUID: "",
|
|
35725
|
+
error: "Invalid token purpose"
|
|
35726
|
+
};
|
|
35727
|
+
}
|
|
35728
|
+
const tokenHash = crypto$1.createHash("sha256").update(decoded.jti).digest("hex");
|
|
35729
|
+
const firebaseUserData2 = await strapi2.db.query(FIREBASE_USER_DATA_CONTENT_TYPE).findOne({
|
|
35730
|
+
where: { documentId: decoded.sub }
|
|
35731
|
+
});
|
|
35732
|
+
if (!firebaseUserData2) {
|
|
35733
|
+
return {
|
|
35734
|
+
valid: false,
|
|
35735
|
+
firebaseUserDataDocumentId: "",
|
|
35736
|
+
firebaseUID: "",
|
|
35737
|
+
error: "User not found"
|
|
35738
|
+
};
|
|
35739
|
+
}
|
|
35740
|
+
if (firebaseUserData2.verificationTokenHash !== tokenHash) {
|
|
35741
|
+
return {
|
|
35742
|
+
valid: false,
|
|
35743
|
+
firebaseUserDataDocumentId: "",
|
|
35744
|
+
firebaseUID: "",
|
|
35745
|
+
error: "Verification link has already been used or is invalid"
|
|
35746
|
+
};
|
|
35747
|
+
}
|
|
35748
|
+
if (firebaseUserData2.verificationTokenExpiresAt) {
|
|
35749
|
+
const expiresAt = new Date(firebaseUserData2.verificationTokenExpiresAt);
|
|
35750
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
35751
|
+
return {
|
|
35752
|
+
valid: false,
|
|
35753
|
+
firebaseUserDataDocumentId: "",
|
|
35754
|
+
firebaseUID: "",
|
|
35755
|
+
error: "Verification link has expired"
|
|
35756
|
+
};
|
|
35757
|
+
}
|
|
35758
|
+
}
|
|
35759
|
+
return {
|
|
35760
|
+
valid: true,
|
|
35761
|
+
firebaseUserDataDocumentId: firebaseUserData2.documentId,
|
|
35762
|
+
firebaseUID: firebaseUserData2.firebaseUserID,
|
|
35763
|
+
email: decoded.email
|
|
35764
|
+
};
|
|
35765
|
+
} catch (error2) {
|
|
35766
|
+
if (error2.name === "TokenExpiredError") {
|
|
35767
|
+
return {
|
|
35768
|
+
valid: false,
|
|
35769
|
+
firebaseUserDataDocumentId: "",
|
|
35770
|
+
firebaseUID: "",
|
|
35771
|
+
error: "Verification link has expired"
|
|
35772
|
+
};
|
|
35773
|
+
}
|
|
35774
|
+
if (error2.name === "JsonWebTokenError") {
|
|
35775
|
+
return {
|
|
35776
|
+
valid: false,
|
|
35777
|
+
firebaseUserDataDocumentId: "",
|
|
35778
|
+
firebaseUID: "",
|
|
35779
|
+
error: "Invalid verification link"
|
|
35780
|
+
};
|
|
35781
|
+
}
|
|
35782
|
+
throw error2;
|
|
35783
|
+
}
|
|
35784
|
+
},
|
|
35785
|
+
/**
|
|
35786
|
+
* Invalidate a verification token after use
|
|
35787
|
+
* @param firebaseUserDataDocumentId - The documentId of the firebase-user-data record
|
|
35788
|
+
*/
|
|
35789
|
+
async invalidateVerificationToken(firebaseUserDataDocumentId) {
|
|
35790
|
+
await strapi2.db.query(FIREBASE_USER_DATA_CONTENT_TYPE).update({
|
|
35791
|
+
where: { documentId: firebaseUserDataDocumentId },
|
|
35792
|
+
data: {
|
|
35793
|
+
verificationTokenHash: null,
|
|
35794
|
+
verificationTokenExpiresAt: null
|
|
35795
|
+
}
|
|
35796
|
+
});
|
|
35797
|
+
strapi2.log.debug(`Invalidated verification token for user ${firebaseUserDataDocumentId}`);
|
|
35117
35798
|
}
|
|
35118
35799
|
};
|
|
35119
35800
|
};
|