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.
@@ -369,293 +369,6 @@ const contentTypes = {
369
369
  "firebase-authentication-configuration": { schema: firebaseAuthenticationConfiguration },
370
370
  "firebase-user-data": { schema: firebaseUserData }
371
371
  };
372
- const pluginName = "firebase-authentication";
373
- const PLUGIN_NAME = "firebase-authentication";
374
- const PLUGIN_UID = `plugin::${PLUGIN_NAME}`;
375
- const CONFIG_CONTENT_TYPE = `${PLUGIN_UID}.firebase-authentication-configuration`;
376
- const DEFAULT_PASSWORD_RESET_URL = "http://localhost:3000/reset-password";
377
- const DEFAULT_PASSWORD_REGEX = "^.{6,}$";
378
- const DEFAULT_PASSWORD_MESSAGE = "Password must be at least 6 characters long";
379
- const DEFAULT_RESET_EMAIL_SUBJECT = "Reset Your Password";
380
- const ERROR_MESSAGES = {
381
- FIREBASE_NOT_INITIALIZED: "Firebase is not initialized. Please upload Firebase service account configuration via Settings → Firebase Authentication.",
382
- INVALID_JSON: "Invalid JSON format. Please ensure you copied the entire JSON content correctly.",
383
- MISSING_DATA: "data is missing",
384
- SOMETHING_WENT_WRONG: "Something went wrong",
385
- AUTHENTICATION_FAILED: "Authentication failed",
386
- TOKEN_MISSING: "idToken is missing!",
387
- EMAIL_PASSWORD_REQUIRED: "Email and password are required",
388
- PASSWORD_REQUIRED: "Password is required",
389
- AUTHORIZATION_REQUIRED: "Authorization token is required",
390
- INVALID_TOKEN: "Invalid or expired token",
391
- USER_NOT_FOUND: "User not found",
392
- USER_NO_EMAIL: "User does not have an email address",
393
- FIREBASE_LINK_FAILED: "Failed to generate Firebase reset link",
394
- CONFIG_NOT_FOUND: "No config found",
395
- INVALID_SERVICE_ACCOUNT: "Invalid service account JSON",
396
- WEB_API_NOT_CONFIGURED: "Email/password authentication is not available. Web API Key is not configured.",
397
- RESET_URL_NOT_CONFIGURED: "Password reset URL is not configured",
398
- RESET_URL_MUST_BE_HTTPS: "Password reset URL must use HTTPS in production",
399
- RESET_URL_INVALID_FORMAT: "Password reset URL is not a valid URL format",
400
- USER_NOT_LINKED_FIREBASE: "User is not linked to Firebase authentication",
401
- OVERRIDE_USER_ID_REQUIRED: "Override user ID is required",
402
- EITHER_EMAIL_OR_PHONE_REQUIRED: "Either email or phoneNumber is required",
403
- DELETION_NO_CONFIG: "No Firebase configs exists for deletion"
404
- };
405
- const SUCCESS_MESSAGES = {
406
- FIREBASE_INITIALIZED: "Firebase successfully initialized",
407
- FIREBASE_CONFIG_DELETED: "Firebase config deleted and reinitialized",
408
- PASSWORD_RESET_EMAIL_SENT: "If an account with that email exists, a password reset link has been sent.",
409
- SERVER_RESTARTING: "SERVER IS RESTARTING"
410
- };
411
- const CONFIG_KEYS = {
412
- ENCRYPTION_KEY: `${PLUGIN_UID}.FIREBASE_JSON_ENCRYPTION_KEY`
413
- };
414
- const REQUIRED_FIELDS = {
415
- SERVICE_ACCOUNT: ["private_key", "client_email", "project_id", "type"],
416
- WEB_CONFIG: ["apiKey", "authDomain"]
417
- // These indicate wrong JSON type
418
- };
419
- const VALIDATION_MESSAGES = {
420
- INVALID_SERVICE_ACCOUNT: "Invalid Service Account JSON. Missing required fields:",
421
- 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.",
422
- 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!"
423
- };
424
- const firebaseController = {
425
- async validateToken(ctx) {
426
- strapi.log.debug("validateToken called");
427
- try {
428
- const { idToken, profileMetaData } = ctx.request.body || {};
429
- const populate2 = ctx.request.query.populate || [];
430
- if (!idToken) {
431
- return ctx.badRequest(ERROR_MESSAGES.TOKEN_MISSING);
432
- }
433
- const result = await strapi.plugin(pluginName).service("firebaseService").validateFirebaseToken(idToken, profileMetaData, populate2);
434
- ctx.body = result;
435
- } catch (error2) {
436
- strapi.log.error(`validateToken controller error: ${error2.message}`);
437
- if (error2.name === "ValidationError") {
438
- return ctx.badRequest(error2.message);
439
- }
440
- if (error2.name === "UnauthorizedError") {
441
- return ctx.unauthorized(error2.message);
442
- }
443
- throw error2;
444
- }
445
- },
446
- async deleteByEmail(email2) {
447
- const user = await strapi.firebase.auth().getUserByEmail(email2);
448
- await strapi.plugin(pluginName).service("firebaseService").delete(user.toJSON().uid);
449
- return user.toJSON();
450
- },
451
- async overrideAccess(ctx) {
452
- try {
453
- const { overrideUserId } = ctx.request.body || {};
454
- const populate2 = ctx.request.query.populate || [];
455
- const result = await strapi.plugin(pluginName).service("firebaseService").overrideFirebaseAccess(overrideUserId, populate2);
456
- ctx.body = result;
457
- } catch (error2) {
458
- if (error2.name === "ValidationError") {
459
- return ctx.badRequest(error2.message);
460
- }
461
- if (error2.name === "NotFoundError") {
462
- return ctx.notFound(error2.message);
463
- }
464
- throw error2;
465
- }
466
- },
467
- /**
468
- * Controller method for email/password authentication
469
- * Handles the `/api/firebase-authentication/emailLogin` endpoint
470
- *
471
- * @param ctx - Koa context object
472
- * @returns Promise that sets ctx.body with user data and JWT or error message
473
- *
474
- * @remarks
475
- * This controller acts as a proxy to Firebase's Identity Toolkit API,
476
- * allowing users to authenticate with email/password and receive a Strapi JWT.
477
- *
478
- * HTTP Status Codes:
479
- * - `400`: Validation errors (missing credentials, invalid email/password)
480
- * - `500`: Server errors (missing configuration, Firebase API issues)
481
- */
482
- async emailLogin(ctx) {
483
- strapi.log.debug("emailLogin controller");
484
- try {
485
- const { email: email2, password } = ctx.request.body || {};
486
- const populate2 = ctx.request.query.populate || [];
487
- const result = await strapi.plugin(pluginName).service("firebaseService").emailLogin(email2, password, populate2);
488
- ctx.body = result;
489
- } catch (error2) {
490
- strapi.log.error("emailLogin controller error:", error2);
491
- if (error2.name === "ValidationError") {
492
- ctx.status = 400;
493
- } else if (error2.name === "ApplicationError") {
494
- ctx.status = 500;
495
- } else {
496
- ctx.status = 500;
497
- }
498
- ctx.body = { error: error2.message };
499
- }
500
- },
501
- /**
502
- * Forgot password - sends reset email
503
- * POST /api/firebase-authentication/forgotPassword
504
- * Public endpoint - no authentication required
505
- */
506
- async forgotPassword(ctx) {
507
- strapi.log.debug("forgotPassword endpoint called");
508
- try {
509
- const { email: email2 } = ctx.request.body || {};
510
- ctx.body = await strapi.plugin(pluginName).service("firebaseService").forgotPassword(email2);
511
- } catch (error2) {
512
- strapi.log.error("forgotPassword controller error:", error2);
513
- if (error2.name === "ValidationError") {
514
- ctx.status = 400;
515
- } else if (error2.name === "NotFoundError") {
516
- ctx.status = 404;
517
- } else {
518
- ctx.status = 500;
519
- }
520
- ctx.body = { error: error2.message };
521
- }
522
- },
523
- /**
524
- * Reset password - authenticated password change
525
- * POST /api/firebase-authentication/resetPassword
526
- * Public endpoint - requires valid JWT in Authorization header
527
- * Used for admin-initiated resets or user self-service password changes
528
- * NOT used for forgot password email flow (which uses Firebase's hosted UI)
529
- */
530
- async resetPassword(ctx) {
531
- strapi.log.debug("resetPassword endpoint called");
532
- try {
533
- const { password } = ctx.request.body || {};
534
- const token = ctx.request.headers.authorization?.replace("Bearer ", "");
535
- const populate2 = ctx.request.query.populate || [];
536
- ctx.body = await strapi.plugin(pluginName).service("firebaseService").resetPassword(password, token, populate2);
537
- } catch (error2) {
538
- strapi.log.error("resetPassword controller error:", error2);
539
- if (error2.name === "ValidationError" || error2.name === "UnauthorizedError") {
540
- ctx.status = 401;
541
- } else {
542
- ctx.status = 500;
543
- }
544
- ctx.body = { error: error2.message };
545
- }
546
- },
547
- async requestMagicLink(ctx) {
548
- try {
549
- const { email: email2 } = ctx.request.body || {};
550
- const result = await strapi.plugin("firebase-authentication").service("firebaseService").requestMagicLink(email2);
551
- ctx.body = result;
552
- } catch (error2) {
553
- if (error2.name === "ValidationError" || error2.name === "ApplicationError") {
554
- throw error2;
555
- }
556
- strapi.log.error("requestMagicLink controller error:", error2);
557
- ctx.body = {
558
- success: false,
559
- message: "An error occurred while processing your request"
560
- };
561
- }
562
- },
563
- /**
564
- * Reset password using custom JWT token
565
- * POST /api/firebase-authentication/resetPasswordWithToken
566
- * Public endpoint - token provides authentication
567
- *
568
- * @param ctx - Koa context with { token, newPassword } in body
569
- * @returns { success: true, message: "Password has been reset successfully" }
570
- */
571
- async resetPasswordWithToken(ctx) {
572
- strapi.log.debug("resetPasswordWithToken endpoint called");
573
- try {
574
- const { token, newPassword } = ctx.request.body || {};
575
- if (!token) {
576
- ctx.status = 400;
577
- ctx.body = { error: "Token is required" };
578
- return;
579
- }
580
- if (!newPassword) {
581
- ctx.status = 400;
582
- ctx.body = { error: "New password is required" };
583
- return;
584
- }
585
- const result = await strapi.plugin(pluginName).service("userService").resetPasswordWithToken(token, newPassword);
586
- ctx.body = result;
587
- } catch (error2) {
588
- strapi.log.error("resetPasswordWithToken controller error:", error2);
589
- if (error2.name === "ValidationError") {
590
- ctx.status = 400;
591
- ctx.body = { error: error2.message };
592
- return;
593
- }
594
- if (error2.name === "NotFoundError") {
595
- ctx.status = 404;
596
- ctx.body = { error: error2.message };
597
- return;
598
- }
599
- ctx.status = 500;
600
- ctx.body = { error: "An error occurred while resetting your password" };
601
- }
602
- },
603
- /**
604
- * Send email verification - public endpoint
605
- * POST /api/firebase-authentication/sendVerificationEmail
606
- * Public endpoint - no authentication required
607
- */
608
- async sendVerificationEmail(ctx) {
609
- strapi.log.debug("sendVerificationEmail endpoint called");
610
- try {
611
- const { email: email2 } = ctx.request.body || {};
612
- ctx.body = await strapi.plugin(pluginName).service("firebaseService").sendVerificationEmail(email2);
613
- } catch (error2) {
614
- strapi.log.error("sendVerificationEmail controller error:", error2);
615
- if (error2.name === "ValidationError") {
616
- ctx.status = 400;
617
- } else {
618
- ctx.status = 500;
619
- }
620
- ctx.body = { error: error2.message };
621
- }
622
- },
623
- /**
624
- * Verify email using custom JWT token
625
- * POST /api/firebase-authentication/verifyEmail
626
- * Public endpoint - token provides authentication
627
- *
628
- * @param ctx - Koa context with { token } in body
629
- * @returns { success: true, message: "Email verified successfully" }
630
- */
631
- async verifyEmail(ctx) {
632
- strapi.log.debug("verifyEmail endpoint called");
633
- try {
634
- const { token } = ctx.request.body || {};
635
- if (!token) {
636
- ctx.status = 400;
637
- ctx.body = { error: "Token is required" };
638
- return;
639
- }
640
- const result = await strapi.plugin(pluginName).service("firebaseService").verifyEmail(token);
641
- ctx.body = result;
642
- } catch (error2) {
643
- strapi.log.error("verifyEmail controller error:", error2);
644
- if (error2.name === "ValidationError") {
645
- ctx.status = 400;
646
- ctx.body = { error: error2.message };
647
- return;
648
- }
649
- if (error2.name === "NotFoundError") {
650
- ctx.status = 404;
651
- ctx.body = { error: error2.message };
652
- return;
653
- }
654
- ctx.status = 500;
655
- ctx.body = { error: "An error occurred while verifying your email" };
656
- }
657
- }
658
- };
659
372
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
660
373
  function getDefaultExportFromCjs(x) {
661
374
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
@@ -670,7 +383,7 @@ var lodash = { exports: {} };
670
383
  * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
671
384
  */
672
385
  lodash.exports;
673
- (function(module, exports) {
386
+ (function(module, exports$1) {
674
387
  (function() {
675
388
  var undefined$1;
676
389
  var VERSION = "4.17.21";
@@ -998,7 +711,7 @@ lodash.exports;
998
711
  var freeGlobal2 = typeof commonjsGlobal == "object" && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal;
999
712
  var freeSelf2 = typeof self == "object" && self && self.Object === Object && self;
1000
713
  var root2 = freeGlobal2 || freeSelf2 || Function("return this")();
1001
- var freeExports = exports && !exports.nodeType && exports;
714
+ var freeExports = exports$1 && !exports$1.nodeType && exports$1;
1002
715
  var freeModule = freeExports && true && module && !module.nodeType && module;
1003
716
  var moduleExports = freeModule && freeModule.exports === freeExports;
1004
717
  var freeProcess = moduleExports && freeGlobal2.process;
@@ -6228,7 +5941,7 @@ var lodash_min = { exports: {} };
6228
5941
  * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
6229
5942
  */
6230
5943
  lodash_min.exports;
6231
- (function(module, exports) {
5944
+ (function(module, exports$1) {
6232
5945
  (function() {
6233
5946
  function n(n2, t3, r2) {
6234
5947
  switch (r2.length) {
@@ -6661,7 +6374,7 @@ lodash_min.exports;
6661
6374
  "œ": "oe",
6662
6375
  "ʼn": "'n",
6663
6376
  "ſ": "s"
6664
- }, 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 && !exports.nodeType && exports, ue = ee && true && module && !module.nodeType && module, ie = ue && ue.exports === ee, oe = ie && ne.process, fe = function() {
6377
+ }, 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 && module && !module.nodeType && module, ie = ue && ue.exports === ee, oe = ie && ne.process, fe = function() {
6665
6378
  try {
6666
6379
  var n2 = ue && ue.require && ue.require("util").types;
6667
6380
  return n2 ? n2 : oe && oe.binding && oe.binding("util");
@@ -9334,8 +9047,8 @@ lodash_min.exports;
9334
9047
  })(lodash_min, lodash_min.exports);
9335
9048
  var lodash_minExports = lodash_min.exports;
9336
9049
  var _mapping = {};
9337
- (function(exports) {
9338
- exports.aliasToReal = {
9050
+ (function(exports$1) {
9051
+ exports$1.aliasToReal = {
9339
9052
  // Lodash aliases.
9340
9053
  "each": "forEach",
9341
9054
  "eachRight": "forEachRight",
@@ -9400,7 +9113,7 @@ var _mapping = {};
9400
9113
  "whereEq": "isMatch",
9401
9114
  "zipObj": "zipObject"
9402
9115
  };
9403
- exports.aryMethod = {
9116
+ exports$1.aryMethod = {
9404
9117
  "1": [
9405
9118
  "assignAll",
9406
9119
  "assignInAll",
@@ -9635,12 +9348,12 @@ var _mapping = {};
9635
9348
  "updateWith"
9636
9349
  ]
9637
9350
  };
9638
- exports.aryRearg = {
9351
+ exports$1.aryRearg = {
9639
9352
  "2": [1, 0],
9640
9353
  "3": [2, 0, 1],
9641
9354
  "4": [3, 2, 0, 1]
9642
9355
  };
9643
- exports.iterateeAry = {
9356
+ exports$1.iterateeAry = {
9644
9357
  "dropRightWhile": 1,
9645
9358
  "dropWhile": 1,
9646
9359
  "every": 1,
@@ -9678,11 +9391,11 @@ var _mapping = {};
9678
9391
  "times": 1,
9679
9392
  "transform": 2
9680
9393
  };
9681
- exports.iterateeRearg = {
9394
+ exports$1.iterateeRearg = {
9682
9395
  "mapKeys": [1],
9683
9396
  "reduceRight": [1, 0]
9684
9397
  };
9685
- exports.methodRearg = {
9398
+ exports$1.methodRearg = {
9686
9399
  "assignInAllWith": [1, 0],
9687
9400
  "assignInWith": [1, 2, 0],
9688
9401
  "assignAllWith": [1, 0],
@@ -9713,7 +9426,7 @@ var _mapping = {};
9713
9426
  "xorWith": [1, 2, 0],
9714
9427
  "zipWith": [1, 2, 0]
9715
9428
  };
9716
- exports.methodSpread = {
9429
+ exports$1.methodSpread = {
9717
9430
  "assignAll": { "start": 0 },
9718
9431
  "assignAllWith": { "start": 0 },
9719
9432
  "assignInAll": { "start": 0 },
@@ -9729,7 +9442,7 @@ var _mapping = {};
9729
9442
  "without": { "start": 1 },
9730
9443
  "zipAll": { "start": 0 }
9731
9444
  };
9732
- exports.mutate = {
9445
+ exports$1.mutate = {
9733
9446
  "array": {
9734
9447
  "fill": true,
9735
9448
  "pull": true,
@@ -9766,8 +9479,8 @@ var _mapping = {};
9766
9479
  "updateWith": true
9767
9480
  }
9768
9481
  };
9769
- exports.realToAlias = function() {
9770
- var hasOwnProperty2 = Object.prototype.hasOwnProperty, object2 = exports.aliasToReal, result = {};
9482
+ exports$1.realToAlias = function() {
9483
+ var hasOwnProperty2 = Object.prototype.hasOwnProperty, object2 = exports$1.aliasToReal, result = {};
9771
9484
  for (var key in object2) {
9772
9485
  var value = object2[key];
9773
9486
  if (hasOwnProperty2.call(result, value)) {
@@ -9778,7 +9491,7 @@ var _mapping = {};
9778
9491
  }
9779
9492
  return result;
9780
9493
  }();
9781
- exports.remap = {
9494
+ exports$1.remap = {
9782
9495
  "assignAll": "assign",
9783
9496
  "assignAllWith": "assignWith",
9784
9497
  "assignInAll": "assignIn",
@@ -9812,7 +9525,7 @@ var _mapping = {};
9812
9525
  "trimCharsStart": "trimStart",
9813
9526
  "zipAll": "zip"
9814
9527
  };
9815
- exports.skipFixed = {
9528
+ exports$1.skipFixed = {
9816
9529
  "castArray": true,
9817
9530
  "flow": true,
9818
9531
  "flowRight": true,
@@ -9821,7 +9534,7 @@ var _mapping = {};
9821
9534
  "rearg": true,
9822
9535
  "runInContext": true
9823
9536
  };
9824
- exports.skipRearg = {
9537
+ exports$1.skipRearg = {
9825
9538
  "add": true,
9826
9539
  "assign": true,
9827
9540
  "assignIn": true,
@@ -10275,13 +9988,13 @@ const traverseEntity = async (visitor2, options2, entity) => {
10275
9988
  if (fp.isNil(value) || fp.isNil(attribute)) {
10276
9989
  continue;
10277
9990
  }
10278
- parent = {
10279
- schema: schema2,
10280
- key,
10281
- attribute,
10282
- path: newPath
10283
- };
10284
9991
  if (isRelationalAttribute(attribute)) {
9992
+ parent = {
9993
+ schema: schema2,
9994
+ key,
9995
+ attribute,
9996
+ path: newPath
9997
+ };
10285
9998
  const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
10286
9999
  const method = isMorphRelation ? traverseMorphRelationTarget : traverseRelationTarget(getModel(attribute.target));
10287
10000
  if (fp.isArray(value)) {
@@ -10300,6 +10013,12 @@ const traverseEntity = async (visitor2, options2, entity) => {
10300
10013
  continue;
10301
10014
  }
10302
10015
  if (isMediaAttribute(attribute)) {
10016
+ parent = {
10017
+ schema: schema2,
10018
+ key,
10019
+ attribute,
10020
+ path: newPath
10021
+ };
10303
10022
  if (fp.isArray(value)) {
10304
10023
  const res = new Array(value.length);
10305
10024
  for (let i2 = 0; i2 < value.length; i2 += 1) {
@@ -10316,6 +10035,12 @@ const traverseEntity = async (visitor2, options2, entity) => {
10316
10035
  continue;
10317
10036
  }
10318
10037
  if (attribute.type === "component") {
10038
+ parent = {
10039
+ schema: schema2,
10040
+ key,
10041
+ attribute,
10042
+ path: newPath
10043
+ };
10319
10044
  const targetSchema = getModel(attribute.component);
10320
10045
  if (fp.isArray(value)) {
10321
10046
  const res = new Array(value.length);
@@ -10333,6 +10058,12 @@ const traverseEntity = async (visitor2, options2, entity) => {
10333
10058
  continue;
10334
10059
  }
10335
10060
  if (attribute.type === "dynamiczone" && fp.isArray(value)) {
10061
+ parent = {
10062
+ schema: schema2,
10063
+ key,
10064
+ attribute,
10065
+ path: newPath
10066
+ };
10336
10067
  const res = new Array(value.length);
10337
10068
  for (let i2 = 0; i2 < value.length; i2 += 1) {
10338
10069
  const arrayPath = {
@@ -10357,7 +10088,7 @@ const createVisitorUtils = ({ data }) => ({
10357
10088
  });
10358
10089
  fp.curry(traverseEntity);
10359
10090
  var dist = { exports: {} };
10360
- (function(module, exports) {
10091
+ (function(module, exports$1) {
10361
10092
  !function(t2, n) {
10362
10093
  module.exports = n(require$$0$2, crypto$1);
10363
10094
  }(commonjsGlobal, function(t2, n) {
@@ -11905,9 +11636,9 @@ function stubFalse() {
11905
11636
  }
11906
11637
  var stubFalse_1 = stubFalse;
11907
11638
  isBuffer$2.exports;
11908
- (function(module, exports) {
11639
+ (function(module, exports$1) {
11909
11640
  var root2 = _root, stubFalse2 = stubFalse_1;
11910
- var freeExports = exports && !exports.nodeType && exports;
11641
+ var freeExports = exports$1 && !exports$1.nodeType && exports$1;
11911
11642
  var freeModule = freeExports && true && module && !module.nodeType && module;
11912
11643
  var moduleExports = freeModule && freeModule.exports === freeExports;
11913
11644
  var Buffer2 = moduleExports ? root2.Buffer : void 0;
@@ -11934,9 +11665,9 @@ function baseUnary$1(func) {
11934
11665
  var _baseUnary = baseUnary$1;
11935
11666
  var _nodeUtil = { exports: {} };
11936
11667
  _nodeUtil.exports;
11937
- (function(module, exports) {
11668
+ (function(module, exports$1) {
11938
11669
  var freeGlobal2 = _freeGlobal;
11939
- var freeExports = exports && !exports.nodeType && exports;
11670
+ var freeExports = exports$1 && !exports$1.nodeType && exports$1;
11940
11671
  var freeModule = freeExports && true && module && !module.nodeType && module;
11941
11672
  var moduleExports = freeModule && freeModule.exports === freeExports;
11942
11673
  var freeProcess = moduleExports && freeGlobal2.process;
@@ -14895,7 +14626,7 @@ function toIdentifier(str2) {
14895
14626
  Object.defineProperty(func, "name", desc);
14896
14627
  }
14897
14628
  }
14898
- function populateConstructorExports(exports, codes2, HttpError) {
14629
+ function populateConstructorExports(exports$1, codes2, HttpError) {
14899
14630
  codes2.forEach(function forEachCode(code) {
14900
14631
  var CodeError;
14901
14632
  var name = toIdentifier2(statuses$1.message[code]);
@@ -14908,8 +14639,8 @@ function toIdentifier(str2) {
14908
14639
  break;
14909
14640
  }
14910
14641
  if (CodeError) {
14911
- exports[code] = CodeError;
14912
- exports[name] = CodeError;
14642
+ exports$1[code] = CodeError;
14643
+ exports$1[name] = CodeError;
14913
14644
  }
14914
14645
  });
14915
14646
  }
@@ -14931,7 +14662,7 @@ const formatYupErrors = (yupError) => ({
14931
14662
  message: yupError.message
14932
14663
  });
14933
14664
  let ApplicationError$2 = class ApplicationError extends Error {
14934
- constructor(message = "An application error occured", details = {}) {
14665
+ constructor(message = "An application error occurred", details = {}) {
14935
14666
  super();
14936
14667
  this.name = "ApplicationError";
14937
14668
  this.message = message;
@@ -18127,8 +17858,8 @@ pkgDir$1.exports.sync = (cwd2) => {
18127
17858
  };
18128
17859
  var pkgDirExports = pkgDir$1.exports;
18129
17860
  var utils$8 = {};
18130
- (function(exports) {
18131
- exports.isInteger = (num) => {
17861
+ (function(exports$1) {
17862
+ exports$1.isInteger = (num) => {
18132
17863
  if (typeof num === "number") {
18133
17864
  return Number.isInteger(num);
18134
17865
  }
@@ -18137,13 +17868,13 @@ var utils$8 = {};
18137
17868
  }
18138
17869
  return false;
18139
17870
  };
18140
- exports.find = (node, type2) => node.nodes.find((node2) => node2.type === type2);
18141
- exports.exceedsLimit = (min, max, step = 1, limit) => {
17871
+ exports$1.find = (node, type2) => node.nodes.find((node2) => node2.type === type2);
17872
+ exports$1.exceedsLimit = (min, max, step = 1, limit) => {
18142
17873
  if (limit === false) return false;
18143
- if (!exports.isInteger(min) || !exports.isInteger(max)) return false;
17874
+ if (!exports$1.isInteger(min) || !exports$1.isInteger(max)) return false;
18144
17875
  return (Number(max) - Number(min)) / Number(step) >= limit;
18145
17876
  };
18146
- exports.escapeNode = (block, n = 0, type2) => {
17877
+ exports$1.escapeNode = (block, n = 0, type2) => {
18147
17878
  const node = block.nodes[n];
18148
17879
  if (!node) return;
18149
17880
  if (type2 && node.type === type2 || node.type === "open" || node.type === "close") {
@@ -18153,7 +17884,7 @@ var utils$8 = {};
18153
17884
  }
18154
17885
  }
18155
17886
  };
18156
- exports.encloseBrace = (node) => {
17887
+ exports$1.encloseBrace = (node) => {
18157
17888
  if (node.type !== "brace") return false;
18158
17889
  if (node.commas >> 0 + node.ranges >> 0 === 0) {
18159
17890
  node.invalid = true;
@@ -18161,7 +17892,7 @@ var utils$8 = {};
18161
17892
  }
18162
17893
  return false;
18163
17894
  };
18164
- exports.isInvalidBrace = (block) => {
17895
+ exports$1.isInvalidBrace = (block) => {
18165
17896
  if (block.type !== "brace") return false;
18166
17897
  if (block.invalid === true || block.dollar) return true;
18167
17898
  if (block.commas >> 0 + block.ranges >> 0 === 0) {
@@ -18174,18 +17905,18 @@ var utils$8 = {};
18174
17905
  }
18175
17906
  return false;
18176
17907
  };
18177
- exports.isOpenOrClose = (node) => {
17908
+ exports$1.isOpenOrClose = (node) => {
18178
17909
  if (node.type === "open" || node.type === "close") {
18179
17910
  return true;
18180
17911
  }
18181
17912
  return node.open === true || node.close === true;
18182
17913
  };
18183
- exports.reduce = (nodes) => nodes.reduce((acc, node) => {
17914
+ exports$1.reduce = (nodes) => nodes.reduce((acc, node) => {
18184
17915
  if (node.type === "text") acc.push(node.value);
18185
17916
  if (node.type === "range") node.type = "text";
18186
17917
  return acc;
18187
17918
  }, []);
18188
- exports.flatten = (...args) => {
17919
+ exports$1.flatten = (...args) => {
18189
17920
  const result = [];
18190
17921
  const flat = (arr) => {
18191
17922
  for (let i = 0; i < arr.length; i++) {
@@ -19287,7 +19018,7 @@ var constants$5 = {
19287
19018
  return win32 === true ? WINDOWS_CHARS : POSIX_CHARS;
19288
19019
  }
19289
19020
  };
19290
- (function(exports) {
19021
+ (function(exports$1) {
19291
19022
  const path2 = require$$0__default;
19292
19023
  const win32 = process.platform === "win32";
19293
19024
  const {
@@ -19296,36 +19027,36 @@ var constants$5 = {
19296
19027
  REGEX_SPECIAL_CHARS,
19297
19028
  REGEX_SPECIAL_CHARS_GLOBAL
19298
19029
  } = constants$5;
19299
- exports.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
19300
- exports.hasRegexChars = (str2) => REGEX_SPECIAL_CHARS.test(str2);
19301
- exports.isRegexChar = (str2) => str2.length === 1 && exports.hasRegexChars(str2);
19302
- exports.escapeRegex = (str2) => str2.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
19303
- exports.toPosixSlashes = (str2) => str2.replace(REGEX_BACKSLASH, "/");
19304
- exports.removeBackslashes = (str2) => {
19030
+ exports$1.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
19031
+ exports$1.hasRegexChars = (str2) => REGEX_SPECIAL_CHARS.test(str2);
19032
+ exports$1.isRegexChar = (str2) => str2.length === 1 && exports$1.hasRegexChars(str2);
19033
+ exports$1.escapeRegex = (str2) => str2.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
19034
+ exports$1.toPosixSlashes = (str2) => str2.replace(REGEX_BACKSLASH, "/");
19035
+ exports$1.removeBackslashes = (str2) => {
19305
19036
  return str2.replace(REGEX_REMOVE_BACKSLASH, (match) => {
19306
19037
  return match === "\\" ? "" : match;
19307
19038
  });
19308
19039
  };
19309
- exports.supportsLookbehinds = () => {
19040
+ exports$1.supportsLookbehinds = () => {
19310
19041
  const segs = process.version.slice(1).split(".").map(Number);
19311
19042
  if (segs.length === 3 && segs[0] >= 9 || segs[0] === 8 && segs[1] >= 10) {
19312
19043
  return true;
19313
19044
  }
19314
19045
  return false;
19315
19046
  };
19316
- exports.isWindows = (options2) => {
19047
+ exports$1.isWindows = (options2) => {
19317
19048
  if (options2 && typeof options2.windows === "boolean") {
19318
19049
  return options2.windows;
19319
19050
  }
19320
19051
  return win32 === true || path2.sep === "\\";
19321
19052
  };
19322
- exports.escapeLast = (input, char, lastIdx) => {
19053
+ exports$1.escapeLast = (input, char, lastIdx) => {
19323
19054
  const idx = input.lastIndexOf(char, lastIdx);
19324
19055
  if (idx === -1) return input;
19325
- if (input[idx - 1] === "\\") return exports.escapeLast(input, char, idx - 1);
19056
+ if (input[idx - 1] === "\\") return exports$1.escapeLast(input, char, idx - 1);
19326
19057
  return `${input.slice(0, idx)}\\${input.slice(idx)}`;
19327
19058
  };
19328
- exports.removePrefix = (input, state = {}) => {
19059
+ exports$1.removePrefix = (input, state = {}) => {
19329
19060
  let output = input;
19330
19061
  if (output.startsWith("./")) {
19331
19062
  output = output.slice(2);
@@ -19333,7 +19064,7 @@ var constants$5 = {
19333
19064
  }
19334
19065
  return output;
19335
19066
  };
19336
- exports.wrapOutput = (input, state = {}, options2 = {}) => {
19067
+ exports$1.wrapOutput = (input, state = {}, options2 = {}) => {
19337
19068
  const prepend = options2.contains ? "" : "^";
19338
19069
  const append2 = options2.contains ? "" : "$";
19339
19070
  let output = `${prepend}(?:${input})${append2}`;
@@ -22888,6 +22619,18 @@ function charFromCodepoint(c) {
22888
22619
  (c - 65536 & 1023) + 56320
22889
22620
  );
22890
22621
  }
22622
+ function setProperty(object2, key, value) {
22623
+ if (key === "__proto__") {
22624
+ Object.defineProperty(object2, key, {
22625
+ configurable: true,
22626
+ enumerable: true,
22627
+ writable: true,
22628
+ value
22629
+ });
22630
+ } else {
22631
+ object2[key] = value;
22632
+ }
22633
+ }
22891
22634
  var simpleEscapeCheck = new Array(256);
22892
22635
  var simpleEscapeMap = new Array(256);
22893
22636
  for (var i = 0; i < 256; i++) {
@@ -22994,7 +22737,7 @@ function mergeMappings(state, destination, source, overridableKeys) {
22994
22737
  for (index2 = 0, quantity = sourceKeys.length; index2 < quantity; index2 += 1) {
22995
22738
  key = sourceKeys[index2];
22996
22739
  if (!_hasOwnProperty$1.call(destination, key)) {
22997
- destination[key] = source[key];
22740
+ setProperty(destination, key, source[key]);
22998
22741
  overridableKeys[key] = true;
22999
22742
  }
23000
22743
  }
@@ -23033,7 +22776,7 @@ function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valu
23033
22776
  state.position = startPos || state.position;
23034
22777
  throwError(state, "duplicated mapping key");
23035
22778
  }
23036
- _result[keyNode] = valueNode;
22779
+ setProperty(_result, keyNode, valueNode);
23037
22780
  delete overridableKeys[keyNode];
23038
22781
  }
23039
22782
  return _result;
@@ -28936,6 +28679,247 @@ _enum([
28936
28679
  "published"
28937
28680
  ]).describe("Filter by publication status");
28938
28681
  string().describe("Search query string");
28682
+ const pluginName = "firebase-authentication";
28683
+ const PLUGIN_NAME = "firebase-authentication";
28684
+ const PLUGIN_UID = `plugin::${PLUGIN_NAME}`;
28685
+ const CONFIG_CONTENT_TYPE = `${PLUGIN_UID}.firebase-authentication-configuration`;
28686
+ const DEFAULT_PASSWORD_RESET_URL = "http://localhost:3000/reset-password";
28687
+ const DEFAULT_PASSWORD_REGEX = "^.{6,}$";
28688
+ const DEFAULT_PASSWORD_MESSAGE = "Password must be at least 6 characters long";
28689
+ const DEFAULT_RESET_EMAIL_SUBJECT = "Reset Your Password";
28690
+ const ERROR_MESSAGES = {
28691
+ FIREBASE_NOT_INITIALIZED: "Firebase is not initialized. Please upload Firebase service account configuration via Settings → Firebase Authentication.",
28692
+ INVALID_JSON: "Invalid JSON format. Please ensure you copied the entire JSON content correctly.",
28693
+ MISSING_DATA: "data is missing",
28694
+ SOMETHING_WENT_WRONG: "Something went wrong",
28695
+ AUTHENTICATION_FAILED: "Authentication failed",
28696
+ TOKEN_MISSING: "idToken is missing!",
28697
+ EMAIL_PASSWORD_REQUIRED: "Email and password are required",
28698
+ PASSWORD_REQUIRED: "Password is required",
28699
+ AUTHORIZATION_REQUIRED: "Authorization token is required",
28700
+ INVALID_TOKEN: "Invalid or expired token",
28701
+ USER_NOT_FOUND: "User not found",
28702
+ USER_NO_EMAIL: "User does not have an email address",
28703
+ FIREBASE_LINK_FAILED: "Failed to generate Firebase reset link",
28704
+ CONFIG_NOT_FOUND: "No config found",
28705
+ INVALID_SERVICE_ACCOUNT: "Invalid service account JSON",
28706
+ WEB_API_NOT_CONFIGURED: "Email/password authentication is not available. Web API Key is not configured.",
28707
+ RESET_URL_NOT_CONFIGURED: "Password reset URL is not configured",
28708
+ RESET_URL_MUST_BE_HTTPS: "Password reset URL must use HTTPS in production",
28709
+ RESET_URL_INVALID_FORMAT: "Password reset URL is not a valid URL format",
28710
+ USER_NOT_LINKED_FIREBASE: "User is not linked to Firebase authentication",
28711
+ OVERRIDE_USER_ID_REQUIRED: "Override user ID is required",
28712
+ EITHER_EMAIL_OR_PHONE_REQUIRED: "Either email or phoneNumber is required",
28713
+ DELETION_NO_CONFIG: "No Firebase configs exists for deletion"
28714
+ };
28715
+ const SUCCESS_MESSAGES = {
28716
+ FIREBASE_INITIALIZED: "Firebase successfully initialized",
28717
+ FIREBASE_CONFIG_DELETED: "Firebase config deleted and reinitialized",
28718
+ PASSWORD_RESET_EMAIL_SENT: "If an account with that email exists, a password reset link has been sent.",
28719
+ SERVER_RESTARTING: "SERVER IS RESTARTING"
28720
+ };
28721
+ const CONFIG_KEYS = {
28722
+ ENCRYPTION_KEY: `${PLUGIN_UID}.FIREBASE_JSON_ENCRYPTION_KEY`
28723
+ };
28724
+ const REQUIRED_FIELDS = {
28725
+ SERVICE_ACCOUNT: ["private_key", "client_email", "project_id", "type"],
28726
+ WEB_CONFIG: ["apiKey", "authDomain"]
28727
+ // These indicate wrong JSON type
28728
+ };
28729
+ const VALIDATION_MESSAGES = {
28730
+ INVALID_SERVICE_ACCOUNT: "Invalid Service Account JSON. Missing required fields:",
28731
+ 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.",
28732
+ 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!"
28733
+ };
28734
+ const firebaseController = {
28735
+ async validateToken(ctx) {
28736
+ strapi.log.debug("validateToken called");
28737
+ try {
28738
+ const { idToken, profileMetaData } = ctx.request.body || {};
28739
+ const populate2 = ctx.request.query.populate || [];
28740
+ if (!idToken) {
28741
+ return ctx.badRequest(ERROR_MESSAGES.TOKEN_MISSING);
28742
+ }
28743
+ const result = await strapi.plugin(pluginName).service("firebaseService").validateFirebaseToken(idToken, profileMetaData, populate2);
28744
+ ctx.body = result;
28745
+ } catch (error2) {
28746
+ strapi.log.error(`validateToken controller error: ${error2.message}`);
28747
+ if (error2.name === "ValidationError") {
28748
+ return ctx.badRequest(error2.message);
28749
+ }
28750
+ if (error2.name === "UnauthorizedError") {
28751
+ return ctx.unauthorized(error2.message);
28752
+ }
28753
+ throw error2;
28754
+ }
28755
+ },
28756
+ async deleteByEmail(email2) {
28757
+ try {
28758
+ const user = await strapi.firebase.auth().getUserByEmail(email2);
28759
+ await strapi.plugin(pluginName).service("firebaseService").delete(user.toJSON().uid);
28760
+ return user.toJSON();
28761
+ } catch (error2) {
28762
+ strapi.log.error("deleteByEmail error:", error2);
28763
+ throw error2;
28764
+ }
28765
+ },
28766
+ async overrideAccess(ctx) {
28767
+ try {
28768
+ const { overrideUserId } = ctx.request.body || {};
28769
+ const populate2 = ctx.request.query.populate || [];
28770
+ const result = await strapi.plugin(pluginName).service("firebaseService").overrideFirebaseAccess(overrideUserId, populate2);
28771
+ ctx.body = result;
28772
+ } catch (error2) {
28773
+ if (error2.name === "ValidationError") {
28774
+ return ctx.badRequest(error2.message);
28775
+ }
28776
+ if (error2.name === "NotFoundError") {
28777
+ return ctx.notFound(error2.message);
28778
+ }
28779
+ throw error2;
28780
+ }
28781
+ },
28782
+ /**
28783
+ * Controller method for email/password authentication
28784
+ * Handles the `/api/firebase-authentication/emailLogin` endpoint
28785
+ *
28786
+ * @param ctx - Koa context object
28787
+ * @returns Promise that sets ctx.body with user data and JWT or error message
28788
+ *
28789
+ * @remarks
28790
+ * This controller acts as a proxy to Firebase's Identity Toolkit API,
28791
+ * allowing users to authenticate with email/password and receive a Strapi JWT.
28792
+ *
28793
+ * HTTP Status Codes:
28794
+ * - `400`: Validation errors (missing credentials, invalid email/password)
28795
+ * - `500`: Server errors (missing configuration, Firebase API issues)
28796
+ */
28797
+ async emailLogin(ctx) {
28798
+ strapi.log.debug("emailLogin controller");
28799
+ try {
28800
+ const { email: email2, password } = ctx.request.body || {};
28801
+ const populate2 = ctx.request.query.populate || [];
28802
+ const result = await strapi.plugin(pluginName).service("firebaseService").emailLogin(email2, password, populate2);
28803
+ ctx.body = result;
28804
+ } catch (error2) {
28805
+ strapi.log.error("emailLogin controller error:", error2);
28806
+ throw error2;
28807
+ }
28808
+ },
28809
+ /**
28810
+ * Forgot password - sends reset email
28811
+ * POST /api/firebase-authentication/forgotPassword
28812
+ * Public endpoint - no authentication required
28813
+ */
28814
+ async forgotPassword(ctx) {
28815
+ strapi.log.debug("forgotPassword endpoint called");
28816
+ try {
28817
+ const { email: email2 } = ctx.request.body || {};
28818
+ ctx.body = await strapi.plugin(pluginName).service("firebaseService").forgotPassword(email2);
28819
+ } catch (error2) {
28820
+ strapi.log.error("forgotPassword controller error:", error2);
28821
+ throw error2;
28822
+ }
28823
+ },
28824
+ /**
28825
+ * Reset password - authenticated password change
28826
+ * POST /api/firebase-authentication/resetPassword
28827
+ * Authenticated endpoint - requires valid JWT (enforced by is-authenticated policy)
28828
+ * Used for admin-initiated resets or user self-service password changes
28829
+ * NOT used for forgot password email flow (which uses Firebase's hosted UI)
28830
+ */
28831
+ async resetPassword(ctx) {
28832
+ strapi.log.debug("resetPassword endpoint called");
28833
+ try {
28834
+ const { password } = ctx.request.body || {};
28835
+ const user = ctx.state.user;
28836
+ const populate2 = ctx.request.query.populate || [];
28837
+ ctx.body = await strapi.plugin(pluginName).service("firebaseService").resetPassword(password, user, populate2);
28838
+ } catch (error2) {
28839
+ strapi.log.error("resetPassword controller error:", error2);
28840
+ throw error2;
28841
+ }
28842
+ },
28843
+ async requestMagicLink(ctx) {
28844
+ try {
28845
+ const { email: email2 } = ctx.request.body || {};
28846
+ const result = await strapi.plugin("firebase-authentication").service("firebaseService").requestMagicLink(email2);
28847
+ ctx.body = result;
28848
+ } catch (error2) {
28849
+ if (error2.name === "ValidationError" || error2.name === "ApplicationError") {
28850
+ throw error2;
28851
+ }
28852
+ strapi.log.error("requestMagicLink controller error:", error2);
28853
+ ctx.body = {
28854
+ success: false,
28855
+ message: "An error occurred while processing your request"
28856
+ };
28857
+ }
28858
+ },
28859
+ /**
28860
+ * Reset password using custom JWT token
28861
+ * POST /api/firebase-authentication/resetPasswordWithToken
28862
+ * Public endpoint - token provides authentication
28863
+ *
28864
+ * @param ctx - Koa context with { token, newPassword } in body
28865
+ * @returns { success: true, message: "Password has been reset successfully" }
28866
+ */
28867
+ async resetPasswordWithToken(ctx) {
28868
+ strapi.log.debug("resetPasswordWithToken endpoint called");
28869
+ try {
28870
+ const { token, newPassword } = ctx.request.body || {};
28871
+ if (!token) {
28872
+ throw new ValidationError$1("Token is required");
28873
+ }
28874
+ if (!newPassword) {
28875
+ throw new ValidationError$1("New password is required");
28876
+ }
28877
+ const result = await strapi.plugin(pluginName).service("userService").resetPasswordWithToken(token, newPassword);
28878
+ ctx.body = result;
28879
+ } catch (error2) {
28880
+ strapi.log.error("resetPasswordWithToken controller error:", error2);
28881
+ throw error2;
28882
+ }
28883
+ },
28884
+ /**
28885
+ * Send email verification - public endpoint
28886
+ * POST /api/firebase-authentication/sendVerificationEmail
28887
+ * Authenticated endpoint - sends verification email to the logged-in user's email
28888
+ */
28889
+ async sendVerificationEmail(ctx) {
28890
+ strapi.log.debug("sendVerificationEmail endpoint called");
28891
+ try {
28892
+ const user = ctx.state.user;
28893
+ const email2 = user.email;
28894
+ ctx.body = await strapi.plugin(pluginName).service("firebaseService").sendVerificationEmail(email2);
28895
+ } catch (error2) {
28896
+ strapi.log.error("sendVerificationEmail controller error:", error2);
28897
+ throw error2;
28898
+ }
28899
+ },
28900
+ /**
28901
+ * Verify email using custom JWT token
28902
+ * POST /api/firebase-authentication/verifyEmail
28903
+ * Public endpoint - token provides authentication
28904
+ *
28905
+ * @param ctx - Koa context with { token } in body
28906
+ * @returns { success: true, message: "Email verified successfully" }
28907
+ */
28908
+ async verifyEmail(ctx) {
28909
+ strapi.log.debug("verifyEmail endpoint called");
28910
+ try {
28911
+ const { token } = ctx.request.body || {};
28912
+ if (!token) {
28913
+ throw new ValidationError$1("Token is required");
28914
+ }
28915
+ const result = await strapi.plugin(pluginName).service("firebaseService").verifyEmail(token);
28916
+ ctx.body = result;
28917
+ } catch (error2) {
28918
+ strapi.log.error("verifyEmail controller error:", error2);
28919
+ throw error2;
28920
+ }
28921
+ }
28922
+ };
28939
28923
  const STRAPI_DESTINATION = "strapi";
28940
28924
  const FIREBASE_DESTINATION = "firebase";
28941
28925
  const userController = {
@@ -29197,7 +29181,16 @@ const controllers = {
29197
29181
  settingsController
29198
29182
  };
29199
29183
  const middlewares = {};
29200
- const policies = {};
29184
+ const isAuthenticated = async (policyContext) => {
29185
+ const user = policyContext.state.user;
29186
+ if (!user) {
29187
+ throw new UnauthorizedError("Authentication required");
29188
+ }
29189
+ return true;
29190
+ };
29191
+ const policies = {
29192
+ "is-authenticated": isAuthenticated
29193
+ };
29201
29194
  const settingsRoute = [
29202
29195
  {
29203
29196
  method: "POST",
@@ -29347,9 +29340,7 @@ const contentApi = {
29347
29340
  path: "/resetPassword",
29348
29341
  handler: "firebaseController.resetPassword",
29349
29342
  config: {
29350
- auth: false,
29351
- // Public endpoint - authenticated password change, requires valid JWT in Authorization header
29352
- policies: []
29343
+ policies: ["plugin::firebase-authentication.is-authenticated"]
29353
29344
  }
29354
29345
  },
29355
29346
  {
@@ -29387,9 +29378,7 @@ const contentApi = {
29387
29378
  path: "/sendVerificationEmail",
29388
29379
  handler: "firebaseController.sendVerificationEmail",
29389
29380
  config: {
29390
- auth: false,
29391
- // Public endpoint - sends email verification link
29392
- policies: []
29381
+ policies: ["plugin::firebase-authentication.is-authenticated"]
29393
29382
  }
29394
29383
  },
29395
29384
  {
@@ -30898,20 +30887,17 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30898
30887
  * 2. User-initiated password change (when already authenticated)
30899
30888
  *
30900
30889
  * NOT used for forgot password email flow - that now uses Firebase's hosted UI
30890
+ *
30891
+ * @param password - New password to set
30892
+ * @param user - Authenticated user from ctx.state.user (populated by is-authenticated policy)
30893
+ * @param populate - Fields to populate in response
30901
30894
  */
30902
- resetPassword: async (password, token, populate2) => {
30895
+ resetPassword: async (password, user, populate2) => {
30903
30896
  if (!password) {
30904
30897
  throw new ValidationError$1("Password is required");
30905
30898
  }
30906
- if (!token) {
30907
- throw new UnauthorizedError("Authorization token is required");
30908
- }
30909
- let decoded;
30910
- try {
30911
- const jwtService = strapi2.plugin("users-permissions").service("jwt");
30912
- decoded = await jwtService.verify(token);
30913
- } catch (error2) {
30914
- throw new UnauthorizedError("Invalid or expired token");
30899
+ if (!user || !user.id) {
30900
+ throw new UnauthorizedError("Authentication required");
30915
30901
  }
30916
30902
  const config2 = await strapi2.plugin("firebase-authentication").service("settingsService").getFirebaseConfigJson();
30917
30903
  const passwordRegex = config2?.passwordRequirementsRegex || "^.{6,}$";
@@ -30922,8 +30908,7 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30922
30908
  }
30923
30909
  try {
30924
30910
  const strapiUser = await strapi2.db.query("plugin::users-permissions.user").findOne({
30925
- where: { id: decoded.id }
30926
- // Use numeric id from JWT
30911
+ where: { id: user.id }
30927
30912
  });
30928
30913
  if (!strapiUser) {
30929
30914
  throw new NotFoundError("User not found");
@@ -32585,7 +32570,7 @@ var TokenExpiredError_1 = TokenExpiredError$1;
32585
32570
  var jws$3 = {};
32586
32571
  var safeBuffer = { exports: {} };
32587
32572
  /*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
32588
- (function(module, exports) {
32573
+ (function(module, exports$1) {
32589
32574
  var buffer = require$$0$7;
32590
32575
  var Buffer2 = buffer.Buffer;
32591
32576
  function copyProps(src, dst) {
@@ -32596,8 +32581,8 @@ var safeBuffer = { exports: {} };
32596
32581
  if (Buffer2.from && Buffer2.alloc && Buffer2.allocUnsafe && Buffer2.allocUnsafeSlow) {
32597
32582
  module.exports = buffer;
32598
32583
  } else {
32599
- copyProps(buffer, exports);
32600
- exports.Buffer = SafeBuffer;
32584
+ copyProps(buffer, exports$1);
32585
+ exports$1.Buffer = SafeBuffer;
32601
32586
  }
32602
32587
  function SafeBuffer(arg, encodingOrOffset, length) {
32603
32588
  return Buffer2(arg, encodingOrOffset, length);
@@ -33112,7 +33097,12 @@ function jwsSign(opts) {
33112
33097
  return util$1.format("%s.%s", securedInput, signature);
33113
33098
  }
33114
33099
  function SignStream$1(opts) {
33115
- var secret = opts.secret || opts.privateKey || opts.key;
33100
+ var secret = opts.secret;
33101
+ secret = secret == null ? opts.privateKey : secret;
33102
+ secret = secret == null ? opts.key : secret;
33103
+ if (/^hs/i.test(opts.header.alg) === true && secret == null) {
33104
+ throw new TypeError("secret must be a string or buffer or a KeyObject");
33105
+ }
33116
33106
  var secretStream = new DataStream$1(secret);
33117
33107
  this.readable = true;
33118
33108
  this.header = opts.header;
@@ -33218,7 +33208,12 @@ function jwsDecode(jwsSig, opts) {
33218
33208
  }
33219
33209
  function VerifyStream$1(opts) {
33220
33210
  opts = opts || {};
33221
- var secretOrKey = opts.secret || opts.publicKey || opts.key;
33211
+ var secretOrKey = opts.secret;
33212
+ secretOrKey = secretOrKey == null ? opts.publicKey : secretOrKey;
33213
+ secretOrKey = secretOrKey == null ? opts.key : secretOrKey;
33214
+ if (/^hs/i.test(opts.algorithm) === true && secretOrKey == null) {
33215
+ throw new TypeError("secret must be a string or buffer or a KeyObject");
33216
+ }
33222
33217
  var secretStream = new DataStream(secretOrKey);
33223
33218
  this.readable = true;
33224
33219
  this.algorithm = opts.algorithm;
@@ -33461,19 +33456,19 @@ var constants$1 = {
33461
33456
  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) : () => {
33462
33457
  };
33463
33458
  var debug_1 = debug$1;
33464
- (function(module, exports) {
33459
+ (function(module, exports$1) {
33465
33460
  const {
33466
33461
  MAX_SAFE_COMPONENT_LENGTH: MAX_SAFE_COMPONENT_LENGTH2,
33467
33462
  MAX_SAFE_BUILD_LENGTH: MAX_SAFE_BUILD_LENGTH2,
33468
33463
  MAX_LENGTH: MAX_LENGTH2
33469
33464
  } = constants$1;
33470
33465
  const debug2 = debug_1;
33471
- exports = module.exports = {};
33472
- const re2 = exports.re = [];
33473
- const safeRe = exports.safeRe = [];
33474
- const src = exports.src = [];
33475
- const safeSrc = exports.safeSrc = [];
33476
- const t2 = exports.t = {};
33466
+ exports$1 = module.exports = {};
33467
+ const re2 = exports$1.re = [];
33468
+ const safeRe = exports$1.safeRe = [];
33469
+ const src = exports$1.src = [];
33470
+ const safeSrc = exports$1.safeSrc = [];
33471
+ const t2 = exports$1.t = {};
33477
33472
  let R = 0;
33478
33473
  const LETTERDASHNUMBER = "[a-zA-Z0-9-]";
33479
33474
  const safeRegexReplacements = [
@@ -33526,18 +33521,18 @@ var debug_1 = debug$1;
33526
33521
  createToken("COERCERTLFULL", src[t2.COERCEFULL], true);
33527
33522
  createToken("LONETILDE", "(?:~>?)");
33528
33523
  createToken("TILDETRIM", `(\\s*)${src[t2.LONETILDE]}\\s+`, true);
33529
- exports.tildeTrimReplace = "$1~";
33524
+ exports$1.tildeTrimReplace = "$1~";
33530
33525
  createToken("TILDE", `^${src[t2.LONETILDE]}${src[t2.XRANGEPLAIN]}$`);
33531
33526
  createToken("TILDELOOSE", `^${src[t2.LONETILDE]}${src[t2.XRANGEPLAINLOOSE]}$`);
33532
33527
  createToken("LONECARET", "(?:\\^)");
33533
33528
  createToken("CARETTRIM", `(\\s*)${src[t2.LONECARET]}\\s+`, true);
33534
- exports.caretTrimReplace = "$1^";
33529
+ exports$1.caretTrimReplace = "$1^";
33535
33530
  createToken("CARET", `^${src[t2.LONECARET]}${src[t2.XRANGEPLAIN]}$`);
33536
33531
  createToken("CARETLOOSE", `^${src[t2.LONECARET]}${src[t2.XRANGEPLAINLOOSE]}$`);
33537
33532
  createToken("COMPARATORLOOSE", `^${src[t2.GTLT]}\\s*(${src[t2.LOOSEPLAIN]})$|^$`);
33538
33533
  createToken("COMPARATOR", `^${src[t2.GTLT]}\\s*(${src[t2.FULLPLAIN]})$|^$`);
33539
33534
  createToken("COMPARATORTRIM", `(\\s*)${src[t2.GTLT]}\\s*(${src[t2.LOOSEPLAIN]}|${src[t2.XRANGEPLAIN]})`, true);
33540
- exports.comparatorTrimReplace = "$1$2$3";
33535
+ exports$1.comparatorTrimReplace = "$1$2$3";
33541
33536
  createToken("HYPHENRANGE", `^\\s*(${src[t2.XRANGEPLAIN]})\\s+-\\s+(${src[t2.XRANGEPLAIN]})\\s*$`);
33542
33537
  createToken("HYPHENRANGELOOSE", `^\\s*(${src[t2.XRANGEPLAINLOOSE]})\\s+-\\s+(${src[t2.XRANGEPLAINLOOSE]})\\s*$`);
33543
33538
  createToken("STAR", "(<|>)?=?\\s*\\*");