strapi-plugin-magic-link-v5 5.5.6 → 5.5.8

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.
@@ -357,38 +357,14 @@ _Falls du diesen Link nicht angefordert hast, ignoriere diese Nachricht._`,
357
357
  try {
358
358
  const licenseGuardService = strapi2.plugin("magic-link").service("license-guard");
359
359
  setTimeout(async () => {
360
- const licenseStatus = await licenseGuardService.initialize();
361
- if (!licenseStatus.valid && licenseStatus.demo) {
362
- strapi2.log.warn("╔════════════════════════════════════════════════════════════════╗");
363
- strapi2.log.warn("║ [WARNING] MAGIC LINK PLUGIN RUNNING IN DEMO MODE ║");
364
- strapi2.log.warn("║ ║");
365
- strapi2.log.warn("║ To activate, create a license via Admin UI: ║");
366
- strapi2.log.warn("║ Go to Settings → Magic Link → License ║");
367
- strapi2.log.warn("╚════════════════════════════════════════════════════════════════╝");
368
- } else if (licenseStatus.valid) {
369
- const pluginStore2 = strapi2.store({
370
- type: "plugin",
371
- name: "magic-link"
372
- });
373
- const storedKey = await pluginStore2.get({ key: "licenseKey" });
374
- strapi2.log.info("╔════════════════════════════════════════════════════════════════╗");
375
- strapi2.log.info("║ [SUCCESS] MAGIC LINK PLUGIN LICENSE ACTIVE ║");
376
- strapi2.log.info("║ ║");
377
- if (licenseStatus.data) {
378
- strapi2.log.info(`║ License: ${licenseStatus.data.licenseKey} ║`);
379
- strapi2.log.info(`║ User: ${licenseStatus.data.firstName} ${licenseStatus.data.lastName}`.padEnd(66) + "║");
380
- strapi2.log.info(`║ Email: ${licenseStatus.data.email}`.padEnd(66) + "║");
381
- } else if (storedKey) {
382
- strapi2.log.info(`║ License: ${storedKey} (Offline Mode) ║`);
383
- strapi2.log.info(`║ Status: Grace Period Active ║`);
384
- }
385
- strapi2.log.info("║ ║");
386
- strapi2.log.info("║ [AUTO] Pinging every 15 minutes ║");
387
- strapi2.log.info("╚════════════════════════════════════════════════════════════════╝");
360
+ try {
361
+ await licenseGuardService.initialize();
362
+ } catch (initErr) {
363
+ strapi2.log.debug("[magic-link] license-guard initialize() warning:", initErr.message);
388
364
  }
389
365
  }, 3e3);
390
366
  } catch (error) {
391
- strapi2.log.error("[ERROR] Error initializing License Guard:", error);
367
+ strapi2.log.debug("[magic-link] license-guard service not available at bootstrap:", error.message);
392
368
  }
393
369
  try {
394
370
  setTimeout(async () => {
@@ -416,12 +392,11 @@ _Falls du diesen Link nicht angefordert hast, ignoriere diese Nachricht._`,
416
392
  var destroy$1 = ({ strapi: strapi2 }) => {
417
393
  try {
418
394
  const licenseGuardService = strapi2.plugin("magic-link")?.service("license-guard");
419
- if (licenseGuardService) {
395
+ if (licenseGuardService && typeof licenseGuardService.cleanup === "function") {
420
396
  licenseGuardService.cleanup();
421
- strapi2.log.info("[SUCCESS] License Guard cleanup completed");
422
397
  }
423
398
  } catch (error) {
424
- strapi2.log.error("[ERROR] Error during License Guard cleanup:", error);
399
+ strapi2.log.debug("[magic-link] license-guard cleanup warning:", error.message);
425
400
  }
426
401
  };
427
402
  var passwordlessCompat$2 = (config2, { strapi: strapi2 }) => {
@@ -1206,9 +1181,7 @@ var auth$1 = {
1206
1181
  return i18n.sendError(ctx, "token.invalid", 400);
1207
1182
  }
1208
1183
  const settings = await magicLink2.settings();
1209
- const licenseGuard2 = strapi.plugin("magic-link").service("license-guard");
1210
- const hasOTPFeature = await licenseGuard2.hasFeature("otp-email");
1211
- if (settings.otp_enabled && hasOTPFeature) {
1184
+ if (settings.otp_enabled) {
1212
1185
  await strapi.documents("plugin::magic-link.token").update({
1213
1186
  documentId: token2.documentId,
1214
1187
  data: {
@@ -1675,7 +1648,6 @@ Thanks for joining. Your Magic Link is on the way.`,
1675
1648
  const { email: normalizedEmail, totpCode } = parseBody$1(loginWithTotpSchema, ctx.request.body);
1676
1649
  const magicLink2 = strapi.plugin("magic-link").service("magic-link");
1677
1650
  const otpService = strapi.plugin("magic-link").service("otp");
1678
- const licenseGuard2 = strapi.plugin("magic-link").service("license-guard");
1679
1651
  const rateLimiter2 = strapi.plugin("magic-link").service("rate-limiter");
1680
1652
  const userService = strapi.plugin("users-permissions").service("user");
1681
1653
  const jwtService = strapi.plugin("users-permissions").service("jwt");
@@ -1694,10 +1666,6 @@ Thanks for joining. Your Magic Link is on the way.`,
1694
1666
  if (!settings.totp_as_primary_auth) {
1695
1667
  return ctx.forbidden("TOTP login is not enabled");
1696
1668
  }
1697
- const hasFeature = await licenseGuard2.hasFeature("otp-totp");
1698
- if (!hasFeature) {
1699
- return ctx.forbidden("TOTP login requires Advanced license");
1700
- }
1701
1669
  const GENERIC_INVALID = "Invalid credentials";
1702
1670
  const users = await strapi.documents("plugin::users-permissions.user").findMany({
1703
1671
  filters: { email: normalizedEmail },
@@ -2870,49 +2838,43 @@ var jwt$1 = {
2870
2838
  };
2871
2839
  var license$1 = {
2872
2840
  /**
2873
- * Get current license status
2841
+ * Returns the current license status. Always reports the plugin as
2842
+ * usable; `hasKey` indicates whether the admin has activated a key.
2843
+ *
2844
+ * @route GET /magic-link/license/status
2845
+ * @returns {{ success: true, valid: true, hasKey: boolean, data: object|null }}
2874
2846
  */
2875
2847
  async getStatus(ctx) {
2876
2848
  try {
2877
2849
  const licenseGuard2 = strapi.plugin("magic-link").service("license-guard");
2878
- const pluginStore = strapi.store({
2879
- type: "plugin",
2880
- name: "magic-link"
2881
- });
2850
+ const pluginStore = strapi.store({ type: "plugin", name: "magic-link" });
2882
2851
  const licenseKey = await pluginStore.get({ key: "licenseKey" });
2883
2852
  if (!licenseKey) {
2884
2853
  return ctx.send({
2885
- success: false,
2886
- demo: true,
2887
- valid: false,
2888
- message: "No license found. Running in demo mode."
2854
+ success: true,
2855
+ valid: true,
2856
+ // plugin is always usable in the marketplace build
2857
+ hasKey: false,
2858
+ data: null,
2859
+ message: "Plugin is active. License key activation is optional."
2889
2860
  });
2890
2861
  }
2891
- const verification = await licenseGuard2.verifyLicense(licenseKey);
2892
2862
  const license2 = await licenseGuard2.getLicenseByKey(licenseKey);
2893
2863
  return ctx.send({
2894
2864
  success: true,
2895
- valid: verification.valid,
2896
- demo: false,
2865
+ valid: true,
2866
+ hasKey: true,
2897
2867
  data: {
2898
2868
  licenseKey,
2899
2869
  email: license2?.email || null,
2900
2870
  firstName: license2?.firstName || null,
2901
2871
  lastName: license2?.lastName || null,
2902
- isActive: license2?.isActive || false,
2903
- isExpired: license2?.isExpired || false,
2904
- isOnline: license2?.isOnline || false,
2905
- expiresAt: license2?.expiresAt,
2906
- lastPingAt: license2?.lastPingAt,
2907
- deviceName: license2?.deviceName,
2908
- deviceId: license2?.deviceId,
2909
- ipAddress: license2?.ipAddress,
2910
- features: {
2911
- premium: license2?.featurePremium || false,
2912
- advanced: license2?.featureAdvanced || false,
2913
- enterprise: license2?.featureEnterprise || false,
2914
- custom: license2?.featureCustom || false
2915
- },
2872
+ isActive: license2?.isActive ?? true,
2873
+ isExpired: license2?.isExpired ?? false,
2874
+ expiresAt: license2?.expiresAt || null,
2875
+ deviceName: license2?.deviceName || null,
2876
+ deviceId: license2?.deviceId || null,
2877
+ ipAddress: license2?.ipAddress || null,
2916
2878
  maxDevices: license2?.maxDevices || 1,
2917
2879
  currentDevices: license2?.currentDevices || 0
2918
2880
  }
@@ -2923,7 +2885,11 @@ var license$1 = {
2923
2885
  }
2924
2886
  },
2925
2887
  /**
2926
- * Auto-create license with logged-in admin user data
2888
+ * Auto-create a license using the currently logged-in admin user's
2889
+ * data. Best-effort: a remote failure simply leaves the install
2890
+ * un-keyed, the plugin keeps working.
2891
+ *
2892
+ * @route POST /magic-link/license/auto-create
2927
2893
  */
2928
2894
  async autoCreate(ctx) {
2929
2895
  try {
@@ -2938,15 +2904,9 @@ var license$1 = {
2938
2904
  lastName: adminUser.lastname || "User"
2939
2905
  });
2940
2906
  if (!license2) {
2941
- return ctx.badRequest("Failed to create license");
2907
+ return ctx.badRequest("License server unreachable. The plugin keeps working without a key.");
2942
2908
  }
2943
2909
  await licenseGuard2.storeLicenseKey(license2.licenseKey);
2944
- const pingInterval = licenseGuard2.startPinging(license2.licenseKey, 15);
2945
- strapi.licenseGuard = {
2946
- licenseKey: license2.licenseKey,
2947
- pingInterval,
2948
- data: license2
2949
- };
2950
2910
  return ctx.send({
2951
2911
  success: true,
2952
2912
  message: "License automatically created and activated",
@@ -2958,26 +2918,22 @@ var license$1 = {
2958
2918
  }
2959
2919
  },
2960
2920
  /**
2961
- * Create and activate a new license
2921
+ * Create and activate a new license with explicit user details.
2922
+ *
2923
+ * @route POST /magic-link/license/create
2962
2924
  */
2963
2925
  async createAndActivate(ctx) {
2964
2926
  try {
2965
- const { email: email2, firstName, lastName } = ctx.request.body;
2927
+ const { email: email2, firstName, lastName } = ctx.request.body || {};
2966
2928
  if (!email2 || !firstName || !lastName) {
2967
2929
  return ctx.badRequest("Email, firstName, and lastName are required");
2968
2930
  }
2969
2931
  const licenseGuard2 = strapi.plugin("magic-link").service("license-guard");
2970
2932
  const license2 = await licenseGuard2.createLicense({ email: email2, firstName, lastName });
2971
2933
  if (!license2) {
2972
- return ctx.badRequest("Failed to create license");
2934
+ return ctx.badRequest("License server unreachable. The plugin keeps working without a key.");
2973
2935
  }
2974
2936
  await licenseGuard2.storeLicenseKey(license2.licenseKey);
2975
- const pingInterval = licenseGuard2.startPinging(license2.licenseKey, 15);
2976
- strapi.licenseGuard = {
2977
- licenseKey: license2.licenseKey,
2978
- pingInterval,
2979
- data: license2
2980
- };
2981
2937
  return ctx.send({
2982
2938
  success: true,
2983
2939
  message: "License created and activated successfully",
@@ -2989,39 +2945,40 @@ var license$1 = {
2989
2945
  }
2990
2946
  },
2991
2947
  /**
2992
- * Manually ping the current license
2948
+ * Manual refresh: re-verifies the stored license once and returns the
2949
+ * latest details. Replaces the periodic-ping endpoint that used to run
2950
+ * every 15 minutes — admins can call this on demand from the UI to
2951
+ * pull updated info.
2952
+ *
2953
+ * @route POST /magic-link/license/ping
2993
2954
  */
2994
2955
  async ping(ctx) {
2995
2956
  try {
2996
- const pluginStore = strapi.store({
2997
- type: "plugin",
2998
- name: "magic-link"
2999
- });
2957
+ const pluginStore = strapi.store({ type: "plugin", name: "magic-link" });
3000
2958
  const licenseKey = await pluginStore.get({ key: "licenseKey" });
3001
2959
  if (!licenseKey) {
3002
2960
  return ctx.badRequest("No license key found");
3003
2961
  }
3004
2962
  const licenseGuard2 = strapi.plugin("magic-link").service("license-guard");
3005
- const pingResult = await licenseGuard2.pingLicense(licenseKey);
3006
- if (!pingResult) {
3007
- return ctx.badRequest("Ping failed");
3008
- }
2963
+ const verification = await licenseGuard2.verifyLicense(licenseKey);
3009
2964
  return ctx.send({
3010
2965
  success: true,
3011
- message: "License pinged successfully",
3012
- data: pingResult
2966
+ message: verification.valid ? "License refreshed successfully" : "License key could not be verified, but the plugin keeps working.",
2967
+ data: verification.data
3013
2968
  });
3014
2969
  } catch (error) {
3015
- strapi.log.error("Error pinging license:", error);
3016
- return ctx.badRequest("Error pinging license");
2970
+ strapi.log.error("Error refreshing license:", error);
2971
+ return ctx.badRequest("Error refreshing license");
3017
2972
  }
3018
2973
  },
3019
2974
  /**
3020
- * Store and validate an existing license key
3021
- */
2975
+ * Store and validate an existing license key.
2976
+ *
2977
+ * @route POST /magic-link/license/store-key
2978
+ */
3022
2979
  async storeKey(ctx) {
3023
2980
  try {
3024
- const { licenseKey, email: email2 } = ctx.request.body;
2981
+ const { licenseKey, email: email2 } = ctx.request.body || {};
3025
2982
  if (!licenseKey || !licenseKey.trim()) {
3026
2983
  return ctx.badRequest("License key is required");
3027
2984
  }
@@ -3033,26 +2990,35 @@ var license$1 = {
3033
2990
  const licenseGuard2 = strapi.plugin("magic-link").service("license-guard");
3034
2991
  const verification = await licenseGuard2.verifyLicense(trimmedKey);
3035
2992
  if (!verification.valid) {
3036
- strapi.log.warn(`[WARNING] Invalid license key attempted: ${trimmedKey.substring(0, 8)}...`);
2993
+ if (verification.networkError) {
2994
+ strapi.log.warn(
2995
+ `[WARNING] License server unreachable during activation. Storing key anyway: ${trimmedKey.substring(0, 8)}...`
2996
+ );
2997
+ await licenseGuard2.storeLicenseKey(trimmedKey);
2998
+ return ctx.send({
2999
+ success: true,
3000
+ message: "License key stored (license server was unreachable; will retry on the next manual refresh).",
3001
+ data: { licenseKey: trimmedKey, email: trimmedEmail }
3002
+ });
3003
+ }
3004
+ strapi.log.warn(`[WARNING] Invalid license key: ${trimmedKey.substring(0, 8)}...`);
3037
3005
  return ctx.badRequest("Invalid or expired license key");
3038
3006
  }
3039
3007
  const license2 = await licenseGuard2.getLicenseByKey(trimmedKey);
3040
3008
  if (!license2) {
3041
- strapi.log.warn(`[WARNING] License not found in database: ${trimmedKey.substring(0, 8)}...`);
3009
+ strapi.log.warn(`[WARNING] License not found: ${trimmedKey.substring(0, 8)}...`);
3042
3010
  return ctx.badRequest("License not found");
3043
3011
  }
3044
- if (license2.email.toLowerCase() !== trimmedEmail) {
3045
- strapi.log.warn(`[WARNING] Email mismatch for license key: ${trimmedKey.substring(0, 8)}... (Attempted: ${trimmedEmail})`);
3012
+ if (license2.email && license2.email.toLowerCase() !== trimmedEmail) {
3013
+ strapi.log.warn(
3014
+ `[WARNING] Email mismatch for license key: ${trimmedKey.substring(0, 8)}... (attempted: ${trimmedEmail})`
3015
+ );
3046
3016
  return ctx.badRequest("Email address does not match this license key");
3047
3017
  }
3048
3018
  await licenseGuard2.storeLicenseKey(trimmedKey);
3049
- const pingInterval = licenseGuard2.startPinging(trimmedKey, 15);
3050
- strapi.licenseGuard = {
3051
- licenseKey: trimmedKey,
3052
- pingInterval,
3053
- data: verification.data
3054
- };
3055
- strapi.log.info(`[SUCCESS] Existing license key validated and stored: ${trimmedKey.substring(0, 8)}... (Email: ${trimmedEmail})`);
3019
+ strapi.log.info(
3020
+ `[SUCCESS] License key validated and stored: ${trimmedKey.substring(0, 8)}... (email: ${trimmedEmail})`
3021
+ );
3056
3022
  return ctx.send({
3057
3023
  success: true,
3058
3024
  message: "License key validated and activated successfully",
@@ -3204,11 +3170,6 @@ var otp$3 = {
3204
3170
  if (!otpSettings.enabled) {
3205
3171
  return ctx.badRequest("OTP is not enabled");
3206
3172
  }
3207
- const licenseGuard2 = strapi.plugin("magic-link").service("license-guard");
3208
- const hasOTPFeature = await licenseGuard2.hasFeature("otp-email");
3209
- if (!hasOTPFeature) {
3210
- return ctx.forbidden("OTP feature requires Premium license or higher");
3211
- }
3212
3173
  const rlFail = await enforceOtpRateLimits(ctx, normalizedEmail, "send");
3213
3174
  if (rlFail) return ctx.tooManyRequests(rlFail.message);
3214
3175
  const pluginStore = strapi.store({ type: "plugin", name: "magic-link" });
@@ -3500,11 +3461,6 @@ var otp$3 = {
3500
3461
  */
3501
3462
  async setupTOTP(ctx) {
3502
3463
  try {
3503
- const licenseGuard2 = strapi.plugin("magic-link").service("license-guard");
3504
- const hasFeature = await licenseGuard2.hasFeature("otp-totp");
3505
- if (!hasFeature) {
3506
- return ctx.forbidden("TOTP feature requires Advanced or Enterprise license");
3507
- }
3508
3464
  const { email: email2, id: userId } = ctx.state.user;
3509
3465
  const otpService = strapi.plugin("magic-link").service("otp");
3510
3466
  const setupData = await otpService.setupTOTP(userId, email2);
@@ -3574,11 +3530,6 @@ var otp$3 = {
3574
3530
  */
3575
3531
  async generateBackupCodes(ctx) {
3576
3532
  try {
3577
- const licenseGuard2 = strapi.plugin("magic-link").service("license-guard");
3578
- const hasFeature = await licenseGuard2.hasFeature("otp-backup-codes");
3579
- if (!hasFeature) {
3580
- return ctx.forbidden("Backup codes feature requires Enterprise license");
3581
- }
3582
3533
  const { id: userId } = ctx.state.user;
3583
3534
  const otpService = strapi.plugin("magic-link").service("otp");
3584
3535
  const backupCodes = await otpService.generateBackupCodes(userId);
@@ -3748,61 +3699,6 @@ const passwordlessCompat = passwordlessCompat$2;
3748
3699
  var middlewares$1 = {
3749
3700
  "passwordless-compat": passwordlessCompat
3750
3701
  };
3751
- const { PolicyError } = require$$1$1.errors;
3752
- var licenseCheck$1 = async (policyContext, config2, { strapi: strapi2 }) => {
3753
- try {
3754
- const licenseGuard2 = strapi2.plugin("magic-link").service("license-guard");
3755
- const pluginStore = strapi2.store({ type: "plugin", name: "magic-link" });
3756
- const licenseKey = await pluginStore.get({ key: "licenseKey" });
3757
- if (!licenseKey) {
3758
- strapi2.log.warn("[ACCESS-DENIED] No license key found");
3759
- throw new PolicyError("No license found. Please activate the plugin first.", {
3760
- policy: "license-check",
3761
- errCode: "LICENSE_MISSING"
3762
- });
3763
- }
3764
- const verification = await licenseGuard2.verifyLicense(licenseKey, true);
3765
- if (!verification.valid) {
3766
- strapi2.log.warn("[ACCESS-DENIED] Invalid license");
3767
- throw new PolicyError("Invalid or expired license.", {
3768
- policy: "license-check",
3769
- errCode: "LICENSE_INVALID"
3770
- });
3771
- }
3772
- const license2 = await licenseGuard2.getLicenseByKey(licenseKey);
3773
- if (!license2) {
3774
- strapi2.log.warn("[ACCESS-DENIED] License not found in database");
3775
- throw new PolicyError("License not found. Please contact support.", {
3776
- policy: "license-check",
3777
- errCode: "LICENSE_NOT_FOUND"
3778
- });
3779
- }
3780
- if (!license2.isActive) {
3781
- strapi2.log.warn("[ACCESS-DENIED] License is inactive");
3782
- throw new PolicyError("License is inactive. Please activate your license.", {
3783
- policy: "license-check",
3784
- errCode: "LICENSE_INACTIVE"
3785
- });
3786
- }
3787
- if (license2.isExpired) {
3788
- strapi2.log.warn("[ACCESS-DENIED] License has expired");
3789
- throw new PolicyError("License has expired. Please renew your license.", {
3790
- policy: "license-check",
3791
- errCode: "LICENSE_EXPIRED"
3792
- });
3793
- }
3794
- return true;
3795
- } catch (error) {
3796
- if (error instanceof PolicyError) {
3797
- throw error;
3798
- }
3799
- strapi2.log.error("Error checking license:", error);
3800
- throw new PolicyError("Error verifying license. Please try again.", {
3801
- policy: "license-check",
3802
- errCode: "LICENSE_CHECK_ERROR"
3803
- });
3804
- }
3805
- };
3806
3702
  var compatibilityCheck$1 = async (policyContext, config2, { strapi: strapi2 }) => {
3807
3703
  const pluginStore = strapi2.store({
3808
3704
  type: "plugin",
@@ -3824,10 +3720,8 @@ var compatibilityCheck$1 = async (policyContext, config2, { strapi: strapi2 }) =
3824
3720
  }
3825
3721
  return true;
3826
3722
  };
3827
- const licenseCheck = licenseCheck$1;
3828
3723
  const compatibilityCheck = compatibilityCheck$1;
3829
3724
  var policies$1 = {
3830
- "license-check": licenseCheck,
3831
3725
  "compatibility-check": compatibilityCheck
3832
3726
  };
3833
3727
  var contentApi$1 = {
@@ -3839,7 +3733,7 @@ var contentApi$1 = {
3839
3733
  handler: "auth.login",
3840
3734
  config: {
3841
3735
  auth: false,
3842
- policies: ["plugin::magic-link.license-check"]
3736
+ policies: []
3843
3737
  }
3844
3738
  },
3845
3739
  {
@@ -3848,7 +3742,7 @@ var contentApi$1 = {
3848
3742
  handler: "auth.sendLink",
3849
3743
  config: {
3850
3744
  auth: false,
3851
- policies: ["plugin::magic-link.license-check"]
3745
+ policies: []
3852
3746
  }
3853
3747
  },
3854
3748
  // OTP Routes
@@ -3858,7 +3752,7 @@ var contentApi$1 = {
3858
3752
  handler: "otp.send",
3859
3753
  config: {
3860
3754
  auth: false,
3861
- policies: ["plugin::magic-link.license-check"]
3755
+ policies: []
3862
3756
  }
3863
3757
  },
3864
3758
  {
@@ -3867,7 +3761,7 @@ var contentApi$1 = {
3867
3761
  handler: "otp.verify",
3868
3762
  config: {
3869
3763
  auth: false,
3870
- policies: ["plugin::magic-link.license-check"]
3764
+ policies: []
3871
3765
  }
3872
3766
  },
3873
3767
  {
@@ -3876,7 +3770,7 @@ var contentApi$1 = {
3876
3770
  handler: "otp.resend",
3877
3771
  config: {
3878
3772
  auth: false,
3879
- policies: ["plugin::magic-link.license-check"]
3773
+ policies: []
3880
3774
  }
3881
3775
  },
3882
3776
  // MFA Routes
@@ -3886,7 +3780,7 @@ var contentApi$1 = {
3886
3780
  handler: "auth.verifyMFATOTP",
3887
3781
  config: {
3888
3782
  auth: false,
3889
- policies: ["plugin::magic-link.license-check"]
3783
+ policies: []
3890
3784
  }
3891
3785
  },
3892
3786
  {
@@ -3895,7 +3789,7 @@ var contentApi$1 = {
3895
3789
  handler: "auth.loginWithTOTP",
3896
3790
  config: {
3897
3791
  auth: false,
3898
- policies: ["plugin::magic-link.license-check"]
3792
+ policies: []
3899
3793
  }
3900
3794
  }
3901
3795
  ]
@@ -4195,10 +4089,7 @@ var compatibility$1 = {
4195
4089
  handler: "auth.login",
4196
4090
  config: {
4197
4091
  auth: false,
4198
- policies: [
4199
- "plugin::magic-link.compatibility-check",
4200
- "plugin::magic-link.license-check"
4201
- ],
4092
+ policies: ["plugin::magic-link.compatibility-check"],
4202
4093
  description: "Compatibility route for strapi-plugin-passwordless"
4203
4094
  }
4204
4095
  },
@@ -4209,10 +4100,7 @@ var compatibility$1 = {
4209
4100
  handler: "auth.sendLink",
4210
4101
  config: {
4211
4102
  auth: false,
4212
- policies: [
4213
- "plugin::magic-link.compatibility-check",
4214
- "plugin::magic-link.license-check"
4215
- ],
4103
+ policies: ["plugin::magic-link.compatibility-check"],
4216
4104
  description: "Compatibility route for strapi-plugin-passwordless"
4217
4105
  }
4218
4106
  }
@@ -4628,6 +4516,50 @@ var magicLink$1 = ({ strapi: strapi2 }) => ({
4628
4516
  strapi2.log.error("MagicMail send failed, falling back to default provider:", error);
4629
4517
  }
4630
4518
  }
4519
+ const wantsEmailDesigner = settings?.use_email_designer && settings?.email_designer_template_id;
4520
+ const hasEmailDesignerPlugin = Boolean(strapi2.plugin("email-designer-5"));
4521
+ if (wantsEmailDesigner && hasEmailDesignerPlugin) {
4522
+ const templateId = parseInt(settings.email_designer_template_id, 10);
4523
+ if (!Number.isNaN(templateId) && templateId > 0) {
4524
+ try {
4525
+ await strapi2.plugin("email-designer-5").service("email").sendTemplatedEmail(
4526
+ {
4527
+ to: token2.email,
4528
+ from: settings.from_email ? `${settings.from_name || "Magic Link"} <${settings.from_email}>` : void 0,
4529
+ replyTo: settings.response_email || void 0
4530
+ },
4531
+ {
4532
+ templateReferenceId: templateId,
4533
+ subject: settings.object || emailConfig.object
4534
+ },
4535
+ {
4536
+ ...templatePayload,
4537
+ user: { email: token2.email },
4538
+ magicLink: loginUrl,
4539
+ expiresAt: token2.expires_at ? new Date(token2.expires_at).toISOString() : void 0,
4540
+ expiryText: expiryTextForTemplate
4541
+ }
4542
+ );
4543
+ strapi2.log.info(
4544
+ `Magic Link email sent via Email Designer 5 (template ${templateId}) to ${token2.email}`
4545
+ );
4546
+ return token2;
4547
+ } catch (emailDesignerError) {
4548
+ strapi2.log.error(
4549
+ "Email Designer 5 send failed, falling back to default provider:",
4550
+ emailDesignerError
4551
+ );
4552
+ }
4553
+ } else {
4554
+ strapi2.log.warn(
4555
+ `Invalid Email Designer template ID: '${settings.email_designer_template_id}', falling back to default email`
4556
+ );
4557
+ }
4558
+ } else if (wantsEmailDesigner && !hasEmailDesignerPlugin) {
4559
+ strapi2.log.debug(
4560
+ "Email Designer enabled but plugin not installed — falling back to default email"
4561
+ );
4562
+ }
4631
4563
  await strapi2.plugin("email").service("email").send({
4632
4564
  to: token2.email,
4633
4565
  from: emailConfig.from,
@@ -4810,265 +4742,8 @@ var store$1 = ({ strapi: strapi2 }) => ({
4810
4742
  return pluginStore.set({ key: "settings", value: settings });
4811
4743
  }
4812
4744
  });
4813
- var features$1 = {
4814
- // FREE/DEMO Features
4815
- free: {
4816
- maxTokens: 100,
4817
- // 100 aktive Tokens gleichzeitig
4818
- maxSessions: 50,
4819
- // 50 aktive JWT Sessions
4820
- maxIPBans: 10,
4821
- // 10 gebannte IPs
4822
- features: [
4823
- "basic-magic-link",
4824
- // Basis Magic Link Auth
4825
- "email-templates",
4826
- // Basis Email Templates
4827
- "jwt-sessions",
4828
- // JWT Session Management
4829
- "ip-tracking",
4830
- // IP Tracking für Security
4831
- "rate-limiting",
4832
- // Basis Rate Limiting
4833
- "auto-user-creation",
4834
- // Auto User Creation
4835
- "token-management",
4836
- // Token Dashboard
4837
- "ip-bans"
4838
- // IP Ban Management
4839
- ]
4840
- },
4841
- // PREMIUM Features
4842
- premium: {
4843
- maxTokens: 1e3,
4844
- // 1000 aktive Tokens
4845
- maxSessions: 500,
4846
- // 500 aktive Sessions
4847
- maxIPBans: 100,
4848
- // 100 gebannte IPs
4849
- features: [
4850
- "basic-magic-link",
4851
- "email-templates",
4852
- "jwt-sessions",
4853
- "ip-tracking",
4854
- "rate-limiting",
4855
- "auto-user-creation",
4856
- "token-management",
4857
- "ip-bans",
4858
- "otp-email",
4859
- // Email OTP (6-digit code)
4860
- "mfa-totp",
4861
- // MFA: Magic Link + TOTP (Szenario 1)
4862
- "custom-email-templates",
4863
- // Custom HTML/Text Templates
4864
- "email-designer-integration",
4865
- // Email Designer 5 Support
4866
- "magic-mail-integration",
4867
- // MagicMail Integration
4868
- "advanced-rate-limiting",
4869
- // Custom Rate Limits
4870
- "session-management",
4871
- // Erweiterte Session Verwaltung
4872
- "bulk-operations",
4873
- // Bulk Token/Session Operations
4874
- "extended-logging",
4875
- // Erweiterte Login-Info Logs
4876
- "custom-redirect-urls"
4877
- // Custom Redirect URLs per Token
4878
- ]
4879
- },
4880
- // ADVANCED Features
4881
- advanced: {
4882
- maxTokens: -1,
4883
- // Unlimited
4884
- maxSessions: -1,
4885
- // Unlimited
4886
- maxIPBans: -1,
4887
- // Unlimited
4888
- features: [
4889
- "basic-magic-link",
4890
- "email-templates",
4891
- "jwt-sessions",
4892
- "ip-tracking",
4893
- "rate-limiting",
4894
- "auto-user-creation",
4895
- "token-management",
4896
- "ip-bans",
4897
- "otp-email",
4898
- "mfa-totp",
4899
- // MFA: Magic Link + TOTP (Szenario 1)
4900
- "otp-sms",
4901
- // SMS OTP via Twilio/Vonage
4902
- "otp-totp",
4903
- // TOTP (Authenticator Apps)
4904
- "totp-primary-login",
4905
- // Login with Email + TOTP only (Szenario 2)
4906
- "custom-email-templates",
4907
- "email-designer-integration",
4908
- "magic-mail-integration",
4909
- "advanced-rate-limiting",
4910
- "session-management",
4911
- "bulk-operations",
4912
- "extended-logging",
4913
- "custom-redirect-urls",
4914
- "webhook-notifications",
4915
- // Webhooks für Events
4916
- "api-access-tokens",
4917
- // API Tokens für Programmatic Access
4918
- "advanced-analytics",
4919
- // Analytics Dashboard
4920
- "custom-token-context",
4921
- // Custom Context Data in Tokens
4922
- "geolocation-tracking",
4923
- // GeoIP Tracking
4924
- "device-fingerprinting",
4925
- // Device Fingerprinting
4926
- "suspicious-login-alerts"
4927
- // Alerts für verdächtige Logins
4928
- ]
4929
- },
4930
- // ENTERPRISE Features
4931
- enterprise: {
4932
- maxTokens: -1,
4933
- maxSessions: -1,
4934
- maxIPBans: -1,
4935
- features: [
4936
- "basic-magic-link",
4937
- "email-templates",
4938
- "jwt-sessions",
4939
- "ip-tracking",
4940
- "rate-limiting",
4941
- "auto-user-creation",
4942
- "token-management",
4943
- "ip-bans",
4944
- "otp-email",
4945
- "mfa-totp",
4946
- // MFA: Magic Link + TOTP (Szenario 1)
4947
- "otp-sms",
4948
- "otp-totp",
4949
- "totp-primary-login",
4950
- // Login with Email + TOTP only (Szenario 2)
4951
- "otp-backup-codes",
4952
- // Backup Recovery Codes
4953
- "biometric-support",
4954
- // WebAuthn/FIDO2
4955
- "custom-email-templates",
4956
- "email-designer-integration",
4957
- "magic-mail-integration",
4958
- "advanced-rate-limiting",
4959
- "session-management",
4960
- "bulk-operations",
4961
- "extended-logging",
4962
- "custom-redirect-urls",
4963
- "webhook-notifications",
4964
- "api-access-tokens",
4965
- "advanced-analytics",
4966
- "custom-token-context",
4967
- "geolocation-tracking",
4968
- "device-fingerprinting",
4969
- "suspicious-login-alerts",
4970
- "sso-integration",
4971
- // SSO/SAML Integration
4972
- "ldap-sync",
4973
- // LDAP/AD Sync
4974
- "audit-logs",
4975
- // Vollständige Audit Logs
4976
- "compliance-reports",
4977
- // GDPR/SOC2 Reports
4978
- "priority-support",
4979
- // 24/7 Priority Support
4980
- "custom-security-rules",
4981
- // Custom Security Rules
4982
- "multi-tenant",
4983
- // Multi-Tenant Support
4984
- "white-label"
4985
- // White-Label Branding
4986
- ]
4987
- },
4988
- /**
4989
- * Check if a feature is available for given license tier
4990
- */
4991
- hasFeature(licenseData, featureName) {
4992
- if (!licenseData) {
4993
- return this.free.features.includes(featureName);
4994
- }
4995
- let isEnterprise = false;
4996
- let isAdvanced = false;
4997
- let isPremium = false;
4998
- if (licenseData.tier) {
4999
- isEnterprise = licenseData.tier === "enterprise";
5000
- isAdvanced = licenseData.tier === "advanced";
5001
- isPremium = licenseData.tier === "premium";
5002
- }
5003
- if (licenseData.features) {
5004
- isEnterprise = isEnterprise || licenseData.features.enterprise === true;
5005
- isAdvanced = isAdvanced || licenseData.features.advanced === true;
5006
- isPremium = isPremium || licenseData.features.premium === true;
5007
- }
5008
- if (licenseData.featureEnterprise === true) {
5009
- isEnterprise = true;
5010
- }
5011
- if (licenseData.featureAdvanced === true) {
5012
- isAdvanced = true;
5013
- }
5014
- if (licenseData.featurePremium === true) {
5015
- isPremium = true;
5016
- }
5017
- if (isEnterprise && this.enterprise.features.includes(featureName)) {
5018
- return true;
5019
- }
5020
- if (isAdvanced && this.advanced.features.includes(featureName)) {
5021
- return true;
5022
- }
5023
- if (isPremium && this.premium.features.includes(featureName)) {
5024
- return true;
5025
- }
5026
- return this.free.features.includes(featureName);
5027
- },
5028
- /**
5029
- * Get max allowed tokens for license tier
5030
- */
5031
- getMaxTokens(licenseData) {
5032
- if (!licenseData) return this.free.maxTokens;
5033
- if (licenseData.featureEnterprise === true || licenseData.features?.enterprise === true) return this.enterprise.maxTokens;
5034
- if (licenseData.featureAdvanced === true || licenseData.features?.advanced === true) return this.advanced.maxTokens;
5035
- if (licenseData.featurePremium === true || licenseData.features?.premium === true) return this.premium.maxTokens;
5036
- return this.free.maxTokens;
5037
- },
5038
- /**
5039
- * Get max allowed sessions for license tier
5040
- */
5041
- getMaxSessions(licenseData) {
5042
- if (!licenseData) return this.free.maxSessions;
5043
- if (licenseData.featureEnterprise === true || licenseData.features?.enterprise === true) return this.enterprise.maxSessions;
5044
- if (licenseData.featureAdvanced === true || licenseData.features?.advanced === true) return this.advanced.maxSessions;
5045
- if (licenseData.featurePremium === true || licenseData.features?.premium === true) return this.premium.maxSessions;
5046
- return this.free.maxSessions;
5047
- },
5048
- /**
5049
- * Get max allowed IP bans for license tier
5050
- */
5051
- getMaxIPBans(licenseData) {
5052
- if (!licenseData) return this.free.maxIPBans;
5053
- if (licenseData.featureEnterprise === true || licenseData.features?.enterprise === true) return this.enterprise.maxIPBans;
5054
- if (licenseData.featureAdvanced === true || licenseData.features?.advanced === true) return this.advanced.maxIPBans;
5055
- if (licenseData.featurePremium === true || licenseData.features?.premium === true) return this.premium.maxIPBans;
5056
- return this.free.maxIPBans;
5057
- },
5058
- /**
5059
- * Get tier name from license data
5060
- */
5061
- getTierName(licenseData) {
5062
- if (!licenseData) return "free";
5063
- if (licenseData.featureEnterprise === true || licenseData.features?.enterprise === true) return "enterprise";
5064
- if (licenseData.featureAdvanced === true || licenseData.features?.advanced === true) return "advanced";
5065
- if (licenseData.featurePremium === true || licenseData.features?.premium === true) return "premium";
5066
- return "free";
5067
- }
5068
- };
5069
4745
  const crypto$1 = require$$0$3;
5070
4746
  const os = require$$1$3;
5071
- const features = features$1;
5072
4747
  const LICENSE_SERVER_URL = "https://magicapi.fitlex.me";
5073
4748
  const envTimeout = Number(process.env.MAGIC_LICENSE_TIMEOUT_MS);
5074
4749
  const DEFAULT_FETCH_TIMEOUT_MS = Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : 12e3;
@@ -5076,7 +4751,7 @@ const FETCH_RETRIES = 1;
5076
4751
  const FETCH_RETRY_BACKOFF_MS = 750;
5077
4752
  async function fetchWithTimeout(url, options2 = {}, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
5078
4753
  let lastError;
5079
- for (let attempt = 0; attempt <= FETCH_RETRIES; attempt++) {
4754
+ for (let attempt = 0; attempt <= FETCH_RETRIES; attempt += 1) {
5080
4755
  const controller2 = new AbortController();
5081
4756
  const timer = setTimeout(() => controller2.abort(), timeoutMs);
5082
4757
  try {
@@ -5095,15 +4770,22 @@ async function fetchWithTimeout(url, options2 = {}, timeoutMs = DEFAULT_FETCH_TI
5095
4770
  throw lastError;
5096
4771
  }
5097
4772
  var licenseGuard$1 = ({ strapi: strapi2 }) => ({
4773
+ // ======================================================================
4774
+ // Network helpers (used only during admin-side key activation)
4775
+ // ======================================================================
5098
4776
  /**
5099
- * Get license server URL (hardcoded and immutable for security)
5100
- * @returns {string} The fixed license server URL
4777
+ * License server URL. Hard-coded — kept simple because the only call
4778
+ * site is the admin-side activation form. Not user-configurable.
4779
+ * @returns {string}
5101
4780
  */
5102
4781
  getLicenseServerUrl() {
5103
4782
  return LICENSE_SERVER_URL;
5104
4783
  },
5105
4784
  /**
5106
- * Generate a unique device ID based on machine identifiers
4785
+ * Hashed device identifier used by the upstream activation server to
4786
+ * de-duplicate seats. Hash of MAC addresses + hostname; the raw values
4787
+ * never leave the host.
4788
+ * @returns {string}
5107
4789
  */
5108
4790
  generateDeviceId() {
5109
4791
  try {
@@ -5120,11 +4802,12 @@ var licenseGuard$1 = ({ strapi: strapi2 }) => ({
5120
4802
  return crypto$1.createHash("sha256").update(identifier).digest("hex").substring(0, 32);
5121
4803
  } catch (error) {
5122
4804
  strapi2.log.error("Error generating device ID:", error);
5123
- return crypto$1.randomBytes(16).digest("hex");
4805
+ return crypto$1.randomBytes(16).toString("hex");
5124
4806
  }
5125
4807
  },
5126
4808
  /**
5127
- * Get device name
4809
+ * Friendly device name for the activation payload.
4810
+ * @returns {string}
5128
4811
  */
5129
4812
  getDeviceName() {
5130
4813
  try {
@@ -5134,7 +4817,9 @@ var licenseGuard$1 = ({ strapi: strapi2 }) => ({
5134
4817
  }
5135
4818
  },
5136
4819
  /**
5137
- * Get server IP address
4820
+ * First non-internal IPv4 address. Best-effort, used in the activation
4821
+ * payload only.
4822
+ * @returns {string}
5138
4823
  */
5139
4824
  getIpAddress() {
5140
4825
  try {
@@ -5152,13 +4837,22 @@ var licenseGuard$1 = ({ strapi: strapi2 }) => ({
5152
4837
  }
5153
4838
  },
5154
4839
  /**
5155
- * Get user agent (server context)
4840
+ * Server-side user-agent string for the activation payload.
4841
+ * @returns {string}
5156
4842
  */
5157
4843
  getUserAgent() {
5158
4844
  return `Strapi/${strapi2.config.info.strapi} Node/${process.version} ${os.platform()}/${os.release()}`;
5159
4845
  },
4846
+ // ======================================================================
4847
+ // License creation / verification (admin activation path only)
4848
+ // ======================================================================
5160
4849
  /**
5161
- * Create a license
4850
+ * Create a license on the upstream activation server. Used by the
4851
+ * admin "auto-create" / "create" flows. Failure to create is non-fatal
4852
+ * — the plugin keeps working, the user just doesn't get a key.
4853
+ *
4854
+ * @param {{ email: string, firstName?: string, lastName?: string }} args
4855
+ * @returns {Promise<object|null>} Upstream license payload, or null on failure
5162
4856
  */
5163
4857
  async createLicense({ email: email2, firstName, lastName }) {
5164
4858
  try {
@@ -5169,9 +4863,7 @@ var licenseGuard$1 = ({ strapi: strapi2 }) => ({
5169
4863
  const licenseServerUrl = this.getLicenseServerUrl();
5170
4864
  const response = await fetchWithTimeout(`${licenseServerUrl}/api/licenses/create`, {
5171
4865
  method: "POST",
5172
- headers: {
5173
- "Content-Type": "application/json"
5174
- },
4866
+ headers: { "Content-Type": "application/json" },
5175
4867
  body: JSON.stringify({
5176
4868
  email: email2,
5177
4869
  firstName,
@@ -5186,390 +4878,201 @@ var licenseGuard$1 = ({ strapi: strapi2 }) => ({
5186
4878
  });
5187
4879
  const data = await response.json();
5188
4880
  if (data.success) {
5189
- strapi2.log.info("[SUCCESS] License created successfully:", data.data.licenseKey);
4881
+ strapi2.log.info("[SUCCESS] License created:", data.data.licenseKey);
5190
4882
  return data.data;
5191
- } else {
5192
- strapi2.log.error("[ERROR] License creation failed:", data);
5193
- return null;
5194
4883
  }
4884
+ strapi2.log.warn("[WARNING] License creation rejected by server:", data.message || "unknown");
4885
+ return null;
5195
4886
  } catch (error) {
5196
- strapi2.log.error("[ERROR] Error creating license:", error);
4887
+ strapi2.log.error("[ERROR] Error creating license:", error.message);
5197
4888
  return null;
5198
4889
  }
5199
4890
  },
5200
4891
  /**
5201
- * Verify a license (with grace period support)
4892
+ * Verify a license against the upstream activation server.
4893
+ * Called once when the admin saves a license key — never during
4894
+ * runtime feature checks, never on a schedule.
4895
+ *
4896
+ * @param {string} licenseKey
4897
+ * @returns {Promise<{valid: boolean, data: object|null}>}
5202
4898
  */
5203
- async verifyLicense(licenseKey, allowGracePeriod = false) {
4899
+ async verifyLicense(licenseKey) {
5204
4900
  try {
5205
- const controller2 = new AbortController();
5206
- const timeoutId = setTimeout(() => controller2.abort(), 5e3);
5207
4901
  const licenseServerUrl = this.getLicenseServerUrl();
5208
4902
  const response = await fetchWithTimeout(`${licenseServerUrl}/api/licenses/verify`, {
5209
4903
  method: "POST",
5210
- headers: {
5211
- "Content-Type": "application/json"
5212
- },
4904
+ headers: { "Content-Type": "application/json" },
5213
4905
  body: JSON.stringify({
5214
4906
  licenseKey,
5215
4907
  pluginName: "magic-link",
5216
4908
  productName: "Magic Link - Passwordless Authentication"
5217
- }),
5218
- signal: controller2.signal
4909
+ })
5219
4910
  });
5220
- clearTimeout(timeoutId);
5221
4911
  const data = await response.json();
5222
4912
  if (data.success) {
5223
- const isValid = data.data.isActive && !data.data.isExpired;
4913
+ const isValid = !!(data.data && data.data.isActive && !data.data.isExpired);
5224
4914
  if (isValid) {
5225
- const pluginStore = strapi2.store({
5226
- type: "plugin",
5227
- name: "magic-link"
5228
- });
4915
+ const pluginStore = strapi2.store({ type: "plugin", name: "magic-link" });
5229
4916
  await pluginStore.set({ key: "lastValidated", value: (/* @__PURE__ */ new Date()).toISOString() });
5230
4917
  }
5231
- return {
5232
- valid: isValid,
5233
- data: data.data
5234
- };
5235
- } else {
5236
- strapi2.log.warn(`[WARNING] License verification failed: ${data.message || "Unknown error"} (Key: ${licenseKey?.substring(0, 8)}...)`);
5237
- return { valid: false, data: null };
5238
- }
5239
- } catch (error) {
5240
- if (allowGracePeriod) {
5241
- strapi2.log.warn(`[WARNING] Cannot verify license online: ${error.message} (Key: ${licenseKey?.substring(0, 8)}...)`);
5242
- strapi2.log.info(`[INFO] Grace period active - accepting stored license key`);
5243
- return { valid: true, data: null, gracePeriod: true };
4918
+ return { valid: isValid, data: data.data };
5244
4919
  }
5245
- strapi2.log.error(`[ERROR] Error verifying license: ${error.message} (Key: ${licenseKey?.substring(0, 8)}...)`);
4920
+ strapi2.log.warn(
4921
+ `[WARNING] License verification failed: ${data.message || "Unknown error"} (Key: ${licenseKey?.substring(0, 8)}...)`
4922
+ );
5246
4923
  return { valid: false, data: null };
5247
- }
5248
- },
5249
- /**
5250
- * Ping a license (lightweight check)
5251
- */
5252
- async pingLicense(licenseKey) {
5253
- try {
5254
- const licenseServerUrl = this.getLicenseServerUrl();
5255
- const response = await fetchWithTimeout(`${licenseServerUrl}/api/licenses/ping`, {
5256
- method: "POST",
5257
- headers: {
5258
- "Content-Type": "application/json"
5259
- },
5260
- body: JSON.stringify({
5261
- licenseKey,
5262
- pluginName: "magic-link",
5263
- productName: "Magic Link - Passwordless Authentication"
5264
- })
5265
- });
5266
- const data = await response.json();
5267
- if (data.success) {
5268
- strapi2.log.debug(`[PING] License ping successful: ${data.data?.isActive ? "ACTIVE" : "INACTIVE"} (Key: ${licenseKey?.substring(0, 8)}...)`);
5269
- return data.data;
5270
- } else {
5271
- strapi2.log.debug(`[WARNING] License ping failed: ${data.message || "Unknown error"} (Key: ${licenseKey?.substring(0, 8)}...)`);
5272
- return null;
5273
- }
5274
- } catch (error) {
5275
- strapi2.log.debug(`License ping error: ${error.message} (Key: ${licenseKey?.substring(0, 8)}...)`);
5276
- return null;
5277
- }
5278
- },
5279
- /**
5280
- * Upgrade a license
5281
- */
5282
- async upgradeLicense(licenseId, features2) {
5283
- try {
5284
- const licenseServerUrl = this.getLicenseServerUrl();
5285
- const response = await fetchWithTimeout(`${licenseServerUrl}/api/licenses/${licenseId}/upgrade`, {
5286
- method: "POST",
5287
- headers: {
5288
- "Content-Type": "application/json"
5289
- },
5290
- body: JSON.stringify(features2)
5291
- });
5292
- const data = await response.json();
5293
- if (data.success) {
5294
- strapi2.log.info("[SUCCESS] License upgraded:", licenseId);
5295
- return data.data;
5296
- } else {
5297
- strapi2.log.error("[ERROR] License upgrade failed:", licenseId);
5298
- return null;
5299
- }
5300
4924
  } catch (error) {
5301
- strapi2.log.error("[ERROR] Error upgrading license:", error);
5302
- return null;
4925
+ strapi2.log.warn(
4926
+ `[WARNING] License server unreachable during activation: ${error.message} (Key: ${licenseKey?.substring(0, 8)}...). Activation will not block this install.`
4927
+ );
4928
+ return { valid: false, data: null, networkError: true };
5303
4929
  }
5304
4930
  },
5305
4931
  /**
5306
- * Get license by key
4932
+ * Look up a license by key on the upstream server. Used by the admin
4933
+ * License page to display details. Network failures yield null.
4934
+ *
4935
+ * @param {string} licenseKey
4936
+ * @returns {Promise<object|null>}
5307
4937
  */
5308
4938
  async getLicenseByKey(licenseKey) {
5309
4939
  try {
5310
4940
  const licenseServerUrl = this.getLicenseServerUrl();
5311
4941
  const response = await fetchWithTimeout(`${licenseServerUrl}/api/licenses/key/${licenseKey}`);
5312
4942
  const data = await response.json();
5313
- if (data.success) {
5314
- return data.data;
5315
- }
4943
+ if (data.success) return data.data;
5316
4944
  return null;
5317
4945
  } catch (error) {
5318
- strapi2.log.error("[ERROR] Error fetching license by key:", error);
4946
+ strapi2.log.warn("[WARNING] Could not fetch license by key:", error.message);
5319
4947
  return null;
5320
4948
  }
5321
4949
  },
5322
4950
  /**
5323
- * Get licenses by email
4951
+ * Look up licenses by user email. Used by admin "auto-create" to
4952
+ * decide whether to reuse an existing key.
4953
+ *
4954
+ * @param {string} email
4955
+ * @returns {Promise<Array>}
5324
4956
  */
5325
4957
  async getLicensesByEmail(email2) {
5326
4958
  try {
5327
4959
  const licenseServerUrl = this.getLicenseServerUrl();
5328
- const response = await fetchWithTimeout(`${licenseServerUrl}/api/licenses/email/${encodeURIComponent(email2)}`);
4960
+ const response = await fetchWithTimeout(
4961
+ `${licenseServerUrl}/api/licenses/email/${encodeURIComponent(email2)}`
4962
+ );
5329
4963
  const data = await response.json();
5330
- if (data.success) {
5331
- return data.data;
5332
- }
4964
+ if (data.success) return data.data;
5333
4965
  return [];
5334
4966
  } catch (error) {
5335
- strapi2.log.error("[ERROR] Error fetching licenses by email:", error);
4967
+ strapi2.log.warn("[WARNING] Could not fetch licenses by email:", error.message);
5336
4968
  return [];
5337
4969
  }
5338
4970
  },
4971
+ // ======================================================================
4972
+ // Storage (plugin store)
4973
+ // ======================================================================
5339
4974
  /**
5340
- * Get online statistics
5341
- */
5342
- async getOnlineStats() {
5343
- try {
5344
- const licenseServerUrl = this.getLicenseServerUrl();
5345
- const response = await fetchWithTimeout(`${licenseServerUrl}/api/licenses/stats/online`);
5346
- const data = await response.json();
5347
- if (data.success) {
5348
- return data.data;
5349
- }
5350
- return null;
5351
- } catch (error) {
5352
- strapi2.log.error("[ERROR] Error fetching online stats:", error);
5353
- return null;
5354
- }
5355
- },
5356
- /**
5357
- * Start periodic pinging for a license
5358
- */
5359
- startPinging(licenseKey, intervalMinutes = 15) {
5360
- const intervalMs = intervalMinutes * 60 * 1e3;
5361
- this.pingLicense(licenseKey);
5362
- const pingInterval = setInterval(async () => {
5363
- await this.pingLicense(licenseKey);
5364
- }, intervalMs);
5365
- return pingInterval;
5366
- },
5367
- /**
5368
- * Initialize license guard
5369
- * Checks for existing license or prompts for creation
5370
- */
5371
- async initialize() {
5372
- try {
5373
- const pluginStore = strapi2.store({
5374
- type: "plugin",
5375
- name: "magic-link"
5376
- });
5377
- let licenseKey = await pluginStore.get({ key: "licenseKey" });
5378
- const lastValidated = await pluginStore.get({ key: "lastValidated" });
5379
- const now = /* @__PURE__ */ new Date();
5380
- const gracePeriodHours = 24;
5381
- let withinGracePeriod = false;
5382
- if (lastValidated) {
5383
- const lastValidatedDate = new Date(lastValidated);
5384
- const hoursSinceValidation = (now - lastValidatedDate) / (1e3 * 60 * 60);
5385
- withinGracePeriod = hoursSinceValidation < gracePeriodHours;
5386
- }
5387
- if (licenseKey) {
5388
- const verification = await this.verifyLicense(licenseKey, true);
5389
- if (verification.valid) {
5390
- const pingInterval = this.startPinging(licenseKey, 15);
5391
- strapi2.licenseGuard = {
5392
- licenseKey,
5393
- pingInterval,
5394
- data: verification.data
5395
- };
5396
- return { valid: true, data: verification.data };
5397
- } else {
5398
- strapi2.log.warn("[WARNING] Stored license is invalid or expired");
5399
- if (!withinGracePeriod) {
5400
- await pluginStore.delete({ key: "licenseKey" });
5401
- await pluginStore.delete({ key: "lastValidated" });
5402
- }
5403
- }
5404
- }
5405
- strapi2.log.warn("[WARNING] No valid license found. Plugin will run in demo mode.");
5406
- return { valid: false, demo: true };
5407
- } catch (error) {
5408
- strapi2.log.error("[ERROR] Error initializing license guard:", error);
5409
- return { valid: false, error: error.message };
5410
- }
5411
- },
5412
- /**
5413
- * Store license key after creation
4975
+ * Persist a license key to the plugin store. Used by the admin
4976
+ * activation form.
4977
+ *
4978
+ * @param {string} licenseKey
4979
+ * @returns {Promise<boolean>}
5414
4980
  */
5415
4981
  async storeLicenseKey(licenseKey) {
5416
4982
  try {
5417
- strapi2.log.info(`[LICENSE] Storing license key: ${licenseKey?.substring(0, 8)}...`);
5418
- const pluginStore = strapi2.store({
5419
- type: "plugin",
5420
- name: "magic-link"
5421
- });
4983
+ const pluginStore = strapi2.store({ type: "plugin", name: "magic-link" });
5422
4984
  await pluginStore.set({ key: "licenseKey", value: licenseKey });
5423
4985
  await pluginStore.set({ key: "lastValidated", value: (/* @__PURE__ */ new Date()).toISOString() });
5424
- const stored = await pluginStore.get({ key: "licenseKey" });
5425
- if (stored === licenseKey) {
5426
- strapi2.log.info("[SUCCESS] License key stored and verified successfully");
5427
- return true;
5428
- } else {
5429
- strapi2.log.error("[ERROR] License key storage verification failed");
5430
- return false;
5431
- }
4986
+ strapi2.log.info("[SUCCESS] License key stored");
4987
+ return true;
5432
4988
  } catch (error) {
5433
- strapi2.log.error("[ERROR] Error storing license key:", error);
4989
+ strapi2.log.error("[ERROR] Error storing license key:", error.message);
5434
4990
  return false;
5435
4991
  }
5436
4992
  },
4993
+ // ======================================================================
4994
+ // Lifecycle (no-ops in the marketplace build)
4995
+ // ======================================================================
4996
+ /**
4997
+ * Boot-time initializer. In the marketplace build this is a no-op:
4998
+ * we do not auto-verify the stored key and we do not start a periodic
4999
+ * ping. Kept as a stable export so bootstrap.js continues to work.
5000
+ *
5001
+ * @returns {Promise<{valid: boolean}>}
5002
+ */
5003
+ async initialize() {
5004
+ return { valid: true };
5005
+ },
5437
5006
  /**
5438
- * Cleanup on plugin destroy
5007
+ * Plugin destroy hook. No-op (no interval to clear in the marketplace
5008
+ * build).
5439
5009
  */
5440
5010
  cleanup() {
5441
- if (strapi2.licenseGuard && strapi2.licenseGuard.pingInterval) {
5442
- clearInterval(strapi2.licenseGuard.pingInterval);
5443
- strapi2.log.info("[INFO] License pinging stopped");
5444
- }
5445
5011
  },
5012
+ // ======================================================================
5013
+ // Permissive feature stubs (kept for backward compatibility with any
5014
+ // legacy caller — they always permit, never gate)
5015
+ // ======================================================================
5446
5016
  /**
5447
- * Check if a feature is available
5448
- * @param {string} featureName - Feature name to check
5449
- * @returns {boolean}
5017
+ * Always-permissive feature check. Kept so any legacy `if (await
5018
+ * licenseGuard.hasFeature(...))` site continues to compile and runs
5019
+ * the gated branch.
5020
+ *
5021
+ * @returns {Promise<boolean>} always true
5450
5022
  */
5451
- async hasFeature(featureName) {
5452
- try {
5453
- const pluginStore = strapi2.store({ type: "plugin", name: "magic-link" });
5454
- const licenseKey = await pluginStore.get({ key: "licenseKey" });
5455
- if (!licenseKey) {
5456
- return features.hasFeature(null, featureName);
5457
- }
5458
- const licenseData = await this.getLicenseByKey(licenseKey);
5459
- return features.hasFeature(licenseData, featureName);
5460
- } catch (error) {
5461
- strapi2.log.error("Error checking feature:", error);
5462
- return false;
5463
- }
5023
+ // eslint-disable-next-line no-unused-vars
5024
+ async hasFeature(_featureName) {
5025
+ return true;
5464
5026
  },
5465
5027
  /**
5466
- * Get max allowed tokens
5467
- * @returns {number} Max tokens (-1 = unlimited)
5028
+ * @returns {Promise<number>} -1 (unlimited)
5468
5029
  */
5469
5030
  async getMaxTokens() {
5470
- try {
5471
- const pluginStore = strapi2.store({ type: "plugin", name: "magic-link" });
5472
- const licenseKey = await pluginStore.get({ key: "licenseKey" });
5473
- if (!licenseKey) {
5474
- return features.getMaxTokens(null);
5475
- }
5476
- const licenseData = await this.getLicenseByKey(licenseKey);
5477
- return features.getMaxTokens(licenseData);
5478
- } catch (error) {
5479
- strapi2.log.error("Error getting max tokens:", error);
5480
- return features.free.maxTokens;
5481
- }
5031
+ return -1;
5482
5032
  },
5483
5033
  /**
5484
- * Get max allowed sessions
5485
- * @returns {number} Max sessions (-1 = unlimited)
5034
+ * @returns {Promise<number>} -1 (unlimited)
5486
5035
  */
5487
5036
  async getMaxSessions() {
5488
- try {
5489
- const pluginStore = strapi2.store({ type: "plugin", name: "magic-link" });
5490
- const licenseKey = await pluginStore.get({ key: "licenseKey" });
5491
- if (!licenseKey) {
5492
- return features.getMaxSessions(null);
5493
- }
5494
- const licenseData = await this.getLicenseByKey(licenseKey);
5495
- return features.getMaxSessions(licenseData);
5496
- } catch (error) {
5497
- strapi2.log.error("Error getting max sessions:", error);
5498
- return features.free.maxSessions;
5499
- }
5037
+ return -1;
5500
5038
  },
5501
5039
  /**
5502
- * Get max allowed IP bans
5503
- * @returns {number} Max IP bans (-1 = unlimited)
5040
+ * @returns {Promise<number>} -1 (unlimited)
5504
5041
  */
5505
5042
  async getMaxIPBans() {
5506
- try {
5507
- const pluginStore = strapi2.store({ type: "plugin", name: "magic-link" });
5508
- const licenseKey = await pluginStore.get({ key: "licenseKey" });
5509
- if (!licenseKey) {
5510
- return features.getMaxIPBans(null);
5511
- }
5512
- const licenseData = await this.getLicenseByKey(licenseKey);
5513
- return features.getMaxIPBans(licenseData);
5514
- } catch (error) {
5515
- strapi2.log.error("Error getting max IP bans:", error);
5516
- return features.free.maxIPBans;
5517
- }
5043
+ return -1;
5518
5044
  },
5519
5045
  /**
5520
- * Get available OTP types
5521
- * @returns {Array} Available OTP types
5046
+ * @returns {Promise<string[]>} every supported OTP type
5522
5047
  */
5523
5048
  async getAvailableOTPTypes() {
5524
- try {
5525
- const pluginStore = strapi2.store({ type: "plugin", name: "magic-link" });
5526
- const licenseKey = await pluginStore.get({ key: "licenseKey" });
5527
- if (!licenseKey) {
5528
- return features.getAvailableOTPTypes(null);
5529
- }
5530
- const licenseData = await this.getLicenseByKey(licenseKey);
5531
- return features.getAvailableOTPTypes(licenseData);
5532
- } catch (error) {
5533
- strapi2.log.error("Error getting available OTP types:", error);
5534
- return [];
5535
- }
5049
+ return ["email", "totp", "backup-codes"];
5536
5050
  },
5537
5051
  /**
5538
- * Get license tier info
5539
- * @returns {Object} License tier information
5052
+ * Tier info for UI display. Reports `pro` when a key is stored,
5053
+ * `free` otherwise. Pure information — no behavior depends on this.
5054
+ *
5055
+ * @returns {Promise<{tier: string, features: object, hasKey: boolean}>}
5540
5056
  */
5541
5057
  async getLicenseTierInfo() {
5542
5058
  try {
5543
5059
  const pluginStore = strapi2.store({ type: "plugin", name: "magic-link" });
5544
5060
  const licenseKey = await pluginStore.get({ key: "licenseKey" });
5545
- if (!licenseKey) {
5546
- return {
5547
- tier: "free",
5548
- features: {
5549
- premium: false,
5550
- advanced: false,
5551
- enterprise: false
5552
- }
5553
- };
5554
- }
5555
- const licenseData = await this.getLicenseByKey(licenseKey);
5061
+ const hasKey = !!licenseKey;
5556
5062
  return {
5557
- tier: licenseData?.tier || "free",
5063
+ tier: hasKey ? "pro" : "free",
5064
+ hasKey,
5558
5065
  features: {
5559
- premium: licenseData?.featurePremium || licenseData?.features?.premium || false,
5560
- advanced: licenseData?.featureAdvanced || licenseData?.features?.advanced || false,
5561
- enterprise: licenseData?.featureEnterprise || licenseData?.features?.enterprise || false
5066
+ premium: hasKey,
5067
+ advanced: hasKey,
5068
+ enterprise: hasKey
5562
5069
  }
5563
5070
  };
5564
5071
  } catch (error) {
5565
- strapi2.log.error("Error getting license tier info:", error);
5566
5072
  return {
5567
5073
  tier: "free",
5568
- features: {
5569
- premium: false,
5570
- advanced: false,
5571
- enterprise: false
5572
- }
5074
+ hasKey: false,
5075
+ features: { premium: false, advanced: false, enterprise: false }
5573
5076
  };
5574
5077
  }
5575
5078
  }