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.
- package/CHANGELOG.md +14 -0
- package/README.md +28 -47
- package/dist/_chunks/LicenseGuard-CiyjFnrw.js +54 -0
- package/dist/_chunks/LicenseGuard-DJ5fq2UZ.mjs +55 -0
- package/dist/_chunks/{index-PgHpSdc8.js → index-01GHNxSr.js} +6 -35
- package/dist/_chunks/{index-CxCULraY.mjs → index-B9Ch8Jpe.mjs} +2 -2
- package/dist/_chunks/{index-34ICAF9_.mjs → index-Bj-erY7g.mjs} +4 -17
- package/dist/_chunks/{index-BFzW7Xex.js → index-CGxbhJfg.js} +4 -17
- package/dist/_chunks/{index-BZFGxKmY.mjs → index-DF3Edydl.mjs} +1 -1
- package/dist/_chunks/{index-B8urUtiz.mjs → index-Dufv-daf.mjs} +7 -36
- package/dist/_chunks/{index-M5s7QN5m.js → index-I72rBTkL.js} +1 -1
- package/dist/_chunks/{index-ivgYEX37.mjs → index-SCxv154G.mjs} +9 -128
- package/dist/_chunks/{index-CddEy_As.js → index-XYWLmmEs.js} +2 -2
- package/dist/_chunks/{index-CuXnqVl4.js → index-zzoovbM0.js} +9 -128
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +257 -754
- package/dist/server/index.mjs +257 -754
- package/package.json +3 -3
- package/dist/_chunks/LicenseGuard-5sMwgS6j.mjs +0 -646
- package/dist/_chunks/LicenseGuard-DqyOwTlf.js +0 -647
- package/dist/_chunks/LicensePage-2M-vOJX8.js +0 -377
- package/dist/_chunks/LicensePage-CCNW2hT6.mjs +0 -375
package/dist/server/index.mjs
CHANGED
|
@@ -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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
strapi2.log.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
*
|
|
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:
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
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:
|
|
2896
|
-
|
|
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
|
|
2903
|
-
isExpired: license2?.isExpired
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
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
|
|
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("
|
|
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("
|
|
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
|
-
*
|
|
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
|
|
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
|
|
3012
|
-
data:
|
|
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
|
|
3016
|
-
return ctx.badRequest("Error
|
|
2970
|
+
strapi.log.error("Error refreshing license:", error);
|
|
2971
|
+
return ctx.badRequest("Error refreshing license");
|
|
3017
2972
|
}
|
|
3018
2973
|
},
|
|
3019
2974
|
/**
|
|
3020
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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
|
-
*
|
|
5100
|
-
*
|
|
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
|
-
*
|
|
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).
|
|
4805
|
+
return crypto$1.randomBytes(16).toString("hex");
|
|
5124
4806
|
}
|
|
5125
4807
|
},
|
|
5126
4808
|
/**
|
|
5127
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
5302
|
-
|
|
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
|
-
*
|
|
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.
|
|
4946
|
+
strapi2.log.warn("[WARNING] Could not fetch license by key:", error.message);
|
|
5319
4947
|
return null;
|
|
5320
4948
|
}
|
|
5321
4949
|
},
|
|
5322
4950
|
/**
|
|
5323
|
-
*
|
|
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(
|
|
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.
|
|
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
|
-
*
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
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.
|
|
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
|
-
|
|
5425
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
5448
|
-
*
|
|
5449
|
-
*
|
|
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
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
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
|
-
*
|
|
5467
|
-
* @returns {number} Max tokens (-1 = unlimited)
|
|
5028
|
+
* @returns {Promise<number>} -1 (unlimited)
|
|
5468
5029
|
*/
|
|
5469
5030
|
async getMaxTokens() {
|
|
5470
|
-
|
|
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
|
-
*
|
|
5485
|
-
* @returns {number} Max sessions (-1 = unlimited)
|
|
5034
|
+
* @returns {Promise<number>} -1 (unlimited)
|
|
5486
5035
|
*/
|
|
5487
5036
|
async getMaxSessions() {
|
|
5488
|
-
|
|
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
|
-
*
|
|
5503
|
-
* @returns {number} Max IP bans (-1 = unlimited)
|
|
5040
|
+
* @returns {Promise<number>} -1 (unlimited)
|
|
5504
5041
|
*/
|
|
5505
5042
|
async getMaxIPBans() {
|
|
5506
|
-
|
|
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
|
-
*
|
|
5521
|
-
* @returns {Array} Available OTP types
|
|
5046
|
+
* @returns {Promise<string[]>} every supported OTP type
|
|
5522
5047
|
*/
|
|
5523
5048
|
async getAvailableOTPTypes() {
|
|
5524
|
-
|
|
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
|
-
*
|
|
5539
|
-
*
|
|
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
|
-
|
|
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:
|
|
5063
|
+
tier: hasKey ? "pro" : "free",
|
|
5064
|
+
hasKey,
|
|
5558
5065
|
features: {
|
|
5559
|
-
premium:
|
|
5560
|
-
advanced:
|
|
5561
|
-
enterprise:
|
|
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
|
-
|
|
5569
|
-
|
|
5570
|
-
advanced: false,
|
|
5571
|
-
enterprise: false
|
|
5572
|
-
}
|
|
5074
|
+
hasKey: false,
|
|
5075
|
+
features: { premium: false, advanced: false, enterprise: false }
|
|
5573
5076
|
};
|
|
5574
5077
|
}
|
|
5575
5078
|
}
|