strapi-plugin-firebase-authentication 1.1.8 → 1.1.10

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.
@@ -167,8 +167,8 @@ const bootstrap = async ({ strapi: strapi2 }) => {
167
167
  }
168
168
  } catch (error2) {
169
169
  strapi2.log.error("Firebase initialization failed during bootstrap:");
170
- strapi2.log.error(" Error:", error2.message);
171
- strapi2.log.error(" Stack:", error2.stack);
170
+ strapi2.log.error(` Error: ${error2.message}`);
171
+ strapi2.log.error(` Stack: ${error2.stack}`);
172
172
  }
173
173
  await strapi2.admin.services.permission.actionProvider.registerMany(actions);
174
174
  if (strapi2.plugin("users-permissions")) {
@@ -178,14 +178,14 @@ const bootstrap = async ({ strapi: strapi2 }) => {
178
178
  if (process.env.RUN_FIREBASE_MIGRATION === "true") {
179
179
  const dryRun = process.env.DRY_RUN === "true";
180
180
  strapi2.log.info("");
181
- strapi2.log.info("🚀 Firebase migration triggered by RUN_FIREBASE_MIGRATION env variable");
181
+ strapi2.log.info("Firebase migration triggered by RUN_FIREBASE_MIGRATION env variable");
182
182
  await migrateFirebaseUserData(strapi2, dryRun);
183
183
  }
184
184
  setImmediate(async () => {
185
185
  try {
186
186
  await strapi2.plugin("firebase-authentication").service("autoLinkService").linkAllUsers(strapi2);
187
187
  } catch (error2) {
188
- strapi2.log.error("Auto-linking failed:", error2);
188
+ strapi2.log.error(`Auto-linking failed: ${error2.message}`);
189
189
  }
190
190
  });
191
191
  };
@@ -410,7 +410,7 @@ const firebaseController = {
410
410
  const result = await strapi.plugin(pluginName).service("firebaseService").validateFirebaseToken(idToken, profileMetaData, populate2);
411
411
  ctx.body = result;
412
412
  } catch (error2) {
413
- strapi.log.error("validateToken controller error:", error2.message);
413
+ strapi.log.error(`validateToken controller error: ${error2.message}`);
414
414
  if (error2.name === "ValidationError") {
415
415
  return ctx.badRequest(error2.message);
416
416
  }
@@ -420,9 +420,6 @@ const firebaseController = {
420
420
  throw error2;
421
421
  }
422
422
  },
423
- async createAlias(ctx) {
424
- ctx.body = await strapi.plugin(pluginName).service("firebaseService").createAlias(ctx);
425
- },
426
423
  async deleteByEmail(email2) {
427
424
  const user = await strapi.firebase.auth().getUserByEmail(email2);
428
425
  await strapi.plugin(pluginName).service("firebaseService").delete(user.toJSON().uid);
@@ -486,7 +483,8 @@ const firebaseController = {
486
483
  async forgotPassword(ctx) {
487
484
  strapi.log.debug("forgotPassword endpoint called");
488
485
  try {
489
- ctx.body = await strapi.plugin(pluginName).service("firebaseService").forgotPassword(ctx);
486
+ const { email: email2 } = ctx.request.body || {};
487
+ ctx.body = await strapi.plugin(pluginName).service("firebaseService").forgotPassword(email2);
490
488
  } catch (error2) {
491
489
  strapi.log.error("forgotPassword controller error:", error2);
492
490
  if (error2.name === "ValidationError") {
@@ -509,7 +507,10 @@ const firebaseController = {
509
507
  async resetPassword(ctx) {
510
508
  strapi.log.debug("resetPassword endpoint called");
511
509
  try {
512
- ctx.body = await strapi.plugin(pluginName).service("firebaseService").resetPassword(ctx);
510
+ const { password } = ctx.request.body || {};
511
+ const token = ctx.request.headers.authorization?.replace("Bearer ", "");
512
+ const populate2 = ctx.request.query.populate || [];
513
+ ctx.body = await strapi.plugin(pluginName).service("firebaseService").resetPassword(password, token, populate2);
513
514
  } catch (error2) {
514
515
  strapi.log.error("resetPassword controller error:", error2);
515
516
  if (error2.name === "ValidationError" || error2.name === "UnauthorizedError") {
@@ -522,7 +523,8 @@ const firebaseController = {
522
523
  },
523
524
  async requestMagicLink(ctx) {
524
525
  try {
525
- const result = await strapi.plugin("firebase-authentication").service("firebaseService").requestMagicLink(ctx);
526
+ const { email: email2 } = ctx.request.body || {};
527
+ const result = await strapi.plugin("firebase-authentication").service("firebaseService").requestMagicLink(email2);
526
528
  ctx.body = result;
527
529
  } catch (error2) {
528
530
  if (error2.name === "ValidationError" || error2.name === "ApplicationError") {
@@ -28905,11 +28907,12 @@ const userController = {
28905
28907
  };
28906
28908
  const settingsController = {
28907
28909
  setFirebaseConfigJson: async (ctx) => {
28908
- ctx.body = await strapi.plugin("firebase-authentication").service("settingsService").setFirebaseConfigJson(ctx);
28910
+ const requestBody = ctx.request.body;
28911
+ ctx.body = await strapi.plugin("firebase-authentication").service("settingsService").setFirebaseConfigJson(requestBody);
28909
28912
  },
28910
28913
  getFirebaseConfigJson: async (ctx) => {
28911
28914
  try {
28912
- const config2 = await strapi.plugin("firebase-authentication").service("settingsService").getFirebaseConfigJson(ctx);
28915
+ const config2 = await strapi.plugin("firebase-authentication").service("settingsService").getFirebaseConfigJson();
28913
28916
  if (!config2) {
28914
28917
  return ctx.send(null);
28915
28918
  }
@@ -28922,7 +28925,7 @@ const settingsController = {
28922
28925
  },
28923
28926
  async delFirebaseConfigJson(ctx) {
28924
28927
  try {
28925
- const isExist = await strapi.plugin("firebase-authentication").service("settingsService").delFirebaseConfigJson(ctx);
28928
+ const isExist = await strapi.plugin("firebase-authentication").service("settingsService").delFirebaseConfigJson();
28926
28929
  if (!isExist) {
28927
28930
  throw new NotFoundError("No Firebase configs exists for deletion");
28928
28931
  }
@@ -29249,7 +29252,7 @@ const settingsService = ({ strapi: strapi2 }) => {
29249
29252
  try {
29250
29253
  strapi2.log.info("Starting Firebase initialization...");
29251
29254
  const res = await strapi2.db.query(CONFIG_CONTENT_TYPE).findOne({ where: {} });
29252
- strapi2.log.debug("Found config:", !!res);
29255
+ strapi2.log.debug(`Found config: ${!!res}`);
29253
29256
  if (!res) {
29254
29257
  strapi2.log.debug("No config found, checking for existing Firebase app...");
29255
29258
  if (strapi2.firebase) {
@@ -29259,7 +29262,7 @@ const settingsService = ({ strapi: strapi2 }) => {
29259
29262
  return;
29260
29263
  }
29261
29264
  const jsonObject = res["firebase_config_json"];
29262
- strapi2.log.debug("Config JSON present:", !!jsonObject?.firebaseConfigJson);
29265
+ strapi2.log.debug(`Config JSON present: ${!!jsonObject?.firebaseConfigJson}`);
29263
29266
  if (!jsonObject || !jsonObject.firebaseConfigJson) {
29264
29267
  strapi2.log.debug("No valid JSON config, checking for existing Firebase app...");
29265
29268
  if (strapi2.firebase) {
@@ -29294,11 +29297,11 @@ const settingsService = ({ strapi: strapi2 }) => {
29294
29297
  strapi2.firebase = admin$1;
29295
29298
  strapi2.log.info("Firebase initialization complete - admin instance attached to strapi.firebase");
29296
29299
  } catch (initError) {
29297
- strapi2.log.error("Failed to initialize Firebase:", initError);
29300
+ strapi2.log.error(`Failed to initialize Firebase: ${initError.message}`);
29298
29301
  throw initError;
29299
29302
  }
29300
29303
  } catch (error2) {
29301
- strapi2.log.error("Firebase bootstrap error:", error2);
29304
+ strapi2.log.error(`Firebase bootstrap error: ${error2.message}`);
29302
29305
  }
29303
29306
  },
29304
29307
  /**
@@ -29346,7 +29349,7 @@ const settingsService = ({ strapi: strapi2 }) => {
29346
29349
  magicLinkExpiryHours: configObject.magicLinkExpiryHours || 1
29347
29350
  };
29348
29351
  } catch (error2) {
29349
- strapi2.log.error("Firebase config error:", error2);
29352
+ strapi2.log.error(`Firebase config error: ${error2.message}`);
29350
29353
  throw new ApplicationError$1("Error retrieving Firebase config", {
29351
29354
  error: error2.message
29352
29355
  });
@@ -29373,10 +29376,9 @@ const settingsService = ({ strapi: strapi2 }) => {
29373
29376
  * The service account JSON is encrypted using AES before storage,
29374
29377
  * while the Web API key and password settings are stored in plain text.
29375
29378
  */
29376
- async setFirebaseConfigJson(ctx) {
29379
+ async setFirebaseConfigJson(requestBody) {
29377
29380
  try {
29378
- strapi2.log.debug("setFirebaseConfigJson", ctx.request);
29379
- const { body: requestBody } = ctx.request;
29381
+ strapi2.log.debug("setFirebaseConfigJson called");
29380
29382
  const firebaseConfigJsonString = requestBody.firebaseConfigJson;
29381
29383
  const firebaseWebApiKey = requestBody.firebaseWebApiKey || null;
29382
29384
  const {
@@ -29447,18 +29449,17 @@ const settingsService = ({ strapi: strapi2 }) => {
29447
29449
  const configData = res.firebaseConfigJson || res.firebase_config_json;
29448
29450
  if (!configData) {
29449
29451
  strapi2.log.error("Firebase config data missing from database response");
29450
- strapi2.log.error("Available keys in response:", Object.keys(res));
29452
+ strapi2.log.error(`Available keys in response: ${JSON.stringify(Object.keys(res))}`);
29451
29453
  throw new ApplicationError2("Failed to retrieve Firebase configuration from database");
29452
29454
  }
29453
29455
  const firebaseConfigHash = configData.firebaseConfigJson;
29454
29456
  if (!firebaseConfigHash) {
29455
- strapi2.log.error("Firebase config hash missing from config data:", configData);
29457
+ strapi2.log.error(`Firebase config hash missing from config data: ${JSON.stringify(configData)}`);
29456
29458
  throw new ApplicationError2("Firebase configuration hash is missing");
29457
29459
  }
29458
29460
  const firebaseConfigJsonValue = await this.decryptJson(encryptionKey, firebaseConfigHash);
29459
- configData.firebaseConfigJson = firebaseConfigJsonValue;
29460
- res.firebaseConfigJson = configData;
29461
- res.firebase_config_json = configData;
29461
+ res.firebaseConfigJson = firebaseConfigJsonValue;
29462
+ res.firebase_config_json = firebaseConfigJsonValue;
29462
29463
  res.firebaseWebApiKey = res.firebase_web_api_key || null;
29463
29464
  res.passwordRequirementsRegex = res.passwordRequirementsRegex || passwordRequirementsRegex;
29464
29465
  res.passwordRequirementsMessage = res.passwordRequirementsMessage || passwordRequirementsMessage;
@@ -29471,13 +29472,12 @@ const settingsService = ({ strapi: strapi2 }) => {
29471
29472
  return res;
29472
29473
  } catch (error2) {
29473
29474
  strapi2.log.error("=== FIREBASE CONFIG SAVE ERROR ===");
29474
- strapi2.log.error("Error name:", error2.name);
29475
- strapi2.log.error("Error message:", error2.message);
29476
- strapi2.log.error("Error stack:", error2.stack);
29475
+ strapi2.log.error(`Error name: ${error2.name}`);
29476
+ strapi2.log.error(`Error message: ${error2.message}`);
29477
+ strapi2.log.error(`Error stack: ${error2.stack}`);
29477
29478
  try {
29478
- const { body } = ctx.request;
29479
- if (body) {
29480
- strapi2.log.error("Request body keys:", Object.keys(body));
29479
+ if (requestBody) {
29480
+ strapi2.log.error(`Request body keys: ${JSON.stringify(Object.keys(requestBody))}`);
29481
29481
  }
29482
29482
  } catch (e) {
29483
29483
  strapi2.log.error("Could not access request body");
@@ -29498,7 +29498,7 @@ const settingsService = ({ strapi: strapi2 }) => {
29498
29498
  try {
29499
29499
  strapi2.log.debug("delFirebaseConfigJson called");
29500
29500
  const isExist = await strapi2.db.query(CONFIG_CONTENT_TYPE).findOne({ where: {} });
29501
- strapi2.log.debug("Config exists:", isExist);
29501
+ strapi2.log.debug(`Config exists: ${!!isExist}`);
29502
29502
  if (!isExist) {
29503
29503
  strapi2.log.info(ERROR_MESSAGES.DELETION_NO_CONFIG);
29504
29504
  return null;
@@ -29506,12 +29506,12 @@ const settingsService = ({ strapi: strapi2 }) => {
29506
29506
  const res = await strapi2.db.query(CONFIG_CONTENT_TYPE).delete({
29507
29507
  where: { id: isExist.id }
29508
29508
  });
29509
- strapi2.log.debug("Delete result:", res);
29509
+ strapi2.log.debug(`Delete result: ${JSON.stringify(res)}`);
29510
29510
  await strapi2.plugin("firebase-authentication").service("settingsService").init();
29511
29511
  strapi2.log.info(SUCCESS_MESSAGES.FIREBASE_CONFIG_DELETED);
29512
29512
  return res;
29513
29513
  } catch (error2) {
29514
- strapi2.log.error("delFirebaseConfigJson error:", error2);
29514
+ strapi2.log.error(`delFirebaseConfigJson error: ${error2.message}`);
29515
29515
  throw new ApplicationError2(ERROR_MESSAGES.SOMETHING_WENT_WRONG, {
29516
29516
  error: error2
29517
29517
  });
@@ -29818,9 +29818,71 @@ const userService = ({ strapi: strapi2 }) => {
29818
29818
  update: async (entityId, payload) => {
29819
29819
  try {
29820
29820
  ensureFirebaseInitialized();
29821
- const firebasePromise = strapi2.firebase.auth().updateUser(entityId, payload);
29822
- return Promise.allSettled([firebasePromise]);
29821
+ const firebaseData = await strapi2.plugin("firebase-authentication").service("firebaseUserDataService").getByFirebaseUID(entityId);
29822
+ if (!firebaseData?.user) {
29823
+ throw new NotFoundError(`User not found for Firebase UID: ${entityId}`);
29824
+ }
29825
+ const firebasePayload = {};
29826
+ if (payload.email !== void 0) firebasePayload.email = payload.email;
29827
+ if (payload.phoneNumber !== void 0) firebasePayload.phoneNumber = payload.phoneNumber;
29828
+ if (payload.displayName !== void 0) firebasePayload.displayName = payload.displayName;
29829
+ if (payload.photoURL !== void 0) firebasePayload.photoURL = payload.photoURL;
29830
+ if (payload.disabled !== void 0) firebasePayload.disabled = payload.disabled;
29831
+ if (payload.emailVerified !== void 0) firebasePayload.emailVerified = payload.emailVerified;
29832
+ if (payload.password !== void 0) firebasePayload.password = payload.password;
29833
+ const firebasePromise = strapi2.firebase.auth().updateUser(entityId, firebasePayload);
29834
+ const strapiPayload = {};
29835
+ if (payload.email !== void 0) {
29836
+ strapiPayload.email = payload.email;
29837
+ }
29838
+ if (payload.phoneNumber !== void 0) {
29839
+ strapiPayload.phoneNumber = payload.phoneNumber;
29840
+ }
29841
+ if (payload.displayName !== void 0) {
29842
+ if (payload.displayName) {
29843
+ const nameParts = payload.displayName.trim().split(" ");
29844
+ strapiPayload.firstName = nameParts[0] || "";
29845
+ strapiPayload.lastName = nameParts.slice(1).join(" ") || "";
29846
+ } else {
29847
+ strapiPayload.firstName = "";
29848
+ strapiPayload.lastName = "";
29849
+ }
29850
+ }
29851
+ if (typeof payload.disabled === "boolean") {
29852
+ strapiPayload.blocked = payload.disabled;
29853
+ }
29854
+ const strapiPromise = Object.keys(strapiPayload).length > 0 ? strapi2.db.query("plugin::users-permissions.user").update({
29855
+ where: { documentId: firebaseData.user.documentId },
29856
+ data: strapiPayload
29857
+ }) : Promise.resolve(firebaseData.user);
29858
+ const results = await Promise.allSettled([firebasePromise, strapiPromise]);
29859
+ strapi2.log.info("User update operation", {
29860
+ userId: entityId,
29861
+ firebaseStatus: results[0].status,
29862
+ strapiStatus: results[1].status,
29863
+ updatedFields: Object.keys(firebasePayload)
29864
+ });
29865
+ if (results[0].status === "rejected" || results[1].status === "rejected") {
29866
+ strapi2.log.error("Partial update failure detected", {
29867
+ userId: entityId,
29868
+ firebaseError: results[0].status === "rejected" ? results[0].reason : null,
29869
+ strapiError: results[1].status === "rejected" ? results[1].reason : null
29870
+ });
29871
+ }
29872
+ return results;
29823
29873
  } catch (e) {
29874
+ if (e.code === "auth/email-already-exists") {
29875
+ throw new ValidationError$1("Email address is already in use by another account");
29876
+ }
29877
+ if (e.code === "auth/phone-number-already-exists") {
29878
+ throw new ValidationError$1("Phone number is already in use by another account");
29879
+ }
29880
+ if (e.code === "auth/invalid-email") {
29881
+ throw new ValidationError$1("Invalid email address format");
29882
+ }
29883
+ if (e.code === "auth/invalid-phone-number") {
29884
+ throw new ValidationError$1("Invalid phone number format");
29885
+ }
29824
29886
  throw new ApplicationError$1(e.message.toString());
29825
29887
  }
29826
29888
  },
@@ -29838,8 +29900,8 @@ const userService = ({ strapi: strapi2 }) => {
29838
29900
  if (!firebaseData?.user) {
29839
29901
  throw new NotFoundError(`User not found for Firebase UID: ${entityId}`);
29840
29902
  }
29841
- return await strapi2.documents("plugin::users-permissions.user").update({
29842
- documentId: firebaseData.user.documentId,
29903
+ return await strapi2.db.query("plugin::users-permissions.user").update({
29904
+ where: { documentId: firebaseData.user.documentId },
29843
29905
  data: payload
29844
29906
  });
29845
29907
  } catch (e) {
@@ -29854,8 +29916,8 @@ const userService = ({ strapi: strapi2 }) => {
29854
29916
  throw new NotFoundError(`User not found for Firebase UID: ${entityId}`);
29855
29917
  }
29856
29918
  const firebasePromise = strapi2.firebase.auth().updateUser(entityId, payload);
29857
- const strapiPromise = strapi2.documents("plugin::users-permissions.user").update({
29858
- documentId: firebaseData.user.documentId,
29919
+ const strapiPromise = strapi2.db.query("plugin::users-permissions.user").update({
29920
+ where: { documentId: firebaseData.user.documentId },
29859
29921
  data: payload
29860
29922
  });
29861
29923
  return Promise.allSettled([firebasePromise, strapiPromise]);
@@ -29871,8 +29933,8 @@ const userService = ({ strapi: strapi2 }) => {
29871
29933
  throw new NotFoundError(`User not found for Firebase UID: ${entityId}`);
29872
29934
  }
29873
29935
  const firebasePromise = strapi2.firebase.auth().deleteUser(entityId);
29874
- const strapiPromise = strapi2.documents("plugin::users-permissions.user").delete({
29875
- documentId: firebaseData.user.documentId
29936
+ const strapiPromise = strapi2.db.query("plugin::users-permissions.user").delete({
29937
+ where: { documentId: firebaseData.user.documentId }
29876
29938
  });
29877
29939
  return Promise.allSettled([firebasePromise, strapiPromise]);
29878
29940
  } catch (e) {
@@ -29894,8 +29956,8 @@ const userService = ({ strapi: strapi2 }) => {
29894
29956
  if (!firebaseData?.user) {
29895
29957
  throw new NotFoundError(`User not found for Firebase UID: ${entityId}`);
29896
29958
  }
29897
- const response = await strapi2.documents("plugin::users-permissions.user").delete({
29898
- documentId: firebaseData.user.documentId
29959
+ const response = await strapi2.db.query("plugin::users-permissions.user").delete({
29960
+ where: { documentId: firebaseData.user.documentId }
29899
29961
  });
29900
29962
  return response;
29901
29963
  } catch (e) {
@@ -30434,8 +30496,8 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30434
30496
  * Forgot password flow - sends reset email
30435
30497
  * Public endpoint that sends a Firebase-hosted password reset email using Firebase's secure hosted UI
30436
30498
  */
30437
- forgotPassword: async (ctx) => {
30438
- const { email: email2 } = ctx.request.body;
30499
+ forgotPassword: async (email2) => {
30500
+ strapi2.log.info(`[forgotPassword] Starting password reset for email: ${email2}`);
30439
30501
  if (!email2) {
30440
30502
  throw new ValidationError$1("Email is required");
30441
30503
  }
@@ -30476,8 +30538,16 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30476
30538
  });
30477
30539
  }
30478
30540
  if (!strapiUser) {
30541
+ strapi2.log.warn(`⚠️ [forgotPassword] User not found for email: ${email2}`);
30479
30542
  return { message: "If an account with that email exists, a password reset link has been sent." };
30480
30543
  }
30544
+ strapi2.log.info(
30545
+ `✅ [forgotPassword] User found: ${JSON.stringify({
30546
+ documentId: strapiUser.documentId,
30547
+ email: strapiUser.email,
30548
+ firebaseUID: firebaseUser?.uid || "not in Firebase"
30549
+ })}`
30550
+ );
30481
30551
  const actionCodeSettings = {
30482
30552
  url: resetUrl,
30483
30553
  // Continue URL after reset completes on Firebase's page
@@ -30491,6 +30561,7 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30491
30561
  });
30492
30562
  let resetLink;
30493
30563
  try {
30564
+ strapi2.log.info(`[forgotPassword] Generating Firebase password reset link for: ${strapiUser.email}`);
30494
30565
  resetLink = await Promise.race([
30495
30566
  strapi2.firebase.auth().generatePasswordResetLink(strapiUser.email, actionCodeSettings),
30496
30567
  timeoutPromise
@@ -30502,12 +30573,22 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30502
30573
  strapi2.log.error(`❌ Failed to generate password reset link for ${strapiUser.email}:`, error2);
30503
30574
  throw error2;
30504
30575
  }
30576
+ strapi2.log.info(`[forgotPassword] Attempting to send password reset email to: ${strapiUser.email}`);
30505
30577
  await strapi2.plugin("firebase-authentication").service("emailService").sendPasswordResetEmail(strapiUser, resetLink);
30578
+ strapi2.log.info(`✅ [forgotPassword] Password reset email sent successfully to: ${strapiUser.email}`);
30506
30579
  return {
30507
30580
  message: "If an account with that email exists, a password reset link has been sent."
30508
30581
  };
30509
30582
  } catch (error2) {
30510
- strapi2.log.error("forgotPassword error:", error2);
30583
+ strapi2.log.error(
30584
+ `❌ [forgotPassword] ERROR: ${JSON.stringify({
30585
+ email: email2,
30586
+ message: error2.message,
30587
+ code: error2.code,
30588
+ name: error2.name,
30589
+ stack: error2.stack
30590
+ })}`
30591
+ );
30511
30592
  return {
30512
30593
  message: "If an account with that email exists, a password reset link has been sent."
30513
30594
  };
@@ -30527,13 +30608,10 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30527
30608
  *
30528
30609
  * NOT used for forgot password email flow - that now uses Firebase's hosted UI
30529
30610
  */
30530
- resetPassword: async (ctx) => {
30531
- const { password } = ctx.request.body;
30532
- const populate2 = ctx.request.query.populate || [];
30611
+ resetPassword: async (password, token, populate2) => {
30533
30612
  if (!password) {
30534
30613
  throw new ValidationError$1("Password is required");
30535
30614
  }
30536
- const token = ctx.request.header.authorization?.replace("Bearer ", "");
30537
30615
  if (!token) {
30538
30616
  throw new UnauthorizedError("Authorization token is required");
30539
30617
  }
@@ -30589,8 +30667,7 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30589
30667
  * Generates a sign-in link using Firebase Admin SDK
30590
30668
  * Note: Verification requires client-side completion
30591
30669
  */
30592
- async requestMagicLink(ctx) {
30593
- const { email: email2 } = ctx.request.body;
30670
+ async requestMagicLink(email2) {
30594
30671
  if (!email2 || typeof email2 !== "string") {
30595
30672
  throw new ValidationError$1("Valid email is required");
30596
30673
  }
@@ -30620,7 +30697,7 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30620
30697
  };
30621
30698
  const magicLink = await strapi2.firebase.auth().generateSignInWithEmailLink(email2, actionCodeSettings);
30622
30699
  if (process.env.NODE_ENV !== "production") {
30623
- strapi2.log.info("🔗 Magic Link Generation Request:");
30700
+ strapi2.log.info("Magic Link Generation Request:");
30624
30701
  strapi2.log.info(` Email: ${email2}`);
30625
30702
  strapi2.log.info(` Verification URL: ${magicLinkUrl}`);
30626
30703
  strapi2.log.info(` Expires: ${config2.magicLinkExpiryHours || 1} hour(s)`);
@@ -31226,7 +31303,7 @@ class EmailService {
31226
31303
  const template = await templateService2.getTemplate("magicLink");
31227
31304
  const compiledSubject = _$1.template(template.subject)(completeVariables);
31228
31305
  strapi.log.info("\n" + "=".repeat(80));
31229
- strapi.log.info("🔗 MAGIC LINK EMAIL (Development Mode)");
31306
+ strapi.log.info("MAGIC LINK EMAIL (Development Mode)");
31230
31307
  strapi.log.info("=".repeat(80));
31231
31308
  strapi.log.info(`To: ${email2}`);
31232
31309
  strapi.log.info(`Subject: ${compiledSubject}`);
@@ -31301,7 +31378,7 @@ const firebaseUserDataService = ({ strapi: strapi2 }) => ({
31301
31378
  }
31302
31379
  });
31303
31380
  } catch (error2) {
31304
- if (error2.code === "23505") {
31381
+ if (error2.code === "23505" || error2.name === "ValidationError") {
31305
31382
  strapi2.log.warn(`Race condition detected for user ${userId}, retrying findFirst`);
31306
31383
  firebaseData = await strapi2.documents("plugin::firebase-authentication.firebase-user-data").findFirst({
31307
31384
  filters: { user: { documentId: { $eq: userId } } }
@@ -31341,7 +31418,7 @@ const autoLinkService = {
31341
31418
  errors: 0
31342
31419
  };
31343
31420
  try {
31344
- strapi2.log.info("🔗 Auto-linking Strapi users with Firebase users...");
31421
+ strapi2.log.info("Auto-linking Strapi users with Firebase users...");
31345
31422
  if (!strapi2.firebase) {
31346
31423
  strapi2.log.warn("Firebase not initialized - skipping auto-linking");
31347
31424
  return result;
@@ -31380,6 +31457,7 @@ const autoLinkService = {
31380
31457
  }
31381
31458
  }
31382
31459
  for (const strapiUser of strapiUsers) {
31460
+ let firebaseUser = null;
31383
31461
  try {
31384
31462
  const existing = await strapi2.db.query("plugin::firebase-authentication.firebase-user-data").findOne({
31385
31463
  select: ["id"],
@@ -31389,7 +31467,7 @@ const autoLinkService = {
31389
31467
  result.skipped++;
31390
31468
  continue;
31391
31469
  }
31392
- let firebaseUser = null;
31470
+ firebaseUser = null;
31393
31471
  if (strapiUser.email) {
31394
31472
  firebaseUser = firebaseByEmail.get(strapiUser.email.toLowerCase());
31395
31473
  }
@@ -31400,6 +31478,23 @@ const autoLinkService = {
31400
31478
  result.skipped++;
31401
31479
  continue;
31402
31480
  }
31481
+ const existingUIDLink = await strapi2.db.query("plugin::firebase-authentication.firebase-user-data").findOne({
31482
+ where: { firebaseUserID: firebaseUser.uid },
31483
+ populate: ["user"]
31484
+ });
31485
+ if (existingUIDLink && existingUIDLink.user?.documentId !== strapiUser.documentId) {
31486
+ strapi2.log.warn(
31487
+ `⚠️ [Auto-Link] Skipping '${strapiUser.username || strapiUser.email}' - Firebase UID already linked`
31488
+ );
31489
+ strapi2.log.warn(` Strapi User: ${strapiUser.email} (documentId: ${strapiUser.documentId})`);
31490
+ strapi2.log.warn(` Firebase UID: ${firebaseUser.uid}`);
31491
+ strapi2.log.warn(
31492
+ ` Already linked to: ${existingUIDLink.user?.email} (documentId: ${existingUIDLink.user?.documentId})`
31493
+ );
31494
+ strapi2.log.warn(` Action: Resolve duplicate users or manually unlink conflicting record`);
31495
+ result.skipped++;
31496
+ continue;
31497
+ }
31403
31498
  try {
31404
31499
  const firebaseData = {
31405
31500
  firebaseUserID: firebaseUser.uid
@@ -31418,18 +31513,42 @@ const autoLinkService = {
31418
31513
  throw createError;
31419
31514
  }
31420
31515
  } catch (error2) {
31421
- result.errors++;
31422
- strapi2.log.error(`Error linking user ${strapiUser.username}:`, error2);
31516
+ if (error2.message?.includes("This attribute must be unique")) {
31517
+ strapi2.log.warn(
31518
+ `⚠️ [Auto-Link] Unique constraint conflict for '${strapiUser.username || strapiUser.email}'`
31519
+ );
31520
+ strapi2.log.warn(` Strapi User: ${strapiUser.email} (documentId: ${strapiUser.documentId})`);
31521
+ if (firebaseUser) {
31522
+ strapi2.log.warn(` Firebase UID: ${firebaseUser.uid}`);
31523
+ strapi2.log.warn(` Cause: This Firebase UID is already linked to another Strapi user`);
31524
+ strapi2.log.warn(
31525
+ ` Action: Query firebase_user_data table to find conflict - WHERE firebase_user_id = '${firebaseUser.uid}'`
31526
+ );
31527
+ }
31528
+ result.skipped++;
31529
+ } else {
31530
+ result.errors++;
31531
+ strapi2.log.error(
31532
+ `❌ [Auto-Link] Unexpected error linking user '${strapiUser.username || strapiUser.email}': ${error2.message}`
31533
+ );
31534
+ }
31423
31535
  }
31424
31536
  }
31425
- strapi2.log.info(
31426
- `✅ Auto-linking complete: ${result.linked} linked, ${result.skipped} skipped, ${result.errors} errors`
31427
- );
31537
+ if (result.errors > 0) {
31538
+ strapi2.log.error(
31539
+ `❌ Auto-linking completed with unexpected errors: ${result.linked} linked, ${result.skipped} skipped, ${result.errors} errors`
31540
+ );
31541
+ strapi2.log.error(` Review error logs above for resolution steps`);
31542
+ } else {
31543
+ strapi2.log.info(
31544
+ `✅ Auto-linking complete: ${result.linked} linked, ${result.skipped} skipped, ${result.errors} errors`
31545
+ );
31546
+ }
31428
31547
  strapi2.log.info(
31429
31548
  ` Total: ${result.totalStrapiUsers} Strapi users, ${result.totalFirebaseUsers} Firebase users`
31430
31549
  );
31431
31550
  } catch (error2) {
31432
- strapi2.log.error("Fatal error during auto-linking:", error2);
31551
+ strapi2.log.error(`❌ Fatal error during auto-linking: ${error2.message}`);
31433
31552
  }
31434
31553
  return result;
31435
31554
  }
@@ -1,7 +1,6 @@
1
1
  import { Context } from "koa";
2
2
  declare const firebaseController: {
3
3
  validateToken(ctx: any): Promise<any>;
4
- createAlias(ctx: any): Promise<void>;
5
4
  deleteByEmail(email: any): Promise<any>;
6
5
  overrideAccess(ctx: any): Promise<any>;
7
6
  /**
@@ -1,7 +1,6 @@
1
1
  declare const _default: {
2
2
  firebaseController: {
3
3
  validateToken(ctx: any): Promise<any>;
4
- createAlias(ctx: any): Promise<void>;
5
4
  deleteByEmail(email: any): Promise<any>;
6
5
  overrideAccess(ctx: any): Promise<any>;
7
6
  emailLogin(ctx: any): Promise<void>;
@@ -22,7 +22,6 @@ declare const _default: {
22
22
  controllers: {
23
23
  firebaseController: {
24
24
  validateToken(ctx: any): Promise<any>;
25
- createAlias(ctx: any): Promise<void>;
26
25
  deleteByEmail(email: any): Promise<any>;
27
26
  overrideAccess(ctx: any): Promise<any>;
28
27
  emailLogin(ctx: any): Promise<void>;
@@ -94,7 +93,7 @@ declare const _default: {
94
93
  magicLinkEmailSubject: any;
95
94
  magicLinkExpiryHours: any;
96
95
  }>;
97
- setFirebaseConfigJson(ctx: import("koa").Context | import("koa").DefaultContext): Promise<any>;
96
+ setFirebaseConfigJson(requestBody: any): Promise<any>;
98
97
  delFirebaseConfigJson: () => Promise<any>;
99
98
  updateMagicLinkSettings(settings: any): Promise<{
100
99
  enableMagicLink: any;
@@ -120,7 +119,7 @@ declare const _default: {
120
119
  };
121
120
  }>;
122
121
  updateFirebaseUser: (entityId: any, payload: any) => Promise<any>;
123
- update: (entityId: any, payload: any) => Promise<[PromiseSettledResult<any>]>;
122
+ update: (entityId: any, payload: any) => Promise<[PromiseSettledResult<any>, PromiseSettledResult<any>]>;
124
123
  resetPasswordFirebaseUser: (entityId: any, payload: any) => Promise<any>;
125
124
  resetPasswordStrapiUser: (entityId: any, payload: any) => Promise<any>;
126
125
  resetPassword: (entityId: any, payload: any) => Promise<[PromiseSettledResult<any>, PromiseSettledResult<any>]>;
@@ -156,14 +155,14 @@ declare const _default: {
156
155
  user: any;
157
156
  jwt: any;
158
157
  }>;
159
- forgotPassword: (ctx: any) => Promise<{
158
+ forgotPassword: (email: string) => Promise<{
160
159
  message: string;
161
160
  }>;
162
- resetPassword: (ctx: any) => Promise<{
161
+ resetPassword: (password: string, token: string, populate: any[]) => Promise<{
163
162
  user: any;
164
163
  jwt: any;
165
164
  }>;
166
- requestMagicLink(ctx: any): Promise<{
165
+ requestMagicLink(email: string): Promise<{
167
166
  debug: {
168
167
  linkSent: any;
169
168
  email: string;
@@ -68,7 +68,7 @@ declare const _default: ({ strapi }: {
68
68
  * Forgot password flow - sends reset email
69
69
  * Public endpoint that sends a Firebase-hosted password reset email using Firebase's secure hosted UI
70
70
  */
71
- forgotPassword: (ctx: any) => Promise<{
71
+ forgotPassword: (email: string) => Promise<{
72
72
  message: string;
73
73
  }>;
74
74
  /**
@@ -85,7 +85,7 @@ declare const _default: ({ strapi }: {
85
85
  *
86
86
  * NOT used for forgot password email flow - that now uses Firebase's hosted UI
87
87
  */
88
- resetPassword: (ctx: any) => Promise<{
88
+ resetPassword: (password: string, token: string, populate: any[]) => Promise<{
89
89
  user: any;
90
90
  jwt: any;
91
91
  }>;
@@ -94,7 +94,7 @@ declare const _default: ({ strapi }: {
94
94
  * Generates a sign-in link using Firebase Admin SDK
95
95
  * Note: Verification requires client-side completion
96
96
  */
97
- requestMagicLink(ctx: any): Promise<{
97
+ requestMagicLink(email: string): Promise<{
98
98
  debug: {
99
99
  linkSent: any;
100
100
  email: string;