strapi-plugin-firebase-authentication 1.1.12 → 1.2.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/_chunks/{App-BqjE8BHb.js → App-CAgq2cOo.js} +369 -288
- package/dist/_chunks/{App-BY1gNGKH.mjs → App-CyeC5fLT.mjs} +291 -210
- package/dist/_chunks/api-BL-wXuSb.js +56776 -0
- package/dist/_chunks/api-Bjer83Qp.mjs +56759 -0
- package/dist/_chunks/index-Bg-lGlji.mjs +2758 -0
- package/dist/_chunks/{index-C4t4JZZ_.js → index-CTenjpqN.js} +285 -172
- package/dist/_chunks/{index-D8pv1Q6h.mjs → index-Kidqhaeq.mjs} +268 -155
- package/dist/_chunks/index-Rervtuh1.js +2757 -0
- package/dist/admin/index.js +1 -3
- package/dist/admin/index.mjs +2 -4
- 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 +1008 -332
- package/dist/server/index.mjs +1008 -332
- 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 +16 -1
- 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 +51 -5
- package/dist/server/src/policies/index.d.ts +3 -1
- package/dist/server/src/policies/is-authenticated.d.ts +6 -0
- package/dist/server/src/routes/content-api.d.ts +10 -2
- package/dist/server/src/routes/index.d.ts +10 -2
- package/dist/server/src/services/emailService.d.ts +10 -0
- package/dist/server/src/services/firebaseService.d.ts +21 -1
- package/dist/server/src/services/index.d.ts +18 -1
- 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 +8 -8
- package/dist/_chunks/api-DQCdqlCd.js +0 -35
- package/dist/_chunks/api-D_4cdJU5.mjs +0 -36
- package/dist/_chunks/index-DlPxMuSK.js +0 -814
- package/dist/_chunks/index-DtGfwf9S.mjs +0 -815
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 = {
|
|
@@ -385,238 +401,6 @@ const contentTypes = {
|
|
|
385
401
|
"firebase-authentication-configuration": { schema: firebaseAuthenticationConfiguration },
|
|
386
402
|
"firebase-user-data": { schema: firebaseUserData }
|
|
387
403
|
};
|
|
388
|
-
const pluginName = "firebase-authentication";
|
|
389
|
-
const PLUGIN_NAME = "firebase-authentication";
|
|
390
|
-
const PLUGIN_UID = `plugin::${PLUGIN_NAME}`;
|
|
391
|
-
const CONFIG_CONTENT_TYPE = `${PLUGIN_UID}.firebase-authentication-configuration`;
|
|
392
|
-
const DEFAULT_PASSWORD_RESET_URL = "http://localhost:3000/reset-password";
|
|
393
|
-
const DEFAULT_PASSWORD_REGEX = "^.{6,}$";
|
|
394
|
-
const DEFAULT_PASSWORD_MESSAGE = "Password must be at least 6 characters long";
|
|
395
|
-
const DEFAULT_RESET_EMAIL_SUBJECT = "Reset Your Password";
|
|
396
|
-
const ERROR_MESSAGES = {
|
|
397
|
-
FIREBASE_NOT_INITIALIZED: "Firebase is not initialized. Please upload Firebase service account configuration via Settings → Firebase Authentication.",
|
|
398
|
-
INVALID_JSON: "Invalid JSON format. Please ensure you copied the entire JSON content correctly.",
|
|
399
|
-
MISSING_DATA: "data is missing",
|
|
400
|
-
SOMETHING_WENT_WRONG: "Something went wrong",
|
|
401
|
-
AUTHENTICATION_FAILED: "Authentication failed",
|
|
402
|
-
TOKEN_MISSING: "idToken is missing!",
|
|
403
|
-
EMAIL_PASSWORD_REQUIRED: "Email and password are required",
|
|
404
|
-
PASSWORD_REQUIRED: "Password is required",
|
|
405
|
-
AUTHORIZATION_REQUIRED: "Authorization token is required",
|
|
406
|
-
INVALID_TOKEN: "Invalid or expired token",
|
|
407
|
-
USER_NOT_FOUND: "User not found",
|
|
408
|
-
USER_NO_EMAIL: "User does not have an email address",
|
|
409
|
-
FIREBASE_LINK_FAILED: "Failed to generate Firebase reset link",
|
|
410
|
-
CONFIG_NOT_FOUND: "No config found",
|
|
411
|
-
INVALID_SERVICE_ACCOUNT: "Invalid service account JSON",
|
|
412
|
-
WEB_API_NOT_CONFIGURED: "Email/password authentication is not available. Web API Key is not configured.",
|
|
413
|
-
RESET_URL_NOT_CONFIGURED: "Password reset URL is not configured",
|
|
414
|
-
RESET_URL_MUST_BE_HTTPS: "Password reset URL must use HTTPS in production",
|
|
415
|
-
RESET_URL_INVALID_FORMAT: "Password reset URL is not a valid URL format",
|
|
416
|
-
USER_NOT_LINKED_FIREBASE: "User is not linked to Firebase authentication",
|
|
417
|
-
OVERRIDE_USER_ID_REQUIRED: "Override user ID is required",
|
|
418
|
-
EITHER_EMAIL_OR_PHONE_REQUIRED: "Either email or phoneNumber is required",
|
|
419
|
-
DELETION_NO_CONFIG: "No Firebase configs exists for deletion"
|
|
420
|
-
};
|
|
421
|
-
const SUCCESS_MESSAGES = {
|
|
422
|
-
FIREBASE_INITIALIZED: "Firebase successfully initialized",
|
|
423
|
-
FIREBASE_CONFIG_DELETED: "Firebase config deleted and reinitialized",
|
|
424
|
-
PASSWORD_RESET_EMAIL_SENT: "If an account with that email exists, a password reset link has been sent.",
|
|
425
|
-
SERVER_RESTARTING: "SERVER IS RESTARTING"
|
|
426
|
-
};
|
|
427
|
-
const CONFIG_KEYS = {
|
|
428
|
-
ENCRYPTION_KEY: `${PLUGIN_UID}.FIREBASE_JSON_ENCRYPTION_KEY`
|
|
429
|
-
};
|
|
430
|
-
const REQUIRED_FIELDS = {
|
|
431
|
-
SERVICE_ACCOUNT: ["private_key", "client_email", "project_id", "type"],
|
|
432
|
-
WEB_CONFIG: ["apiKey", "authDomain"]
|
|
433
|
-
// These indicate wrong JSON type
|
|
434
|
-
};
|
|
435
|
-
const VALIDATION_MESSAGES = {
|
|
436
|
-
INVALID_SERVICE_ACCOUNT: "Invalid Service Account JSON. Missing required fields:",
|
|
437
|
-
WRONG_JSON_TYPE: "You uploaded a Web App Config (SDK snippet) instead of a Service Account JSON. Please go to Firebase Console → Service Accounts tab → Generate New Private Key to download the correct file.",
|
|
438
|
-
SERVICE_ACCOUNT_HELP: "Please download the correct JSON from Firebase Console → Service Accounts → Generate New Private Key. Do NOT use the Web App Config (SDK snippet) - that is a different file!"
|
|
439
|
-
};
|
|
440
|
-
const firebaseController = {
|
|
441
|
-
async validateToken(ctx) {
|
|
442
|
-
strapi.log.debug("validateToken called");
|
|
443
|
-
try {
|
|
444
|
-
const { idToken, profileMetaData } = ctx.request.body || {};
|
|
445
|
-
const populate2 = ctx.request.query.populate || [];
|
|
446
|
-
if (!idToken) {
|
|
447
|
-
return ctx.badRequest(ERROR_MESSAGES.TOKEN_MISSING);
|
|
448
|
-
}
|
|
449
|
-
const result = await strapi.plugin(pluginName).service("firebaseService").validateFirebaseToken(idToken, profileMetaData, populate2);
|
|
450
|
-
ctx.body = result;
|
|
451
|
-
} catch (error2) {
|
|
452
|
-
strapi.log.error(`validateToken controller error: ${error2.message}`);
|
|
453
|
-
if (error2.name === "ValidationError") {
|
|
454
|
-
return ctx.badRequest(error2.message);
|
|
455
|
-
}
|
|
456
|
-
if (error2.name === "UnauthorizedError") {
|
|
457
|
-
return ctx.unauthorized(error2.message);
|
|
458
|
-
}
|
|
459
|
-
throw error2;
|
|
460
|
-
}
|
|
461
|
-
},
|
|
462
|
-
async deleteByEmail(email2) {
|
|
463
|
-
const user = await strapi.firebase.auth().getUserByEmail(email2);
|
|
464
|
-
await strapi.plugin(pluginName).service("firebaseService").delete(user.toJSON().uid);
|
|
465
|
-
return user.toJSON();
|
|
466
|
-
},
|
|
467
|
-
async overrideAccess(ctx) {
|
|
468
|
-
try {
|
|
469
|
-
const { overrideUserId } = ctx.request.body || {};
|
|
470
|
-
const populate2 = ctx.request.query.populate || [];
|
|
471
|
-
const result = await strapi.plugin(pluginName).service("firebaseService").overrideFirebaseAccess(overrideUserId, populate2);
|
|
472
|
-
ctx.body = result;
|
|
473
|
-
} catch (error2) {
|
|
474
|
-
if (error2.name === "ValidationError") {
|
|
475
|
-
return ctx.badRequest(error2.message);
|
|
476
|
-
}
|
|
477
|
-
if (error2.name === "NotFoundError") {
|
|
478
|
-
return ctx.notFound(error2.message);
|
|
479
|
-
}
|
|
480
|
-
throw error2;
|
|
481
|
-
}
|
|
482
|
-
},
|
|
483
|
-
/**
|
|
484
|
-
* Controller method for email/password authentication
|
|
485
|
-
* Handles the `/api/firebase-authentication/emailLogin` endpoint
|
|
486
|
-
*
|
|
487
|
-
* @param ctx - Koa context object
|
|
488
|
-
* @returns Promise that sets ctx.body with user data and JWT or error message
|
|
489
|
-
*
|
|
490
|
-
* @remarks
|
|
491
|
-
* This controller acts as a proxy to Firebase's Identity Toolkit API,
|
|
492
|
-
* allowing users to authenticate with email/password and receive a Strapi JWT.
|
|
493
|
-
*
|
|
494
|
-
* HTTP Status Codes:
|
|
495
|
-
* - `400`: Validation errors (missing credentials, invalid email/password)
|
|
496
|
-
* - `500`: Server errors (missing configuration, Firebase API issues)
|
|
497
|
-
*/
|
|
498
|
-
async emailLogin(ctx) {
|
|
499
|
-
strapi.log.debug("emailLogin controller");
|
|
500
|
-
try {
|
|
501
|
-
const { email: email2, password } = ctx.request.body || {};
|
|
502
|
-
const populate2 = ctx.request.query.populate || [];
|
|
503
|
-
const result = await strapi.plugin(pluginName).service("firebaseService").emailLogin(email2, password, populate2);
|
|
504
|
-
ctx.body = result;
|
|
505
|
-
} catch (error2) {
|
|
506
|
-
strapi.log.error("emailLogin controller error:", error2);
|
|
507
|
-
if (error2.name === "ValidationError") {
|
|
508
|
-
ctx.status = 400;
|
|
509
|
-
} else if (error2.name === "ApplicationError") {
|
|
510
|
-
ctx.status = 500;
|
|
511
|
-
} else {
|
|
512
|
-
ctx.status = 500;
|
|
513
|
-
}
|
|
514
|
-
ctx.body = { error: error2.message };
|
|
515
|
-
}
|
|
516
|
-
},
|
|
517
|
-
/**
|
|
518
|
-
* Forgot password - sends reset email
|
|
519
|
-
* POST /api/firebase-authentication/forgotPassword
|
|
520
|
-
* Public endpoint - no authentication required
|
|
521
|
-
*/
|
|
522
|
-
async forgotPassword(ctx) {
|
|
523
|
-
strapi.log.debug("forgotPassword endpoint called");
|
|
524
|
-
try {
|
|
525
|
-
const { email: email2 } = ctx.request.body || {};
|
|
526
|
-
ctx.body = await strapi.plugin(pluginName).service("firebaseService").forgotPassword(email2);
|
|
527
|
-
} catch (error2) {
|
|
528
|
-
strapi.log.error("forgotPassword controller error:", error2);
|
|
529
|
-
if (error2.name === "ValidationError") {
|
|
530
|
-
ctx.status = 400;
|
|
531
|
-
} else if (error2.name === "NotFoundError") {
|
|
532
|
-
ctx.status = 404;
|
|
533
|
-
} else {
|
|
534
|
-
ctx.status = 500;
|
|
535
|
-
}
|
|
536
|
-
ctx.body = { error: error2.message };
|
|
537
|
-
}
|
|
538
|
-
},
|
|
539
|
-
/**
|
|
540
|
-
* Reset password - authenticated password change
|
|
541
|
-
* POST /api/firebase-authentication/resetPassword
|
|
542
|
-
* Public endpoint - requires valid JWT in Authorization header
|
|
543
|
-
* Used for admin-initiated resets or user self-service password changes
|
|
544
|
-
* NOT used for forgot password email flow (which uses Firebase's hosted UI)
|
|
545
|
-
*/
|
|
546
|
-
async resetPassword(ctx) {
|
|
547
|
-
strapi.log.debug("resetPassword endpoint called");
|
|
548
|
-
try {
|
|
549
|
-
const { password } = ctx.request.body || {};
|
|
550
|
-
const token = ctx.request.headers.authorization?.replace("Bearer ", "");
|
|
551
|
-
const populate2 = ctx.request.query.populate || [];
|
|
552
|
-
ctx.body = await strapi.plugin(pluginName).service("firebaseService").resetPassword(password, token, populate2);
|
|
553
|
-
} catch (error2) {
|
|
554
|
-
strapi.log.error("resetPassword controller error:", error2);
|
|
555
|
-
if (error2.name === "ValidationError" || error2.name === "UnauthorizedError") {
|
|
556
|
-
ctx.status = 401;
|
|
557
|
-
} else {
|
|
558
|
-
ctx.status = 500;
|
|
559
|
-
}
|
|
560
|
-
ctx.body = { error: error2.message };
|
|
561
|
-
}
|
|
562
|
-
},
|
|
563
|
-
async requestMagicLink(ctx) {
|
|
564
|
-
try {
|
|
565
|
-
const { email: email2 } = ctx.request.body || {};
|
|
566
|
-
const result = await strapi.plugin("firebase-authentication").service("firebaseService").requestMagicLink(email2);
|
|
567
|
-
ctx.body = result;
|
|
568
|
-
} catch (error2) {
|
|
569
|
-
if (error2.name === "ValidationError" || error2.name === "ApplicationError") {
|
|
570
|
-
throw error2;
|
|
571
|
-
}
|
|
572
|
-
strapi.log.error("requestMagicLink controller error:", error2);
|
|
573
|
-
ctx.body = {
|
|
574
|
-
success: false,
|
|
575
|
-
message: "An error occurred while processing your request"
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
},
|
|
579
|
-
/**
|
|
580
|
-
* Reset password using custom JWT token
|
|
581
|
-
* POST /api/firebase-authentication/resetPasswordWithToken
|
|
582
|
-
* Public endpoint - token provides authentication
|
|
583
|
-
*
|
|
584
|
-
* @param ctx - Koa context with { token, newPassword } in body
|
|
585
|
-
* @returns { success: true, message: "Password has been reset successfully" }
|
|
586
|
-
*/
|
|
587
|
-
async resetPasswordWithToken(ctx) {
|
|
588
|
-
strapi.log.debug("resetPasswordWithToken endpoint called");
|
|
589
|
-
try {
|
|
590
|
-
const { token, newPassword } = ctx.request.body || {};
|
|
591
|
-
if (!token) {
|
|
592
|
-
ctx.status = 400;
|
|
593
|
-
ctx.body = { error: "Token is required" };
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
if (!newPassword) {
|
|
597
|
-
ctx.status = 400;
|
|
598
|
-
ctx.body = { error: "New password is required" };
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
const result = await strapi.plugin(pluginName).service("userService").resetPasswordWithToken(token, newPassword);
|
|
602
|
-
ctx.body = result;
|
|
603
|
-
} catch (error2) {
|
|
604
|
-
strapi.log.error("resetPasswordWithToken controller error:", error2);
|
|
605
|
-
if (error2.name === "ValidationError") {
|
|
606
|
-
ctx.status = 400;
|
|
607
|
-
ctx.body = { error: error2.message };
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
if (error2.name === "NotFoundError") {
|
|
611
|
-
ctx.status = 404;
|
|
612
|
-
ctx.body = { error: error2.message };
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
ctx.status = 500;
|
|
616
|
-
ctx.body = { error: "An error occurred while resetting your password" };
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
};
|
|
620
404
|
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
|
|
621
405
|
function getDefaultExportFromCjs(x) {
|
|
622
406
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
@@ -631,7 +415,7 @@ var lodash = { exports: {} };
|
|
|
631
415
|
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
632
416
|
*/
|
|
633
417
|
lodash.exports;
|
|
634
|
-
(function(module2,
|
|
418
|
+
(function(module2, exports$1) {
|
|
635
419
|
(function() {
|
|
636
420
|
var undefined$1;
|
|
637
421
|
var VERSION = "4.17.21";
|
|
@@ -959,7 +743,7 @@ lodash.exports;
|
|
|
959
743
|
var freeGlobal2 = typeof commonjsGlobal == "object" && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal;
|
|
960
744
|
var freeSelf2 = typeof self == "object" && self && self.Object === Object && self;
|
|
961
745
|
var root2 = freeGlobal2 || freeSelf2 || Function("return this")();
|
|
962
|
-
var freeExports =
|
|
746
|
+
var freeExports = exports$1 && !exports$1.nodeType && exports$1;
|
|
963
747
|
var freeModule = freeExports && true && module2 && !module2.nodeType && module2;
|
|
964
748
|
var moduleExports = freeModule && freeModule.exports === freeExports;
|
|
965
749
|
var freeProcess = moduleExports && freeGlobal2.process;
|
|
@@ -6189,7 +5973,7 @@ var lodash_min = { exports: {} };
|
|
|
6189
5973
|
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
6190
5974
|
*/
|
|
6191
5975
|
lodash_min.exports;
|
|
6192
|
-
(function(module2,
|
|
5976
|
+
(function(module2, exports$1) {
|
|
6193
5977
|
(function() {
|
|
6194
5978
|
function n(n2, t3, r2) {
|
|
6195
5979
|
switch (r2.length) {
|
|
@@ -6622,7 +6406,7 @@ lodash_min.exports;
|
|
|
6622
6406
|
"œ": "oe",
|
|
6623
6407
|
"ʼn": "'n",
|
|
6624
6408
|
"ſ": "s"
|
|
6625
|
-
}, Hr = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }, Jr = { "&": "&", "<": "<", ">": ">", """: '"', "'": "'" }, Yr = { "\\": "\\", "'": "'", "\n": "n", "\r": "r", "\u2028": "u2028", "\u2029": "u2029" }, Qr = parseFloat, Xr = parseInt, ne = "object" == typeof commonjsGlobal && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal, te = "object" == typeof self && self && self.Object === Object && self, re2 = ne || te || Function("return this")(), ee =
|
|
6409
|
+
}, Hr = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }, Jr = { "&": "&", "<": "<", ">": ">", """: '"', "'": "'" }, Yr = { "\\": "\\", "'": "'", "\n": "n", "\r": "r", "\u2028": "u2028", "\u2029": "u2029" }, Qr = parseFloat, Xr = parseInt, ne = "object" == typeof commonjsGlobal && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal, te = "object" == typeof self && self && self.Object === Object && self, re2 = ne || te || Function("return this")(), ee = exports$1 && !exports$1.nodeType && exports$1, ue = ee && true && module2 && !module2.nodeType && module2, ie = ue && ue.exports === ee, oe = ie && ne.process, fe = function() {
|
|
6626
6410
|
try {
|
|
6627
6411
|
var n2 = ue && ue.require && ue.require("util").types;
|
|
6628
6412
|
return n2 ? n2 : oe && oe.binding && oe.binding("util");
|
|
@@ -9295,8 +9079,8 @@ lodash_min.exports;
|
|
|
9295
9079
|
})(lodash_min, lodash_min.exports);
|
|
9296
9080
|
var lodash_minExports = lodash_min.exports;
|
|
9297
9081
|
var _mapping = {};
|
|
9298
|
-
(function(
|
|
9299
|
-
|
|
9082
|
+
(function(exports$1) {
|
|
9083
|
+
exports$1.aliasToReal = {
|
|
9300
9084
|
// Lodash aliases.
|
|
9301
9085
|
"each": "forEach",
|
|
9302
9086
|
"eachRight": "forEachRight",
|
|
@@ -9361,7 +9145,7 @@ var _mapping = {};
|
|
|
9361
9145
|
"whereEq": "isMatch",
|
|
9362
9146
|
"zipObj": "zipObject"
|
|
9363
9147
|
};
|
|
9364
|
-
|
|
9148
|
+
exports$1.aryMethod = {
|
|
9365
9149
|
"1": [
|
|
9366
9150
|
"assignAll",
|
|
9367
9151
|
"assignInAll",
|
|
@@ -9596,12 +9380,12 @@ var _mapping = {};
|
|
|
9596
9380
|
"updateWith"
|
|
9597
9381
|
]
|
|
9598
9382
|
};
|
|
9599
|
-
|
|
9383
|
+
exports$1.aryRearg = {
|
|
9600
9384
|
"2": [1, 0],
|
|
9601
9385
|
"3": [2, 0, 1],
|
|
9602
9386
|
"4": [3, 2, 0, 1]
|
|
9603
9387
|
};
|
|
9604
|
-
|
|
9388
|
+
exports$1.iterateeAry = {
|
|
9605
9389
|
"dropRightWhile": 1,
|
|
9606
9390
|
"dropWhile": 1,
|
|
9607
9391
|
"every": 1,
|
|
@@ -9639,11 +9423,11 @@ var _mapping = {};
|
|
|
9639
9423
|
"times": 1,
|
|
9640
9424
|
"transform": 2
|
|
9641
9425
|
};
|
|
9642
|
-
|
|
9426
|
+
exports$1.iterateeRearg = {
|
|
9643
9427
|
"mapKeys": [1],
|
|
9644
9428
|
"reduceRight": [1, 0]
|
|
9645
9429
|
};
|
|
9646
|
-
|
|
9430
|
+
exports$1.methodRearg = {
|
|
9647
9431
|
"assignInAllWith": [1, 0],
|
|
9648
9432
|
"assignInWith": [1, 2, 0],
|
|
9649
9433
|
"assignAllWith": [1, 0],
|
|
@@ -9674,7 +9458,7 @@ var _mapping = {};
|
|
|
9674
9458
|
"xorWith": [1, 2, 0],
|
|
9675
9459
|
"zipWith": [1, 2, 0]
|
|
9676
9460
|
};
|
|
9677
|
-
|
|
9461
|
+
exports$1.methodSpread = {
|
|
9678
9462
|
"assignAll": { "start": 0 },
|
|
9679
9463
|
"assignAllWith": { "start": 0 },
|
|
9680
9464
|
"assignInAll": { "start": 0 },
|
|
@@ -9690,7 +9474,7 @@ var _mapping = {};
|
|
|
9690
9474
|
"without": { "start": 1 },
|
|
9691
9475
|
"zipAll": { "start": 0 }
|
|
9692
9476
|
};
|
|
9693
|
-
|
|
9477
|
+
exports$1.mutate = {
|
|
9694
9478
|
"array": {
|
|
9695
9479
|
"fill": true,
|
|
9696
9480
|
"pull": true,
|
|
@@ -9727,8 +9511,8 @@ var _mapping = {};
|
|
|
9727
9511
|
"updateWith": true
|
|
9728
9512
|
}
|
|
9729
9513
|
};
|
|
9730
|
-
|
|
9731
|
-
var hasOwnProperty2 = Object.prototype.hasOwnProperty, object2 =
|
|
9514
|
+
exports$1.realToAlias = function() {
|
|
9515
|
+
var hasOwnProperty2 = Object.prototype.hasOwnProperty, object2 = exports$1.aliasToReal, result = {};
|
|
9732
9516
|
for (var key in object2) {
|
|
9733
9517
|
var value = object2[key];
|
|
9734
9518
|
if (hasOwnProperty2.call(result, value)) {
|
|
@@ -9739,7 +9523,7 @@ var _mapping = {};
|
|
|
9739
9523
|
}
|
|
9740
9524
|
return result;
|
|
9741
9525
|
}();
|
|
9742
|
-
|
|
9526
|
+
exports$1.remap = {
|
|
9743
9527
|
"assignAll": "assign",
|
|
9744
9528
|
"assignAllWith": "assignWith",
|
|
9745
9529
|
"assignInAll": "assignIn",
|
|
@@ -9773,7 +9557,7 @@ var _mapping = {};
|
|
|
9773
9557
|
"trimCharsStart": "trimStart",
|
|
9774
9558
|
"zipAll": "zip"
|
|
9775
9559
|
};
|
|
9776
|
-
|
|
9560
|
+
exports$1.skipFixed = {
|
|
9777
9561
|
"castArray": true,
|
|
9778
9562
|
"flow": true,
|
|
9779
9563
|
"flowRight": true,
|
|
@@ -9782,7 +9566,7 @@ var _mapping = {};
|
|
|
9782
9566
|
"rearg": true,
|
|
9783
9567
|
"runInContext": true
|
|
9784
9568
|
};
|
|
9785
|
-
|
|
9569
|
+
exports$1.skipRearg = {
|
|
9786
9570
|
"add": true,
|
|
9787
9571
|
"assign": true,
|
|
9788
9572
|
"assignIn": true,
|
|
@@ -10236,13 +10020,13 @@ const traverseEntity = async (visitor2, options2, entity) => {
|
|
|
10236
10020
|
if (fp.isNil(value) || fp.isNil(attribute)) {
|
|
10237
10021
|
continue;
|
|
10238
10022
|
}
|
|
10239
|
-
parent = {
|
|
10240
|
-
schema: schema2,
|
|
10241
|
-
key,
|
|
10242
|
-
attribute,
|
|
10243
|
-
path: newPath
|
|
10244
|
-
};
|
|
10245
10023
|
if (isRelationalAttribute(attribute)) {
|
|
10024
|
+
parent = {
|
|
10025
|
+
schema: schema2,
|
|
10026
|
+
key,
|
|
10027
|
+
attribute,
|
|
10028
|
+
path: newPath
|
|
10029
|
+
};
|
|
10246
10030
|
const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
|
|
10247
10031
|
const method = isMorphRelation ? traverseMorphRelationTarget : traverseRelationTarget(getModel(attribute.target));
|
|
10248
10032
|
if (fp.isArray(value)) {
|
|
@@ -10261,6 +10045,12 @@ const traverseEntity = async (visitor2, options2, entity) => {
|
|
|
10261
10045
|
continue;
|
|
10262
10046
|
}
|
|
10263
10047
|
if (isMediaAttribute(attribute)) {
|
|
10048
|
+
parent = {
|
|
10049
|
+
schema: schema2,
|
|
10050
|
+
key,
|
|
10051
|
+
attribute,
|
|
10052
|
+
path: newPath
|
|
10053
|
+
};
|
|
10264
10054
|
if (fp.isArray(value)) {
|
|
10265
10055
|
const res = new Array(value.length);
|
|
10266
10056
|
for (let i2 = 0; i2 < value.length; i2 += 1) {
|
|
@@ -10277,6 +10067,12 @@ const traverseEntity = async (visitor2, options2, entity) => {
|
|
|
10277
10067
|
continue;
|
|
10278
10068
|
}
|
|
10279
10069
|
if (attribute.type === "component") {
|
|
10070
|
+
parent = {
|
|
10071
|
+
schema: schema2,
|
|
10072
|
+
key,
|
|
10073
|
+
attribute,
|
|
10074
|
+
path: newPath
|
|
10075
|
+
};
|
|
10280
10076
|
const targetSchema = getModel(attribute.component);
|
|
10281
10077
|
if (fp.isArray(value)) {
|
|
10282
10078
|
const res = new Array(value.length);
|
|
@@ -10294,6 +10090,12 @@ const traverseEntity = async (visitor2, options2, entity) => {
|
|
|
10294
10090
|
continue;
|
|
10295
10091
|
}
|
|
10296
10092
|
if (attribute.type === "dynamiczone" && fp.isArray(value)) {
|
|
10093
|
+
parent = {
|
|
10094
|
+
schema: schema2,
|
|
10095
|
+
key,
|
|
10096
|
+
attribute,
|
|
10097
|
+
path: newPath
|
|
10098
|
+
};
|
|
10297
10099
|
const res = new Array(value.length);
|
|
10298
10100
|
for (let i2 = 0; i2 < value.length; i2 += 1) {
|
|
10299
10101
|
const arrayPath = {
|
|
@@ -10318,7 +10120,7 @@ const createVisitorUtils = ({ data }) => ({
|
|
|
10318
10120
|
});
|
|
10319
10121
|
fp.curry(traverseEntity);
|
|
10320
10122
|
var dist = { exports: {} };
|
|
10321
|
-
(function(module2,
|
|
10123
|
+
(function(module2, exports$1) {
|
|
10322
10124
|
!function(t2, n) {
|
|
10323
10125
|
module2.exports = n(require$$0__default.default, crypto__default.default);
|
|
10324
10126
|
}(commonjsGlobal, function(t2, n) {
|
|
@@ -11866,9 +11668,9 @@ function stubFalse() {
|
|
|
11866
11668
|
}
|
|
11867
11669
|
var stubFalse_1 = stubFalse;
|
|
11868
11670
|
isBuffer$2.exports;
|
|
11869
|
-
(function(module2,
|
|
11671
|
+
(function(module2, exports$1) {
|
|
11870
11672
|
var root2 = _root, stubFalse2 = stubFalse_1;
|
|
11871
|
-
var freeExports =
|
|
11673
|
+
var freeExports = exports$1 && !exports$1.nodeType && exports$1;
|
|
11872
11674
|
var freeModule = freeExports && true && module2 && !module2.nodeType && module2;
|
|
11873
11675
|
var moduleExports = freeModule && freeModule.exports === freeExports;
|
|
11874
11676
|
var Buffer2 = moduleExports ? root2.Buffer : void 0;
|
|
@@ -11895,9 +11697,9 @@ function baseUnary$1(func) {
|
|
|
11895
11697
|
var _baseUnary = baseUnary$1;
|
|
11896
11698
|
var _nodeUtil = { exports: {} };
|
|
11897
11699
|
_nodeUtil.exports;
|
|
11898
|
-
(function(module2,
|
|
11700
|
+
(function(module2, exports$1) {
|
|
11899
11701
|
var freeGlobal2 = _freeGlobal;
|
|
11900
|
-
var freeExports =
|
|
11702
|
+
var freeExports = exports$1 && !exports$1.nodeType && exports$1;
|
|
11901
11703
|
var freeModule = freeExports && true && module2 && !module2.nodeType && module2;
|
|
11902
11704
|
var moduleExports = freeModule && freeModule.exports === freeExports;
|
|
11903
11705
|
var freeProcess = moduleExports && freeGlobal2.process;
|
|
@@ -14856,7 +14658,7 @@ function toIdentifier(str2) {
|
|
|
14856
14658
|
Object.defineProperty(func, "name", desc);
|
|
14857
14659
|
}
|
|
14858
14660
|
}
|
|
14859
|
-
function populateConstructorExports(
|
|
14661
|
+
function populateConstructorExports(exports$1, codes2, HttpError) {
|
|
14860
14662
|
codes2.forEach(function forEachCode(code) {
|
|
14861
14663
|
var CodeError;
|
|
14862
14664
|
var name = toIdentifier2(statuses$1.message[code]);
|
|
@@ -14869,8 +14671,8 @@ function toIdentifier(str2) {
|
|
|
14869
14671
|
break;
|
|
14870
14672
|
}
|
|
14871
14673
|
if (CodeError) {
|
|
14872
|
-
|
|
14873
|
-
|
|
14674
|
+
exports$1[code] = CodeError;
|
|
14675
|
+
exports$1[name] = CodeError;
|
|
14874
14676
|
}
|
|
14875
14677
|
});
|
|
14876
14678
|
}
|
|
@@ -14892,7 +14694,7 @@ const formatYupErrors = (yupError) => ({
|
|
|
14892
14694
|
message: yupError.message
|
|
14893
14695
|
});
|
|
14894
14696
|
let ApplicationError$2 = class ApplicationError extends Error {
|
|
14895
|
-
constructor(message = "An application error
|
|
14697
|
+
constructor(message = "An application error occurred", details = {}) {
|
|
14896
14698
|
super();
|
|
14897
14699
|
this.name = "ApplicationError";
|
|
14898
14700
|
this.message = message;
|
|
@@ -18088,8 +17890,8 @@ pkgDir$1.exports.sync = (cwd2) => {
|
|
|
18088
17890
|
};
|
|
18089
17891
|
var pkgDirExports = pkgDir$1.exports;
|
|
18090
17892
|
var utils$8 = {};
|
|
18091
|
-
(function(
|
|
18092
|
-
|
|
17893
|
+
(function(exports$1) {
|
|
17894
|
+
exports$1.isInteger = (num) => {
|
|
18093
17895
|
if (typeof num === "number") {
|
|
18094
17896
|
return Number.isInteger(num);
|
|
18095
17897
|
}
|
|
@@ -18098,13 +17900,13 @@ var utils$8 = {};
|
|
|
18098
17900
|
}
|
|
18099
17901
|
return false;
|
|
18100
17902
|
};
|
|
18101
|
-
|
|
18102
|
-
|
|
17903
|
+
exports$1.find = (node, type2) => node.nodes.find((node2) => node2.type === type2);
|
|
17904
|
+
exports$1.exceedsLimit = (min, max, step = 1, limit) => {
|
|
18103
17905
|
if (limit === false) return false;
|
|
18104
|
-
if (!
|
|
17906
|
+
if (!exports$1.isInteger(min) || !exports$1.isInteger(max)) return false;
|
|
18105
17907
|
return (Number(max) - Number(min)) / Number(step) >= limit;
|
|
18106
17908
|
};
|
|
18107
|
-
|
|
17909
|
+
exports$1.escapeNode = (block, n = 0, type2) => {
|
|
18108
17910
|
const node = block.nodes[n];
|
|
18109
17911
|
if (!node) return;
|
|
18110
17912
|
if (type2 && node.type === type2 || node.type === "open" || node.type === "close") {
|
|
@@ -18114,7 +17916,7 @@ var utils$8 = {};
|
|
|
18114
17916
|
}
|
|
18115
17917
|
}
|
|
18116
17918
|
};
|
|
18117
|
-
|
|
17919
|
+
exports$1.encloseBrace = (node) => {
|
|
18118
17920
|
if (node.type !== "brace") return false;
|
|
18119
17921
|
if (node.commas >> 0 + node.ranges >> 0 === 0) {
|
|
18120
17922
|
node.invalid = true;
|
|
@@ -18122,7 +17924,7 @@ var utils$8 = {};
|
|
|
18122
17924
|
}
|
|
18123
17925
|
return false;
|
|
18124
17926
|
};
|
|
18125
|
-
|
|
17927
|
+
exports$1.isInvalidBrace = (block) => {
|
|
18126
17928
|
if (block.type !== "brace") return false;
|
|
18127
17929
|
if (block.invalid === true || block.dollar) return true;
|
|
18128
17930
|
if (block.commas >> 0 + block.ranges >> 0 === 0) {
|
|
@@ -18135,18 +17937,18 @@ var utils$8 = {};
|
|
|
18135
17937
|
}
|
|
18136
17938
|
return false;
|
|
18137
17939
|
};
|
|
18138
|
-
|
|
17940
|
+
exports$1.isOpenOrClose = (node) => {
|
|
18139
17941
|
if (node.type === "open" || node.type === "close") {
|
|
18140
17942
|
return true;
|
|
18141
17943
|
}
|
|
18142
17944
|
return node.open === true || node.close === true;
|
|
18143
17945
|
};
|
|
18144
|
-
|
|
17946
|
+
exports$1.reduce = (nodes) => nodes.reduce((acc, node) => {
|
|
18145
17947
|
if (node.type === "text") acc.push(node.value);
|
|
18146
17948
|
if (node.type === "range") node.type = "text";
|
|
18147
17949
|
return acc;
|
|
18148
17950
|
}, []);
|
|
18149
|
-
|
|
17951
|
+
exports$1.flatten = (...args) => {
|
|
18150
17952
|
const result = [];
|
|
18151
17953
|
const flat = (arr) => {
|
|
18152
17954
|
for (let i = 0; i < arr.length; i++) {
|
|
@@ -19248,7 +19050,7 @@ var constants$5 = {
|
|
|
19248
19050
|
return win32 === true ? WINDOWS_CHARS : POSIX_CHARS;
|
|
19249
19051
|
}
|
|
19250
19052
|
};
|
|
19251
|
-
(function(
|
|
19053
|
+
(function(exports$1) {
|
|
19252
19054
|
const path2 = require$$0__namespace.default;
|
|
19253
19055
|
const win32 = process.platform === "win32";
|
|
19254
19056
|
const {
|
|
@@ -19257,36 +19059,36 @@ var constants$5 = {
|
|
|
19257
19059
|
REGEX_SPECIAL_CHARS,
|
|
19258
19060
|
REGEX_SPECIAL_CHARS_GLOBAL
|
|
19259
19061
|
} = constants$5;
|
|
19260
|
-
|
|
19261
|
-
|
|
19262
|
-
|
|
19263
|
-
|
|
19264
|
-
|
|
19265
|
-
|
|
19062
|
+
exports$1.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
|
|
19063
|
+
exports$1.hasRegexChars = (str2) => REGEX_SPECIAL_CHARS.test(str2);
|
|
19064
|
+
exports$1.isRegexChar = (str2) => str2.length === 1 && exports$1.hasRegexChars(str2);
|
|
19065
|
+
exports$1.escapeRegex = (str2) => str2.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
|
|
19066
|
+
exports$1.toPosixSlashes = (str2) => str2.replace(REGEX_BACKSLASH, "/");
|
|
19067
|
+
exports$1.removeBackslashes = (str2) => {
|
|
19266
19068
|
return str2.replace(REGEX_REMOVE_BACKSLASH, (match) => {
|
|
19267
19069
|
return match === "\\" ? "" : match;
|
|
19268
19070
|
});
|
|
19269
19071
|
};
|
|
19270
|
-
|
|
19072
|
+
exports$1.supportsLookbehinds = () => {
|
|
19271
19073
|
const segs = process.version.slice(1).split(".").map(Number);
|
|
19272
19074
|
if (segs.length === 3 && segs[0] >= 9 || segs[0] === 8 && segs[1] >= 10) {
|
|
19273
19075
|
return true;
|
|
19274
19076
|
}
|
|
19275
19077
|
return false;
|
|
19276
19078
|
};
|
|
19277
|
-
|
|
19079
|
+
exports$1.isWindows = (options2) => {
|
|
19278
19080
|
if (options2 && typeof options2.windows === "boolean") {
|
|
19279
19081
|
return options2.windows;
|
|
19280
19082
|
}
|
|
19281
19083
|
return win32 === true || path2.sep === "\\";
|
|
19282
19084
|
};
|
|
19283
|
-
|
|
19085
|
+
exports$1.escapeLast = (input, char, lastIdx) => {
|
|
19284
19086
|
const idx = input.lastIndexOf(char, lastIdx);
|
|
19285
19087
|
if (idx === -1) return input;
|
|
19286
|
-
if (input[idx - 1] === "\\") return
|
|
19088
|
+
if (input[idx - 1] === "\\") return exports$1.escapeLast(input, char, idx - 1);
|
|
19287
19089
|
return `${input.slice(0, idx)}\\${input.slice(idx)}`;
|
|
19288
19090
|
};
|
|
19289
|
-
|
|
19091
|
+
exports$1.removePrefix = (input, state = {}) => {
|
|
19290
19092
|
let output = input;
|
|
19291
19093
|
if (output.startsWith("./")) {
|
|
19292
19094
|
output = output.slice(2);
|
|
@@ -19294,7 +19096,7 @@ var constants$5 = {
|
|
|
19294
19096
|
}
|
|
19295
19097
|
return output;
|
|
19296
19098
|
};
|
|
19297
|
-
|
|
19099
|
+
exports$1.wrapOutput = (input, state = {}, options2 = {}) => {
|
|
19298
19100
|
const prepend = options2.contains ? "" : "^";
|
|
19299
19101
|
const append2 = options2.contains ? "" : "$";
|
|
19300
19102
|
let output = `${prepend}(?:${input})${append2}`;
|
|
@@ -22849,6 +22651,18 @@ function charFromCodepoint(c) {
|
|
|
22849
22651
|
(c - 65536 & 1023) + 56320
|
|
22850
22652
|
);
|
|
22851
22653
|
}
|
|
22654
|
+
function setProperty(object2, key, value) {
|
|
22655
|
+
if (key === "__proto__") {
|
|
22656
|
+
Object.defineProperty(object2, key, {
|
|
22657
|
+
configurable: true,
|
|
22658
|
+
enumerable: true,
|
|
22659
|
+
writable: true,
|
|
22660
|
+
value
|
|
22661
|
+
});
|
|
22662
|
+
} else {
|
|
22663
|
+
object2[key] = value;
|
|
22664
|
+
}
|
|
22665
|
+
}
|
|
22852
22666
|
var simpleEscapeCheck = new Array(256);
|
|
22853
22667
|
var simpleEscapeMap = new Array(256);
|
|
22854
22668
|
for (var i = 0; i < 256; i++) {
|
|
@@ -22955,7 +22769,7 @@ function mergeMappings(state, destination, source, overridableKeys) {
|
|
|
22955
22769
|
for (index2 = 0, quantity = sourceKeys.length; index2 < quantity; index2 += 1) {
|
|
22956
22770
|
key = sourceKeys[index2];
|
|
22957
22771
|
if (!_hasOwnProperty$1.call(destination, key)) {
|
|
22958
|
-
destination
|
|
22772
|
+
setProperty(destination, key, source[key]);
|
|
22959
22773
|
overridableKeys[key] = true;
|
|
22960
22774
|
}
|
|
22961
22775
|
}
|
|
@@ -22994,7 +22808,7 @@ function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valu
|
|
|
22994
22808
|
state.position = startPos || state.position;
|
|
22995
22809
|
throwError(state, "duplicated mapping key");
|
|
22996
22810
|
}
|
|
22997
|
-
_result
|
|
22811
|
+
setProperty(_result, keyNode, valueNode);
|
|
22998
22812
|
delete overridableKeys[keyNode];
|
|
22999
22813
|
}
|
|
23000
22814
|
return _result;
|
|
@@ -28897,6 +28711,247 @@ _enum([
|
|
|
28897
28711
|
"published"
|
|
28898
28712
|
]).describe("Filter by publication status");
|
|
28899
28713
|
string().describe("Search query string");
|
|
28714
|
+
const pluginName = "firebase-authentication";
|
|
28715
|
+
const PLUGIN_NAME = "firebase-authentication";
|
|
28716
|
+
const PLUGIN_UID = `plugin::${PLUGIN_NAME}`;
|
|
28717
|
+
const CONFIG_CONTENT_TYPE = `${PLUGIN_UID}.firebase-authentication-configuration`;
|
|
28718
|
+
const DEFAULT_PASSWORD_RESET_URL = "http://localhost:3000/reset-password";
|
|
28719
|
+
const DEFAULT_PASSWORD_REGEX = "^.{6,}$";
|
|
28720
|
+
const DEFAULT_PASSWORD_MESSAGE = "Password must be at least 6 characters long";
|
|
28721
|
+
const DEFAULT_RESET_EMAIL_SUBJECT = "Reset Your Password";
|
|
28722
|
+
const ERROR_MESSAGES = {
|
|
28723
|
+
FIREBASE_NOT_INITIALIZED: "Firebase is not initialized. Please upload Firebase service account configuration via Settings → Firebase Authentication.",
|
|
28724
|
+
INVALID_JSON: "Invalid JSON format. Please ensure you copied the entire JSON content correctly.",
|
|
28725
|
+
MISSING_DATA: "data is missing",
|
|
28726
|
+
SOMETHING_WENT_WRONG: "Something went wrong",
|
|
28727
|
+
AUTHENTICATION_FAILED: "Authentication failed",
|
|
28728
|
+
TOKEN_MISSING: "idToken is missing!",
|
|
28729
|
+
EMAIL_PASSWORD_REQUIRED: "Email and password are required",
|
|
28730
|
+
PASSWORD_REQUIRED: "Password is required",
|
|
28731
|
+
AUTHORIZATION_REQUIRED: "Authorization token is required",
|
|
28732
|
+
INVALID_TOKEN: "Invalid or expired token",
|
|
28733
|
+
USER_NOT_FOUND: "User not found",
|
|
28734
|
+
USER_NO_EMAIL: "User does not have an email address",
|
|
28735
|
+
FIREBASE_LINK_FAILED: "Failed to generate Firebase reset link",
|
|
28736
|
+
CONFIG_NOT_FOUND: "No config found",
|
|
28737
|
+
INVALID_SERVICE_ACCOUNT: "Invalid service account JSON",
|
|
28738
|
+
WEB_API_NOT_CONFIGURED: "Email/password authentication is not available. Web API Key is not configured.",
|
|
28739
|
+
RESET_URL_NOT_CONFIGURED: "Password reset URL is not configured",
|
|
28740
|
+
RESET_URL_MUST_BE_HTTPS: "Password reset URL must use HTTPS in production",
|
|
28741
|
+
RESET_URL_INVALID_FORMAT: "Password reset URL is not a valid URL format",
|
|
28742
|
+
USER_NOT_LINKED_FIREBASE: "User is not linked to Firebase authentication",
|
|
28743
|
+
OVERRIDE_USER_ID_REQUIRED: "Override user ID is required",
|
|
28744
|
+
EITHER_EMAIL_OR_PHONE_REQUIRED: "Either email or phoneNumber is required",
|
|
28745
|
+
DELETION_NO_CONFIG: "No Firebase configs exists for deletion"
|
|
28746
|
+
};
|
|
28747
|
+
const SUCCESS_MESSAGES = {
|
|
28748
|
+
FIREBASE_INITIALIZED: "Firebase successfully initialized",
|
|
28749
|
+
FIREBASE_CONFIG_DELETED: "Firebase config deleted and reinitialized",
|
|
28750
|
+
PASSWORD_RESET_EMAIL_SENT: "If an account with that email exists, a password reset link has been sent.",
|
|
28751
|
+
SERVER_RESTARTING: "SERVER IS RESTARTING"
|
|
28752
|
+
};
|
|
28753
|
+
const CONFIG_KEYS = {
|
|
28754
|
+
ENCRYPTION_KEY: `${PLUGIN_UID}.FIREBASE_JSON_ENCRYPTION_KEY`
|
|
28755
|
+
};
|
|
28756
|
+
const REQUIRED_FIELDS = {
|
|
28757
|
+
SERVICE_ACCOUNT: ["private_key", "client_email", "project_id", "type"],
|
|
28758
|
+
WEB_CONFIG: ["apiKey", "authDomain"]
|
|
28759
|
+
// These indicate wrong JSON type
|
|
28760
|
+
};
|
|
28761
|
+
const VALIDATION_MESSAGES = {
|
|
28762
|
+
INVALID_SERVICE_ACCOUNT: "Invalid Service Account JSON. Missing required fields:",
|
|
28763
|
+
WRONG_JSON_TYPE: "You uploaded a Web App Config (SDK snippet) instead of a Service Account JSON. Please go to Firebase Console → Service Accounts tab → Generate New Private Key to download the correct file.",
|
|
28764
|
+
SERVICE_ACCOUNT_HELP: "Please download the correct JSON from Firebase Console → Service Accounts → Generate New Private Key. Do NOT use the Web App Config (SDK snippet) - that is a different file!"
|
|
28765
|
+
};
|
|
28766
|
+
const firebaseController = {
|
|
28767
|
+
async validateToken(ctx) {
|
|
28768
|
+
strapi.log.debug("validateToken called");
|
|
28769
|
+
try {
|
|
28770
|
+
const { idToken, profileMetaData } = ctx.request.body || {};
|
|
28771
|
+
const populate2 = ctx.request.query.populate || [];
|
|
28772
|
+
if (!idToken) {
|
|
28773
|
+
return ctx.badRequest(ERROR_MESSAGES.TOKEN_MISSING);
|
|
28774
|
+
}
|
|
28775
|
+
const result = await strapi.plugin(pluginName).service("firebaseService").validateFirebaseToken(idToken, profileMetaData, populate2);
|
|
28776
|
+
ctx.body = result;
|
|
28777
|
+
} catch (error2) {
|
|
28778
|
+
strapi.log.error(`validateToken controller error: ${error2.message}`);
|
|
28779
|
+
if (error2.name === "ValidationError") {
|
|
28780
|
+
return ctx.badRequest(error2.message);
|
|
28781
|
+
}
|
|
28782
|
+
if (error2.name === "UnauthorizedError") {
|
|
28783
|
+
return ctx.unauthorized(error2.message);
|
|
28784
|
+
}
|
|
28785
|
+
throw error2;
|
|
28786
|
+
}
|
|
28787
|
+
},
|
|
28788
|
+
async deleteByEmail(email2) {
|
|
28789
|
+
try {
|
|
28790
|
+
const user = await strapi.firebase.auth().getUserByEmail(email2);
|
|
28791
|
+
await strapi.plugin(pluginName).service("firebaseService").delete(user.toJSON().uid);
|
|
28792
|
+
return user.toJSON();
|
|
28793
|
+
} catch (error2) {
|
|
28794
|
+
strapi.log.error("deleteByEmail error:", error2);
|
|
28795
|
+
throw error2;
|
|
28796
|
+
}
|
|
28797
|
+
},
|
|
28798
|
+
async overrideAccess(ctx) {
|
|
28799
|
+
try {
|
|
28800
|
+
const { overrideUserId } = ctx.request.body || {};
|
|
28801
|
+
const populate2 = ctx.request.query.populate || [];
|
|
28802
|
+
const result = await strapi.plugin(pluginName).service("firebaseService").overrideFirebaseAccess(overrideUserId, populate2);
|
|
28803
|
+
ctx.body = result;
|
|
28804
|
+
} catch (error2) {
|
|
28805
|
+
if (error2.name === "ValidationError") {
|
|
28806
|
+
return ctx.badRequest(error2.message);
|
|
28807
|
+
}
|
|
28808
|
+
if (error2.name === "NotFoundError") {
|
|
28809
|
+
return ctx.notFound(error2.message);
|
|
28810
|
+
}
|
|
28811
|
+
throw error2;
|
|
28812
|
+
}
|
|
28813
|
+
},
|
|
28814
|
+
/**
|
|
28815
|
+
* Controller method for email/password authentication
|
|
28816
|
+
* Handles the `/api/firebase-authentication/emailLogin` endpoint
|
|
28817
|
+
*
|
|
28818
|
+
* @param ctx - Koa context object
|
|
28819
|
+
* @returns Promise that sets ctx.body with user data and JWT or error message
|
|
28820
|
+
*
|
|
28821
|
+
* @remarks
|
|
28822
|
+
* This controller acts as a proxy to Firebase's Identity Toolkit API,
|
|
28823
|
+
* allowing users to authenticate with email/password and receive a Strapi JWT.
|
|
28824
|
+
*
|
|
28825
|
+
* HTTP Status Codes:
|
|
28826
|
+
* - `400`: Validation errors (missing credentials, invalid email/password)
|
|
28827
|
+
* - `500`: Server errors (missing configuration, Firebase API issues)
|
|
28828
|
+
*/
|
|
28829
|
+
async emailLogin(ctx) {
|
|
28830
|
+
strapi.log.debug("emailLogin controller");
|
|
28831
|
+
try {
|
|
28832
|
+
const { email: email2, password } = ctx.request.body || {};
|
|
28833
|
+
const populate2 = ctx.request.query.populate || [];
|
|
28834
|
+
const result = await strapi.plugin(pluginName).service("firebaseService").emailLogin(email2, password, populate2);
|
|
28835
|
+
ctx.body = result;
|
|
28836
|
+
} catch (error2) {
|
|
28837
|
+
strapi.log.error("emailLogin controller error:", error2);
|
|
28838
|
+
throw error2;
|
|
28839
|
+
}
|
|
28840
|
+
},
|
|
28841
|
+
/**
|
|
28842
|
+
* Forgot password - sends reset email
|
|
28843
|
+
* POST /api/firebase-authentication/forgotPassword
|
|
28844
|
+
* Public endpoint - no authentication required
|
|
28845
|
+
*/
|
|
28846
|
+
async forgotPassword(ctx) {
|
|
28847
|
+
strapi.log.debug("forgotPassword endpoint called");
|
|
28848
|
+
try {
|
|
28849
|
+
const { email: email2 } = ctx.request.body || {};
|
|
28850
|
+
ctx.body = await strapi.plugin(pluginName).service("firebaseService").forgotPassword(email2);
|
|
28851
|
+
} catch (error2) {
|
|
28852
|
+
strapi.log.error("forgotPassword controller error:", error2);
|
|
28853
|
+
throw error2;
|
|
28854
|
+
}
|
|
28855
|
+
},
|
|
28856
|
+
/**
|
|
28857
|
+
* Reset password - authenticated password change
|
|
28858
|
+
* POST /api/firebase-authentication/resetPassword
|
|
28859
|
+
* Authenticated endpoint - requires valid JWT (enforced by is-authenticated policy)
|
|
28860
|
+
* Used for admin-initiated resets or user self-service password changes
|
|
28861
|
+
* NOT used for forgot password email flow (which uses Firebase's hosted UI)
|
|
28862
|
+
*/
|
|
28863
|
+
async resetPassword(ctx) {
|
|
28864
|
+
strapi.log.debug("resetPassword endpoint called");
|
|
28865
|
+
try {
|
|
28866
|
+
const { password } = ctx.request.body || {};
|
|
28867
|
+
const user = ctx.state.user;
|
|
28868
|
+
const populate2 = ctx.request.query.populate || [];
|
|
28869
|
+
ctx.body = await strapi.plugin(pluginName).service("firebaseService").resetPassword(password, user, populate2);
|
|
28870
|
+
} catch (error2) {
|
|
28871
|
+
strapi.log.error("resetPassword controller error:", error2);
|
|
28872
|
+
throw error2;
|
|
28873
|
+
}
|
|
28874
|
+
},
|
|
28875
|
+
async requestMagicLink(ctx) {
|
|
28876
|
+
try {
|
|
28877
|
+
const { email: email2 } = ctx.request.body || {};
|
|
28878
|
+
const result = await strapi.plugin("firebase-authentication").service("firebaseService").requestMagicLink(email2);
|
|
28879
|
+
ctx.body = result;
|
|
28880
|
+
} catch (error2) {
|
|
28881
|
+
if (error2.name === "ValidationError" || error2.name === "ApplicationError") {
|
|
28882
|
+
throw error2;
|
|
28883
|
+
}
|
|
28884
|
+
strapi.log.error("requestMagicLink controller error:", error2);
|
|
28885
|
+
ctx.body = {
|
|
28886
|
+
success: false,
|
|
28887
|
+
message: "An error occurred while processing your request"
|
|
28888
|
+
};
|
|
28889
|
+
}
|
|
28890
|
+
},
|
|
28891
|
+
/**
|
|
28892
|
+
* Reset password using custom JWT token
|
|
28893
|
+
* POST /api/firebase-authentication/resetPasswordWithToken
|
|
28894
|
+
* Public endpoint - token provides authentication
|
|
28895
|
+
*
|
|
28896
|
+
* @param ctx - Koa context with { token, newPassword } in body
|
|
28897
|
+
* @returns { success: true, message: "Password has been reset successfully" }
|
|
28898
|
+
*/
|
|
28899
|
+
async resetPasswordWithToken(ctx) {
|
|
28900
|
+
strapi.log.debug("resetPasswordWithToken endpoint called");
|
|
28901
|
+
try {
|
|
28902
|
+
const { token, newPassword } = ctx.request.body || {};
|
|
28903
|
+
if (!token) {
|
|
28904
|
+
throw new ValidationError$1("Token is required");
|
|
28905
|
+
}
|
|
28906
|
+
if (!newPassword) {
|
|
28907
|
+
throw new ValidationError$1("New password is required");
|
|
28908
|
+
}
|
|
28909
|
+
const result = await strapi.plugin(pluginName).service("userService").resetPasswordWithToken(token, newPassword);
|
|
28910
|
+
ctx.body = result;
|
|
28911
|
+
} catch (error2) {
|
|
28912
|
+
strapi.log.error("resetPasswordWithToken controller error:", error2);
|
|
28913
|
+
throw error2;
|
|
28914
|
+
}
|
|
28915
|
+
},
|
|
28916
|
+
/**
|
|
28917
|
+
* Send email verification - public endpoint
|
|
28918
|
+
* POST /api/firebase-authentication/sendVerificationEmail
|
|
28919
|
+
* Authenticated endpoint - sends verification email to the logged-in user's email
|
|
28920
|
+
*/
|
|
28921
|
+
async sendVerificationEmail(ctx) {
|
|
28922
|
+
strapi.log.debug("sendVerificationEmail endpoint called");
|
|
28923
|
+
try {
|
|
28924
|
+
const user = ctx.state.user;
|
|
28925
|
+
const email2 = user.email;
|
|
28926
|
+
ctx.body = await strapi.plugin(pluginName).service("firebaseService").sendVerificationEmail(email2);
|
|
28927
|
+
} catch (error2) {
|
|
28928
|
+
strapi.log.error("sendVerificationEmail controller error:", error2);
|
|
28929
|
+
throw error2;
|
|
28930
|
+
}
|
|
28931
|
+
},
|
|
28932
|
+
/**
|
|
28933
|
+
* Verify email using custom JWT token
|
|
28934
|
+
* POST /api/firebase-authentication/verifyEmail
|
|
28935
|
+
* Public endpoint - token provides authentication
|
|
28936
|
+
*
|
|
28937
|
+
* @param ctx - Koa context with { token } in body
|
|
28938
|
+
* @returns { success: true, message: "Email verified successfully" }
|
|
28939
|
+
*/
|
|
28940
|
+
async verifyEmail(ctx) {
|
|
28941
|
+
strapi.log.debug("verifyEmail endpoint called");
|
|
28942
|
+
try {
|
|
28943
|
+
const { token } = ctx.request.body || {};
|
|
28944
|
+
if (!token) {
|
|
28945
|
+
throw new ValidationError$1("Token is required");
|
|
28946
|
+
}
|
|
28947
|
+
const result = await strapi.plugin(pluginName).service("firebaseService").verifyEmail(token);
|
|
28948
|
+
ctx.body = result;
|
|
28949
|
+
} catch (error2) {
|
|
28950
|
+
strapi.log.error("verifyEmail controller error:", error2);
|
|
28951
|
+
throw error2;
|
|
28952
|
+
}
|
|
28953
|
+
}
|
|
28954
|
+
};
|
|
28900
28955
|
const STRAPI_DESTINATION = "strapi";
|
|
28901
28956
|
const FIREBASE_DESTINATION = "firebase";
|
|
28902
28957
|
const userController = {
|
|
@@ -28982,6 +29037,17 @@ const userController = {
|
|
|
28982
29037
|
} catch (error2) {
|
|
28983
29038
|
throw new ApplicationError$2(error2.message || "Failed to send password reset email");
|
|
28984
29039
|
}
|
|
29040
|
+
},
|
|
29041
|
+
sendVerificationEmail: async (ctx) => {
|
|
29042
|
+
const userId = ctx.params.id;
|
|
29043
|
+
if (!userId) {
|
|
29044
|
+
throw new ValidationError$1("User ID is required");
|
|
29045
|
+
}
|
|
29046
|
+
try {
|
|
29047
|
+
ctx.body = await strapi.plugin("firebase-authentication").service("userService").sendVerificationEmail(userId);
|
|
29048
|
+
} catch (error2) {
|
|
29049
|
+
throw new ApplicationError$2(error2.message || "Failed to send verification email");
|
|
29050
|
+
}
|
|
28985
29051
|
}
|
|
28986
29052
|
};
|
|
28987
29053
|
const settingsController = {
|
|
@@ -29084,7 +29150,9 @@ const settingsController = {
|
|
|
29084
29150
|
enableMagicLink = false,
|
|
29085
29151
|
magicLinkUrl = "http://localhost:1338/verify-magic-link.html",
|
|
29086
29152
|
magicLinkEmailSubject = "Sign in to Your Application",
|
|
29087
|
-
magicLinkExpiryHours = 1
|
|
29153
|
+
magicLinkExpiryHours = 1,
|
|
29154
|
+
emailVerificationUrl = "http://localhost:3000/verify-email",
|
|
29155
|
+
emailVerificationEmailSubject = "Verify Your Email"
|
|
29088
29156
|
} = requestBody;
|
|
29089
29157
|
const existingConfig = await strapi.db.query("plugin::firebase-authentication.firebase-authentication-configuration").findOne({ where: {} });
|
|
29090
29158
|
let result;
|
|
@@ -29098,7 +29166,9 @@ const settingsController = {
|
|
|
29098
29166
|
enableMagicLink,
|
|
29099
29167
|
magicLinkUrl,
|
|
29100
29168
|
magicLinkEmailSubject,
|
|
29101
|
-
magicLinkExpiryHours
|
|
29169
|
+
magicLinkExpiryHours,
|
|
29170
|
+
emailVerificationUrl,
|
|
29171
|
+
emailVerificationEmailSubject
|
|
29102
29172
|
}
|
|
29103
29173
|
});
|
|
29104
29174
|
} else {
|
|
@@ -29112,7 +29182,9 @@ const settingsController = {
|
|
|
29112
29182
|
enableMagicLink,
|
|
29113
29183
|
magicLinkUrl,
|
|
29114
29184
|
magicLinkEmailSubject,
|
|
29115
|
-
magicLinkExpiryHours
|
|
29185
|
+
magicLinkExpiryHours,
|
|
29186
|
+
emailVerificationUrl,
|
|
29187
|
+
emailVerificationEmailSubject
|
|
29116
29188
|
}
|
|
29117
29189
|
});
|
|
29118
29190
|
}
|
|
@@ -29124,7 +29196,9 @@ const settingsController = {
|
|
|
29124
29196
|
enableMagicLink: result.enableMagicLink,
|
|
29125
29197
|
magicLinkUrl: result.magicLinkUrl,
|
|
29126
29198
|
magicLinkEmailSubject: result.magicLinkEmailSubject,
|
|
29127
|
-
magicLinkExpiryHours: result.magicLinkExpiryHours
|
|
29199
|
+
magicLinkExpiryHours: result.magicLinkExpiryHours,
|
|
29200
|
+
emailVerificationUrl: result.emailVerificationUrl,
|
|
29201
|
+
emailVerificationEmailSubject: result.emailVerificationEmailSubject
|
|
29128
29202
|
};
|
|
29129
29203
|
} catch (error2) {
|
|
29130
29204
|
throw new ApplicationError$2("Error saving password configuration", {
|
|
@@ -29139,7 +29213,16 @@ const controllers = {
|
|
|
29139
29213
|
settingsController
|
|
29140
29214
|
};
|
|
29141
29215
|
const middlewares = {};
|
|
29142
|
-
const
|
|
29216
|
+
const isAuthenticated = async (policyContext) => {
|
|
29217
|
+
const user = policyContext.state.user;
|
|
29218
|
+
if (!user) {
|
|
29219
|
+
throw new UnauthorizedError("Authentication required");
|
|
29220
|
+
}
|
|
29221
|
+
return true;
|
|
29222
|
+
};
|
|
29223
|
+
const policies = {
|
|
29224
|
+
"is-authenticated": isAuthenticated
|
|
29225
|
+
};
|
|
29143
29226
|
const settingsRoute = [
|
|
29144
29227
|
{
|
|
29145
29228
|
method: "POST",
|
|
@@ -29217,6 +29300,14 @@ const admin = {
|
|
|
29217
29300
|
policies: ["admin::isAuthenticatedAdmin"]
|
|
29218
29301
|
}
|
|
29219
29302
|
},
|
|
29303
|
+
{
|
|
29304
|
+
method: "PUT",
|
|
29305
|
+
path: "/users/sendVerificationEmail/:id",
|
|
29306
|
+
handler: "userController.sendVerificationEmail",
|
|
29307
|
+
config: {
|
|
29308
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
29309
|
+
}
|
|
29310
|
+
},
|
|
29220
29311
|
{
|
|
29221
29312
|
method: "GET",
|
|
29222
29313
|
path: "/users/:id",
|
|
@@ -29281,9 +29372,7 @@ const contentApi = {
|
|
|
29281
29372
|
path: "/resetPassword",
|
|
29282
29373
|
handler: "firebaseController.resetPassword",
|
|
29283
29374
|
config: {
|
|
29284
|
-
|
|
29285
|
-
// Public endpoint - authenticated password change, requires valid JWT in Authorization header
|
|
29286
|
-
policies: []
|
|
29375
|
+
policies: ["plugin::firebase-authentication.is-authenticated"]
|
|
29287
29376
|
}
|
|
29288
29377
|
},
|
|
29289
29378
|
{
|
|
@@ -29315,6 +29404,24 @@ const contentApi = {
|
|
|
29315
29404
|
// Public endpoint - token provides authentication
|
|
29316
29405
|
policies: []
|
|
29317
29406
|
}
|
|
29407
|
+
},
|
|
29408
|
+
{
|
|
29409
|
+
method: "POST",
|
|
29410
|
+
path: "/sendVerificationEmail",
|
|
29411
|
+
handler: "firebaseController.sendVerificationEmail",
|
|
29412
|
+
config: {
|
|
29413
|
+
policies: ["plugin::firebase-authentication.is-authenticated"]
|
|
29414
|
+
}
|
|
29415
|
+
},
|
|
29416
|
+
{
|
|
29417
|
+
method: "POST",
|
|
29418
|
+
path: "/verifyEmail",
|
|
29419
|
+
handler: "firebaseController.verifyEmail",
|
|
29420
|
+
config: {
|
|
29421
|
+
auth: false,
|
|
29422
|
+
// Public endpoint - token provides authentication
|
|
29423
|
+
policies: []
|
|
29424
|
+
}
|
|
29318
29425
|
}
|
|
29319
29426
|
]
|
|
29320
29427
|
};
|
|
@@ -29435,7 +29542,10 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29435
29542
|
enableMagicLink: configObject.enableMagicLink || false,
|
|
29436
29543
|
magicLinkUrl: configObject.magicLinkUrl || "http://localhost:1338/verify-magic-link.html",
|
|
29437
29544
|
magicLinkEmailSubject: configObject.magicLinkEmailSubject || "Sign in to Your Application",
|
|
29438
|
-
magicLinkExpiryHours: configObject.magicLinkExpiryHours || 1
|
|
29545
|
+
magicLinkExpiryHours: configObject.magicLinkExpiryHours || 1,
|
|
29546
|
+
// Include email verification configuration fields
|
|
29547
|
+
emailVerificationUrl: configObject.emailVerificationUrl || "http://localhost:3000/verify-email",
|
|
29548
|
+
emailVerificationEmailSubject: configObject.emailVerificationEmailSubject || "Verify Your Email"
|
|
29439
29549
|
};
|
|
29440
29550
|
} catch (error2) {
|
|
29441
29551
|
strapi2.log.error(`Firebase config error: ${error2.message}`);
|
|
@@ -29478,7 +29588,9 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29478
29588
|
enableMagicLink = false,
|
|
29479
29589
|
magicLinkUrl = "http://localhost:1338/verify-magic-link.html",
|
|
29480
29590
|
magicLinkEmailSubject = "Sign in to Your Application",
|
|
29481
|
-
magicLinkExpiryHours = 1
|
|
29591
|
+
magicLinkExpiryHours = 1,
|
|
29592
|
+
emailVerificationUrl = "http://localhost:3000/verify-email",
|
|
29593
|
+
emailVerificationEmailSubject = "Verify Your Email"
|
|
29482
29594
|
} = requestBody;
|
|
29483
29595
|
if (!requestBody) throw new ValidationError3(ERROR_MESSAGES.MISSING_DATA);
|
|
29484
29596
|
try {
|
|
@@ -29514,7 +29626,9 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29514
29626
|
enableMagicLink,
|
|
29515
29627
|
magicLinkUrl,
|
|
29516
29628
|
magicLinkEmailSubject,
|
|
29517
|
-
magicLinkExpiryHours
|
|
29629
|
+
magicLinkExpiryHours,
|
|
29630
|
+
emailVerificationUrl,
|
|
29631
|
+
emailVerificationEmailSubject
|
|
29518
29632
|
}
|
|
29519
29633
|
});
|
|
29520
29634
|
} else {
|
|
@@ -29530,11 +29644,20 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29530
29644
|
enableMagicLink,
|
|
29531
29645
|
magicLinkUrl,
|
|
29532
29646
|
magicLinkEmailSubject,
|
|
29533
|
-
magicLinkExpiryHours
|
|
29647
|
+
magicLinkExpiryHours,
|
|
29648
|
+
emailVerificationUrl,
|
|
29649
|
+
emailVerificationEmailSubject
|
|
29534
29650
|
}
|
|
29535
29651
|
});
|
|
29536
29652
|
}
|
|
29537
29653
|
await strapi2.plugin("firebase-authentication").service("settingsService").init();
|
|
29654
|
+
setImmediate(async () => {
|
|
29655
|
+
try {
|
|
29656
|
+
await strapi2.plugin("firebase-authentication").service("autoLinkService").linkAllUsers(strapi2);
|
|
29657
|
+
} catch (error2) {
|
|
29658
|
+
strapi2.log.error(`Auto-linking after config save failed: ${error2.message}`);
|
|
29659
|
+
}
|
|
29660
|
+
});
|
|
29538
29661
|
const configData = res.firebaseConfigJson || res.firebase_config_json;
|
|
29539
29662
|
if (!configData) {
|
|
29540
29663
|
strapi2.log.error("Firebase config data missing from database response");
|
|
@@ -29558,6 +29681,8 @@ const settingsService = ({ strapi: strapi2 }) => {
|
|
|
29558
29681
|
res.magicLinkUrl = res.magicLinkUrl || magicLinkUrl;
|
|
29559
29682
|
res.magicLinkEmailSubject = res.magicLinkEmailSubject || magicLinkEmailSubject;
|
|
29560
29683
|
res.magicLinkExpiryHours = res.magicLinkExpiryHours || magicLinkExpiryHours;
|
|
29684
|
+
res.emailVerificationUrl = res.emailVerificationUrl || emailVerificationUrl;
|
|
29685
|
+
res.emailVerificationEmailSubject = res.emailVerificationEmailSubject || emailVerificationEmailSubject;
|
|
29561
29686
|
return res;
|
|
29562
29687
|
} catch (error2) {
|
|
29563
29688
|
strapi2.log.error("=== FIREBASE CONFIG SAVE ERROR ===");
|
|
@@ -30217,6 +30342,40 @@ const userService = ({ strapi: strapi2 }) => {
|
|
|
30217
30342
|
}
|
|
30218
30343
|
throw new ApplicationError$2(e.message?.toString() || "Failed to reset password");
|
|
30219
30344
|
}
|
|
30345
|
+
},
|
|
30346
|
+
/**
|
|
30347
|
+
* Send email verification email (admin-initiated)
|
|
30348
|
+
* @param entityId - Firebase UID of the user
|
|
30349
|
+
*/
|
|
30350
|
+
sendVerificationEmail: async (entityId) => {
|
|
30351
|
+
try {
|
|
30352
|
+
ensureFirebaseInitialized();
|
|
30353
|
+
const user = await strapi2.firebase.auth().getUser(entityId);
|
|
30354
|
+
if (!user.email) {
|
|
30355
|
+
throw new ApplicationError$2("User does not have an email address");
|
|
30356
|
+
}
|
|
30357
|
+
if (user.emailVerified) {
|
|
30358
|
+
return { success: true, message: "Email is already verified" };
|
|
30359
|
+
}
|
|
30360
|
+
const config2 = await strapi2.db.query("plugin::firebase-authentication.firebase-authentication-configuration").findOne({ where: {} });
|
|
30361
|
+
const emailVerificationUrl = config2?.emailVerificationUrl;
|
|
30362
|
+
if (!emailVerificationUrl) {
|
|
30363
|
+
throw new ApplicationError$2("Email verification URL is not configured");
|
|
30364
|
+
}
|
|
30365
|
+
const firebaseUserData2 = await strapi2.plugin("firebase-authentication").service("firebaseUserDataService").getByFirebaseUID(entityId);
|
|
30366
|
+
if (!firebaseUserData2) {
|
|
30367
|
+
throw new ApplicationError$2("User is not linked to Firebase authentication");
|
|
30368
|
+
}
|
|
30369
|
+
const tokenService2 = strapi2.plugin("firebase-authentication").service("tokenService");
|
|
30370
|
+
const token = await tokenService2.generateVerificationToken(firebaseUserData2.documentId, user.email);
|
|
30371
|
+
const verificationLink = `${emailVerificationUrl}?token=${token}`;
|
|
30372
|
+
strapi2.log.debug(`Generated email verification link for user ${user.email}`);
|
|
30373
|
+
const emailService2 = strapi2.plugin("firebase-authentication").service("emailService");
|
|
30374
|
+
return await emailService2.sendVerificationEmail(user, verificationLink);
|
|
30375
|
+
} catch (e) {
|
|
30376
|
+
strapi2.log.error(`sendVerificationEmail error: ${e.message}`);
|
|
30377
|
+
throw new ApplicationError$2(e.message?.toString() || "Failed to send verification email");
|
|
30378
|
+
}
|
|
30220
30379
|
}
|
|
30221
30380
|
};
|
|
30222
30381
|
};
|
|
@@ -30760,20 +30919,17 @@ const firebaseService = ({ strapi: strapi2 }) => ({
|
|
|
30760
30919
|
* 2. User-initiated password change (when already authenticated)
|
|
30761
30920
|
*
|
|
30762
30921
|
* NOT used for forgot password email flow - that now uses Firebase's hosted UI
|
|
30922
|
+
*
|
|
30923
|
+
* @param password - New password to set
|
|
30924
|
+
* @param user - Authenticated user from ctx.state.user (populated by is-authenticated policy)
|
|
30925
|
+
* @param populate - Fields to populate in response
|
|
30763
30926
|
*/
|
|
30764
|
-
resetPassword: async (password,
|
|
30927
|
+
resetPassword: async (password, user, populate2) => {
|
|
30765
30928
|
if (!password) {
|
|
30766
30929
|
throw new ValidationError$1("Password is required");
|
|
30767
30930
|
}
|
|
30768
|
-
if (!
|
|
30769
|
-
throw new UnauthorizedError("
|
|
30770
|
-
}
|
|
30771
|
-
let decoded;
|
|
30772
|
-
try {
|
|
30773
|
-
const jwtService = strapi2.plugin("users-permissions").service("jwt");
|
|
30774
|
-
decoded = await jwtService.verify(token);
|
|
30775
|
-
} catch (error2) {
|
|
30776
|
-
throw new UnauthorizedError("Invalid or expired token");
|
|
30931
|
+
if (!user || !user.id) {
|
|
30932
|
+
throw new UnauthorizedError("Authentication required");
|
|
30777
30933
|
}
|
|
30778
30934
|
const config2 = await strapi2.plugin("firebase-authentication").service("settingsService").getFirebaseConfigJson();
|
|
30779
30935
|
const passwordRegex = config2?.passwordRequirementsRegex || "^.{6,}$";
|
|
@@ -30784,8 +30940,7 @@ const firebaseService = ({ strapi: strapi2 }) => ({
|
|
|
30784
30940
|
}
|
|
30785
30941
|
try {
|
|
30786
30942
|
const strapiUser = await strapi2.db.query("plugin::users-permissions.user").findOne({
|
|
30787
|
-
where: { id:
|
|
30788
|
-
// Use numeric id from JWT
|
|
30943
|
+
where: { id: user.id }
|
|
30789
30944
|
});
|
|
30790
30945
|
if (!strapiUser) {
|
|
30791
30946
|
throw new NotFoundError("User not found");
|
|
@@ -30896,6 +31051,159 @@ const firebaseService = ({ strapi: strapi2 }) => ({
|
|
|
30896
31051
|
verificationUrl: magicLinkUrl
|
|
30897
31052
|
};
|
|
30898
31053
|
}
|
|
31054
|
+
},
|
|
31055
|
+
/**
|
|
31056
|
+
* Send email verification - public endpoint
|
|
31057
|
+
* Generates a verification token and sends an email to the user
|
|
31058
|
+
* Security: Always returns generic success message to prevent email enumeration
|
|
31059
|
+
*/
|
|
31060
|
+
async sendVerificationEmail(email2) {
|
|
31061
|
+
strapi2.log.info(`[sendVerificationEmail] Starting email verification for: ${email2}`);
|
|
31062
|
+
if (!email2) {
|
|
31063
|
+
throw new ValidationError$1("Email is required");
|
|
31064
|
+
}
|
|
31065
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
31066
|
+
if (!emailRegex.test(email2)) {
|
|
31067
|
+
throw new ValidationError$1("Invalid email format");
|
|
31068
|
+
}
|
|
31069
|
+
const config2 = await strapi2.plugin("firebase-authentication").service("settingsService").getFirebaseConfigJson();
|
|
31070
|
+
const verificationUrl = config2?.emailVerificationUrl;
|
|
31071
|
+
if (!verificationUrl) {
|
|
31072
|
+
throw new ApplicationError$2("Email verification URL is not configured");
|
|
31073
|
+
}
|
|
31074
|
+
if (process.env.NODE_ENV === "production" && !verificationUrl.startsWith("https://")) {
|
|
31075
|
+
throw new ApplicationError$2("Email verification URL must use HTTPS in production");
|
|
31076
|
+
}
|
|
31077
|
+
try {
|
|
31078
|
+
new URL(verificationUrl);
|
|
31079
|
+
} catch (error2) {
|
|
31080
|
+
throw new ApplicationError$2("Email verification URL is not a valid URL format");
|
|
31081
|
+
}
|
|
31082
|
+
try {
|
|
31083
|
+
let firebaseUser;
|
|
31084
|
+
try {
|
|
31085
|
+
firebaseUser = await strapi2.firebase.auth().getUserByEmail(email2);
|
|
31086
|
+
} catch (fbError) {
|
|
31087
|
+
strapi2.log.debug("User not found in Firebase");
|
|
31088
|
+
}
|
|
31089
|
+
if (!firebaseUser) {
|
|
31090
|
+
strapi2.log.warn(`⚠️ [sendVerificationEmail] User not found in Firebase for email: ${email2}`);
|
|
31091
|
+
return { message: "If an account with that email exists, a verification link has been sent." };
|
|
31092
|
+
}
|
|
31093
|
+
if (firebaseUser.emailVerified) {
|
|
31094
|
+
strapi2.log.info(`[sendVerificationEmail] User ${email2} is already verified`);
|
|
31095
|
+
return { message: "Email is already verified." };
|
|
31096
|
+
}
|
|
31097
|
+
const firebaseData = await strapi2.plugin("firebase-authentication").service("firebaseUserDataService").getByFirebaseUID(firebaseUser.uid);
|
|
31098
|
+
if (!firebaseData) {
|
|
31099
|
+
strapi2.log.warn(`⚠️ [sendVerificationEmail] No firebase-user-data record for: ${email2}`);
|
|
31100
|
+
return { message: "If an account with that email exists, a verification link has been sent." };
|
|
31101
|
+
}
|
|
31102
|
+
strapi2.log.info(
|
|
31103
|
+
`✅ [sendVerificationEmail] User found: ${JSON.stringify({
|
|
31104
|
+
firebaseUID: firebaseUser.uid,
|
|
31105
|
+
email: firebaseUser.email,
|
|
31106
|
+
emailVerified: firebaseUser.emailVerified
|
|
31107
|
+
})}`
|
|
31108
|
+
);
|
|
31109
|
+
const tokenService2 = strapi2.plugin("firebase-authentication").service("tokenService");
|
|
31110
|
+
const token = await tokenService2.generateVerificationToken(firebaseData.documentId, email2);
|
|
31111
|
+
const verificationLink = `${verificationUrl}?token=${token}`;
|
|
31112
|
+
strapi2.log.info(`✅ [sendVerificationEmail] Verification link generated for ${email2}`);
|
|
31113
|
+
strapi2.log.info(`[sendVerificationEmail] Attempting to send verification email to: ${email2}`);
|
|
31114
|
+
await strapi2.plugin("firebase-authentication").service("emailService").sendVerificationEmail(firebaseUser, verificationLink);
|
|
31115
|
+
strapi2.log.info(`✅ [sendVerificationEmail] Verification email sent successfully to: ${email2}`);
|
|
31116
|
+
return {
|
|
31117
|
+
message: "If an account with that email exists, a verification link has been sent."
|
|
31118
|
+
};
|
|
31119
|
+
} catch (error2) {
|
|
31120
|
+
strapi2.log.error(
|
|
31121
|
+
`❌ [sendVerificationEmail] ERROR: ${JSON.stringify({
|
|
31122
|
+
email: email2,
|
|
31123
|
+
message: error2.message,
|
|
31124
|
+
code: error2.code,
|
|
31125
|
+
name: error2.name,
|
|
31126
|
+
stack: error2.stack
|
|
31127
|
+
})}`
|
|
31128
|
+
);
|
|
31129
|
+
return {
|
|
31130
|
+
message: "If an account with that email exists, a verification link has been sent."
|
|
31131
|
+
};
|
|
31132
|
+
}
|
|
31133
|
+
},
|
|
31134
|
+
/**
|
|
31135
|
+
* Verify email with token - public endpoint
|
|
31136
|
+
* Validates the token and marks the user's email as verified in Firebase
|
|
31137
|
+
*/
|
|
31138
|
+
async verifyEmail(token) {
|
|
31139
|
+
strapi2.log.info(`[verifyEmail] Starting email verification with token`);
|
|
31140
|
+
if (!token) {
|
|
31141
|
+
throw new ValidationError$1("Verification token is required");
|
|
31142
|
+
}
|
|
31143
|
+
const tokenService2 = strapi2.plugin("firebase-authentication").service("tokenService");
|
|
31144
|
+
const validationResult = await tokenService2.validateVerificationToken(token);
|
|
31145
|
+
if (!validationResult.valid) {
|
|
31146
|
+
strapi2.log.warn(`[verifyEmail] Token validation failed: ${validationResult.error}`);
|
|
31147
|
+
throw new ValidationError$1(validationResult.error || "Invalid verification link");
|
|
31148
|
+
}
|
|
31149
|
+
const { firebaseUID, firebaseUserDataDocumentId, email: tokenEmail } = validationResult;
|
|
31150
|
+
try {
|
|
31151
|
+
const firebaseUser = await strapi2.firebase.auth().getUser(firebaseUID);
|
|
31152
|
+
if (tokenEmail && firebaseUser.email !== tokenEmail) {
|
|
31153
|
+
strapi2.log.warn(
|
|
31154
|
+
`[verifyEmail] Email changed: token email ${tokenEmail} != current email ${firebaseUser.email}`
|
|
31155
|
+
);
|
|
31156
|
+
await tokenService2.invalidateVerificationToken(firebaseUserDataDocumentId);
|
|
31157
|
+
throw new ValidationError$1(
|
|
31158
|
+
"Email address has changed since verification was requested. Please request a new verification link."
|
|
31159
|
+
);
|
|
31160
|
+
}
|
|
31161
|
+
if (firebaseUser.emailVerified) {
|
|
31162
|
+
strapi2.log.info(`[verifyEmail] User ${firebaseUser.email} is already verified`);
|
|
31163
|
+
await tokenService2.invalidateVerificationToken(firebaseUserDataDocumentId);
|
|
31164
|
+
return {
|
|
31165
|
+
success: true,
|
|
31166
|
+
message: "Email is already verified."
|
|
31167
|
+
};
|
|
31168
|
+
}
|
|
31169
|
+
await strapi2.firebase.auth().updateUser(firebaseUID, {
|
|
31170
|
+
emailVerified: true
|
|
31171
|
+
});
|
|
31172
|
+
try {
|
|
31173
|
+
const firebaseUserDataService2 = strapi2.plugin("firebase-authentication").service("firebaseUserDataService");
|
|
31174
|
+
const firebaseUserData2 = await firebaseUserDataService2.getByFirebaseUID(firebaseUID);
|
|
31175
|
+
if (firebaseUserData2?.user?.documentId) {
|
|
31176
|
+
await strapi2.db.query("plugin::users-permissions.user").update({
|
|
31177
|
+
where: { documentId: firebaseUserData2.user.documentId },
|
|
31178
|
+
data: { confirmed: true }
|
|
31179
|
+
});
|
|
31180
|
+
strapi2.log.info(`✅ [verifyEmail] Strapi user confirmed for: ${firebaseUserData2.user.documentId}`);
|
|
31181
|
+
}
|
|
31182
|
+
} catch (strapiUpdateError) {
|
|
31183
|
+
strapi2.log.warn(
|
|
31184
|
+
`[verifyEmail] Failed to update Strapi user confirmed status: ${strapiUpdateError.message}`
|
|
31185
|
+
);
|
|
31186
|
+
}
|
|
31187
|
+
strapi2.log.info(`✅ [verifyEmail] Email verified successfully for: ${firebaseUser.email}`);
|
|
31188
|
+
await tokenService2.invalidateVerificationToken(firebaseUserDataDocumentId);
|
|
31189
|
+
return {
|
|
31190
|
+
success: true,
|
|
31191
|
+
message: "Email verified successfully."
|
|
31192
|
+
};
|
|
31193
|
+
} catch (error2) {
|
|
31194
|
+
strapi2.log.error(
|
|
31195
|
+
`❌ [verifyEmail] ERROR: ${JSON.stringify({
|
|
31196
|
+
firebaseUID,
|
|
31197
|
+
message: error2.message,
|
|
31198
|
+
code: error2.code,
|
|
31199
|
+
name: error2.name
|
|
31200
|
+
})}`
|
|
31201
|
+
);
|
|
31202
|
+
if (error2 instanceof ValidationError$1) {
|
|
31203
|
+
throw error2;
|
|
31204
|
+
}
|
|
31205
|
+
throw new ApplicationError$2("Failed to verify email. Please try again.");
|
|
31206
|
+
}
|
|
30899
31207
|
}
|
|
30900
31208
|
});
|
|
30901
31209
|
const passwordResetTemplate = {
|
|
@@ -31275,13 +31583,144 @@ The <%= appName %> Team
|
|
|
31275
31583
|
Need help? Contact us at <%= supportEmail %>
|
|
31276
31584
|
<% } %>
|
|
31277
31585
|
|
|
31586
|
+
© <%= year %> <%= appName %>. All rights reserved.
|
|
31587
|
+
`.trim()
|
|
31588
|
+
};
|
|
31589
|
+
const emailVerificationTemplate = {
|
|
31590
|
+
subject: "Verify Your Email - <%= appName %>",
|
|
31591
|
+
html: `
|
|
31592
|
+
<!DOCTYPE html>
|
|
31593
|
+
<html lang="en">
|
|
31594
|
+
<head>
|
|
31595
|
+
<meta charset="UTF-8">
|
|
31596
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
31597
|
+
<title>Verify Your Email</title>
|
|
31598
|
+
</head>
|
|
31599
|
+
<body style="margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f4f4f4;">
|
|
31600
|
+
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;">
|
|
31601
|
+
<tr>
|
|
31602
|
+
<td align="center" style="padding: 40px 0;">
|
|
31603
|
+
<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);">
|
|
31604
|
+
<!-- Header -->
|
|
31605
|
+
<tr>
|
|
31606
|
+
<td style="padding: 40px 40px 20px 40px; text-align: center;">
|
|
31607
|
+
<h1 style="margin: 0; font-size: 28px; color: #333333; font-weight: 600;">
|
|
31608
|
+
Verify Your Email Address
|
|
31609
|
+
</h1>
|
|
31610
|
+
</td>
|
|
31611
|
+
</tr>
|
|
31612
|
+
|
|
31613
|
+
<!-- Content -->
|
|
31614
|
+
<tr>
|
|
31615
|
+
<td style="padding: 20px 40px;">
|
|
31616
|
+
<p style="margin: 0 0 20px 0; font-size: 16px; line-height: 1.6; color: #555555;">
|
|
31617
|
+
Hello<% if (user.firstName) { %> <%= user.firstName %><% } %>,
|
|
31618
|
+
</p>
|
|
31619
|
+
|
|
31620
|
+
<p style="margin: 0 0 20px 0; font-size: 16px; line-height: 1.6; color: #555555;">
|
|
31621
|
+
Thank you for signing up with <strong><%= appName %></strong>! Please verify your email address <strong><%= user.email %></strong> to complete your registration.
|
|
31622
|
+
</p>
|
|
31623
|
+
|
|
31624
|
+
<p style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #555555;">
|
|
31625
|
+
Click the button below to verify your email:
|
|
31626
|
+
</p>
|
|
31627
|
+
|
|
31628
|
+
<!-- CTA Button -->
|
|
31629
|
+
<table role="presentation" style="width: 100%; border-collapse: collapse;">
|
|
31630
|
+
<tr>
|
|
31631
|
+
<td align="center" style="padding: 0 0 30px 0;">
|
|
31632
|
+
<a href="<%= verificationLink %>"
|
|
31633
|
+
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;">
|
|
31634
|
+
Verify Email Address
|
|
31635
|
+
</a>
|
|
31636
|
+
</td>
|
|
31637
|
+
</tr>
|
|
31638
|
+
</table>
|
|
31639
|
+
|
|
31640
|
+
<p style="margin: 0 0 20px 0; font-size: 14px; line-height: 1.6; color: #777777;">
|
|
31641
|
+
Or copy and paste this link into your browser:
|
|
31642
|
+
</p>
|
|
31643
|
+
|
|
31644
|
+
<p style="margin: 0 0 30px 0; font-size: 14px; line-height: 1.6; word-break: break-all; color: #28a745;">
|
|
31645
|
+
<%= verificationLink %>
|
|
31646
|
+
</p>
|
|
31647
|
+
|
|
31648
|
+
<!-- Security Notice -->
|
|
31649
|
+
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #fff3cd; border-radius: 4px; margin: 0 0 20px 0;">
|
|
31650
|
+
<tr>
|
|
31651
|
+
<td style="padding: 12px 16px;">
|
|
31652
|
+
<p style="margin: 0; font-size: 14px; line-height: 1.6; color: #856404;">
|
|
31653
|
+
<strong>⚠️ Important:</strong> This link will expire in <strong><%= expiresIn %></strong>.
|
|
31654
|
+
If you didn't create an account with us, you can safely ignore this email.
|
|
31655
|
+
</p>
|
|
31656
|
+
</td>
|
|
31657
|
+
</tr>
|
|
31658
|
+
</table>
|
|
31659
|
+
|
|
31660
|
+
<p style="margin: 0 0 20px 0; font-size: 16px; line-height: 1.6; color: #555555;">
|
|
31661
|
+
For security reasons, please do not share this link with anyone.
|
|
31662
|
+
</p>
|
|
31663
|
+
</td>
|
|
31664
|
+
</tr>
|
|
31665
|
+
|
|
31666
|
+
<!-- Footer -->
|
|
31667
|
+
<tr>
|
|
31668
|
+
<td style="padding: 30px 40px 40px 40px; border-top: 1px solid #eeeeee;">
|
|
31669
|
+
<p style="margin: 0 0 10px 0; font-size: 14px; line-height: 1.6; color: #999999; text-align: center;">
|
|
31670
|
+
Best regards,<br>
|
|
31671
|
+
The <%= appName %> Team
|
|
31672
|
+
</p>
|
|
31673
|
+
|
|
31674
|
+
<% if (supportEmail) { %>
|
|
31675
|
+
<p style="margin: 0 0 10px 0; font-size: 12px; line-height: 1.6; color: #999999; text-align: center;">
|
|
31676
|
+
Need help? Contact us at <a href="mailto:<%= supportEmail %>" style="color: #28a745; text-decoration: none;"><%= supportEmail %></a>
|
|
31677
|
+
</p>
|
|
31678
|
+
<% } %>
|
|
31679
|
+
|
|
31680
|
+
<p style="margin: 0; font-size: 12px; line-height: 1.6; color: #999999; text-align: center;">
|
|
31681
|
+
© <%= year %> <%= appName %>. All rights reserved.
|
|
31682
|
+
</p>
|
|
31683
|
+
</td>
|
|
31684
|
+
</tr>
|
|
31685
|
+
</table>
|
|
31686
|
+
</td>
|
|
31687
|
+
</tr>
|
|
31688
|
+
</table>
|
|
31689
|
+
</body>
|
|
31690
|
+
</html>
|
|
31691
|
+
`.trim(),
|
|
31692
|
+
text: `
|
|
31693
|
+
Verify Your Email Address
|
|
31694
|
+
|
|
31695
|
+
Hello<% if (user.firstName) { %> <%= user.firstName %><% } %>,
|
|
31696
|
+
|
|
31697
|
+
Thank you for signing up with <%= appName %>! Please verify your email address <%= user.email %> to complete your registration.
|
|
31698
|
+
|
|
31699
|
+
To verify your email, please visit the following link:
|
|
31700
|
+
|
|
31701
|
+
<%= verificationLink %>
|
|
31702
|
+
|
|
31703
|
+
This link will expire in <%= expiresIn %>.
|
|
31704
|
+
|
|
31705
|
+
If you didn't create an account with us, you can safely ignore this email.
|
|
31706
|
+
|
|
31707
|
+
For security reasons, please do not share this link with anyone.
|
|
31708
|
+
|
|
31709
|
+
Best regards,
|
|
31710
|
+
The <%= appName %> Team
|
|
31711
|
+
|
|
31712
|
+
<% if (supportEmail) { %>
|
|
31713
|
+
Need help? Contact us at <%= supportEmail %>
|
|
31714
|
+
<% } %>
|
|
31715
|
+
|
|
31278
31716
|
© <%= year %> <%= appName %>. All rights reserved.
|
|
31279
31717
|
`.trim()
|
|
31280
31718
|
};
|
|
31281
31719
|
const defaultTemplates = {
|
|
31282
31720
|
passwordReset: passwordResetTemplate,
|
|
31283
31721
|
magicLink: magicLinkTemplate,
|
|
31284
|
-
passwordChanged: passwordChangedTemplate
|
|
31722
|
+
passwordChanged: passwordChangedTemplate,
|
|
31723
|
+
emailVerification: emailVerificationTemplate
|
|
31285
31724
|
};
|
|
31286
31725
|
class TemplateService {
|
|
31287
31726
|
/**
|
|
@@ -31693,6 +32132,114 @@ class EmailService {
|
|
|
31693
32132
|
message: "Password changed but confirmation email could not be sent"
|
|
31694
32133
|
};
|
|
31695
32134
|
}
|
|
32135
|
+
/**
|
|
32136
|
+
* Send email verification email with three-tier fallback system
|
|
32137
|
+
* Tier 1: Strapi Email Plugin (if configured)
|
|
32138
|
+
* Tier 2: Custom Hook Function (if provided in config)
|
|
32139
|
+
* Tier 3: Development Console Logging (dev mode only)
|
|
32140
|
+
*/
|
|
32141
|
+
async sendVerificationEmail(user, verificationLink) {
|
|
32142
|
+
if (!user.email) {
|
|
32143
|
+
throw new ValidationError$1("User does not have an email address");
|
|
32144
|
+
}
|
|
32145
|
+
const variables = {
|
|
32146
|
+
user: {
|
|
32147
|
+
email: user.email,
|
|
32148
|
+
firstName: user.firstName || user.displayName?.split(" ")[0],
|
|
32149
|
+
lastName: user.lastName,
|
|
32150
|
+
displayName: user.displayName,
|
|
32151
|
+
phoneNumber: user.phoneNumber,
|
|
32152
|
+
uid: user.uid
|
|
32153
|
+
},
|
|
32154
|
+
verificationLink,
|
|
32155
|
+
expiresIn: "1 hour"
|
|
32156
|
+
};
|
|
32157
|
+
const settingsService2 = strapi.plugin("firebase-authentication").service("settingsService");
|
|
32158
|
+
const dbConfig = await settingsService2.getFirebaseConfigJson();
|
|
32159
|
+
const customSubject = dbConfig?.emailVerificationEmailSubject;
|
|
32160
|
+
const pluginConfig = strapi.config.get("plugin::firebase-authentication");
|
|
32161
|
+
const appConfig = pluginConfig?.app || {};
|
|
32162
|
+
const completeVariables = {
|
|
32163
|
+
...variables,
|
|
32164
|
+
appName: appConfig?.name || "Your Application",
|
|
32165
|
+
appUrl: appConfig?.url || process.env.PUBLIC_URL || "http://localhost:3000",
|
|
32166
|
+
supportEmail: appConfig?.supportEmail,
|
|
32167
|
+
year: (/* @__PURE__ */ new Date()).getFullYear(),
|
|
32168
|
+
expiresIn: variables.expiresIn
|
|
32169
|
+
};
|
|
32170
|
+
const templateService2 = strapi.plugin("firebase-authentication").service("templateService");
|
|
32171
|
+
const template = await templateService2.getTemplate("emailVerification");
|
|
32172
|
+
const subjectTemplate = customSubject || template.subject;
|
|
32173
|
+
const compiledSubject = _$1.template(subjectTemplate)(completeVariables);
|
|
32174
|
+
try {
|
|
32175
|
+
const compiledHtml = template.html ? _$1.template(template.html)(completeVariables) : void 0;
|
|
32176
|
+
const compiledText = template.text ? _$1.template(template.text)(completeVariables) : void 0;
|
|
32177
|
+
const emailPlugin = strapi.plugin("email");
|
|
32178
|
+
if (!emailPlugin) {
|
|
32179
|
+
throw new Error("Email plugin not found");
|
|
32180
|
+
}
|
|
32181
|
+
const emailService2 = emailPlugin.service("email");
|
|
32182
|
+
await emailService2.send({
|
|
32183
|
+
to: user.email,
|
|
32184
|
+
subject: compiledSubject,
|
|
32185
|
+
html: compiledHtml,
|
|
32186
|
+
text: compiledText
|
|
32187
|
+
});
|
|
32188
|
+
strapi.log.info(`✅ Email verification sent via Strapi email plugin to ${user.email}`);
|
|
32189
|
+
return {
|
|
32190
|
+
success: true,
|
|
32191
|
+
message: `Verification email sent to ${user.email}`
|
|
32192
|
+
};
|
|
32193
|
+
} catch (tier1Error) {
|
|
32194
|
+
strapi.log.debug(`Strapi email plugin failed: ${tier1Error.message}. Trying fallback options...`);
|
|
32195
|
+
}
|
|
32196
|
+
const customSender = pluginConfig?.sendVerificationEmail;
|
|
32197
|
+
if (customSender && typeof customSender === "function") {
|
|
32198
|
+
try {
|
|
32199
|
+
const compiledHtml = template.html ? _$1.template(template.html)(completeVariables) : void 0;
|
|
32200
|
+
const compiledText = template.text ? _$1.template(template.text)(completeVariables) : void 0;
|
|
32201
|
+
await customSender({
|
|
32202
|
+
to: user.email,
|
|
32203
|
+
subject: compiledSubject,
|
|
32204
|
+
html: compiledHtml,
|
|
32205
|
+
text: compiledText,
|
|
32206
|
+
verificationLink,
|
|
32207
|
+
variables: completeVariables
|
|
32208
|
+
});
|
|
32209
|
+
strapi.log.info(`✅ Email verification sent via custom hook to ${user.email}`);
|
|
32210
|
+
return {
|
|
32211
|
+
success: true,
|
|
32212
|
+
message: `Verification email sent to ${user.email}`
|
|
32213
|
+
};
|
|
32214
|
+
} catch (tier2Error) {
|
|
32215
|
+
strapi.log.error(`Custom hook failed: ${tier2Error.message}. Continuing to next fallback...`);
|
|
32216
|
+
}
|
|
32217
|
+
}
|
|
32218
|
+
if (process.env.NODE_ENV !== "production") {
|
|
32219
|
+
try {
|
|
32220
|
+
strapi.log.info("\n" + "=".repeat(80));
|
|
32221
|
+
strapi.log.info("EMAIL VERIFICATION (Development Mode)");
|
|
32222
|
+
strapi.log.info("=".repeat(80));
|
|
32223
|
+
strapi.log.info(`To: ${user.email}`);
|
|
32224
|
+
strapi.log.info(`Subject: ${compiledSubject}`);
|
|
32225
|
+
strapi.log.info(`Verification Link: ${verificationLink}`);
|
|
32226
|
+
strapi.log.info(`Expires In: 1 hour`);
|
|
32227
|
+
strapi.log.info("=".repeat(80));
|
|
32228
|
+
strapi.log.info("Note: Email not sent - no email service configured");
|
|
32229
|
+
strapi.log.info("Copy the link above and open in your browser to verify");
|
|
32230
|
+
strapi.log.info("=".repeat(80) + "\n");
|
|
32231
|
+
return {
|
|
32232
|
+
success: true,
|
|
32233
|
+
message: "Verification link logged to console (development mode)"
|
|
32234
|
+
};
|
|
32235
|
+
} catch (tier3Error) {
|
|
32236
|
+
strapi.log.error(`Development fallback failed: ${tier3Error.message}`);
|
|
32237
|
+
}
|
|
32238
|
+
}
|
|
32239
|
+
throw new ApplicationError$2(
|
|
32240
|
+
"Email service is not configured. Please configure Strapi email plugin or provide custom sendVerificationEmail function in plugin config."
|
|
32241
|
+
);
|
|
32242
|
+
}
|
|
31696
32243
|
}
|
|
31697
32244
|
const emailService = ({ strapi: strapi2 }) => new EmailService();
|
|
31698
32245
|
const firebaseUserDataService = ({ strapi: strapi2 }) => ({
|
|
@@ -32055,7 +32602,7 @@ var TokenExpiredError_1 = TokenExpiredError$1;
|
|
|
32055
32602
|
var jws$3 = {};
|
|
32056
32603
|
var safeBuffer = { exports: {} };
|
|
32057
32604
|
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
|
32058
|
-
(function(module2,
|
|
32605
|
+
(function(module2, exports$1) {
|
|
32059
32606
|
var buffer = require$$0__default$5.default;
|
|
32060
32607
|
var Buffer2 = buffer.Buffer;
|
|
32061
32608
|
function copyProps(src, dst) {
|
|
@@ -32066,8 +32613,8 @@ var safeBuffer = { exports: {} };
|
|
|
32066
32613
|
if (Buffer2.from && Buffer2.alloc && Buffer2.allocUnsafe && Buffer2.allocUnsafeSlow) {
|
|
32067
32614
|
module2.exports = buffer;
|
|
32068
32615
|
} else {
|
|
32069
|
-
copyProps(buffer,
|
|
32070
|
-
|
|
32616
|
+
copyProps(buffer, exports$1);
|
|
32617
|
+
exports$1.Buffer = SafeBuffer;
|
|
32071
32618
|
}
|
|
32072
32619
|
function SafeBuffer(arg, encodingOrOffset, length) {
|
|
32073
32620
|
return Buffer2(arg, encodingOrOffset, length);
|
|
@@ -32582,7 +33129,12 @@ function jwsSign(opts) {
|
|
|
32582
33129
|
return util$1.format("%s.%s", securedInput, signature);
|
|
32583
33130
|
}
|
|
32584
33131
|
function SignStream$1(opts) {
|
|
32585
|
-
var secret = opts.secret
|
|
33132
|
+
var secret = opts.secret;
|
|
33133
|
+
secret = secret == null ? opts.privateKey : secret;
|
|
33134
|
+
secret = secret == null ? opts.key : secret;
|
|
33135
|
+
if (/^hs/i.test(opts.header.alg) === true && secret == null) {
|
|
33136
|
+
throw new TypeError("secret must be a string or buffer or a KeyObject");
|
|
33137
|
+
}
|
|
32586
33138
|
var secretStream = new DataStream$1(secret);
|
|
32587
33139
|
this.readable = true;
|
|
32588
33140
|
this.header = opts.header;
|
|
@@ -32688,7 +33240,12 @@ function jwsDecode(jwsSig, opts) {
|
|
|
32688
33240
|
}
|
|
32689
33241
|
function VerifyStream$1(opts) {
|
|
32690
33242
|
opts = opts || {};
|
|
32691
|
-
var secretOrKey = opts.secret
|
|
33243
|
+
var secretOrKey = opts.secret;
|
|
33244
|
+
secretOrKey = secretOrKey == null ? opts.publicKey : secretOrKey;
|
|
33245
|
+
secretOrKey = secretOrKey == null ? opts.key : secretOrKey;
|
|
33246
|
+
if (/^hs/i.test(opts.algorithm) === true && secretOrKey == null) {
|
|
33247
|
+
throw new TypeError("secret must be a string or buffer or a KeyObject");
|
|
33248
|
+
}
|
|
32692
33249
|
var secretStream = new DataStream(secretOrKey);
|
|
32693
33250
|
this.readable = true;
|
|
32694
33251
|
this.algorithm = opts.algorithm;
|
|
@@ -32931,19 +33488,19 @@ var constants$1 = {
|
|
|
32931
33488
|
const debug$1 = typeof process === "object" && process.env && process.env.NODE_DEBUG && /\bsemver\b/i.test(process.env.NODE_DEBUG) ? (...args) => console.error("SEMVER", ...args) : () => {
|
|
32932
33489
|
};
|
|
32933
33490
|
var debug_1 = debug$1;
|
|
32934
|
-
(function(module2,
|
|
33491
|
+
(function(module2, exports$1) {
|
|
32935
33492
|
const {
|
|
32936
33493
|
MAX_SAFE_COMPONENT_LENGTH: MAX_SAFE_COMPONENT_LENGTH2,
|
|
32937
33494
|
MAX_SAFE_BUILD_LENGTH: MAX_SAFE_BUILD_LENGTH2,
|
|
32938
33495
|
MAX_LENGTH: MAX_LENGTH2
|
|
32939
33496
|
} = constants$1;
|
|
32940
33497
|
const debug2 = debug_1;
|
|
32941
|
-
|
|
32942
|
-
const re2 =
|
|
32943
|
-
const safeRe =
|
|
32944
|
-
const src =
|
|
32945
|
-
const safeSrc =
|
|
32946
|
-
const t2 =
|
|
33498
|
+
exports$1 = module2.exports = {};
|
|
33499
|
+
const re2 = exports$1.re = [];
|
|
33500
|
+
const safeRe = exports$1.safeRe = [];
|
|
33501
|
+
const src = exports$1.src = [];
|
|
33502
|
+
const safeSrc = exports$1.safeSrc = [];
|
|
33503
|
+
const t2 = exports$1.t = {};
|
|
32947
33504
|
let R = 0;
|
|
32948
33505
|
const LETTERDASHNUMBER = "[a-zA-Z0-9-]";
|
|
32949
33506
|
const safeRegexReplacements = [
|
|
@@ -32996,18 +33553,18 @@ var debug_1 = debug$1;
|
|
|
32996
33553
|
createToken("COERCERTLFULL", src[t2.COERCEFULL], true);
|
|
32997
33554
|
createToken("LONETILDE", "(?:~>?)");
|
|
32998
33555
|
createToken("TILDETRIM", `(\\s*)${src[t2.LONETILDE]}\\s+`, true);
|
|
32999
|
-
|
|
33556
|
+
exports$1.tildeTrimReplace = "$1~";
|
|
33000
33557
|
createToken("TILDE", `^${src[t2.LONETILDE]}${src[t2.XRANGEPLAIN]}$`);
|
|
33001
33558
|
createToken("TILDELOOSE", `^${src[t2.LONETILDE]}${src[t2.XRANGEPLAINLOOSE]}$`);
|
|
33002
33559
|
createToken("LONECARET", "(?:\\^)");
|
|
33003
33560
|
createToken("CARETTRIM", `(\\s*)${src[t2.LONECARET]}\\s+`, true);
|
|
33004
|
-
|
|
33561
|
+
exports$1.caretTrimReplace = "$1^";
|
|
33005
33562
|
createToken("CARET", `^${src[t2.LONECARET]}${src[t2.XRANGEPLAIN]}$`);
|
|
33006
33563
|
createToken("CARETLOOSE", `^${src[t2.LONECARET]}${src[t2.XRANGEPLAINLOOSE]}$`);
|
|
33007
33564
|
createToken("COMPARATORLOOSE", `^${src[t2.GTLT]}\\s*(${src[t2.LOOSEPLAIN]})$|^$`);
|
|
33008
33565
|
createToken("COMPARATOR", `^${src[t2.GTLT]}\\s*(${src[t2.FULLPLAIN]})$|^$`);
|
|
33009
33566
|
createToken("COMPARATORTRIM", `(\\s*)${src[t2.GTLT]}\\s*(${src[t2.LOOSEPLAIN]}|${src[t2.XRANGEPLAIN]})`, true);
|
|
33010
|
-
|
|
33567
|
+
exports$1.comparatorTrimReplace = "$1$2$3";
|
|
33011
33568
|
createToken("HYPHENRANGE", `^\\s*(${src[t2.XRANGEPLAIN]})\\s+-\\s+(${src[t2.XRANGEPLAIN]})\\s*$`);
|
|
33012
33569
|
createToken("HYPHENRANGELOOSE", `^\\s*(${src[t2.XRANGEPLAINLOOSE]})\\s+-\\s+(${src[t2.XRANGEPLAINLOOSE]})\\s*$`);
|
|
33013
33570
|
createToken("STAR", "(<|>)?=?\\s*\\*");
|
|
@@ -35146,6 +35703,125 @@ const tokenService = ({ strapi: strapi2 }) => {
|
|
|
35146
35703
|
}
|
|
35147
35704
|
});
|
|
35148
35705
|
strapi2.log.debug(`Invalidated reset token for user ${firebaseUserDataDocumentId}`);
|
|
35706
|
+
},
|
|
35707
|
+
// ==================== EMAIL VERIFICATION TOKENS ====================
|
|
35708
|
+
/**
|
|
35709
|
+
* Generate an email verification token for a user
|
|
35710
|
+
* @param firebaseUserDataDocumentId - The documentId of the firebase-user-data record
|
|
35711
|
+
* @param email - The email address at time of request (for change detection)
|
|
35712
|
+
* @returns The JWT token to include in the verification URL
|
|
35713
|
+
*/
|
|
35714
|
+
async generateVerificationToken(firebaseUserDataDocumentId, email2) {
|
|
35715
|
+
const signingKey = getSigningKey();
|
|
35716
|
+
const jti = crypto__default.default.randomBytes(32).toString("hex");
|
|
35717
|
+
const payload = {
|
|
35718
|
+
sub: firebaseUserDataDocumentId,
|
|
35719
|
+
purpose: "email-verification",
|
|
35720
|
+
email: email2,
|
|
35721
|
+
jti
|
|
35722
|
+
};
|
|
35723
|
+
const token = jwt.sign(payload, signingKey, {
|
|
35724
|
+
expiresIn: "1h"
|
|
35725
|
+
});
|
|
35726
|
+
const tokenHash = crypto__default.default.createHash("sha256").update(jti).digest("hex");
|
|
35727
|
+
const expiresAt = new Date(Date.now() + 60 * 60 * 1e3);
|
|
35728
|
+
await strapi2.db.query(FIREBASE_USER_DATA_CONTENT_TYPE).update({
|
|
35729
|
+
where: { documentId: firebaseUserDataDocumentId },
|
|
35730
|
+
data: {
|
|
35731
|
+
verificationTokenHash: tokenHash,
|
|
35732
|
+
verificationTokenExpiresAt: expiresAt.toISOString()
|
|
35733
|
+
}
|
|
35734
|
+
});
|
|
35735
|
+
strapi2.log.debug(`Generated verification token for user ${firebaseUserDataDocumentId}`);
|
|
35736
|
+
return token;
|
|
35737
|
+
},
|
|
35738
|
+
/**
|
|
35739
|
+
* Validate an email verification token
|
|
35740
|
+
* @param token - The JWT token from the verification URL
|
|
35741
|
+
* @returns Validation result with user info and email if valid
|
|
35742
|
+
*/
|
|
35743
|
+
async validateVerificationToken(token) {
|
|
35744
|
+
const signingKey = getSigningKey();
|
|
35745
|
+
try {
|
|
35746
|
+
const decoded = jwt.verify(token, signingKey);
|
|
35747
|
+
if (decoded.purpose !== "email-verification") {
|
|
35748
|
+
return {
|
|
35749
|
+
valid: false,
|
|
35750
|
+
firebaseUserDataDocumentId: "",
|
|
35751
|
+
firebaseUID: "",
|
|
35752
|
+
error: "Invalid token purpose"
|
|
35753
|
+
};
|
|
35754
|
+
}
|
|
35755
|
+
const tokenHash = crypto__default.default.createHash("sha256").update(decoded.jti).digest("hex");
|
|
35756
|
+
const firebaseUserData2 = await strapi2.db.query(FIREBASE_USER_DATA_CONTENT_TYPE).findOne({
|
|
35757
|
+
where: { documentId: decoded.sub }
|
|
35758
|
+
});
|
|
35759
|
+
if (!firebaseUserData2) {
|
|
35760
|
+
return {
|
|
35761
|
+
valid: false,
|
|
35762
|
+
firebaseUserDataDocumentId: "",
|
|
35763
|
+
firebaseUID: "",
|
|
35764
|
+
error: "User not found"
|
|
35765
|
+
};
|
|
35766
|
+
}
|
|
35767
|
+
if (firebaseUserData2.verificationTokenHash !== tokenHash) {
|
|
35768
|
+
return {
|
|
35769
|
+
valid: false,
|
|
35770
|
+
firebaseUserDataDocumentId: "",
|
|
35771
|
+
firebaseUID: "",
|
|
35772
|
+
error: "Verification link has already been used or is invalid"
|
|
35773
|
+
};
|
|
35774
|
+
}
|
|
35775
|
+
if (firebaseUserData2.verificationTokenExpiresAt) {
|
|
35776
|
+
const expiresAt = new Date(firebaseUserData2.verificationTokenExpiresAt);
|
|
35777
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
35778
|
+
return {
|
|
35779
|
+
valid: false,
|
|
35780
|
+
firebaseUserDataDocumentId: "",
|
|
35781
|
+
firebaseUID: "",
|
|
35782
|
+
error: "Verification link has expired"
|
|
35783
|
+
};
|
|
35784
|
+
}
|
|
35785
|
+
}
|
|
35786
|
+
return {
|
|
35787
|
+
valid: true,
|
|
35788
|
+
firebaseUserDataDocumentId: firebaseUserData2.documentId,
|
|
35789
|
+
firebaseUID: firebaseUserData2.firebaseUserID,
|
|
35790
|
+
email: decoded.email
|
|
35791
|
+
};
|
|
35792
|
+
} catch (error2) {
|
|
35793
|
+
if (error2.name === "TokenExpiredError") {
|
|
35794
|
+
return {
|
|
35795
|
+
valid: false,
|
|
35796
|
+
firebaseUserDataDocumentId: "",
|
|
35797
|
+
firebaseUID: "",
|
|
35798
|
+
error: "Verification link has expired"
|
|
35799
|
+
};
|
|
35800
|
+
}
|
|
35801
|
+
if (error2.name === "JsonWebTokenError") {
|
|
35802
|
+
return {
|
|
35803
|
+
valid: false,
|
|
35804
|
+
firebaseUserDataDocumentId: "",
|
|
35805
|
+
firebaseUID: "",
|
|
35806
|
+
error: "Invalid verification link"
|
|
35807
|
+
};
|
|
35808
|
+
}
|
|
35809
|
+
throw error2;
|
|
35810
|
+
}
|
|
35811
|
+
},
|
|
35812
|
+
/**
|
|
35813
|
+
* Invalidate a verification token after use
|
|
35814
|
+
* @param firebaseUserDataDocumentId - The documentId of the firebase-user-data record
|
|
35815
|
+
*/
|
|
35816
|
+
async invalidateVerificationToken(firebaseUserDataDocumentId) {
|
|
35817
|
+
await strapi2.db.query(FIREBASE_USER_DATA_CONTENT_TYPE).update({
|
|
35818
|
+
where: { documentId: firebaseUserDataDocumentId },
|
|
35819
|
+
data: {
|
|
35820
|
+
verificationTokenHash: null,
|
|
35821
|
+
verificationTokenExpiresAt: null
|
|
35822
|
+
}
|
|
35823
|
+
});
|
|
35824
|
+
strapi2.log.debug(`Invalidated verification token for user ${firebaseUserDataDocumentId}`);
|
|
35149
35825
|
}
|
|
35150
35826
|
};
|
|
35151
35827
|
};
|