@usertour/helpers 0.0.2 → 0.0.3
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/dist/chunk-JQ4E3IDB.js +112 -0
- package/dist/chunk-KJJ6UD5L.js +183 -0
- package/dist/index.cjs +295 -6
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +8 -0
- package/dist/jwt-license-signer.js +3 -107
- package/dist/jwt-license-validator.js +3 -180
- package/package.json +1 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__publicField
|
|
3
|
+
} from "./chunk-XEO3YXBM.js";
|
|
4
|
+
|
|
5
|
+
// src/jwt-license-signer.ts
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import * as jwt from "jsonwebtoken";
|
|
9
|
+
var JWTLicenseSigner = class {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
__publicField(this, "privateKey");
|
|
12
|
+
__publicField(this, "issuer");
|
|
13
|
+
__publicField(this, "algorithm");
|
|
14
|
+
this.privateKey = this.loadPrivateKey(options.privateKeyPath);
|
|
15
|
+
this.issuer = options.issuer || "https://www.usertour.io";
|
|
16
|
+
this.algorithm = options.algorithm || "RS256";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Load RSA private key from file
|
|
20
|
+
*/
|
|
21
|
+
loadPrivateKey(keyPath) {
|
|
22
|
+
try {
|
|
23
|
+
const fullPath = path.resolve(keyPath);
|
|
24
|
+
return fs.readFileSync(fullPath, "utf8");
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw new Error(`Failed to load private key from ${keyPath}: ${error}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Generate a JWT license token
|
|
31
|
+
*/
|
|
32
|
+
generateLicense(options) {
|
|
33
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
34
|
+
const expiresAt = now + options.expiresInDays * 24 * 60 * 60;
|
|
35
|
+
const payload = {
|
|
36
|
+
plan: options.plan,
|
|
37
|
+
sub: options.subject,
|
|
38
|
+
projectId: options.projectId,
|
|
39
|
+
iat: now,
|
|
40
|
+
exp: expiresAt,
|
|
41
|
+
issuer: options.issuer || this.issuer,
|
|
42
|
+
features: options.features
|
|
43
|
+
};
|
|
44
|
+
try {
|
|
45
|
+
return jwt.sign(payload, this.privateKey, {
|
|
46
|
+
algorithm: this.algorithm,
|
|
47
|
+
issuer: this.issuer
|
|
48
|
+
});
|
|
49
|
+
} catch (error) {
|
|
50
|
+
throw new Error(`Failed to generate JWT license: ${error}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Generate a license and return both token and payload info
|
|
55
|
+
*/
|
|
56
|
+
generateLicenseWithInfo(options) {
|
|
57
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
58
|
+
const expiresAt = now + options.expiresInDays * 24 * 60 * 60;
|
|
59
|
+
const payload = {
|
|
60
|
+
plan: options.plan,
|
|
61
|
+
sub: options.subject,
|
|
62
|
+
projectId: options.projectId,
|
|
63
|
+
iat: now,
|
|
64
|
+
exp: expiresAt,
|
|
65
|
+
issuer: options.issuer || this.issuer,
|
|
66
|
+
features: options.features
|
|
67
|
+
};
|
|
68
|
+
const token = jwt.sign(payload, this.privateKey, {
|
|
69
|
+
algorithm: this.algorithm,
|
|
70
|
+
issuer: this.issuer
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
token,
|
|
74
|
+
payload,
|
|
75
|
+
expiresAt: new Date(expiresAt * 1e3)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Decode a JWT token without verification (for debugging)
|
|
80
|
+
*/
|
|
81
|
+
decodeToken(token) {
|
|
82
|
+
try {
|
|
83
|
+
return jwt.decode(token);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error("Failed to decode JWT token:", error);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get token information without verification
|
|
91
|
+
*/
|
|
92
|
+
getTokenInfo(token) {
|
|
93
|
+
try {
|
|
94
|
+
const decoded = jwt.decode(token, { complete: true });
|
|
95
|
+
if (!decoded || typeof decoded === "string") {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
header: decoded.header,
|
|
100
|
+
payload: decoded.payload,
|
|
101
|
+
signature: decoded.signature
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error("Failed to get token info:", error);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
JWTLicenseSigner
|
|
112
|
+
};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// src/jwt-license-validator.ts
|
|
2
|
+
import * as jwt from "jsonwebtoken";
|
|
3
|
+
var JWTLicenseValidator = {
|
|
4
|
+
/**
|
|
5
|
+
* Validate a JWT license
|
|
6
|
+
* @param license - JWT license string
|
|
7
|
+
* @param publicKey - RSA public key in PEM format
|
|
8
|
+
* @param options - Validation options
|
|
9
|
+
* @returns Validation result
|
|
10
|
+
*/
|
|
11
|
+
validateLicense(license, publicKey, options = {}) {
|
|
12
|
+
try {
|
|
13
|
+
const { checkExpiration = true, currentTime = /* @__PURE__ */ new Date() } = options;
|
|
14
|
+
const decoded = jwt.verify(license, publicKey, {
|
|
15
|
+
algorithms: ["RS256"],
|
|
16
|
+
ignoreExpiration: !checkExpiration
|
|
17
|
+
});
|
|
18
|
+
const fieldValidation = this.validateRequiredFields(decoded);
|
|
19
|
+
if (!fieldValidation.isValid) {
|
|
20
|
+
return fieldValidation;
|
|
21
|
+
}
|
|
22
|
+
if (checkExpiration) {
|
|
23
|
+
const expirationValidation = this.checkExpiration(decoded, currentTime);
|
|
24
|
+
if (!expirationValidation.isValid) {
|
|
25
|
+
return expirationValidation;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const hasFeature = (feature) => {
|
|
29
|
+
return decoded.features.includes("*") || decoded.features.includes(feature);
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
isValid: true,
|
|
33
|
+
payload: decoded,
|
|
34
|
+
isExpired: false,
|
|
35
|
+
hasFeature
|
|
36
|
+
};
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error instanceof jwt.JsonWebTokenError) {
|
|
39
|
+
return {
|
|
40
|
+
isValid: false,
|
|
41
|
+
error: `JWT validation failed: ${error.message}`
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (error instanceof jwt.TokenExpiredError) {
|
|
45
|
+
return {
|
|
46
|
+
isValid: false,
|
|
47
|
+
error: `License expired: ${error.message}`,
|
|
48
|
+
isExpired: true
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
isValid: false,
|
|
53
|
+
error: `License validation failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
/**
|
|
58
|
+
* Validate that all required fields are present in license payload
|
|
59
|
+
* @param payload - License payload to validate
|
|
60
|
+
* @returns Validation result
|
|
61
|
+
*/
|
|
62
|
+
validateRequiredFields(payload) {
|
|
63
|
+
const requiredFields = ["plan", "sub", "projectId", "iat", "exp", "issuer", "features"];
|
|
64
|
+
for (const field of requiredFields) {
|
|
65
|
+
if (!(field in payload)) {
|
|
66
|
+
return {
|
|
67
|
+
isValid: false,
|
|
68
|
+
error: `Missing required field: ${field}`
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (typeof payload.plan !== "string" || !payload.plan.trim()) {
|
|
73
|
+
return {
|
|
74
|
+
isValid: false,
|
|
75
|
+
error: "Invalid plan: must be a non-empty string"
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (typeof payload.sub !== "string" || !payload.sub.trim()) {
|
|
79
|
+
return {
|
|
80
|
+
isValid: false,
|
|
81
|
+
error: "Invalid sub: must be a non-empty string"
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (typeof payload.projectId !== "string" || !payload.projectId.trim()) {
|
|
85
|
+
return {
|
|
86
|
+
isValid: false,
|
|
87
|
+
error: "Invalid projectId: must be a non-empty string"
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (typeof payload.issuer !== "string" || !payload.issuer.trim()) {
|
|
91
|
+
return {
|
|
92
|
+
isValid: false,
|
|
93
|
+
error: "Invalid issuer: must be a non-empty string"
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (!Array.isArray(payload.features)) {
|
|
97
|
+
return {
|
|
98
|
+
isValid: false,
|
|
99
|
+
error: "Invalid features: must be an array"
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (typeof payload.iat !== "number" || payload.iat <= 0) {
|
|
103
|
+
return {
|
|
104
|
+
isValid: false,
|
|
105
|
+
error: "Invalid iat: must be a positive number"
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (typeof payload.exp !== "number" || payload.exp <= 0) {
|
|
109
|
+
return {
|
|
110
|
+
isValid: false,
|
|
111
|
+
error: "Invalid exp: must be a positive number"
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (payload.iat >= payload.exp) {
|
|
115
|
+
return {
|
|
116
|
+
isValid: false,
|
|
117
|
+
error: "Invalid timestamps: iat must be before exp"
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return { isValid: true };
|
|
121
|
+
},
|
|
122
|
+
/**
|
|
123
|
+
* Check if license has expired
|
|
124
|
+
* @param payload - License payload
|
|
125
|
+
* @param currentTime - Current time to check against (defaults to now)
|
|
126
|
+
* @returns Validation result
|
|
127
|
+
*/
|
|
128
|
+
checkExpiration(payload, currentTime = /* @__PURE__ */ new Date()) {
|
|
129
|
+
const now = Math.floor(currentTime.getTime() / 1e3);
|
|
130
|
+
const expiresAt = payload.exp;
|
|
131
|
+
if (now > expiresAt) {
|
|
132
|
+
return {
|
|
133
|
+
isValid: false,
|
|
134
|
+
error: `License expired on ${new Date(expiresAt * 1e3).toISOString()}`,
|
|
135
|
+
isExpired: true
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return { isValid: true, isExpired: false };
|
|
139
|
+
},
|
|
140
|
+
/**
|
|
141
|
+
* Check if license has a specific feature
|
|
142
|
+
* @param payload - License payload
|
|
143
|
+
* @param feature - Feature to check
|
|
144
|
+
* @returns Whether the feature is available
|
|
145
|
+
*/
|
|
146
|
+
hasFeature(payload, feature) {
|
|
147
|
+
return payload.features.includes("*") || payload.features.includes(feature);
|
|
148
|
+
},
|
|
149
|
+
/**
|
|
150
|
+
* Get license expiration status
|
|
151
|
+
* @param payload - License payload
|
|
152
|
+
* @param currentTime - Current time to check against (defaults to now)
|
|
153
|
+
* @returns Expiration information
|
|
154
|
+
*/
|
|
155
|
+
getExpirationInfo(payload, currentTime = /* @__PURE__ */ new Date()) {
|
|
156
|
+
const now = Math.floor(currentTime.getTime() / 1e3);
|
|
157
|
+
const expiresAt = payload.exp;
|
|
158
|
+
const isExpired = now > expiresAt;
|
|
159
|
+
const daysUntilExpiration = Math.ceil((expiresAt - now) / (24 * 60 * 60));
|
|
160
|
+
return {
|
|
161
|
+
isExpired,
|
|
162
|
+
expiresAt: new Date(expiresAt * 1e3),
|
|
163
|
+
daysUntilExpiration: isExpired ? 0 : daysUntilExpiration
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
/**
|
|
167
|
+
* Decode JWT license without verification (for debugging)
|
|
168
|
+
* @param license - JWT license string
|
|
169
|
+
* @returns Decoded payload or null if invalid
|
|
170
|
+
*/
|
|
171
|
+
decodeLicense(license) {
|
|
172
|
+
try {
|
|
173
|
+
const decoded = jwt.decode(license);
|
|
174
|
+
return decoded;
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export {
|
|
182
|
+
JWTLicenseValidator
|
|
183
|
+
};
|
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,8 @@ var src_exports = {};
|
|
|
37
37
|
__export(src_exports, {
|
|
38
38
|
AbortController: () => AbortController,
|
|
39
39
|
ArrayProto: () => ArrayProto,
|
|
40
|
+
JWTLicenseSigner: () => JWTLicenseSigner,
|
|
41
|
+
JWTLicenseValidator: () => JWTLicenseValidator,
|
|
40
42
|
XMLHttpRequest: () => XMLHttpRequest,
|
|
41
43
|
absoluteUrl: () => absoluteUrl,
|
|
42
44
|
assignableWindow: () => assignableWindow,
|
|
@@ -448,8 +450,8 @@ var parseUrl = (url) => {
|
|
|
448
450
|
if (!urlPatterns) {
|
|
449
451
|
return null;
|
|
450
452
|
}
|
|
451
|
-
const [, , scheme = "", domain = "",
|
|
452
|
-
return { scheme, domain, path, query, fragment };
|
|
453
|
+
const [, , scheme = "", domain = "", path2 = "", , query = "", fragment = ""] = urlPatterns;
|
|
454
|
+
return { scheme, domain, path: path2, query, fragment };
|
|
453
455
|
};
|
|
454
456
|
var replaceWildcard = (input, s1, s2) => {
|
|
455
457
|
const withSpecialWords = replaceSpecialWords(input);
|
|
@@ -471,11 +473,11 @@ var parsePattern = (pattern) => {
|
|
|
471
473
|
console.error("Invalid URL pattern:", pattern);
|
|
472
474
|
return null;
|
|
473
475
|
}
|
|
474
|
-
const { scheme, domain, path, query, fragment } = _pattern;
|
|
476
|
+
const { scheme, domain, path: path2, query, fragment } = _pattern;
|
|
475
477
|
const _scheme = scheme ? replaceSpecialWords(scheme) : "[a-z\\d]+";
|
|
476
478
|
const _domain = domain ? replaceWildcard(domain, "[^/]", ".") : "[^/]*";
|
|
477
479
|
const _fragment = fragment ? replaceWildcard(fragment, ".", "/") : "(#.*)?";
|
|
478
|
-
const _path =
|
|
480
|
+
const _path = path2 ? replaceWildcard(path2, "[^?#]", "/") : "/[^?#]*";
|
|
479
481
|
let _query = "(\\?[^#]*)?";
|
|
480
482
|
if (query) {
|
|
481
483
|
new URLSearchParams(query).forEach((value, key) => {
|
|
@@ -1038,8 +1040,8 @@ function formatDate(input) {
|
|
|
1038
1040
|
year: "numeric"
|
|
1039
1041
|
});
|
|
1040
1042
|
}
|
|
1041
|
-
function absoluteUrl(
|
|
1042
|
-
return `${
|
|
1043
|
+
function absoluteUrl(path2) {
|
|
1044
|
+
return `${path2}`;
|
|
1043
1045
|
}
|
|
1044
1046
|
var uuidV4 = () => {
|
|
1045
1047
|
return (0, import_uuid.v4)();
|
|
@@ -1086,10 +1088,297 @@ var getRandomColor = () => {
|
|
|
1086
1088
|
];
|
|
1087
1089
|
return colors[Math.floor(Math.random() * colors.length)];
|
|
1088
1090
|
};
|
|
1091
|
+
|
|
1092
|
+
// src/jwt-license-signer.ts
|
|
1093
|
+
var fs = __toESM(require("fs"), 1);
|
|
1094
|
+
var path = __toESM(require("path"), 1);
|
|
1095
|
+
var jwt = __toESM(require("jsonwebtoken"), 1);
|
|
1096
|
+
var JWTLicenseSigner = class {
|
|
1097
|
+
constructor(options) {
|
|
1098
|
+
__publicField(this, "privateKey");
|
|
1099
|
+
__publicField(this, "issuer");
|
|
1100
|
+
__publicField(this, "algorithm");
|
|
1101
|
+
this.privateKey = this.loadPrivateKey(options.privateKeyPath);
|
|
1102
|
+
this.issuer = options.issuer || "https://www.usertour.io";
|
|
1103
|
+
this.algorithm = options.algorithm || "RS256";
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Load RSA private key from file
|
|
1107
|
+
*/
|
|
1108
|
+
loadPrivateKey(keyPath) {
|
|
1109
|
+
try {
|
|
1110
|
+
const fullPath = path.resolve(keyPath);
|
|
1111
|
+
return fs.readFileSync(fullPath, "utf8");
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
throw new Error(`Failed to load private key from ${keyPath}: ${error}`);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Generate a JWT license token
|
|
1118
|
+
*/
|
|
1119
|
+
generateLicense(options) {
|
|
1120
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1121
|
+
const expiresAt = now + options.expiresInDays * 24 * 60 * 60;
|
|
1122
|
+
const payload = {
|
|
1123
|
+
plan: options.plan,
|
|
1124
|
+
sub: options.subject,
|
|
1125
|
+
projectId: options.projectId,
|
|
1126
|
+
iat: now,
|
|
1127
|
+
exp: expiresAt,
|
|
1128
|
+
issuer: options.issuer || this.issuer,
|
|
1129
|
+
features: options.features
|
|
1130
|
+
};
|
|
1131
|
+
try {
|
|
1132
|
+
return jwt.sign(payload, this.privateKey, {
|
|
1133
|
+
algorithm: this.algorithm,
|
|
1134
|
+
issuer: this.issuer
|
|
1135
|
+
});
|
|
1136
|
+
} catch (error) {
|
|
1137
|
+
throw new Error(`Failed to generate JWT license: ${error}`);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Generate a license and return both token and payload info
|
|
1142
|
+
*/
|
|
1143
|
+
generateLicenseWithInfo(options) {
|
|
1144
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1145
|
+
const expiresAt = now + options.expiresInDays * 24 * 60 * 60;
|
|
1146
|
+
const payload = {
|
|
1147
|
+
plan: options.plan,
|
|
1148
|
+
sub: options.subject,
|
|
1149
|
+
projectId: options.projectId,
|
|
1150
|
+
iat: now,
|
|
1151
|
+
exp: expiresAt,
|
|
1152
|
+
issuer: options.issuer || this.issuer,
|
|
1153
|
+
features: options.features
|
|
1154
|
+
};
|
|
1155
|
+
const token = jwt.sign(payload, this.privateKey, {
|
|
1156
|
+
algorithm: this.algorithm,
|
|
1157
|
+
issuer: this.issuer
|
|
1158
|
+
});
|
|
1159
|
+
return {
|
|
1160
|
+
token,
|
|
1161
|
+
payload,
|
|
1162
|
+
expiresAt: new Date(expiresAt * 1e3)
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Decode a JWT token without verification (for debugging)
|
|
1167
|
+
*/
|
|
1168
|
+
decodeToken(token) {
|
|
1169
|
+
try {
|
|
1170
|
+
return jwt.decode(token);
|
|
1171
|
+
} catch (error) {
|
|
1172
|
+
console.error("Failed to decode JWT token:", error);
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Get token information without verification
|
|
1178
|
+
*/
|
|
1179
|
+
getTokenInfo(token) {
|
|
1180
|
+
try {
|
|
1181
|
+
const decoded = jwt.decode(token, { complete: true });
|
|
1182
|
+
if (!decoded || typeof decoded === "string") {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
return {
|
|
1186
|
+
header: decoded.header,
|
|
1187
|
+
payload: decoded.payload,
|
|
1188
|
+
signature: decoded.signature
|
|
1189
|
+
};
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
console.error("Failed to get token info:", error);
|
|
1192
|
+
return null;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
|
|
1197
|
+
// src/jwt-license-validator.ts
|
|
1198
|
+
var jwt2 = __toESM(require("jsonwebtoken"), 1);
|
|
1199
|
+
var JWTLicenseValidator = {
|
|
1200
|
+
/**
|
|
1201
|
+
* Validate a JWT license
|
|
1202
|
+
* @param license - JWT license string
|
|
1203
|
+
* @param publicKey - RSA public key in PEM format
|
|
1204
|
+
* @param options - Validation options
|
|
1205
|
+
* @returns Validation result
|
|
1206
|
+
*/
|
|
1207
|
+
validateLicense(license, publicKey, options = {}) {
|
|
1208
|
+
try {
|
|
1209
|
+
const { checkExpiration = true, currentTime = /* @__PURE__ */ new Date() } = options;
|
|
1210
|
+
const decoded = jwt2.verify(license, publicKey, {
|
|
1211
|
+
algorithms: ["RS256"],
|
|
1212
|
+
ignoreExpiration: !checkExpiration
|
|
1213
|
+
});
|
|
1214
|
+
const fieldValidation = this.validateRequiredFields(decoded);
|
|
1215
|
+
if (!fieldValidation.isValid) {
|
|
1216
|
+
return fieldValidation;
|
|
1217
|
+
}
|
|
1218
|
+
if (checkExpiration) {
|
|
1219
|
+
const expirationValidation = this.checkExpiration(decoded, currentTime);
|
|
1220
|
+
if (!expirationValidation.isValid) {
|
|
1221
|
+
return expirationValidation;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
const hasFeature = (feature) => {
|
|
1225
|
+
return decoded.features.includes("*") || decoded.features.includes(feature);
|
|
1226
|
+
};
|
|
1227
|
+
return {
|
|
1228
|
+
isValid: true,
|
|
1229
|
+
payload: decoded,
|
|
1230
|
+
isExpired: false,
|
|
1231
|
+
hasFeature
|
|
1232
|
+
};
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
if (error instanceof jwt2.JsonWebTokenError) {
|
|
1235
|
+
return {
|
|
1236
|
+
isValid: false,
|
|
1237
|
+
error: `JWT validation failed: ${error.message}`
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
if (error instanceof jwt2.TokenExpiredError) {
|
|
1241
|
+
return {
|
|
1242
|
+
isValid: false,
|
|
1243
|
+
error: `License expired: ${error.message}`,
|
|
1244
|
+
isExpired: true
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
return {
|
|
1248
|
+
isValid: false,
|
|
1249
|
+
error: `License validation failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
},
|
|
1253
|
+
/**
|
|
1254
|
+
* Validate that all required fields are present in license payload
|
|
1255
|
+
* @param payload - License payload to validate
|
|
1256
|
+
* @returns Validation result
|
|
1257
|
+
*/
|
|
1258
|
+
validateRequiredFields(payload) {
|
|
1259
|
+
const requiredFields = ["plan", "sub", "projectId", "iat", "exp", "issuer", "features"];
|
|
1260
|
+
for (const field of requiredFields) {
|
|
1261
|
+
if (!(field in payload)) {
|
|
1262
|
+
return {
|
|
1263
|
+
isValid: false,
|
|
1264
|
+
error: `Missing required field: ${field}`
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
if (typeof payload.plan !== "string" || !payload.plan.trim()) {
|
|
1269
|
+
return {
|
|
1270
|
+
isValid: false,
|
|
1271
|
+
error: "Invalid plan: must be a non-empty string"
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
if (typeof payload.sub !== "string" || !payload.sub.trim()) {
|
|
1275
|
+
return {
|
|
1276
|
+
isValid: false,
|
|
1277
|
+
error: "Invalid sub: must be a non-empty string"
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
if (typeof payload.projectId !== "string" || !payload.projectId.trim()) {
|
|
1281
|
+
return {
|
|
1282
|
+
isValid: false,
|
|
1283
|
+
error: "Invalid projectId: must be a non-empty string"
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
if (typeof payload.issuer !== "string" || !payload.issuer.trim()) {
|
|
1287
|
+
return {
|
|
1288
|
+
isValid: false,
|
|
1289
|
+
error: "Invalid issuer: must be a non-empty string"
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
if (!Array.isArray(payload.features)) {
|
|
1293
|
+
return {
|
|
1294
|
+
isValid: false,
|
|
1295
|
+
error: "Invalid features: must be an array"
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
if (typeof payload.iat !== "number" || payload.iat <= 0) {
|
|
1299
|
+
return {
|
|
1300
|
+
isValid: false,
|
|
1301
|
+
error: "Invalid iat: must be a positive number"
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
if (typeof payload.exp !== "number" || payload.exp <= 0) {
|
|
1305
|
+
return {
|
|
1306
|
+
isValid: false,
|
|
1307
|
+
error: "Invalid exp: must be a positive number"
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
if (payload.iat >= payload.exp) {
|
|
1311
|
+
return {
|
|
1312
|
+
isValid: false,
|
|
1313
|
+
error: "Invalid timestamps: iat must be before exp"
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
return { isValid: true };
|
|
1317
|
+
},
|
|
1318
|
+
/**
|
|
1319
|
+
* Check if license has expired
|
|
1320
|
+
* @param payload - License payload
|
|
1321
|
+
* @param currentTime - Current time to check against (defaults to now)
|
|
1322
|
+
* @returns Validation result
|
|
1323
|
+
*/
|
|
1324
|
+
checkExpiration(payload, currentTime = /* @__PURE__ */ new Date()) {
|
|
1325
|
+
const now = Math.floor(currentTime.getTime() / 1e3);
|
|
1326
|
+
const expiresAt = payload.exp;
|
|
1327
|
+
if (now > expiresAt) {
|
|
1328
|
+
return {
|
|
1329
|
+
isValid: false,
|
|
1330
|
+
error: `License expired on ${new Date(expiresAt * 1e3).toISOString()}`,
|
|
1331
|
+
isExpired: true
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
return { isValid: true, isExpired: false };
|
|
1335
|
+
},
|
|
1336
|
+
/**
|
|
1337
|
+
* Check if license has a specific feature
|
|
1338
|
+
* @param payload - License payload
|
|
1339
|
+
* @param feature - Feature to check
|
|
1340
|
+
* @returns Whether the feature is available
|
|
1341
|
+
*/
|
|
1342
|
+
hasFeature(payload, feature) {
|
|
1343
|
+
return payload.features.includes("*") || payload.features.includes(feature);
|
|
1344
|
+
},
|
|
1345
|
+
/**
|
|
1346
|
+
* Get license expiration status
|
|
1347
|
+
* @param payload - License payload
|
|
1348
|
+
* @param currentTime - Current time to check against (defaults to now)
|
|
1349
|
+
* @returns Expiration information
|
|
1350
|
+
*/
|
|
1351
|
+
getExpirationInfo(payload, currentTime = /* @__PURE__ */ new Date()) {
|
|
1352
|
+
const now = Math.floor(currentTime.getTime() / 1e3);
|
|
1353
|
+
const expiresAt = payload.exp;
|
|
1354
|
+
const isExpired = now > expiresAt;
|
|
1355
|
+
const daysUntilExpiration = Math.ceil((expiresAt - now) / (24 * 60 * 60));
|
|
1356
|
+
return {
|
|
1357
|
+
isExpired,
|
|
1358
|
+
expiresAt: new Date(expiresAt * 1e3),
|
|
1359
|
+
daysUntilExpiration: isExpired ? 0 : daysUntilExpiration
|
|
1360
|
+
};
|
|
1361
|
+
},
|
|
1362
|
+
/**
|
|
1363
|
+
* Decode JWT license without verification (for debugging)
|
|
1364
|
+
* @param license - JWT license string
|
|
1365
|
+
* @returns Decoded payload or null if invalid
|
|
1366
|
+
*/
|
|
1367
|
+
decodeLicense(license) {
|
|
1368
|
+
try {
|
|
1369
|
+
const decoded = jwt2.decode(license);
|
|
1370
|
+
return decoded;
|
|
1371
|
+
} catch {
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1089
1376
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1090
1377
|
0 && (module.exports = {
|
|
1091
1378
|
AbortController,
|
|
1092
1379
|
ArrayProto,
|
|
1380
|
+
JWTLicenseSigner,
|
|
1381
|
+
JWTLicenseValidator,
|
|
1093
1382
|
XMLHttpRequest,
|
|
1094
1383
|
absoluteUrl,
|
|
1095
1384
|
assignableWindow,
|
package/dist/index.d.cts
CHANGED
|
@@ -11,8 +11,11 @@ export { isPublishedAtLeastOneEnvironment, isPublishedInAllEnvironments } from '
|
|
|
11
11
|
export { deepClone } from './utils.cjs';
|
|
12
12
|
export { generateAutoStateColors, hexToHSLString, hexToRGBStr } from './color.cjs';
|
|
13
13
|
export { absoluteUrl, cn, cuid, evalCode, formatDate, getRandomColor, hexToRgb, isDark, uuidV4 } from './helper.cjs';
|
|
14
|
+
export { GenerateLicenseOptions, JWTLicenseSigner, JWTLicenseSignerOptions } from './jwt-license-signer.cjs';
|
|
15
|
+
export { JWTLicenseValidator } from './jwt-license-validator.cjs';
|
|
14
16
|
export { default as isEqual } from 'fast-deep-equal';
|
|
15
17
|
import '@usertour/types';
|
|
16
18
|
import './storage.cjs';
|
|
17
19
|
import '@usertour/types/';
|
|
18
20
|
import 'clsx';
|
|
21
|
+
import 'jsonwebtoken';
|
package/dist/index.d.ts
CHANGED
|
@@ -11,8 +11,11 @@ export { isPublishedAtLeastOneEnvironment, isPublishedInAllEnvironments } from '
|
|
|
11
11
|
export { deepClone } from './utils.js';
|
|
12
12
|
export { generateAutoStateColors, hexToHSLString, hexToRGBStr } from './color.js';
|
|
13
13
|
export { absoluteUrl, cn, cuid, evalCode, formatDate, getRandomColor, hexToRgb, isDark, uuidV4 } from './helper.js';
|
|
14
|
+
export { GenerateLicenseOptions, JWTLicenseSigner, JWTLicenseSignerOptions } from './jwt-license-signer.js';
|
|
15
|
+
export { JWTLicenseValidator } from './jwt-license-validator.js';
|
|
14
16
|
export { default as isEqual } from 'fast-deep-equal';
|
|
15
17
|
import '@usertour/types';
|
|
16
18
|
import './storage.js';
|
|
17
19
|
import '@usertour/types/';
|
|
18
20
|
import 'clsx';
|
|
21
|
+
import 'jsonwebtoken';
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,12 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
isUrl
|
|
17
17
|
} from "./chunk-ZNFXGN3M.js";
|
|
18
|
+
import {
|
|
19
|
+
JWTLicenseSigner
|
|
20
|
+
} from "./chunk-JQ4E3IDB.js";
|
|
21
|
+
import {
|
|
22
|
+
JWTLicenseValidator
|
|
23
|
+
} from "./chunk-KJJ6UD5L.js";
|
|
18
24
|
import {
|
|
19
25
|
defaultStep
|
|
20
26
|
} from "./chunk-FW54TSA3.js";
|
|
@@ -103,6 +109,8 @@ import "./chunk-XEO3YXBM.js";
|
|
|
103
109
|
export {
|
|
104
110
|
AbortController,
|
|
105
111
|
ArrayProto,
|
|
112
|
+
JWTLicenseSigner,
|
|
113
|
+
JWTLicenseValidator,
|
|
106
114
|
XMLHttpRequest,
|
|
107
115
|
absoluteUrl,
|
|
108
116
|
assignableWindow,
|
|
@@ -1,111 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
|
|
5
|
-
// src/jwt-license-signer.ts
|
|
6
|
-
import * as fs from "fs";
|
|
7
|
-
import * as path from "path";
|
|
8
|
-
import * as jwt from "jsonwebtoken";
|
|
9
|
-
var JWTLicenseSigner = class {
|
|
10
|
-
constructor(options) {
|
|
11
|
-
__publicField(this, "privateKey");
|
|
12
|
-
__publicField(this, "issuer");
|
|
13
|
-
__publicField(this, "algorithm");
|
|
14
|
-
this.privateKey = this.loadPrivateKey(options.privateKeyPath);
|
|
15
|
-
this.issuer = options.issuer || "https://www.usertour.io";
|
|
16
|
-
this.algorithm = options.algorithm || "RS256";
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Load RSA private key from file
|
|
20
|
-
*/
|
|
21
|
-
loadPrivateKey(keyPath) {
|
|
22
|
-
try {
|
|
23
|
-
const fullPath = path.resolve(keyPath);
|
|
24
|
-
return fs.readFileSync(fullPath, "utf8");
|
|
25
|
-
} catch (error) {
|
|
26
|
-
throw new Error(`Failed to load private key from ${keyPath}: ${error}`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Generate a JWT license token
|
|
31
|
-
*/
|
|
32
|
-
generateLicense(options) {
|
|
33
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
34
|
-
const expiresAt = now + options.expiresInDays * 24 * 60 * 60;
|
|
35
|
-
const payload = {
|
|
36
|
-
plan: options.plan,
|
|
37
|
-
sub: options.subject,
|
|
38
|
-
projectId: options.projectId,
|
|
39
|
-
iat: now,
|
|
40
|
-
exp: expiresAt,
|
|
41
|
-
issuer: options.issuer || this.issuer,
|
|
42
|
-
features: options.features
|
|
43
|
-
};
|
|
44
|
-
try {
|
|
45
|
-
return jwt.sign(payload, this.privateKey, {
|
|
46
|
-
algorithm: this.algorithm,
|
|
47
|
-
issuer: this.issuer
|
|
48
|
-
});
|
|
49
|
-
} catch (error) {
|
|
50
|
-
throw new Error(`Failed to generate JWT license: ${error}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Generate a license and return both token and payload info
|
|
55
|
-
*/
|
|
56
|
-
generateLicenseWithInfo(options) {
|
|
57
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
58
|
-
const expiresAt = now + options.expiresInDays * 24 * 60 * 60;
|
|
59
|
-
const payload = {
|
|
60
|
-
plan: options.plan,
|
|
61
|
-
sub: options.subject,
|
|
62
|
-
projectId: options.projectId,
|
|
63
|
-
iat: now,
|
|
64
|
-
exp: expiresAt,
|
|
65
|
-
issuer: options.issuer || this.issuer,
|
|
66
|
-
features: options.features
|
|
67
|
-
};
|
|
68
|
-
const token = jwt.sign(payload, this.privateKey, {
|
|
69
|
-
algorithm: this.algorithm,
|
|
70
|
-
issuer: this.issuer
|
|
71
|
-
});
|
|
72
|
-
return {
|
|
73
|
-
token,
|
|
74
|
-
payload,
|
|
75
|
-
expiresAt: new Date(expiresAt * 1e3)
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Decode a JWT token without verification (for debugging)
|
|
80
|
-
*/
|
|
81
|
-
decodeToken(token) {
|
|
82
|
-
try {
|
|
83
|
-
return jwt.decode(token);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error("Failed to decode JWT token:", error);
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Get token information without verification
|
|
91
|
-
*/
|
|
92
|
-
getTokenInfo(token) {
|
|
93
|
-
try {
|
|
94
|
-
const decoded = jwt.decode(token, { complete: true });
|
|
95
|
-
if (!decoded || typeof decoded === "string") {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
return {
|
|
99
|
-
header: decoded.header,
|
|
100
|
-
payload: decoded.payload,
|
|
101
|
-
signature: decoded.signature
|
|
102
|
-
};
|
|
103
|
-
} catch (error) {
|
|
104
|
-
console.error("Failed to get token info:", error);
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
};
|
|
2
|
+
JWTLicenseSigner
|
|
3
|
+
} from "./chunk-JQ4E3IDB.js";
|
|
4
|
+
import "./chunk-XEO3YXBM.js";
|
|
109
5
|
export {
|
|
110
6
|
JWTLicenseSigner
|
|
111
7
|
};
|
|
@@ -1,184 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
JWTLicenseValidator
|
|
3
|
+
} from "./chunk-KJJ6UD5L.js";
|
|
1
4
|
import "./chunk-XEO3YXBM.js";
|
|
2
|
-
|
|
3
|
-
// src/jwt-license-validator.ts
|
|
4
|
-
import * as jwt from "jsonwebtoken";
|
|
5
|
-
var JWTLicenseValidator = {
|
|
6
|
-
/**
|
|
7
|
-
* Validate a JWT license
|
|
8
|
-
* @param license - JWT license string
|
|
9
|
-
* @param publicKey - RSA public key in PEM format
|
|
10
|
-
* @param options - Validation options
|
|
11
|
-
* @returns Validation result
|
|
12
|
-
*/
|
|
13
|
-
validateLicense(license, publicKey, options = {}) {
|
|
14
|
-
try {
|
|
15
|
-
const { checkExpiration = true, currentTime = /* @__PURE__ */ new Date() } = options;
|
|
16
|
-
const decoded = jwt.verify(license, publicKey, {
|
|
17
|
-
algorithms: ["RS256"],
|
|
18
|
-
ignoreExpiration: !checkExpiration
|
|
19
|
-
});
|
|
20
|
-
const fieldValidation = this.validateRequiredFields(decoded);
|
|
21
|
-
if (!fieldValidation.isValid) {
|
|
22
|
-
return fieldValidation;
|
|
23
|
-
}
|
|
24
|
-
if (checkExpiration) {
|
|
25
|
-
const expirationValidation = this.checkExpiration(decoded, currentTime);
|
|
26
|
-
if (!expirationValidation.isValid) {
|
|
27
|
-
return expirationValidation;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const hasFeature = (feature) => {
|
|
31
|
-
return decoded.features.includes("*") || decoded.features.includes(feature);
|
|
32
|
-
};
|
|
33
|
-
return {
|
|
34
|
-
isValid: true,
|
|
35
|
-
payload: decoded,
|
|
36
|
-
isExpired: false,
|
|
37
|
-
hasFeature
|
|
38
|
-
};
|
|
39
|
-
} catch (error) {
|
|
40
|
-
if (error instanceof jwt.JsonWebTokenError) {
|
|
41
|
-
return {
|
|
42
|
-
isValid: false,
|
|
43
|
-
error: `JWT validation failed: ${error.message}`
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
if (error instanceof jwt.TokenExpiredError) {
|
|
47
|
-
return {
|
|
48
|
-
isValid: false,
|
|
49
|
-
error: `License expired: ${error.message}`,
|
|
50
|
-
isExpired: true
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
return {
|
|
54
|
-
isValid: false,
|
|
55
|
-
error: `License validation failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
/**
|
|
60
|
-
* Validate that all required fields are present in license payload
|
|
61
|
-
* @param payload - License payload to validate
|
|
62
|
-
* @returns Validation result
|
|
63
|
-
*/
|
|
64
|
-
validateRequiredFields(payload) {
|
|
65
|
-
const requiredFields = ["plan", "sub", "projectId", "iat", "exp", "issuer", "features"];
|
|
66
|
-
for (const field of requiredFields) {
|
|
67
|
-
if (!(field in payload)) {
|
|
68
|
-
return {
|
|
69
|
-
isValid: false,
|
|
70
|
-
error: `Missing required field: ${field}`
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (typeof payload.plan !== "string" || !payload.plan.trim()) {
|
|
75
|
-
return {
|
|
76
|
-
isValid: false,
|
|
77
|
-
error: "Invalid plan: must be a non-empty string"
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
if (typeof payload.sub !== "string" || !payload.sub.trim()) {
|
|
81
|
-
return {
|
|
82
|
-
isValid: false,
|
|
83
|
-
error: "Invalid sub: must be a non-empty string"
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
if (typeof payload.projectId !== "string" || !payload.projectId.trim()) {
|
|
87
|
-
return {
|
|
88
|
-
isValid: false,
|
|
89
|
-
error: "Invalid projectId: must be a non-empty string"
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
if (typeof payload.issuer !== "string" || !payload.issuer.trim()) {
|
|
93
|
-
return {
|
|
94
|
-
isValid: false,
|
|
95
|
-
error: "Invalid issuer: must be a non-empty string"
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
if (!Array.isArray(payload.features)) {
|
|
99
|
-
return {
|
|
100
|
-
isValid: false,
|
|
101
|
-
error: "Invalid features: must be an array"
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
if (typeof payload.iat !== "number" || payload.iat <= 0) {
|
|
105
|
-
return {
|
|
106
|
-
isValid: false,
|
|
107
|
-
error: "Invalid iat: must be a positive number"
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
if (typeof payload.exp !== "number" || payload.exp <= 0) {
|
|
111
|
-
return {
|
|
112
|
-
isValid: false,
|
|
113
|
-
error: "Invalid exp: must be a positive number"
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
if (payload.iat >= payload.exp) {
|
|
117
|
-
return {
|
|
118
|
-
isValid: false,
|
|
119
|
-
error: "Invalid timestamps: iat must be before exp"
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
return { isValid: true };
|
|
123
|
-
},
|
|
124
|
-
/**
|
|
125
|
-
* Check if license has expired
|
|
126
|
-
* @param payload - License payload
|
|
127
|
-
* @param currentTime - Current time to check against (defaults to now)
|
|
128
|
-
* @returns Validation result
|
|
129
|
-
*/
|
|
130
|
-
checkExpiration(payload, currentTime = /* @__PURE__ */ new Date()) {
|
|
131
|
-
const now = Math.floor(currentTime.getTime() / 1e3);
|
|
132
|
-
const expiresAt = payload.exp;
|
|
133
|
-
if (now > expiresAt) {
|
|
134
|
-
return {
|
|
135
|
-
isValid: false,
|
|
136
|
-
error: `License expired on ${new Date(expiresAt * 1e3).toISOString()}`,
|
|
137
|
-
isExpired: true
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
return { isValid: true, isExpired: false };
|
|
141
|
-
},
|
|
142
|
-
/**
|
|
143
|
-
* Check if license has a specific feature
|
|
144
|
-
* @param payload - License payload
|
|
145
|
-
* @param feature - Feature to check
|
|
146
|
-
* @returns Whether the feature is available
|
|
147
|
-
*/
|
|
148
|
-
hasFeature(payload, feature) {
|
|
149
|
-
return payload.features.includes("*") || payload.features.includes(feature);
|
|
150
|
-
},
|
|
151
|
-
/**
|
|
152
|
-
* Get license expiration status
|
|
153
|
-
* @param payload - License payload
|
|
154
|
-
* @param currentTime - Current time to check against (defaults to now)
|
|
155
|
-
* @returns Expiration information
|
|
156
|
-
*/
|
|
157
|
-
getExpirationInfo(payload, currentTime = /* @__PURE__ */ new Date()) {
|
|
158
|
-
const now = Math.floor(currentTime.getTime() / 1e3);
|
|
159
|
-
const expiresAt = payload.exp;
|
|
160
|
-
const isExpired = now > expiresAt;
|
|
161
|
-
const daysUntilExpiration = Math.ceil((expiresAt - now) / (24 * 60 * 60));
|
|
162
|
-
return {
|
|
163
|
-
isExpired,
|
|
164
|
-
expiresAt: new Date(expiresAt * 1e3),
|
|
165
|
-
daysUntilExpiration: isExpired ? 0 : daysUntilExpiration
|
|
166
|
-
};
|
|
167
|
-
},
|
|
168
|
-
/**
|
|
169
|
-
* Decode JWT license without verification (for debugging)
|
|
170
|
-
* @param license - JWT license string
|
|
171
|
-
* @returns Decoded payload or null if invalid
|
|
172
|
-
*/
|
|
173
|
-
decodeLicense(license) {
|
|
174
|
-
try {
|
|
175
|
-
const decoded = jwt.decode(license);
|
|
176
|
-
return decoded;
|
|
177
|
-
} catch {
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
5
|
export {
|
|
183
6
|
JWTLicenseValidator
|
|
184
7
|
};
|