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.
@@ -199,8 +199,8 @@ const bootstrap = async ({ strapi: strapi2 }) => {
199
199
  }
200
200
  } catch (error2) {
201
201
  strapi2.log.error("Firebase initialization failed during bootstrap:");
202
- strapi2.log.error(" Error:", error2.message);
203
- strapi2.log.error(" Stack:", error2.stack);
202
+ strapi2.log.error(` Error: ${error2.message}`);
203
+ strapi2.log.error(` Stack: ${error2.stack}`);
204
204
  }
205
205
  await strapi2.admin.services.permission.actionProvider.registerMany(actions);
206
206
  if (strapi2.plugin("users-permissions")) {
@@ -210,14 +210,14 @@ const bootstrap = async ({ strapi: strapi2 }) => {
210
210
  if (process.env.RUN_FIREBASE_MIGRATION === "true") {
211
211
  const dryRun = process.env.DRY_RUN === "true";
212
212
  strapi2.log.info("");
213
- strapi2.log.info("🚀 Firebase migration triggered by RUN_FIREBASE_MIGRATION env variable");
213
+ strapi2.log.info("Firebase migration triggered by RUN_FIREBASE_MIGRATION env variable");
214
214
  await migrateFirebaseUserData(strapi2, dryRun);
215
215
  }
216
216
  setImmediate(async () => {
217
217
  try {
218
218
  await strapi2.plugin("firebase-authentication").service("autoLinkService").linkAllUsers(strapi2);
219
219
  } catch (error2) {
220
- strapi2.log.error("Auto-linking failed:", error2);
220
+ strapi2.log.error(`Auto-linking failed: ${error2.message}`);
221
221
  }
222
222
  });
223
223
  };
@@ -442,7 +442,7 @@ const firebaseController = {
442
442
  const result = await strapi.plugin(pluginName).service("firebaseService").validateFirebaseToken(idToken, profileMetaData, populate2);
443
443
  ctx.body = result;
444
444
  } catch (error2) {
445
- strapi.log.error("validateToken controller error:", error2.message);
445
+ strapi.log.error(`validateToken controller error: ${error2.message}`);
446
446
  if (error2.name === "ValidationError") {
447
447
  return ctx.badRequest(error2.message);
448
448
  }
@@ -452,9 +452,6 @@ const firebaseController = {
452
452
  throw error2;
453
453
  }
454
454
  },
455
- async createAlias(ctx) {
456
- ctx.body = await strapi.plugin(pluginName).service("firebaseService").createAlias(ctx);
457
- },
458
455
  async deleteByEmail(email2) {
459
456
  const user = await strapi.firebase.auth().getUserByEmail(email2);
460
457
  await strapi.plugin(pluginName).service("firebaseService").delete(user.toJSON().uid);
@@ -518,7 +515,8 @@ const firebaseController = {
518
515
  async forgotPassword(ctx) {
519
516
  strapi.log.debug("forgotPassword endpoint called");
520
517
  try {
521
- ctx.body = await strapi.plugin(pluginName).service("firebaseService").forgotPassword(ctx);
518
+ const { email: email2 } = ctx.request.body || {};
519
+ ctx.body = await strapi.plugin(pluginName).service("firebaseService").forgotPassword(email2);
522
520
  } catch (error2) {
523
521
  strapi.log.error("forgotPassword controller error:", error2);
524
522
  if (error2.name === "ValidationError") {
@@ -541,7 +539,10 @@ const firebaseController = {
541
539
  async resetPassword(ctx) {
542
540
  strapi.log.debug("resetPassword endpoint called");
543
541
  try {
544
- ctx.body = await strapi.plugin(pluginName).service("firebaseService").resetPassword(ctx);
542
+ const { password } = ctx.request.body || {};
543
+ const token = ctx.request.headers.authorization?.replace("Bearer ", "");
544
+ const populate2 = ctx.request.query.populate || [];
545
+ ctx.body = await strapi.plugin(pluginName).service("firebaseService").resetPassword(password, token, populate2);
545
546
  } catch (error2) {
546
547
  strapi.log.error("resetPassword controller error:", error2);
547
548
  if (error2.name === "ValidationError" || error2.name === "UnauthorizedError") {
@@ -554,7 +555,8 @@ const firebaseController = {
554
555
  },
555
556
  async requestMagicLink(ctx) {
556
557
  try {
557
- const result = await strapi.plugin("firebase-authentication").service("firebaseService").requestMagicLink(ctx);
558
+ const { email: email2 } = ctx.request.body || {};
559
+ const result = await strapi.plugin("firebase-authentication").service("firebaseService").requestMagicLink(email2);
558
560
  ctx.body = result;
559
561
  } catch (error2) {
560
562
  if (error2.name === "ValidationError" || error2.name === "ApplicationError") {
@@ -28937,11 +28939,12 @@ const userController = {
28937
28939
  };
28938
28940
  const settingsController = {
28939
28941
  setFirebaseConfigJson: async (ctx) => {
28940
- ctx.body = await strapi.plugin("firebase-authentication").service("settingsService").setFirebaseConfigJson(ctx);
28942
+ const requestBody = ctx.request.body;
28943
+ ctx.body = await strapi.plugin("firebase-authentication").service("settingsService").setFirebaseConfigJson(requestBody);
28941
28944
  },
28942
28945
  getFirebaseConfigJson: async (ctx) => {
28943
28946
  try {
28944
- const config2 = await strapi.plugin("firebase-authentication").service("settingsService").getFirebaseConfigJson(ctx);
28947
+ const config2 = await strapi.plugin("firebase-authentication").service("settingsService").getFirebaseConfigJson();
28945
28948
  if (!config2) {
28946
28949
  return ctx.send(null);
28947
28950
  }
@@ -28954,7 +28957,7 @@ const settingsController = {
28954
28957
  },
28955
28958
  async delFirebaseConfigJson(ctx) {
28956
28959
  try {
28957
- const isExist = await strapi.plugin("firebase-authentication").service("settingsService").delFirebaseConfigJson(ctx);
28960
+ const isExist = await strapi.plugin("firebase-authentication").service("settingsService").delFirebaseConfigJson();
28958
28961
  if (!isExist) {
28959
28962
  throw new NotFoundError("No Firebase configs exists for deletion");
28960
28963
  }
@@ -29281,7 +29284,7 @@ const settingsService = ({ strapi: strapi2 }) => {
29281
29284
  try {
29282
29285
  strapi2.log.info("Starting Firebase initialization...");
29283
29286
  const res = await strapi2.db.query(CONFIG_CONTENT_TYPE).findOne({ where: {} });
29284
- strapi2.log.debug("Found config:", !!res);
29287
+ strapi2.log.debug(`Found config: ${!!res}`);
29285
29288
  if (!res) {
29286
29289
  strapi2.log.debug("No config found, checking for existing Firebase app...");
29287
29290
  if (strapi2.firebase) {
@@ -29291,7 +29294,7 @@ const settingsService = ({ strapi: strapi2 }) => {
29291
29294
  return;
29292
29295
  }
29293
29296
  const jsonObject = res["firebase_config_json"];
29294
- strapi2.log.debug("Config JSON present:", !!jsonObject?.firebaseConfigJson);
29297
+ strapi2.log.debug(`Config JSON present: ${!!jsonObject?.firebaseConfigJson}`);
29295
29298
  if (!jsonObject || !jsonObject.firebaseConfigJson) {
29296
29299
  strapi2.log.debug("No valid JSON config, checking for existing Firebase app...");
29297
29300
  if (strapi2.firebase) {
@@ -29326,11 +29329,11 @@ const settingsService = ({ strapi: strapi2 }) => {
29326
29329
  strapi2.firebase = admin__default.default;
29327
29330
  strapi2.log.info("Firebase initialization complete - admin instance attached to strapi.firebase");
29328
29331
  } catch (initError) {
29329
- strapi2.log.error("Failed to initialize Firebase:", initError);
29332
+ strapi2.log.error(`Failed to initialize Firebase: ${initError.message}`);
29330
29333
  throw initError;
29331
29334
  }
29332
29335
  } catch (error2) {
29333
- strapi2.log.error("Firebase bootstrap error:", error2);
29336
+ strapi2.log.error(`Firebase bootstrap error: ${error2.message}`);
29334
29337
  }
29335
29338
  },
29336
29339
  /**
@@ -29378,7 +29381,7 @@ const settingsService = ({ strapi: strapi2 }) => {
29378
29381
  magicLinkExpiryHours: configObject.magicLinkExpiryHours || 1
29379
29382
  };
29380
29383
  } catch (error2) {
29381
- strapi2.log.error("Firebase config error:", error2);
29384
+ strapi2.log.error(`Firebase config error: ${error2.message}`);
29382
29385
  throw new ApplicationError$1("Error retrieving Firebase config", {
29383
29386
  error: error2.message
29384
29387
  });
@@ -29405,10 +29408,9 @@ const settingsService = ({ strapi: strapi2 }) => {
29405
29408
  * The service account JSON is encrypted using AES before storage,
29406
29409
  * while the Web API key and password settings are stored in plain text.
29407
29410
  */
29408
- async setFirebaseConfigJson(ctx) {
29411
+ async setFirebaseConfigJson(requestBody) {
29409
29412
  try {
29410
- strapi2.log.debug("setFirebaseConfigJson", ctx.request);
29411
- const { body: requestBody } = ctx.request;
29413
+ strapi2.log.debug("setFirebaseConfigJson called");
29412
29414
  const firebaseConfigJsonString = requestBody.firebaseConfigJson;
29413
29415
  const firebaseWebApiKey = requestBody.firebaseWebApiKey || null;
29414
29416
  const {
@@ -29479,18 +29481,17 @@ const settingsService = ({ strapi: strapi2 }) => {
29479
29481
  const configData = res.firebaseConfigJson || res.firebase_config_json;
29480
29482
  if (!configData) {
29481
29483
  strapi2.log.error("Firebase config data missing from database response");
29482
- strapi2.log.error("Available keys in response:", Object.keys(res));
29484
+ strapi2.log.error(`Available keys in response: ${JSON.stringify(Object.keys(res))}`);
29483
29485
  throw new ApplicationError2("Failed to retrieve Firebase configuration from database");
29484
29486
  }
29485
29487
  const firebaseConfigHash = configData.firebaseConfigJson;
29486
29488
  if (!firebaseConfigHash) {
29487
- strapi2.log.error("Firebase config hash missing from config data:", configData);
29489
+ strapi2.log.error(`Firebase config hash missing from config data: ${JSON.stringify(configData)}`);
29488
29490
  throw new ApplicationError2("Firebase configuration hash is missing");
29489
29491
  }
29490
29492
  const firebaseConfigJsonValue = await this.decryptJson(encryptionKey, firebaseConfigHash);
29491
- configData.firebaseConfigJson = firebaseConfigJsonValue;
29492
- res.firebaseConfigJson = configData;
29493
- res.firebase_config_json = configData;
29493
+ res.firebaseConfigJson = firebaseConfigJsonValue;
29494
+ res.firebase_config_json = firebaseConfigJsonValue;
29494
29495
  res.firebaseWebApiKey = res.firebase_web_api_key || null;
29495
29496
  res.passwordRequirementsRegex = res.passwordRequirementsRegex || passwordRequirementsRegex;
29496
29497
  res.passwordRequirementsMessage = res.passwordRequirementsMessage || passwordRequirementsMessage;
@@ -29503,13 +29504,12 @@ const settingsService = ({ strapi: strapi2 }) => {
29503
29504
  return res;
29504
29505
  } catch (error2) {
29505
29506
  strapi2.log.error("=== FIREBASE CONFIG SAVE ERROR ===");
29506
- strapi2.log.error("Error name:", error2.name);
29507
- strapi2.log.error("Error message:", error2.message);
29508
- strapi2.log.error("Error stack:", error2.stack);
29507
+ strapi2.log.error(`Error name: ${error2.name}`);
29508
+ strapi2.log.error(`Error message: ${error2.message}`);
29509
+ strapi2.log.error(`Error stack: ${error2.stack}`);
29509
29510
  try {
29510
- const { body } = ctx.request;
29511
- if (body) {
29512
- strapi2.log.error("Request body keys:", Object.keys(body));
29511
+ if (requestBody) {
29512
+ strapi2.log.error(`Request body keys: ${JSON.stringify(Object.keys(requestBody))}`);
29513
29513
  }
29514
29514
  } catch (e) {
29515
29515
  strapi2.log.error("Could not access request body");
@@ -29530,7 +29530,7 @@ const settingsService = ({ strapi: strapi2 }) => {
29530
29530
  try {
29531
29531
  strapi2.log.debug("delFirebaseConfigJson called");
29532
29532
  const isExist = await strapi2.db.query(CONFIG_CONTENT_TYPE).findOne({ where: {} });
29533
- strapi2.log.debug("Config exists:", isExist);
29533
+ strapi2.log.debug(`Config exists: ${!!isExist}`);
29534
29534
  if (!isExist) {
29535
29535
  strapi2.log.info(ERROR_MESSAGES.DELETION_NO_CONFIG);
29536
29536
  return null;
@@ -29538,12 +29538,12 @@ const settingsService = ({ strapi: strapi2 }) => {
29538
29538
  const res = await strapi2.db.query(CONFIG_CONTENT_TYPE).delete({
29539
29539
  where: { id: isExist.id }
29540
29540
  });
29541
- strapi2.log.debug("Delete result:", res);
29541
+ strapi2.log.debug(`Delete result: ${JSON.stringify(res)}`);
29542
29542
  await strapi2.plugin("firebase-authentication").service("settingsService").init();
29543
29543
  strapi2.log.info(SUCCESS_MESSAGES.FIREBASE_CONFIG_DELETED);
29544
29544
  return res;
29545
29545
  } catch (error2) {
29546
- strapi2.log.error("delFirebaseConfigJson error:", error2);
29546
+ strapi2.log.error(`delFirebaseConfigJson error: ${error2.message}`);
29547
29547
  throw new ApplicationError2(ERROR_MESSAGES.SOMETHING_WENT_WRONG, {
29548
29548
  error: error2
29549
29549
  });
@@ -29850,9 +29850,71 @@ const userService = ({ strapi: strapi2 }) => {
29850
29850
  update: async (entityId, payload) => {
29851
29851
  try {
29852
29852
  ensureFirebaseInitialized();
29853
- const firebasePromise = strapi2.firebase.auth().updateUser(entityId, payload);
29854
- return Promise.allSettled([firebasePromise]);
29853
+ const firebaseData = await strapi2.plugin("firebase-authentication").service("firebaseUserDataService").getByFirebaseUID(entityId);
29854
+ if (!firebaseData?.user) {
29855
+ throw new NotFoundError(`User not found for Firebase UID: ${entityId}`);
29856
+ }
29857
+ const firebasePayload = {};
29858
+ if (payload.email !== void 0) firebasePayload.email = payload.email;
29859
+ if (payload.phoneNumber !== void 0) firebasePayload.phoneNumber = payload.phoneNumber;
29860
+ if (payload.displayName !== void 0) firebasePayload.displayName = payload.displayName;
29861
+ if (payload.photoURL !== void 0) firebasePayload.photoURL = payload.photoURL;
29862
+ if (payload.disabled !== void 0) firebasePayload.disabled = payload.disabled;
29863
+ if (payload.emailVerified !== void 0) firebasePayload.emailVerified = payload.emailVerified;
29864
+ if (payload.password !== void 0) firebasePayload.password = payload.password;
29865
+ const firebasePromise = strapi2.firebase.auth().updateUser(entityId, firebasePayload);
29866
+ const strapiPayload = {};
29867
+ if (payload.email !== void 0) {
29868
+ strapiPayload.email = payload.email;
29869
+ }
29870
+ if (payload.phoneNumber !== void 0) {
29871
+ strapiPayload.phoneNumber = payload.phoneNumber;
29872
+ }
29873
+ if (payload.displayName !== void 0) {
29874
+ if (payload.displayName) {
29875
+ const nameParts = payload.displayName.trim().split(" ");
29876
+ strapiPayload.firstName = nameParts[0] || "";
29877
+ strapiPayload.lastName = nameParts.slice(1).join(" ") || "";
29878
+ } else {
29879
+ strapiPayload.firstName = "";
29880
+ strapiPayload.lastName = "";
29881
+ }
29882
+ }
29883
+ if (typeof payload.disabled === "boolean") {
29884
+ strapiPayload.blocked = payload.disabled;
29885
+ }
29886
+ const strapiPromise = Object.keys(strapiPayload).length > 0 ? strapi2.db.query("plugin::users-permissions.user").update({
29887
+ where: { documentId: firebaseData.user.documentId },
29888
+ data: strapiPayload
29889
+ }) : Promise.resolve(firebaseData.user);
29890
+ const results = await Promise.allSettled([firebasePromise, strapiPromise]);
29891
+ strapi2.log.info("User update operation", {
29892
+ userId: entityId,
29893
+ firebaseStatus: results[0].status,
29894
+ strapiStatus: results[1].status,
29895
+ updatedFields: Object.keys(firebasePayload)
29896
+ });
29897
+ if (results[0].status === "rejected" || results[1].status === "rejected") {
29898
+ strapi2.log.error("Partial update failure detected", {
29899
+ userId: entityId,
29900
+ firebaseError: results[0].status === "rejected" ? results[0].reason : null,
29901
+ strapiError: results[1].status === "rejected" ? results[1].reason : null
29902
+ });
29903
+ }
29904
+ return results;
29855
29905
  } catch (e) {
29906
+ if (e.code === "auth/email-already-exists") {
29907
+ throw new ValidationError$1("Email address is already in use by another account");
29908
+ }
29909
+ if (e.code === "auth/phone-number-already-exists") {
29910
+ throw new ValidationError$1("Phone number is already in use by another account");
29911
+ }
29912
+ if (e.code === "auth/invalid-email") {
29913
+ throw new ValidationError$1("Invalid email address format");
29914
+ }
29915
+ if (e.code === "auth/invalid-phone-number") {
29916
+ throw new ValidationError$1("Invalid phone number format");
29917
+ }
29856
29918
  throw new ApplicationError$1(e.message.toString());
29857
29919
  }
29858
29920
  },
@@ -29870,8 +29932,8 @@ const userService = ({ strapi: strapi2 }) => {
29870
29932
  if (!firebaseData?.user) {
29871
29933
  throw new NotFoundError(`User not found for Firebase UID: ${entityId}`);
29872
29934
  }
29873
- return await strapi2.documents("plugin::users-permissions.user").update({
29874
- documentId: firebaseData.user.documentId,
29935
+ return await strapi2.db.query("plugin::users-permissions.user").update({
29936
+ where: { documentId: firebaseData.user.documentId },
29875
29937
  data: payload
29876
29938
  });
29877
29939
  } catch (e) {
@@ -29886,8 +29948,8 @@ const userService = ({ strapi: strapi2 }) => {
29886
29948
  throw new NotFoundError(`User not found for Firebase UID: ${entityId}`);
29887
29949
  }
29888
29950
  const firebasePromise = strapi2.firebase.auth().updateUser(entityId, payload);
29889
- const strapiPromise = strapi2.documents("plugin::users-permissions.user").update({
29890
- documentId: firebaseData.user.documentId,
29951
+ const strapiPromise = strapi2.db.query("plugin::users-permissions.user").update({
29952
+ where: { documentId: firebaseData.user.documentId },
29891
29953
  data: payload
29892
29954
  });
29893
29955
  return Promise.allSettled([firebasePromise, strapiPromise]);
@@ -29903,8 +29965,8 @@ const userService = ({ strapi: strapi2 }) => {
29903
29965
  throw new NotFoundError(`User not found for Firebase UID: ${entityId}`);
29904
29966
  }
29905
29967
  const firebasePromise = strapi2.firebase.auth().deleteUser(entityId);
29906
- const strapiPromise = strapi2.documents("plugin::users-permissions.user").delete({
29907
- documentId: firebaseData.user.documentId
29968
+ const strapiPromise = strapi2.db.query("plugin::users-permissions.user").delete({
29969
+ where: { documentId: firebaseData.user.documentId }
29908
29970
  });
29909
29971
  return Promise.allSettled([firebasePromise, strapiPromise]);
29910
29972
  } catch (e) {
@@ -29926,8 +29988,8 @@ const userService = ({ strapi: strapi2 }) => {
29926
29988
  if (!firebaseData?.user) {
29927
29989
  throw new NotFoundError(`User not found for Firebase UID: ${entityId}`);
29928
29990
  }
29929
- const response = await strapi2.documents("plugin::users-permissions.user").delete({
29930
- documentId: firebaseData.user.documentId
29991
+ const response = await strapi2.db.query("plugin::users-permissions.user").delete({
29992
+ where: { documentId: firebaseData.user.documentId }
29931
29993
  });
29932
29994
  return response;
29933
29995
  } catch (e) {
@@ -30466,8 +30528,8 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30466
30528
  * Forgot password flow - sends reset email
30467
30529
  * Public endpoint that sends a Firebase-hosted password reset email using Firebase's secure hosted UI
30468
30530
  */
30469
- forgotPassword: async (ctx) => {
30470
- const { email: email2 } = ctx.request.body;
30531
+ forgotPassword: async (email2) => {
30532
+ strapi2.log.info(`[forgotPassword] Starting password reset for email: ${email2}`);
30471
30533
  if (!email2) {
30472
30534
  throw new ValidationError$1("Email is required");
30473
30535
  }
@@ -30508,8 +30570,16 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30508
30570
  });
30509
30571
  }
30510
30572
  if (!strapiUser) {
30573
+ strapi2.log.warn(`⚠️ [forgotPassword] User not found for email: ${email2}`);
30511
30574
  return { message: "If an account with that email exists, a password reset link has been sent." };
30512
30575
  }
30576
+ strapi2.log.info(
30577
+ `✅ [forgotPassword] User found: ${JSON.stringify({
30578
+ documentId: strapiUser.documentId,
30579
+ email: strapiUser.email,
30580
+ firebaseUID: firebaseUser?.uid || "not in Firebase"
30581
+ })}`
30582
+ );
30513
30583
  const actionCodeSettings = {
30514
30584
  url: resetUrl,
30515
30585
  // Continue URL after reset completes on Firebase's page
@@ -30523,6 +30593,7 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30523
30593
  });
30524
30594
  let resetLink;
30525
30595
  try {
30596
+ strapi2.log.info(`[forgotPassword] Generating Firebase password reset link for: ${strapiUser.email}`);
30526
30597
  resetLink = await Promise.race([
30527
30598
  strapi2.firebase.auth().generatePasswordResetLink(strapiUser.email, actionCodeSettings),
30528
30599
  timeoutPromise
@@ -30534,12 +30605,22 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30534
30605
  strapi2.log.error(`❌ Failed to generate password reset link for ${strapiUser.email}:`, error2);
30535
30606
  throw error2;
30536
30607
  }
30608
+ strapi2.log.info(`[forgotPassword] Attempting to send password reset email to: ${strapiUser.email}`);
30537
30609
  await strapi2.plugin("firebase-authentication").service("emailService").sendPasswordResetEmail(strapiUser, resetLink);
30610
+ strapi2.log.info(`✅ [forgotPassword] Password reset email sent successfully to: ${strapiUser.email}`);
30538
30611
  return {
30539
30612
  message: "If an account with that email exists, a password reset link has been sent."
30540
30613
  };
30541
30614
  } catch (error2) {
30542
- strapi2.log.error("forgotPassword error:", error2);
30615
+ strapi2.log.error(
30616
+ `❌ [forgotPassword] ERROR: ${JSON.stringify({
30617
+ email: email2,
30618
+ message: error2.message,
30619
+ code: error2.code,
30620
+ name: error2.name,
30621
+ stack: error2.stack
30622
+ })}`
30623
+ );
30543
30624
  return {
30544
30625
  message: "If an account with that email exists, a password reset link has been sent."
30545
30626
  };
@@ -30559,13 +30640,10 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30559
30640
  *
30560
30641
  * NOT used for forgot password email flow - that now uses Firebase's hosted UI
30561
30642
  */
30562
- resetPassword: async (ctx) => {
30563
- const { password } = ctx.request.body;
30564
- const populate2 = ctx.request.query.populate || [];
30643
+ resetPassword: async (password, token, populate2) => {
30565
30644
  if (!password) {
30566
30645
  throw new ValidationError$1("Password is required");
30567
30646
  }
30568
- const token = ctx.request.header.authorization?.replace("Bearer ", "");
30569
30647
  if (!token) {
30570
30648
  throw new UnauthorizedError("Authorization token is required");
30571
30649
  }
@@ -30621,8 +30699,7 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30621
30699
  * Generates a sign-in link using Firebase Admin SDK
30622
30700
  * Note: Verification requires client-side completion
30623
30701
  */
30624
- async requestMagicLink(ctx) {
30625
- const { email: email2 } = ctx.request.body;
30702
+ async requestMagicLink(email2) {
30626
30703
  if (!email2 || typeof email2 !== "string") {
30627
30704
  throw new ValidationError$1("Valid email is required");
30628
30705
  }
@@ -30652,7 +30729,7 @@ const firebaseService = ({ strapi: strapi2 }) => ({
30652
30729
  };
30653
30730
  const magicLink = await strapi2.firebase.auth().generateSignInWithEmailLink(email2, actionCodeSettings);
30654
30731
  if (process.env.NODE_ENV !== "production") {
30655
- strapi2.log.info("🔗 Magic Link Generation Request:");
30732
+ strapi2.log.info("Magic Link Generation Request:");
30656
30733
  strapi2.log.info(` Email: ${email2}`);
30657
30734
  strapi2.log.info(` Verification URL: ${magicLinkUrl}`);
30658
30735
  strapi2.log.info(` Expires: ${config2.magicLinkExpiryHours || 1} hour(s)`);
@@ -31258,7 +31335,7 @@ class EmailService {
31258
31335
  const template = await templateService2.getTemplate("magicLink");
31259
31336
  const compiledSubject = _$1.template(template.subject)(completeVariables);
31260
31337
  strapi.log.info("\n" + "=".repeat(80));
31261
- strapi.log.info("🔗 MAGIC LINK EMAIL (Development Mode)");
31338
+ strapi.log.info("MAGIC LINK EMAIL (Development Mode)");
31262
31339
  strapi.log.info("=".repeat(80));
31263
31340
  strapi.log.info(`To: ${email2}`);
31264
31341
  strapi.log.info(`Subject: ${compiledSubject}`);
@@ -31333,7 +31410,7 @@ const firebaseUserDataService = ({ strapi: strapi2 }) => ({
31333
31410
  }
31334
31411
  });
31335
31412
  } catch (error2) {
31336
- if (error2.code === "23505") {
31413
+ if (error2.code === "23505" || error2.name === "ValidationError") {
31337
31414
  strapi2.log.warn(`Race condition detected for user ${userId}, retrying findFirst`);
31338
31415
  firebaseData = await strapi2.documents("plugin::firebase-authentication.firebase-user-data").findFirst({
31339
31416
  filters: { user: { documentId: { $eq: userId } } }
@@ -31373,7 +31450,7 @@ const autoLinkService = {
31373
31450
  errors: 0
31374
31451
  };
31375
31452
  try {
31376
- strapi2.log.info("🔗 Auto-linking Strapi users with Firebase users...");
31453
+ strapi2.log.info("Auto-linking Strapi users with Firebase users...");
31377
31454
  if (!strapi2.firebase) {
31378
31455
  strapi2.log.warn("Firebase not initialized - skipping auto-linking");
31379
31456
  return result;
@@ -31412,6 +31489,7 @@ const autoLinkService = {
31412
31489
  }
31413
31490
  }
31414
31491
  for (const strapiUser of strapiUsers) {
31492
+ let firebaseUser = null;
31415
31493
  try {
31416
31494
  const existing = await strapi2.db.query("plugin::firebase-authentication.firebase-user-data").findOne({
31417
31495
  select: ["id"],
@@ -31421,7 +31499,7 @@ const autoLinkService = {
31421
31499
  result.skipped++;
31422
31500
  continue;
31423
31501
  }
31424
- let firebaseUser = null;
31502
+ firebaseUser = null;
31425
31503
  if (strapiUser.email) {
31426
31504
  firebaseUser = firebaseByEmail.get(strapiUser.email.toLowerCase());
31427
31505
  }
@@ -31432,6 +31510,23 @@ const autoLinkService = {
31432
31510
  result.skipped++;
31433
31511
  continue;
31434
31512
  }
31513
+ const existingUIDLink = await strapi2.db.query("plugin::firebase-authentication.firebase-user-data").findOne({
31514
+ where: { firebaseUserID: firebaseUser.uid },
31515
+ populate: ["user"]
31516
+ });
31517
+ if (existingUIDLink && existingUIDLink.user?.documentId !== strapiUser.documentId) {
31518
+ strapi2.log.warn(
31519
+ `⚠️ [Auto-Link] Skipping '${strapiUser.username || strapiUser.email}' - Firebase UID already linked`
31520
+ );
31521
+ strapi2.log.warn(` Strapi User: ${strapiUser.email} (documentId: ${strapiUser.documentId})`);
31522
+ strapi2.log.warn(` Firebase UID: ${firebaseUser.uid}`);
31523
+ strapi2.log.warn(
31524
+ ` Already linked to: ${existingUIDLink.user?.email} (documentId: ${existingUIDLink.user?.documentId})`
31525
+ );
31526
+ strapi2.log.warn(` Action: Resolve duplicate users or manually unlink conflicting record`);
31527
+ result.skipped++;
31528
+ continue;
31529
+ }
31435
31530
  try {
31436
31531
  const firebaseData = {
31437
31532
  firebaseUserID: firebaseUser.uid
@@ -31450,18 +31545,42 @@ const autoLinkService = {
31450
31545
  throw createError;
31451
31546
  }
31452
31547
  } catch (error2) {
31453
- result.errors++;
31454
- strapi2.log.error(`Error linking user ${strapiUser.username}:`, error2);
31548
+ if (error2.message?.includes("This attribute must be unique")) {
31549
+ strapi2.log.warn(
31550
+ `⚠️ [Auto-Link] Unique constraint conflict for '${strapiUser.username || strapiUser.email}'`
31551
+ );
31552
+ strapi2.log.warn(` Strapi User: ${strapiUser.email} (documentId: ${strapiUser.documentId})`);
31553
+ if (firebaseUser) {
31554
+ strapi2.log.warn(` Firebase UID: ${firebaseUser.uid}`);
31555
+ strapi2.log.warn(` Cause: This Firebase UID is already linked to another Strapi user`);
31556
+ strapi2.log.warn(
31557
+ ` Action: Query firebase_user_data table to find conflict - WHERE firebase_user_id = '${firebaseUser.uid}'`
31558
+ );
31559
+ }
31560
+ result.skipped++;
31561
+ } else {
31562
+ result.errors++;
31563
+ strapi2.log.error(
31564
+ `❌ [Auto-Link] Unexpected error linking user '${strapiUser.username || strapiUser.email}': ${error2.message}`
31565
+ );
31566
+ }
31455
31567
  }
31456
31568
  }
31457
- strapi2.log.info(
31458
- `✅ Auto-linking complete: ${result.linked} linked, ${result.skipped} skipped, ${result.errors} errors`
31459
- );
31569
+ if (result.errors > 0) {
31570
+ strapi2.log.error(
31571
+ `❌ Auto-linking completed with unexpected errors: ${result.linked} linked, ${result.skipped} skipped, ${result.errors} errors`
31572
+ );
31573
+ strapi2.log.error(` Review error logs above for resolution steps`);
31574
+ } else {
31575
+ strapi2.log.info(
31576
+ `✅ Auto-linking complete: ${result.linked} linked, ${result.skipped} skipped, ${result.errors} errors`
31577
+ );
31578
+ }
31460
31579
  strapi2.log.info(
31461
31580
  ` Total: ${result.totalStrapiUsers} Strapi users, ${result.totalFirebaseUsers} Firebase users`
31462
31581
  );
31463
31582
  } catch (error2) {
31464
- strapi2.log.error("Fatal error during auto-linking:", error2);
31583
+ strapi2.log.error(`❌ Fatal error during auto-linking: ${error2.message}`);
31465
31584
  }
31466
31585
  return result;
31467
31586
  }