strapi-plugin-firebase-authentication 1.2.0 → 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.
@@ -401,293 +401,6 @@ const contentTypes = {
401
401
  "firebase-authentication-configuration": { schema: firebaseAuthenticationConfiguration },
402
402
  "firebase-user-data": { schema: firebaseUserData }
403
403
  };
404
- const pluginName = "firebase-authentication";
405
- const PLUGIN_NAME = "firebase-authentication";
406
- const PLUGIN_UID = `plugin::${PLUGIN_NAME}`;
407
- const CONFIG_CONTENT_TYPE = `${PLUGIN_UID}.firebase-authentication-configuration`;
408
- const DEFAULT_PASSWORD_RESET_URL = "http://localhost:3000/reset-password";
409
- const DEFAULT_PASSWORD_REGEX = "^.{6,}$";
410
- const DEFAULT_PASSWORD_MESSAGE = "Password must be at least 6 characters long";
411
- const DEFAULT_RESET_EMAIL_SUBJECT = "Reset Your Password";
412
- const ERROR_MESSAGES = {
413
- FIREBASE_NOT_INITIALIZED: "Firebase is not initialized. Please upload Firebase service account configuration via Settings → Firebase Authentication.",
414
- INVALID_JSON: "Invalid JSON format. Please ensure you copied the entire JSON content correctly.",
415
- MISSING_DATA: "data is missing",
416
- SOMETHING_WENT_WRONG: "Something went wrong",
417
- AUTHENTICATION_FAILED: "Authentication failed",
418
- TOKEN_MISSING: "idToken is missing!",
419
- EMAIL_PASSWORD_REQUIRED: "Email and password are required",
420
- PASSWORD_REQUIRED: "Password is required",
421
- AUTHORIZATION_REQUIRED: "Authorization token is required",
422
- INVALID_TOKEN: "Invalid or expired token",
423
- USER_NOT_FOUND: "User not found",
424
- USER_NO_EMAIL: "User does not have an email address",
425
- FIREBASE_LINK_FAILED: "Failed to generate Firebase reset link",
426
- CONFIG_NOT_FOUND: "No config found",
427
- INVALID_SERVICE_ACCOUNT: "Invalid service account JSON",
428
- WEB_API_NOT_CONFIGURED: "Email/password authentication is not available. Web API Key is not configured.",
429
- RESET_URL_NOT_CONFIGURED: "Password reset URL is not configured",
430
- RESET_URL_MUST_BE_HTTPS: "Password reset URL must use HTTPS in production",
431
- RESET_URL_INVALID_FORMAT: "Password reset URL is not a valid URL format",
432
- USER_NOT_LINKED_FIREBASE: "User is not linked to Firebase authentication",
433
- OVERRIDE_USER_ID_REQUIRED: "Override user ID is required",
434
- EITHER_EMAIL_OR_PHONE_REQUIRED: "Either email or phoneNumber is required",
435
- DELETION_NO_CONFIG: "No Firebase configs exists for deletion"
436
- };
437
- const SUCCESS_MESSAGES = {
438
- FIREBASE_INITIALIZED: "Firebase successfully initialized",
439
- FIREBASE_CONFIG_DELETED: "Firebase config deleted and reinitialized",
440
- PASSWORD_RESET_EMAIL_SENT: "If an account with that email exists, a password reset link has been sent.",
441
- SERVER_RESTARTING: "SERVER IS RESTARTING"
442
- };
443
- const CONFIG_KEYS = {
444
- ENCRYPTION_KEY: `${PLUGIN_UID}.FIREBASE_JSON_ENCRYPTION_KEY`
445
- };
446
- const REQUIRED_FIELDS = {
447
- SERVICE_ACCOUNT: ["private_key", "client_email", "project_id", "type"],
448
- WEB_CONFIG: ["apiKey", "authDomain"]
449
- // These indicate wrong JSON type
450
- };
451
- const VALIDATION_MESSAGES = {
452
- INVALID_SERVICE_ACCOUNT: "Invalid Service Account JSON. Missing required fields:",
453
- 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.",
454
- 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!"
455
- };
456
- const firebaseController = {
457
- async validateToken(ctx) {
458
- strapi.log.debug("validateToken called");
459
- try {
460
- const { idToken, profileMetaData } = ctx.request.body || {};
461
- const populate2 = ctx.request.query.populate || [];
462
- if (!idToken) {
463
- return ctx.badRequest(ERROR_MESSAGES.TOKEN_MISSING);
464
- }
465
- const result = await strapi.plugin(pluginName).service("firebaseService").validateFirebaseToken(idToken, profileMetaData, populate2);
466
- ctx.body = result;
467
- } catch (error2) {
468
- strapi.log.error(`validateToken controller error: ${error2.message}`);
469
- if (error2.name === "ValidationError") {
470
- return ctx.badRequest(error2.message);
471
- }
472
- if (error2.name === "UnauthorizedError") {
473
- return ctx.unauthorized(error2.message);
474
- }
475
- throw error2;
476
- }
477
- },
478
- async deleteByEmail(email2) {
479
- const user = await strapi.firebase.auth().getUserByEmail(email2);
480
- await strapi.plugin(pluginName).service("firebaseService").delete(user.toJSON().uid);
481
- return user.toJSON();
482
- },
483
- async overrideAccess(ctx) {
484
- try {
485
- const { overrideUserId } = ctx.request.body || {};
486
- const populate2 = ctx.request.query.populate || [];
487
- const result = await strapi.plugin(pluginName).service("firebaseService").overrideFirebaseAccess(overrideUserId, populate2);
488
- ctx.body = result;
489
- } catch (error2) {
490
- if (error2.name === "ValidationError") {
491
- return ctx.badRequest(error2.message);
492
- }
493
- if (error2.name === "NotFoundError") {
494
- return ctx.notFound(error2.message);
495
- }
496
- throw error2;
497
- }
498
- },
499
- /**
500
- * Controller method for email/password authentication
501
- * Handles the `/api/firebase-authentication/emailLogin` endpoint
502
- *
503
- * @param ctx - Koa context object
504
- * @returns Promise that sets ctx.body with user data and JWT or error message
505
- *
506
- * @remarks
507
- * This controller acts as a proxy to Firebase's Identity Toolkit API,
508
- * allowing users to authenticate with email/password and receive a Strapi JWT.
509
- *
510
- * HTTP Status Codes:
511
- * - `400`: Validation errors (missing credentials, invalid email/password)
512
- * - `500`: Server errors (missing configuration, Firebase API issues)
513
- */
514
- async emailLogin(ctx) {
515
- strapi.log.debug("emailLogin controller");
516
- try {
517
- const { email: email2, password } = ctx.request.body || {};
518
- const populate2 = ctx.request.query.populate || [];
519
- const result = await strapi.plugin(pluginName).service("firebaseService").emailLogin(email2, password, populate2);
520
- ctx.body = result;
521
- } catch (error2) {
522
- strapi.log.error("emailLogin controller error:", error2);
523
- if (error2.name === "ValidationError") {
524
- ctx.status = 400;
525
- } else if (error2.name === "ApplicationError") {
526
- ctx.status = 500;
527
- } else {
528
- ctx.status = 500;
529
- }
530
- ctx.body = { error: error2.message };
531
- }
532
- },
533
- /**
534
- * Forgot password - sends reset email
535
- * POST /api/firebase-authentication/forgotPassword
536
- * Public endpoint - no authentication required
537
- */
538
- async forgotPassword(ctx) {
539
- strapi.log.debug("forgotPassword endpoint called");
540
- try {
541
- const { email: email2 } = ctx.request.body || {};
542
- ctx.body = await strapi.plugin(pluginName).service("firebaseService").forgotPassword(email2);
543
- } catch (error2) {
544
- strapi.log.error("forgotPassword controller error:", error2);
545
- if (error2.name === "ValidationError") {
546
- ctx.status = 400;
547
- } else if (error2.name === "NotFoundError") {
548
- ctx.status = 404;
549
- } else {
550
- ctx.status = 500;
551
- }
552
- ctx.body = { error: error2.message };
553
- }
554
- },
555
- /**
556
- * Reset password - authenticated password change
557
- * POST /api/firebase-authentication/resetPassword
558
- * Public endpoint - requires valid JWT in Authorization header
559
- * Used for admin-initiated resets or user self-service password changes
560
- * NOT used for forgot password email flow (which uses Firebase's hosted UI)
561
- */
562
- async resetPassword(ctx) {
563
- strapi.log.debug("resetPassword endpoint called");
564
- try {
565
- const { password } = ctx.request.body || {};
566
- const token = ctx.request.headers.authorization?.replace("Bearer ", "");
567
- const populate2 = ctx.request.query.populate || [];
568
- ctx.body = await strapi.plugin(pluginName).service("firebaseService").resetPassword(password, token, populate2);
569
- } catch (error2) {
570
- strapi.log.error("resetPassword controller error:", error2);
571
- if (error2.name === "ValidationError" || error2.name === "UnauthorizedError") {
572
- ctx.status = 401;
573
- } else {
574
- ctx.status = 500;
575
- }
576
- ctx.body = { error: error2.message };
577
- }
578
- },
579
- async requestMagicLink(ctx) {
580
- try {
581
- const { email: email2 } = ctx.request.body || {};
582
- const result = await strapi.plugin("firebase-authentication").service("firebaseService").requestMagicLink(email2);
583
- ctx.body = result;
584
- } catch (error2) {
585
- if (error2.name === "ValidationError" || error2.name === "ApplicationError") {
586
- throw error2;
587
- }
588
- strapi.log.error("requestMagicLink controller error:", error2);
589
- ctx.body = {
590
- success: false,
591
- message: "An error occurred while processing your request"
592
- };
593
- }
594
- },
595
- /**
596
- * Reset password using custom JWT token
597
- * POST /api/firebase-authentication/resetPasswordWithToken
598
- * Public endpoint - token provides authentication
599
- *
600
- * @param ctx - Koa context with { token, newPassword } in body
601
- * @returns { success: true, message: "Password has been reset successfully" }
602
- */
603
- async resetPasswordWithToken(ctx) {
604
- strapi.log.debug("resetPasswordWithToken endpoint called");
605
- try {
606
- const { token, newPassword } = ctx.request.body || {};
607
- if (!token) {
608
- ctx.status = 400;
609
- ctx.body = { error: "Token is required" };
610
- return;
611
- }
612
- if (!newPassword) {
613
- ctx.status = 400;
614
- ctx.body = { error: "New password is required" };
615
- return;
616
- }
617
- const result = await strapi.plugin(pluginName).service("userService").resetPasswordWithToken(token, newPassword);
618
- ctx.body = result;
619
- } catch (error2) {
620
- strapi.log.error("resetPasswordWithToken controller error:", error2);
621
- if (error2.name === "ValidationError") {
622
- ctx.status = 400;
623
- ctx.body = { error: error2.message };
624
- return;
625
- }
626
- if (error2.name === "NotFoundError") {
627
- ctx.status = 404;
628
- ctx.body = { error: error2.message };
629
- return;
630
- }
631
- ctx.status = 500;
632
- ctx.body = { error: "An error occurred while resetting your password" };
633
- }
634
- },
635
- /**
636
- * Send email verification - public endpoint
637
- * POST /api/firebase-authentication/sendVerificationEmail
638
- * Public endpoint - no authentication required
639
- */
640
- async sendVerificationEmail(ctx) {
641
- strapi.log.debug("sendVerificationEmail endpoint called");
642
- try {
643
- const { email: email2 } = ctx.request.body || {};
644
- ctx.body = await strapi.plugin(pluginName).service("firebaseService").sendVerificationEmail(email2);
645
- } catch (error2) {
646
- strapi.log.error("sendVerificationEmail controller error:", error2);
647
- if (error2.name === "ValidationError") {
648
- ctx.status = 400;
649
- } else {
650
- ctx.status = 500;
651
- }
652
- ctx.body = { error: error2.message };
653
- }
654
- },
655
- /**
656
- * Verify email using custom JWT token
657
- * POST /api/firebase-authentication/verifyEmail
658
- * Public endpoint - token provides authentication
659
- *
660
- * @param ctx - Koa context with { token } in body
661
- * @returns { success: true, message: "Email verified successfully" }
662
- */
663
- async verifyEmail(ctx) {
664
- strapi.log.debug("verifyEmail endpoint called");
665
- try {
666
- const { token } = ctx.request.body || {};
667
- if (!token) {
668
- ctx.status = 400;
669
- ctx.body = { error: "Token is required" };
670
- return;
671
- }
672
- const result = await strapi.plugin(pluginName).service("firebaseService").verifyEmail(token);
673
- ctx.body = result;
674
- } catch (error2) {
675
- strapi.log.error("verifyEmail controller error:", error2);
676
- if (error2.name === "ValidationError") {
677
- ctx.status = 400;
678
- ctx.body = { error: error2.message };
679
- return;
680
- }
681
- if (error2.name === "NotFoundError") {
682
- ctx.status = 404;
683
- ctx.body = { error: error2.message };
684
- return;
685
- }
686
- ctx.status = 500;
687
- ctx.body = { error: "An error occurred while verifying your email" };
688
- }
689
- }
690
- };
691
404
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
692
405
  function getDefaultExportFromCjs(x) {
693
406
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
@@ -702,7 +415,7 @@ var lodash = { exports: {} };
702
415
  * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
703
416
  */
704
417
  lodash.exports;
705
- (function(module2, exports2) {
418
+ (function(module2, exports$1) {
706
419
  (function() {
707
420
  var undefined$1;
708
421
  var VERSION = "4.17.21";
@@ -1030,7 +743,7 @@ lodash.exports;
1030
743
  var freeGlobal2 = typeof commonjsGlobal == "object" && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal;
1031
744
  var freeSelf2 = typeof self == "object" && self && self.Object === Object && self;
1032
745
  var root2 = freeGlobal2 || freeSelf2 || Function("return this")();
1033
- var freeExports = exports2 && !exports2.nodeType && exports2;
746
+ var freeExports = exports$1 && !exports$1.nodeType && exports$1;
1034
747
  var freeModule = freeExports && true && module2 && !module2.nodeType && module2;
1035
748
  var moduleExports = freeModule && freeModule.exports === freeExports;
1036
749
  var freeProcess = moduleExports && freeGlobal2.process;
@@ -6260,7 +5973,7 @@ var lodash_min = { exports: {} };
6260
5973
  * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
6261
5974
  */
6262
5975
  lodash_min.exports;
6263
- (function(module2, exports2) {
5976
+ (function(module2, exports$1) {
6264
5977
  (function() {
6265
5978
  function n(n2, t3, r2) {
6266
5979
  switch (r2.length) {
@@ -6693,7 +6406,7 @@ lodash_min.exports;
6693
6406
  "œ": "oe",
6694
6407
  "ʼn": "'n",
6695
6408
  "ſ": "s"
6696
- }, Hr = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }, Jr = { "&amp;": "&", "&lt;": "<", "&gt;": ">", "&quot;": '"', "&#39;": "'" }, 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 = exports2 && !exports2.nodeType && exports2, ue = ee && true && module2 && !module2.nodeType && module2, ie = ue && ue.exports === ee, oe = ie && ne.process, fe = function() {
6409
+ }, Hr = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }, Jr = { "&amp;": "&", "&lt;": "<", "&gt;": ">", "&quot;": '"', "&#39;": "'" }, 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() {
6697
6410
  try {
6698
6411
  var n2 = ue && ue.require && ue.require("util").types;
6699
6412
  return n2 ? n2 : oe && oe.binding && oe.binding("util");
@@ -9366,8 +9079,8 @@ lodash_min.exports;
9366
9079
  })(lodash_min, lodash_min.exports);
9367
9080
  var lodash_minExports = lodash_min.exports;
9368
9081
  var _mapping = {};
9369
- (function(exports2) {
9370
- exports2.aliasToReal = {
9082
+ (function(exports$1) {
9083
+ exports$1.aliasToReal = {
9371
9084
  // Lodash aliases.
9372
9085
  "each": "forEach",
9373
9086
  "eachRight": "forEachRight",
@@ -9432,7 +9145,7 @@ var _mapping = {};
9432
9145
  "whereEq": "isMatch",
9433
9146
  "zipObj": "zipObject"
9434
9147
  };
9435
- exports2.aryMethod = {
9148
+ exports$1.aryMethod = {
9436
9149
  "1": [
9437
9150
  "assignAll",
9438
9151
  "assignInAll",
@@ -9667,12 +9380,12 @@ var _mapping = {};
9667
9380
  "updateWith"
9668
9381
  ]
9669
9382
  };
9670
- exports2.aryRearg = {
9383
+ exports$1.aryRearg = {
9671
9384
  "2": [1, 0],
9672
9385
  "3": [2, 0, 1],
9673
9386
  "4": [3, 2, 0, 1]
9674
9387
  };
9675
- exports2.iterateeAry = {
9388
+ exports$1.iterateeAry = {
9676
9389
  "dropRightWhile": 1,
9677
9390
  "dropWhile": 1,
9678
9391
  "every": 1,
@@ -9710,11 +9423,11 @@ var _mapping = {};
9710
9423
  "times": 1,
9711
9424
  "transform": 2
9712
9425
  };
9713
- exports2.iterateeRearg = {
9426
+ exports$1.iterateeRearg = {
9714
9427
  "mapKeys": [1],
9715
9428
  "reduceRight": [1, 0]
9716
9429
  };
9717
- exports2.methodRearg = {
9430
+ exports$1.methodRearg = {
9718
9431
  "assignInAllWith": [1, 0],
9719
9432
  "assignInWith": [1, 2, 0],
9720
9433
  "assignAllWith": [1, 0],
@@ -9745,7 +9458,7 @@ var _mapping = {};
9745
9458
  "xorWith": [1, 2, 0],
9746
9459
  "zipWith": [1, 2, 0]
9747
9460
  };
9748
- exports2.methodSpread = {
9461
+ exports$1.methodSpread = {
9749
9462
  "assignAll": { "start": 0 },
9750
9463
  "assignAllWith": { "start": 0 },
9751
9464
  "assignInAll": { "start": 0 },
@@ -9761,7 +9474,7 @@ var _mapping = {};
9761
9474
  "without": { "start": 1 },
9762
9475
  "zipAll": { "start": 0 }
9763
9476
  };
9764
- exports2.mutate = {
9477
+ exports$1.mutate = {
9765
9478
  "array": {
9766
9479
  "fill": true,
9767
9480
  "pull": true,
@@ -9798,8 +9511,8 @@ var _mapping = {};
9798
9511
  "updateWith": true
9799
9512
  }
9800
9513
  };
9801
- exports2.realToAlias = function() {
9802
- var hasOwnProperty2 = Object.prototype.hasOwnProperty, object2 = exports2.aliasToReal, result = {};
9514
+ exports$1.realToAlias = function() {
9515
+ var hasOwnProperty2 = Object.prototype.hasOwnProperty, object2 = exports$1.aliasToReal, result = {};
9803
9516
  for (var key in object2) {
9804
9517
  var value = object2[key];
9805
9518
  if (hasOwnProperty2.call(result, value)) {
@@ -9810,7 +9523,7 @@ var _mapping = {};
9810
9523
  }
9811
9524
  return result;
9812
9525
  }();
9813
- exports2.remap = {
9526
+ exports$1.remap = {
9814
9527
  "assignAll": "assign",
9815
9528
  "assignAllWith": "assignWith",
9816
9529
  "assignInAll": "assignIn",
@@ -9844,7 +9557,7 @@ var _mapping = {};
9844
9557
  "trimCharsStart": "trimStart",
9845
9558
  "zipAll": "zip"
9846
9559
  };
9847
- exports2.skipFixed = {
9560
+ exports$1.skipFixed = {
9848
9561
  "castArray": true,
9849
9562
  "flow": true,
9850
9563
  "flowRight": true,
@@ -9853,7 +9566,7 @@ var _mapping = {};
9853
9566
  "rearg": true,
9854
9567
  "runInContext": true
9855
9568
  };
9856
- exports2.skipRearg = {
9569
+ exports$1.skipRearg = {
9857
9570
  "add": true,
9858
9571
  "assign": true,
9859
9572
  "assignIn": true,
@@ -10307,13 +10020,13 @@ const traverseEntity = async (visitor2, options2, entity) => {
10307
10020
  if (fp.isNil(value) || fp.isNil(attribute)) {
10308
10021
  continue;
10309
10022
  }
10310
- parent = {
10311
- schema: schema2,
10312
- key,
10313
- attribute,
10314
- path: newPath
10315
- };
10316
10023
  if (isRelationalAttribute(attribute)) {
10024
+ parent = {
10025
+ schema: schema2,
10026
+ key,
10027
+ attribute,
10028
+ path: newPath
10029
+ };
10317
10030
  const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
10318
10031
  const method = isMorphRelation ? traverseMorphRelationTarget : traverseRelationTarget(getModel(attribute.target));
10319
10032
  if (fp.isArray(value)) {
@@ -10332,6 +10045,12 @@ const traverseEntity = async (visitor2, options2, entity) => {
10332
10045
  continue;
10333
10046
  }
10334
10047
  if (isMediaAttribute(attribute)) {
10048
+ parent = {
10049
+ schema: schema2,
10050
+ key,
10051
+ attribute,
10052
+ path: newPath
10053
+ };
10335
10054
  if (fp.isArray(value)) {
10336
10055
  const res = new Array(value.length);
10337
10056
  for (let i2 = 0; i2 < value.length; i2 += 1) {
@@ -10348,6 +10067,12 @@ const traverseEntity = async (visitor2, options2, entity) => {
10348
10067
  continue;
10349
10068
  }
10350
10069
  if (attribute.type === "component") {
10070
+ parent = {
10071
+ schema: schema2,
10072
+ key,
10073
+ attribute,
10074
+ path: newPath
10075
+ };
10351
10076
  const targetSchema = getModel(attribute.component);
10352
10077
  if (fp.isArray(value)) {
10353
10078
  const res = new Array(value.length);
@@ -10365,6 +10090,12 @@ const traverseEntity = async (visitor2, options2, entity) => {
10365
10090
  continue;
10366
10091
  }
10367
10092
  if (attribute.type === "dynamiczone" && fp.isArray(value)) {
10093
+ parent = {
10094
+ schema: schema2,
10095
+ key,
10096
+ attribute,
10097
+ path: newPath
10098
+ };
10368
10099
  const res = new Array(value.length);
10369
10100
  for (let i2 = 0; i2 < value.length; i2 += 1) {
10370
10101
  const arrayPath = {
@@ -10389,7 +10120,7 @@ const createVisitorUtils = ({ data }) => ({
10389
10120
  });
10390
10121
  fp.curry(traverseEntity);
10391
10122
  var dist = { exports: {} };
10392
- (function(module2, exports2) {
10123
+ (function(module2, exports$1) {
10393
10124
  !function(t2, n) {
10394
10125
  module2.exports = n(require$$0__default.default, crypto__default.default);
10395
10126
  }(commonjsGlobal, function(t2, n) {
@@ -11937,9 +11668,9 @@ function stubFalse() {
11937
11668
  }
11938
11669
  var stubFalse_1 = stubFalse;
11939
11670
  isBuffer$2.exports;
11940
- (function(module2, exports2) {
11671
+ (function(module2, exports$1) {
11941
11672
  var root2 = _root, stubFalse2 = stubFalse_1;
11942
- var freeExports = exports2 && !exports2.nodeType && exports2;
11673
+ var freeExports = exports$1 && !exports$1.nodeType && exports$1;
11943
11674
  var freeModule = freeExports && true && module2 && !module2.nodeType && module2;
11944
11675
  var moduleExports = freeModule && freeModule.exports === freeExports;
11945
11676
  var Buffer2 = moduleExports ? root2.Buffer : void 0;
@@ -11966,9 +11697,9 @@ function baseUnary$1(func) {
11966
11697
  var _baseUnary = baseUnary$1;
11967
11698
  var _nodeUtil = { exports: {} };
11968
11699
  _nodeUtil.exports;
11969
- (function(module2, exports2) {
11700
+ (function(module2, exports$1) {
11970
11701
  var freeGlobal2 = _freeGlobal;
11971
- var freeExports = exports2 && !exports2.nodeType && exports2;
11702
+ var freeExports = exports$1 && !exports$1.nodeType && exports$1;
11972
11703
  var freeModule = freeExports && true && module2 && !module2.nodeType && module2;
11973
11704
  var moduleExports = freeModule && freeModule.exports === freeExports;
11974
11705
  var freeProcess = moduleExports && freeGlobal2.process;
@@ -14927,7 +14658,7 @@ function toIdentifier(str2) {
14927
14658
  Object.defineProperty(func, "name", desc);
14928
14659
  }
14929
14660
  }
14930
- function populateConstructorExports(exports2, codes2, HttpError) {
14661
+ function populateConstructorExports(exports$1, codes2, HttpError) {
14931
14662
  codes2.forEach(function forEachCode(code) {
14932
14663
  var CodeError;
14933
14664
  var name = toIdentifier2(statuses$1.message[code]);
@@ -14940,8 +14671,8 @@ function toIdentifier(str2) {
14940
14671
  break;
14941
14672
  }
14942
14673
  if (CodeError) {
14943
- exports2[code] = CodeError;
14944
- exports2[name] = CodeError;
14674
+ exports$1[code] = CodeError;
14675
+ exports$1[name] = CodeError;
14945
14676
  }
14946
14677
  });
14947
14678
  }
@@ -14963,7 +14694,7 @@ const formatYupErrors = (yupError) => ({
14963
14694
  message: yupError.message
14964
14695
  });
14965
14696
  let ApplicationError$2 = class ApplicationError extends Error {
14966
- constructor(message = "An application error occured", details = {}) {
14697
+ constructor(message = "An application error occurred", details = {}) {
14967
14698
  super();
14968
14699
  this.name = "ApplicationError";
14969
14700
  this.message = message;
@@ -18159,8 +17890,8 @@ pkgDir$1.exports.sync = (cwd2) => {
18159
17890
  };
18160
17891
  var pkgDirExports = pkgDir$1.exports;
18161
17892
  var utils$8 = {};
18162
- (function(exports2) {
18163
- exports2.isInteger = (num) => {
17893
+ (function(exports$1) {
17894
+ exports$1.isInteger = (num) => {
18164
17895
  if (typeof num === "number") {
18165
17896
  return Number.isInteger(num);
18166
17897
  }
@@ -18169,13 +17900,13 @@ var utils$8 = {};
18169
17900
  }
18170
17901
  return false;
18171
17902
  };
18172
- exports2.find = (node, type2) => node.nodes.find((node2) => node2.type === type2);
18173
- exports2.exceedsLimit = (min, max, step = 1, limit) => {
17903
+ exports$1.find = (node, type2) => node.nodes.find((node2) => node2.type === type2);
17904
+ exports$1.exceedsLimit = (min, max, step = 1, limit) => {
18174
17905
  if (limit === false) return false;
18175
- if (!exports2.isInteger(min) || !exports2.isInteger(max)) return false;
17906
+ if (!exports$1.isInteger(min) || !exports$1.isInteger(max)) return false;
18176
17907
  return (Number(max) - Number(min)) / Number(step) >= limit;
18177
17908
  };
18178
- exports2.escapeNode = (block, n = 0, type2) => {
17909
+ exports$1.escapeNode = (block, n = 0, type2) => {
18179
17910
  const node = block.nodes[n];
18180
17911
  if (!node) return;
18181
17912
  if (type2 && node.type === type2 || node.type === "open" || node.type === "close") {
@@ -18185,7 +17916,7 @@ var utils$8 = {};
18185
17916
  }
18186
17917
  }
18187
17918
  };
18188
- exports2.encloseBrace = (node) => {
17919
+ exports$1.encloseBrace = (node) => {
18189
17920
  if (node.type !== "brace") return false;
18190
17921
  if (node.commas >> 0 + node.ranges >> 0 === 0) {
18191
17922
  node.invalid = true;
@@ -18193,7 +17924,7 @@ var utils$8 = {};
18193
17924
  }
18194
17925
  return false;
18195
17926
  };
18196
- exports2.isInvalidBrace = (block) => {
17927
+ exports$1.isInvalidBrace = (block) => {
18197
17928
  if (block.type !== "brace") return false;
18198
17929
  if (block.invalid === true || block.dollar) return true;
18199
17930
  if (block.commas >> 0 + block.ranges >> 0 === 0) {
@@ -18206,18 +17937,18 @@ var utils$8 = {};
18206
17937
  }
18207
17938
  return false;
18208
17939
  };
18209
- exports2.isOpenOrClose = (node) => {
17940
+ exports$1.isOpenOrClose = (node) => {
18210
17941
  if (node.type === "open" || node.type === "close") {
18211
17942
  return true;
18212
17943
  }
18213
17944
  return node.open === true || node.close === true;
18214
17945
  };
18215
- exports2.reduce = (nodes) => nodes.reduce((acc, node) => {
17946
+ exports$1.reduce = (nodes) => nodes.reduce((acc, node) => {
18216
17947
  if (node.type === "text") acc.push(node.value);
18217
17948
  if (node.type === "range") node.type = "text";
18218
17949
  return acc;
18219
17950
  }, []);
18220
- exports2.flatten = (...args) => {
17951
+ exports$1.flatten = (...args) => {
18221
17952
  const result = [];
18222
17953
  const flat = (arr) => {
18223
17954
  for (let i = 0; i < arr.length; i++) {
@@ -19319,7 +19050,7 @@ var constants$5 = {
19319
19050
  return win32 === true ? WINDOWS_CHARS : POSIX_CHARS;
19320
19051
  }
19321
19052
  };
19322
- (function(exports2) {
19053
+ (function(exports$1) {
19323
19054
  const path2 = require$$0__namespace.default;
19324
19055
  const win32 = process.platform === "win32";
19325
19056
  const {
@@ -19328,36 +19059,36 @@ var constants$5 = {
19328
19059
  REGEX_SPECIAL_CHARS,
19329
19060
  REGEX_SPECIAL_CHARS_GLOBAL
19330
19061
  } = constants$5;
19331
- exports2.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
19332
- exports2.hasRegexChars = (str2) => REGEX_SPECIAL_CHARS.test(str2);
19333
- exports2.isRegexChar = (str2) => str2.length === 1 && exports2.hasRegexChars(str2);
19334
- exports2.escapeRegex = (str2) => str2.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
19335
- exports2.toPosixSlashes = (str2) => str2.replace(REGEX_BACKSLASH, "/");
19336
- exports2.removeBackslashes = (str2) => {
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) => {
19337
19068
  return str2.replace(REGEX_REMOVE_BACKSLASH, (match) => {
19338
19069
  return match === "\\" ? "" : match;
19339
19070
  });
19340
19071
  };
19341
- exports2.supportsLookbehinds = () => {
19072
+ exports$1.supportsLookbehinds = () => {
19342
19073
  const segs = process.version.slice(1).split(".").map(Number);
19343
19074
  if (segs.length === 3 && segs[0] >= 9 || segs[0] === 8 && segs[1] >= 10) {
19344
19075
  return true;
19345
19076
  }
19346
19077
  return false;
19347
19078
  };
19348
- exports2.isWindows = (options2) => {
19079
+ exports$1.isWindows = (options2) => {
19349
19080
  if (options2 && typeof options2.windows === "boolean") {
19350
19081
  return options2.windows;
19351
19082
  }
19352
19083
  return win32 === true || path2.sep === "\\";
19353
19084
  };
19354
- exports2.escapeLast = (input, char, lastIdx) => {
19085
+ exports$1.escapeLast = (input, char, lastIdx) => {
19355
19086
  const idx = input.lastIndexOf(char, lastIdx);
19356
19087
  if (idx === -1) return input;
19357
- if (input[idx - 1] === "\\") return exports2.escapeLast(input, char, idx - 1);
19088
+ if (input[idx - 1] === "\\") return exports$1.escapeLast(input, char, idx - 1);
19358
19089
  return `${input.slice(0, idx)}\\${input.slice(idx)}`;
19359
19090
  };
19360
- exports2.removePrefix = (input, state = {}) => {
19091
+ exports$1.removePrefix = (input, state = {}) => {
19361
19092
  let output = input;
19362
19093
  if (output.startsWith("./")) {
19363
19094
  output = output.slice(2);
@@ -19365,7 +19096,7 @@ var constants$5 = {
19365
19096
  }
19366
19097
  return output;
19367
19098
  };
19368
- exports2.wrapOutput = (input, state = {}, options2 = {}) => {
19099
+ exports$1.wrapOutput = (input, state = {}, options2 = {}) => {
19369
19100
  const prepend = options2.contains ? "" : "^";
19370
19101
  const append2 = options2.contains ? "" : "$";
19371
19102
  let output = `${prepend}(?:${input})${append2}`;
@@ -22920,6 +22651,18 @@ function charFromCodepoint(c) {
22920
22651
  (c - 65536 & 1023) + 56320
22921
22652
  );
22922
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
+ }
22923
22666
  var simpleEscapeCheck = new Array(256);
22924
22667
  var simpleEscapeMap = new Array(256);
22925
22668
  for (var i = 0; i < 256; i++) {
@@ -23026,7 +22769,7 @@ function mergeMappings(state, destination, source, overridableKeys) {
23026
22769
  for (index2 = 0, quantity = sourceKeys.length; index2 < quantity; index2 += 1) {
23027
22770
  key = sourceKeys[index2];
23028
22771
  if (!_hasOwnProperty$1.call(destination, key)) {
23029
- destination[key] = source[key];
22772
+ setProperty(destination, key, source[key]);
23030
22773
  overridableKeys[key] = true;
23031
22774
  }
23032
22775
  }
@@ -23065,7 +22808,7 @@ function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valu
23065
22808
  state.position = startPos || state.position;
23066
22809
  throwError(state, "duplicated mapping key");
23067
22810
  }
23068
- _result[keyNode] = valueNode;
22811
+ setProperty(_result, keyNode, valueNode);
23069
22812
  delete overridableKeys[keyNode];
23070
22813
  }
23071
22814
  return _result;
@@ -28968,6 +28711,247 @@ _enum([
28968
28711
  "published"
28969
28712
  ]).describe("Filter by publication status");
28970
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
+ };
28971
28955
  const STRAPI_DESTINATION = "strapi";
28972
28956
  const FIREBASE_DESTINATION = "firebase";
28973
28957
  const userController = {
@@ -29229,7 +29213,16 @@ const controllers = {
29229
29213
  settingsController
29230
29214
  };
29231
29215
  const middlewares = {};
29232
- const policies = {};
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
+ };
29233
29226
  const settingsRoute = [
29234
29227
  {
29235
29228
  method: "POST",
@@ -29379,9 +29372,7 @@ const contentApi = {
29379
29372
  path: "/resetPassword",
29380
29373
  handler: "firebaseController.resetPassword",
29381
29374
  config: {
29382
- auth: false,
29383
- // Public endpoint - authenticated password change, requires valid JWT in Authorization header
29384
- policies: []
29375
+ policies: ["plugin::firebase-authentication.is-authenticated"]
29385
29376
  }
29386
29377
  },
29387
29378
  {
@@ -29419,9 +29410,7 @@ const contentApi = {
29419
29410
  path: "/sendVerificationEmail",
29420
29411
  handler: "firebaseController.sendVerificationEmail",
29421
29412
  config: {
29422
- auth: false,
29423
- // Public endpoint - sends email verification link
29424
- policies: []
29413
+ policies: ["plugin::firebase-authentication.is-authenticated"]
29425
29414
  }
29426
29415
  },
29427
29416
  {
@@ -30930,20 +30919,17 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30930
30919
  * 2. User-initiated password change (when already authenticated)
30931
30920
  *
30932
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
30933
30926
  */
30934
- resetPassword: async (password, token, populate2) => {
30927
+ resetPassword: async (password, user, populate2) => {
30935
30928
  if (!password) {
30936
30929
  throw new ValidationError$1("Password is required");
30937
30930
  }
30938
- if (!token) {
30939
- throw new UnauthorizedError("Authorization token is required");
30940
- }
30941
- let decoded;
30942
- try {
30943
- const jwtService = strapi2.plugin("users-permissions").service("jwt");
30944
- decoded = await jwtService.verify(token);
30945
- } catch (error2) {
30946
- throw new UnauthorizedError("Invalid or expired token");
30931
+ if (!user || !user.id) {
30932
+ throw new UnauthorizedError("Authentication required");
30947
30933
  }
30948
30934
  const config2 = await strapi2.plugin("firebase-authentication").service("settingsService").getFirebaseConfigJson();
30949
30935
  const passwordRegex = config2?.passwordRequirementsRegex || "^.{6,}$";
@@ -30954,8 +30940,7 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30954
30940
  }
30955
30941
  try {
30956
30942
  const strapiUser = await strapi2.db.query("plugin::users-permissions.user").findOne({
30957
- where: { id: decoded.id }
30958
- // Use numeric id from JWT
30943
+ where: { id: user.id }
30959
30944
  });
30960
30945
  if (!strapiUser) {
30961
30946
  throw new NotFoundError("User not found");
@@ -32617,7 +32602,7 @@ var TokenExpiredError_1 = TokenExpiredError$1;
32617
32602
  var jws$3 = {};
32618
32603
  var safeBuffer = { exports: {} };
32619
32604
  /*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
32620
- (function(module2, exports2) {
32605
+ (function(module2, exports$1) {
32621
32606
  var buffer = require$$0__default$5.default;
32622
32607
  var Buffer2 = buffer.Buffer;
32623
32608
  function copyProps(src, dst) {
@@ -32628,8 +32613,8 @@ var safeBuffer = { exports: {} };
32628
32613
  if (Buffer2.from && Buffer2.alloc && Buffer2.allocUnsafe && Buffer2.allocUnsafeSlow) {
32629
32614
  module2.exports = buffer;
32630
32615
  } else {
32631
- copyProps(buffer, exports2);
32632
- exports2.Buffer = SafeBuffer;
32616
+ copyProps(buffer, exports$1);
32617
+ exports$1.Buffer = SafeBuffer;
32633
32618
  }
32634
32619
  function SafeBuffer(arg, encodingOrOffset, length) {
32635
32620
  return Buffer2(arg, encodingOrOffset, length);
@@ -33144,7 +33129,12 @@ function jwsSign(opts) {
33144
33129
  return util$1.format("%s.%s", securedInput, signature);
33145
33130
  }
33146
33131
  function SignStream$1(opts) {
33147
- var secret = opts.secret || opts.privateKey || opts.key;
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
+ }
33148
33138
  var secretStream = new DataStream$1(secret);
33149
33139
  this.readable = true;
33150
33140
  this.header = opts.header;
@@ -33250,7 +33240,12 @@ function jwsDecode(jwsSig, opts) {
33250
33240
  }
33251
33241
  function VerifyStream$1(opts) {
33252
33242
  opts = opts || {};
33253
- var secretOrKey = opts.secret || opts.publicKey || opts.key;
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
+ }
33254
33249
  var secretStream = new DataStream(secretOrKey);
33255
33250
  this.readable = true;
33256
33251
  this.algorithm = opts.algorithm;
@@ -33493,19 +33488,19 @@ var constants$1 = {
33493
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) : () => {
33494
33489
  };
33495
33490
  var debug_1 = debug$1;
33496
- (function(module2, exports2) {
33491
+ (function(module2, exports$1) {
33497
33492
  const {
33498
33493
  MAX_SAFE_COMPONENT_LENGTH: MAX_SAFE_COMPONENT_LENGTH2,
33499
33494
  MAX_SAFE_BUILD_LENGTH: MAX_SAFE_BUILD_LENGTH2,
33500
33495
  MAX_LENGTH: MAX_LENGTH2
33501
33496
  } = constants$1;
33502
33497
  const debug2 = debug_1;
33503
- exports2 = module2.exports = {};
33504
- const re2 = exports2.re = [];
33505
- const safeRe = exports2.safeRe = [];
33506
- const src = exports2.src = [];
33507
- const safeSrc = exports2.safeSrc = [];
33508
- const t2 = exports2.t = {};
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 = {};
33509
33504
  let R = 0;
33510
33505
  const LETTERDASHNUMBER = "[a-zA-Z0-9-]";
33511
33506
  const safeRegexReplacements = [
@@ -33558,18 +33553,18 @@ var debug_1 = debug$1;
33558
33553
  createToken("COERCERTLFULL", src[t2.COERCEFULL], true);
33559
33554
  createToken("LONETILDE", "(?:~>?)");
33560
33555
  createToken("TILDETRIM", `(\\s*)${src[t2.LONETILDE]}\\s+`, true);
33561
- exports2.tildeTrimReplace = "$1~";
33556
+ exports$1.tildeTrimReplace = "$1~";
33562
33557
  createToken("TILDE", `^${src[t2.LONETILDE]}${src[t2.XRANGEPLAIN]}$`);
33563
33558
  createToken("TILDELOOSE", `^${src[t2.LONETILDE]}${src[t2.XRANGEPLAINLOOSE]}$`);
33564
33559
  createToken("LONECARET", "(?:\\^)");
33565
33560
  createToken("CARETTRIM", `(\\s*)${src[t2.LONECARET]}\\s+`, true);
33566
- exports2.caretTrimReplace = "$1^";
33561
+ exports$1.caretTrimReplace = "$1^";
33567
33562
  createToken("CARET", `^${src[t2.LONECARET]}${src[t2.XRANGEPLAIN]}$`);
33568
33563
  createToken("CARETLOOSE", `^${src[t2.LONECARET]}${src[t2.XRANGEPLAINLOOSE]}$`);
33569
33564
  createToken("COMPARATORLOOSE", `^${src[t2.GTLT]}\\s*(${src[t2.LOOSEPLAIN]})$|^$`);
33570
33565
  createToken("COMPARATOR", `^${src[t2.GTLT]}\\s*(${src[t2.FULLPLAIN]})$|^$`);
33571
33566
  createToken("COMPARATORTRIM", `(\\s*)${src[t2.GTLT]}\\s*(${src[t2.LOOSEPLAIN]}|${src[t2.XRANGEPLAIN]})`, true);
33572
- exports2.comparatorTrimReplace = "$1$2$3";
33567
+ exports$1.comparatorTrimReplace = "$1$2$3";
33573
33568
  createToken("HYPHENRANGE", `^\\s*(${src[t2.XRANGEPLAIN]})\\s+-\\s+(${src[t2.XRANGEPLAIN]})\\s*$`);
33574
33569
  createToken("HYPHENRANGELOOSE", `^\\s*(${src[t2.XRANGEPLAINLOOSE]})\\s+-\\s+(${src[t2.XRANGEPLAINLOOSE]})\\s*$`);
33575
33570
  createToken("STAR", "(<|>)?=?\\s*\\*");