@soulbatical/tetra-core 0.7.0 → 0.8.1
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/frontend/index.d.ts +0 -2
- package/dist/frontend/index.d.ts.map +1 -1
- package/dist/frontend/index.js +0 -1
- package/dist/frontend/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/securityMiddleware.js +10 -2
- package/dist/middleware/securityMiddleware.js.map +1 -1
- package/dist/shared/billing/SeatBillingService.d.ts +106 -0
- package/dist/shared/billing/SeatBillingService.d.ts.map +1 -0
- package/dist/shared/billing/SeatBillingService.js +292 -0
- package/dist/shared/billing/SeatBillingService.js.map +1 -0
- package/dist/shared/billing/index.d.ts +4 -0
- package/dist/shared/billing/index.d.ts.map +1 -1
- package/dist/shared/billing/index.js +2 -0
- package/dist/shared/billing/index.js.map +1 -1
- package/dist/shared/billing/seat-pricing.d.ts +53 -0
- package/dist/shared/billing/seat-pricing.d.ts.map +1 -0
- package/dist/shared/billing/seat-pricing.js +81 -0
- package/dist/shared/billing/seat-pricing.js.map +1 -0
- package/dist/shared/email/index.d.ts +1 -1
- package/dist/shared/email/index.d.ts.map +1 -1
- package/dist/shared/email/index.js +1 -1
- package/dist/shared/email/index.js.map +1 -1
- package/dist/shared/email/sanitize.d.ts +14 -0
- package/dist/shared/email/sanitize.d.ts.map +1 -1
- package/dist/shared/email/sanitize.js +20 -0
- package/dist/shared/email/sanitize.js.map +1 -1
- package/dist/shared/license/constants.d.ts +12 -10
- package/dist/shared/license/constants.d.ts.map +1 -1
- package/dist/shared/license/constants.js +12 -10
- package/dist/shared/license/constants.js.map +1 -1
- package/dist/shared/license/generator.d.ts +9 -6
- package/dist/shared/license/generator.d.ts.map +1 -1
- package/dist/shared/license/generator.js +15 -13
- package/dist/shared/license/generator.js.map +1 -1
- package/dist/shared/license/index.d.ts +2 -2
- package/dist/shared/license/index.d.ts.map +1 -1
- package/dist/shared/license/index.js +4 -3
- package/dist/shared/license/index.js.map +1 -1
- package/dist/shared/license/validator.d.ts +2 -8
- package/dist/shared/license/validator.d.ts.map +1 -1
- package/dist/shared/license/validator.js +12 -21
- package/dist/shared/license/validator.js.map +1 -1
- package/package.json +4 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/billing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACxE,YAAY,EACV,UAAU,EACV,aAAa,EACb,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/billing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACxE,YAAY,EACV,UAAU,EACV,aAAa,EACb,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -21,5 +21,7 @@
|
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
export { BillingService } from './BillingService.js';
|
|
24
|
+
export { SeatBillingService } from './SeatBillingService.js';
|
|
25
|
+
export { calculateSeatPrice, seatPriceToCents, DEFAULT_SEAT_TIERS } from './seat-pricing.js';
|
|
24
26
|
export { addBillingRoutes, addBillingWebhookRoutes } from './routes.js';
|
|
25
27
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/shared/billing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/shared/billing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seat-based pricing calculator
|
|
3
|
+
*
|
|
4
|
+
* Shared logic for per-user pricing with volume discounts and tier minimums.
|
|
5
|
+
* Used by: website pricing calculator, SeatBillingService, license validation.
|
|
6
|
+
*
|
|
7
|
+
* @module @soulbatical/tetra-core/billing
|
|
8
|
+
*/
|
|
9
|
+
export interface SeatTier {
|
|
10
|
+
/** Minimum users for this tier (inclusive) */
|
|
11
|
+
min: number;
|
|
12
|
+
/** Maximum users for this tier (inclusive, Infinity for last tier) */
|
|
13
|
+
max: number;
|
|
14
|
+
/** Price per user per month in EUR */
|
|
15
|
+
pricePerUser: number;
|
|
16
|
+
/** Minimum monthly charge for this tier (prevents price dips at tier boundary) */
|
|
17
|
+
minimum: number;
|
|
18
|
+
}
|
|
19
|
+
/** Default Tetra seat pricing tiers */
|
|
20
|
+
export declare const DEFAULT_SEAT_TIERS: SeatTier[];
|
|
21
|
+
export interface SeatPriceResult {
|
|
22
|
+
/** Total monthly price in EUR */
|
|
23
|
+
monthlyPrice: number;
|
|
24
|
+
/** Total yearly price in EUR */
|
|
25
|
+
yearlyPrice: number;
|
|
26
|
+
/** Number of free users */
|
|
27
|
+
freeUsers: number;
|
|
28
|
+
/** Number of paid users */
|
|
29
|
+
paidUsers: number;
|
|
30
|
+
/** Effective price per user (total / users) */
|
|
31
|
+
effectivePricePerUser: number;
|
|
32
|
+
/** Which tier the user count falls in */
|
|
33
|
+
tierIndex: number;
|
|
34
|
+
/** The tier's listed price per user */
|
|
35
|
+
tierPricePerUser: number;
|
|
36
|
+
/** Whether the minimum was applied */
|
|
37
|
+
minimumApplied: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Calculate the monthly price for a given number of seat users.
|
|
41
|
+
*
|
|
42
|
+
* Rules:
|
|
43
|
+
* - First 10 users are free
|
|
44
|
+
* - Price per user decreases with volume
|
|
45
|
+
* - Each tier has a minimum monthly charge (prevents price dips at boundary)
|
|
46
|
+
* - For 1000+ users, the last tier rate applies
|
|
47
|
+
*/
|
|
48
|
+
export declare function calculateSeatPrice(users: number, tiers?: SeatTier[]): SeatPriceResult;
|
|
49
|
+
/**
|
|
50
|
+
* Convert seat price to Stripe-compatible amount in cents.
|
|
51
|
+
*/
|
|
52
|
+
export declare function seatPriceToCents(monthlyPrice: number): number;
|
|
53
|
+
//# sourceMappingURL=seat-pricing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seat-pricing.d.ts","sourceRoot":"","sources":["../../../src/shared/billing/seat-pricing.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,QAAQ;IACvB,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,sEAAsE;IACtE,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,uCAAuC;AACvC,eAAO,MAAM,kBAAkB,EAAE,QAAQ,EAMxC,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,iCAAiC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,qBAAqB,EAAE,MAAM,CAAC;IAC9B,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,gBAAgB,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,QAAQ,EAAuB,GACrC,eAAe,CAuDjB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE7D"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seat-based pricing calculator
|
|
3
|
+
*
|
|
4
|
+
* Shared logic for per-user pricing with volume discounts and tier minimums.
|
|
5
|
+
* Used by: website pricing calculator, SeatBillingService, license validation.
|
|
6
|
+
*
|
|
7
|
+
* @module @soulbatical/tetra-core/billing
|
|
8
|
+
*/
|
|
9
|
+
/** Default Tetra seat pricing tiers */
|
|
10
|
+
export const DEFAULT_SEAT_TIERS = [
|
|
11
|
+
{ min: 1, max: 10, pricePerUser: 0, minimum: 0 },
|
|
12
|
+
{ min: 11, max: 100, pricePerUser: 3.90, minimum: 0 },
|
|
13
|
+
{ min: 101, max: 250, pricePerUser: 2.90, minimum: 351 },
|
|
14
|
+
{ min: 251, max: 500, pricePerUser: 1.90, minimum: 725 },
|
|
15
|
+
{ min: 501, max: 1000, pricePerUser: 0.90, minimum: 950 },
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Calculate the monthly price for a given number of seat users.
|
|
19
|
+
*
|
|
20
|
+
* Rules:
|
|
21
|
+
* - First 10 users are free
|
|
22
|
+
* - Price per user decreases with volume
|
|
23
|
+
* - Each tier has a minimum monthly charge (prevents price dips at boundary)
|
|
24
|
+
* - For 1000+ users, the last tier rate applies
|
|
25
|
+
*/
|
|
26
|
+
export function calculateSeatPrice(users, tiers = DEFAULT_SEAT_TIERS) {
|
|
27
|
+
if (users <= 0) {
|
|
28
|
+
return {
|
|
29
|
+
monthlyPrice: 0,
|
|
30
|
+
yearlyPrice: 0,
|
|
31
|
+
freeUsers: 0,
|
|
32
|
+
paidUsers: 0,
|
|
33
|
+
effectivePricePerUser: 0,
|
|
34
|
+
tierIndex: 0,
|
|
35
|
+
tierPricePerUser: 0,
|
|
36
|
+
minimumApplied: false,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// Find the tier
|
|
40
|
+
let tierIndex = 0;
|
|
41
|
+
let tierPricePerUser = 0;
|
|
42
|
+
for (let i = 0; i < tiers.length; i++) {
|
|
43
|
+
if (users >= tiers[i].min && users <= tiers[i].max) {
|
|
44
|
+
tierIndex = i;
|
|
45
|
+
tierPricePerUser = tiers[i].pricePerUser;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Beyond last tier: use last tier's rate
|
|
50
|
+
if (users > tiers[tiers.length - 1].max) {
|
|
51
|
+
const lastTier = tiers[tiers.length - 1];
|
|
52
|
+
tierIndex = tiers.length - 1;
|
|
53
|
+
tierPricePerUser = lastTier.pricePerUser;
|
|
54
|
+
}
|
|
55
|
+
const tier = tiers[tierIndex];
|
|
56
|
+
const freeUsers = Math.min(users, tiers[0].max);
|
|
57
|
+
const paidUsers = Math.max(0, users - tiers[0].max);
|
|
58
|
+
// Calculate raw price
|
|
59
|
+
const rawPrice = users * tierPricePerUser;
|
|
60
|
+
// Apply minimum
|
|
61
|
+
const minimum = tier?.minimum ?? 0;
|
|
62
|
+
const minimumApplied = rawPrice < minimum && minimum > 0;
|
|
63
|
+
const monthlyPrice = Math.max(rawPrice, minimum);
|
|
64
|
+
return {
|
|
65
|
+
monthlyPrice: Math.round(monthlyPrice * 100) / 100,
|
|
66
|
+
yearlyPrice: Math.round(monthlyPrice * 12 * 100) / 100,
|
|
67
|
+
freeUsers,
|
|
68
|
+
paidUsers,
|
|
69
|
+
effectivePricePerUser: users > 0 ? Math.round((monthlyPrice / users) * 100) / 100 : 0,
|
|
70
|
+
tierIndex,
|
|
71
|
+
tierPricePerUser,
|
|
72
|
+
minimumApplied,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Convert seat price to Stripe-compatible amount in cents.
|
|
77
|
+
*/
|
|
78
|
+
export function seatPriceToCents(monthlyPrice) {
|
|
79
|
+
return Math.round(monthlyPrice * 100);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=seat-pricing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seat-pricing.js","sourceRoot":"","sources":["../../../src/shared/billing/seat-pricing.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,6 @@ export { addEmailAdminRoutes } from './adminRoutes.js';
|
|
|
10
10
|
export { addMailgunWebhookRoutes } from './webhookRoutes.js';
|
|
11
11
|
export { GmailClient, getGmailClient, listGmailAccounts, clearGmailClientCache, disconnectGmail, getGmailAuthUrl, startGmailOAuth, getGmailAuthServerStatus, resetGmailAuthServerState, exchangeGmailCode, addGmailOAuthRoutes, } from './gmail.js';
|
|
12
12
|
export type { GmailTokens, GmailSendOptions, GmailSendResult, GmailSearchResult, GmailMessageSummary, GmailMessage, GmailMessagePart, GmailMessageHeader, GmailAttachmentInfo, GmailProfile, GmailOAuthConfig, GmailAuthServerStatus, GmailOAuthRoutesConfig, } from './gmail.js';
|
|
13
|
-
export { escapeHtml } from './sanitize.js';
|
|
13
|
+
export { escapeHtml, stripCRLF, sanitizeEmailHeader, stripHtml } from './sanitize.js';
|
|
14
14
|
export type { EmailAttachment, EmailConfig, SendEmailOpts, EmailTemplate, EmailLogEntry, MailgunResponse, SmtpResponse, EmailSendResponse, } from './types.js';
|
|
15
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/email/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAG7D,OAAO,EACL,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,eAAe,EACf,wBAAwB,EACxB,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/email/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAG7D,OAAO,EACL,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,eAAe,EACf,wBAAwB,EACxB,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAGtF,YAAY,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACb,aAAa,EACb,aAAa,EACb,eAAe,EACf,YAAY,EACZ,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
|
|
@@ -14,5 +14,5 @@ export { addMailgunWebhookRoutes } from './webhookRoutes.js';
|
|
|
14
14
|
// Gmail (OAuth2 API transport + client for read/search/attachments)
|
|
15
15
|
export { GmailClient, getGmailClient, listGmailAccounts, clearGmailClientCache, disconnectGmail, getGmailAuthUrl, startGmailOAuth, getGmailAuthServerStatus, resetGmailAuthServerState, exchangeGmailCode, addGmailOAuthRoutes, } from './gmail.js';
|
|
16
16
|
// Sanitization
|
|
17
|
-
export { escapeHtml } from './sanitize.js';
|
|
17
|
+
export { escapeHtml, stripCRLF, sanitizeEmailHeader, stripHtml } from './sanitize.js';
|
|
18
18
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/shared/email/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,UAAU;AACV,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,oBAAoB;AACpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,kBAAkB;AAClB,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,oEAAoE;AACpE,OAAO,EACL,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,eAAe,EACf,wBAAwB,EACxB,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAiBpB,eAAe;AACf,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/shared/email/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,UAAU;AACV,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,oBAAoB;AACpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,kBAAkB;AAClB,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,oEAAoE;AACpE,OAAO,EACL,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,eAAe,EACf,wBAAwB,EACxB,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAiBpB,eAAe;AACf,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -8,4 +8,18 @@
|
|
|
8
8
|
* Handles all OWASP-recommended characters: & < > " '
|
|
9
9
|
*/
|
|
10
10
|
export declare function escapeHtml(str: string, maxLen?: number): string;
|
|
11
|
+
/**
|
|
12
|
+
* Strip CRLF characters to prevent SMTP header injection.
|
|
13
|
+
* Use on ALL email header fields: to, from, subject, cc, bcc, reply-to.
|
|
14
|
+
*/
|
|
15
|
+
export declare function stripCRLF(str: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Sanitize an email header field: strip CRLF + trim + length limit.
|
|
18
|
+
* Combines SMTP injection prevention with basic hygiene.
|
|
19
|
+
*/
|
|
20
|
+
export declare function sanitizeEmailHeader(str: string, maxLen?: number): string;
|
|
21
|
+
/**
|
|
22
|
+
* Strip all HTML tags from a string. Use for user input that should be plain text.
|
|
23
|
+
*/
|
|
24
|
+
export declare function stripHtml(str: string): string;
|
|
11
25
|
//# sourceMappingURL=sanitize.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../../src/shared/email/sanitize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAM,GAAG,MAAM,CAQ5D"}
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../../src/shared/email/sanitize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAM,GAAG,MAAM,CAQ5D;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAM,GAAG,MAAM,CAErE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7C"}
|
|
@@ -16,4 +16,24 @@ export function escapeHtml(str, maxLen = 500) {
|
|
|
16
16
|
.replace(/"/g, '"')
|
|
17
17
|
.replace(/'/g, ''');
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Strip CRLF characters to prevent SMTP header injection.
|
|
21
|
+
* Use on ALL email header fields: to, from, subject, cc, bcc, reply-to.
|
|
22
|
+
*/
|
|
23
|
+
export function stripCRLF(str) {
|
|
24
|
+
return str.replace(/[\r\n]/g, '');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Sanitize an email header field: strip CRLF + trim + length limit.
|
|
28
|
+
* Combines SMTP injection prevention with basic hygiene.
|
|
29
|
+
*/
|
|
30
|
+
export function sanitizeEmailHeader(str, maxLen = 200) {
|
|
31
|
+
return stripCRLF(str).trim().slice(0, maxLen);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Strip all HTML tags from a string. Use for user input that should be plain text.
|
|
35
|
+
*/
|
|
36
|
+
export function stripHtml(str) {
|
|
37
|
+
return str.replace(/<[^>]*>/g, '');
|
|
38
|
+
}
|
|
19
39
|
//# sourceMappingURL=sanitize.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../../src/shared/email/sanitize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,MAAM,GAAG,GAAG;IAClD,OAAO,GAAG;SACP,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC;SAChB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC"}
|
|
1
|
+
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../../src/shared/email/sanitize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,MAAM,GAAG,GAAG;IAClD,OAAO,GAAG;SACP,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC;SAChB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,OAAO,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW,EAAE,MAAM,GAAG,GAAG;IAC3D,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AAChD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,OAAO,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -11,17 +11,19 @@ export declare const PLAN_HIERARCHY: Record<string, number>;
|
|
|
11
11
|
export declare const EXPIRY_WARNING_DAYS = 30;
|
|
12
12
|
/** Separator between HMAC and payload in the license key */
|
|
13
13
|
export declare const KEY_SEPARATOR = ".";
|
|
14
|
-
/**
|
|
15
|
-
export declare const
|
|
16
|
-
/** Length of the
|
|
17
|
-
export declare const
|
|
14
|
+
/** Hash algorithm for integrity check */
|
|
15
|
+
export declare const HASH_ALGORITHM = "sha256";
|
|
16
|
+
/** Length of the hash hex digest */
|
|
17
|
+
export declare const HASH_LENGTH = 64;
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* License integrity model (MUI X style).
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* The hash is simply sha256(payload) — no secret key involved.
|
|
22
|
+
* This is an INTEGRITY check, not a security mechanism.
|
|
23
|
+
* Like MUI X and AG Grid, the license system relies on legal compliance,
|
|
24
|
+
* not technical protection. Anyone can generate a valid key.
|
|
25
|
+
*
|
|
26
|
+
* The value is in the ongoing updates, support, and legal right to use —
|
|
27
|
+
* not in preventing key generation.
|
|
25
28
|
*/
|
|
26
|
-
export declare const SIGNING_SECRET = "tetra-license-v1-soulbatical-bv-2026";
|
|
27
29
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/shared/license/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4FAA4F;AAC5F,eAAO,MAAM,WAAW,IAAI,CAAC;AAE7B,oDAAoD;AACpD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAIjD,CAAC;AAEF,mDAAmD;AACnD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,4DAA4D;AAC5D,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/shared/license/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4FAA4F;AAC5F,eAAO,MAAM,WAAW,IAAI,CAAC;AAE7B,oDAAoD;AACpD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAIjD,CAAC;AAEF,mDAAmD;AACnD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,4DAA4D;AAC5D,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC,yCAAyC;AACzC,eAAO,MAAM,cAAc,WAAW,CAAC;AAEvC,oCAAoC;AACpC,eAAO,MAAM,WAAW,KAAK,CAAC;AAE9B;;;;;;;;;;GAUG"}
|
|
@@ -15,17 +15,19 @@ export const PLAN_HIERARCHY = {
|
|
|
15
15
|
export const EXPIRY_WARNING_DAYS = 30;
|
|
16
16
|
/** Separator between HMAC and payload in the license key */
|
|
17
17
|
export const KEY_SEPARATOR = '.';
|
|
18
|
-
/**
|
|
19
|
-
export const
|
|
20
|
-
/** Length of the
|
|
21
|
-
export const
|
|
18
|
+
/** Hash algorithm for integrity check */
|
|
19
|
+
export const HASH_ALGORITHM = 'sha256';
|
|
20
|
+
/** Length of the hash hex digest */
|
|
21
|
+
export const HASH_LENGTH = 64; // sha256 = 32 bytes = 64 hex chars
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* License integrity model (MUI X style).
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* The hash is simply sha256(payload) — no secret key involved.
|
|
26
|
+
* This is an INTEGRITY check, not a security mechanism.
|
|
27
|
+
* Like MUI X and AG Grid, the license system relies on legal compliance,
|
|
28
|
+
* not technical protection. Anyone can generate a valid key.
|
|
29
|
+
*
|
|
30
|
+
* The value is in the ongoing updates, support, and legal right to use —
|
|
31
|
+
* not in preventing key generation.
|
|
29
32
|
*/
|
|
30
|
-
export const SIGNING_SECRET = 'tetra-license-v1-soulbatical-bv-2026';
|
|
31
33
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/shared/license/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4FAA4F;AAC5F,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAE7B,oDAAoD;AACpD,MAAM,CAAC,MAAM,cAAc,GAA2B;IACpD,OAAO,EAAE,CAAC;IACV,GAAG,EAAE,CAAC;IACN,UAAU,EAAE,CAAC;CACd,CAAC;AAEF,mDAAmD;AACnD,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC,4DAA4D;AAC5D,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AAEjC,
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/shared/license/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4FAA4F;AAC5F,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAE7B,oDAAoD;AACpD,MAAM,CAAC,MAAM,cAAc,GAA2B;IACpD,OAAO,EAAE,CAAC;IACV,GAAG,EAAE,CAAC;IACN,UAAU,EAAE,CAAC;CACd,CAAC;AAEF,mDAAmD;AACnD,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC,4DAA4D;AAC5D,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AAEjC,yCAAyC;AACzC,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAC;AAEvC,oCAAoC;AACpC,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC,CAAC,mCAAmC;AAElE;;;;;;;;;;GAUG"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* License Key Generator
|
|
2
|
+
* License Key Generator & Decoder
|
|
3
3
|
*
|
|
4
4
|
* Generates self-validating license keys with embedded metadata.
|
|
5
|
-
* Format: {
|
|
5
|
+
* Format: {SHA256_HEX}{BASE64_PAYLOAD} (no separator, first 64 chars = hash)
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Integrity model (MUI X style): hash = sha256(payload), no secret key.
|
|
8
|
+
* This is legal protection, not technical protection.
|
|
9
|
+
*
|
|
10
|
+
* NOTE: generateLicenseKey is exported for internal/admin use only.
|
|
11
|
+
* Consumer projects should never need to generate keys — they only validate.
|
|
9
12
|
*
|
|
10
13
|
* @module @soulbatical/tetra-core/license
|
|
11
14
|
*/
|
|
@@ -33,11 +36,11 @@ export interface GenerateKeyOptions {
|
|
|
33
36
|
/**
|
|
34
37
|
* Generate a license key.
|
|
35
38
|
*
|
|
36
|
-
* @returns The license key string: {
|
|
39
|
+
* @returns The license key string: {SHA256_HASH}{BASE64_PAYLOAD}
|
|
37
40
|
*/
|
|
38
41
|
export declare function generateLicenseKey(options: GenerateKeyOptions): string;
|
|
39
42
|
/**
|
|
40
|
-
* Decode a license key without validating.
|
|
43
|
+
* Decode a license key without validating the hash.
|
|
41
44
|
* Returns null if the key format is invalid.
|
|
42
45
|
*/
|
|
43
46
|
export declare function decodeLicenseKey(key: string): LicensePayload | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../src/shared/license/generator.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../src/shared/license/generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG5F,MAAM,WAAW,kBAAkB;IACjC,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,iDAAiD;IACjD,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sDAAsD;IACtD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CA0BtE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAiBnE"}
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* License Key Generator
|
|
2
|
+
* License Key Generator & Decoder
|
|
3
3
|
*
|
|
4
4
|
* Generates self-validating license keys with embedded metadata.
|
|
5
|
-
* Format: {
|
|
5
|
+
* Format: {SHA256_HEX}{BASE64_PAYLOAD} (no separator, first 64 chars = hash)
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Integrity model (MUI X style): hash = sha256(payload), no secret key.
|
|
8
|
+
* This is legal protection, not technical protection.
|
|
9
|
+
*
|
|
10
|
+
* NOTE: generateLicenseKey is exported for internal/admin use only.
|
|
11
|
+
* Consumer projects should never need to generate keys — they only validate.
|
|
9
12
|
*
|
|
10
13
|
* @module @soulbatical/tetra-core/license
|
|
11
14
|
*/
|
|
12
15
|
import crypto from 'node:crypto';
|
|
13
|
-
import { KEY_VERSION,
|
|
16
|
+
import { KEY_VERSION, HASH_ALGORITHM, HASH_LENGTH } from './constants.js';
|
|
14
17
|
/**
|
|
15
18
|
* Generate a license key.
|
|
16
19
|
*
|
|
17
|
-
* @returns The license key string: {
|
|
20
|
+
* @returns The license key string: {SHA256_HASH}{BASE64_PAYLOAD}
|
|
18
21
|
*/
|
|
19
22
|
export function generateLicenseKey(options) {
|
|
20
23
|
const payload = {
|
|
@@ -34,22 +37,21 @@ export function generateLicenseKey(options) {
|
|
|
34
37
|
throw new Error(`Invalid expiry date: ${options.expiryDate}`);
|
|
35
38
|
}
|
|
36
39
|
const payloadBase64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
37
|
-
const
|
|
38
|
-
.
|
|
40
|
+
const hash = crypto
|
|
41
|
+
.createHash(HASH_ALGORITHM)
|
|
39
42
|
.update(payloadBase64)
|
|
40
43
|
.digest('hex');
|
|
41
|
-
return `${
|
|
44
|
+
return `${hash}${payloadBase64}`;
|
|
42
45
|
}
|
|
43
46
|
/**
|
|
44
|
-
* Decode a license key without validating.
|
|
47
|
+
* Decode a license key without validating the hash.
|
|
45
48
|
* Returns null if the key format is invalid.
|
|
46
49
|
*/
|
|
47
50
|
export function decodeLicenseKey(key) {
|
|
48
51
|
try {
|
|
49
|
-
|
|
50
|
-
if (separatorIndex === -1)
|
|
52
|
+
if (key.length <= HASH_LENGTH)
|
|
51
53
|
return null;
|
|
52
|
-
const payloadBase64 = key.slice(
|
|
54
|
+
const payloadBase64 = key.slice(HASH_LENGTH);
|
|
53
55
|
const json = Buffer.from(payloadBase64, 'base64url').toString('utf8');
|
|
54
56
|
const payload = JSON.parse(json);
|
|
55
57
|
// Basic shape validation
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../src/shared/license/generator.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../src/shared/license/generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAuB1E;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAA2B;IAC5D,MAAM,OAAO,GAAmB;QAC9B,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;QACtC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,WAAW;QACnC,MAAM,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE;QAC9C,UAAU,EAAE,WAAW;QACvB,GAAG,CAAC,OAAO,CAAC,gBAAgB,IAAI,IAAI,IAAI,EAAE,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,EAAE,CAAC;QACvF,GAAG,CAAC,OAAO,CAAC,cAAc,IAAI,IAAI,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC;QACjF,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,IAAI,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC;KAC5E,CAAC;IAEF,yCAAyC;IACzC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,wBAAwB,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACjF,MAAM,IAAI,GAAG,MAAM;SAChB,UAAU,CAAC,cAAc,CAAC;SAC1B,MAAM,CAAC,aAAa,CAAC;SACrB,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjB,OAAO,GAAG,IAAI,GAAG,aAAa,EAAE,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,IAAI,WAAW;YAAE,OAAO,IAAI,CAAC;QAE3C,MAAM,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAmB,CAAC;QAEnD,yBAAyB;QACzB,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;YAChF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* License Module — Barrel Export
|
|
3
3
|
*
|
|
4
4
|
* Offline license key validation for Tetra Pro features.
|
|
5
|
-
*
|
|
6
|
-
* zero network calls.
|
|
5
|
+
* Integrity check via sha256 hash (MUI X model), self-validating keys,
|
|
6
|
+
* zero network calls. Legal compliance, not technical protection.
|
|
7
7
|
*
|
|
8
8
|
* @module @soulbatical/tetra-core/license
|
|
9
9
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/license/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,YAAY,EACV,WAAW,EACX,YAAY,EACZ,cAAc,EACd,cAAc,EACd,uBAAuB,EACvB,YAAY,EACZ,WAAW,EACX,aAAa,GACd,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/license/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,YAAY,EACV,WAAW,EACX,YAAY,EACZ,cAAc,EACd,cAAc,EACd,uBAAuB,EACvB,YAAY,EACZ,WAAW,EACX,aAAa,GACd,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACtE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG9G,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* License Module — Barrel Export
|
|
3
3
|
*
|
|
4
4
|
* Offline license key validation for Tetra Pro features.
|
|
5
|
-
*
|
|
6
|
-
* zero network calls.
|
|
5
|
+
* Integrity check via sha256 hash (MUI X model), self-validating keys,
|
|
6
|
+
* zero network calls. Legal compliance, not technical protection.
|
|
7
7
|
*
|
|
8
8
|
* @module @soulbatical/tetra-core/license
|
|
9
9
|
*/
|
|
10
|
-
// Generator
|
|
10
|
+
// Generator — exported for internal/admin/CLI use only.
|
|
11
|
+
// Consumer projects should use validateLicense, not generateLicenseKey.
|
|
11
12
|
export { generateLicenseKey, decodeLicenseKey } from './generator.js';
|
|
12
13
|
// Validator (primary API for consumer projects)
|
|
13
14
|
export { validateLicense, clearLicenseCache, isFeatureAvailable, validateLicenseUsage } from './validator.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/shared/license/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAcH,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/shared/license/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAcH,wDAAwD;AACxD,wEAAwE;AACxE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGtE,gDAAgD;AAChD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE9G,YAAY;AACZ,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* License Key Validator
|
|
3
3
|
*
|
|
4
4
|
* Validates license keys offline — zero network calls.
|
|
5
|
-
*
|
|
5
|
+
* Integrity check via sha256 hash (MUI X model), then decode metadata.
|
|
6
|
+
* No signing secret — this is legal protection, not technical protection.
|
|
6
7
|
*
|
|
7
8
|
* @module @soulbatical/tetra-core/license
|
|
8
9
|
*/
|
|
@@ -24,13 +25,6 @@ export declare function clearLicenseCache(): void;
|
|
|
24
25
|
export declare function isFeatureAvailable(requiredPlan: string, requiredScope: LicenseScope, config?: LicenseConfig): boolean;
|
|
25
26
|
/**
|
|
26
27
|
* Validate license usage against org/user limits.
|
|
27
|
-
*
|
|
28
|
-
* Call this after validateLicense() to check if the current usage
|
|
29
|
-
* is within the license's limits. Returns a validation result with
|
|
30
|
-
* status 'org_limit_exceeded' or 'user_limit_exceeded' if over limit.
|
|
31
|
-
*
|
|
32
|
-
* If the license has no limits (maxOrganizations/maxUsersPerOrg undefined),
|
|
33
|
-
* this always returns valid.
|
|
34
28
|
*/
|
|
35
29
|
export declare function validateLicenseUsage(usage: LicenseUsage, config?: LicenseConfig): LicenseValidationResult;
|
|
36
30
|
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/shared/license/validator.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/shared/license/validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAkB,YAAY,EAAE,uBAAuB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAarH;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,uBAAuB,CAwB/E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,YAAY,EAC3B,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAWT;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,MAAM,CAAC,EAAE,aAAa,GACrB,uBAAuB,CA2BzB"}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* License Key Validator
|
|
3
3
|
*
|
|
4
4
|
* Validates license keys offline — zero network calls.
|
|
5
|
-
*
|
|
5
|
+
* Integrity check via sha256 hash (MUI X model), then decode metadata.
|
|
6
|
+
* No signing secret — this is legal protection, not technical protection.
|
|
6
7
|
*
|
|
7
8
|
* @module @soulbatical/tetra-core/license
|
|
8
9
|
*/
|
|
9
10
|
import crypto from 'node:crypto';
|
|
10
|
-
import {
|
|
11
|
+
import { HASH_ALGORITHM, HASH_LENGTH, PLAN_HIERARCHY, EXPIRY_WARNING_DAYS, } from './constants.js';
|
|
11
12
|
import { decodeLicenseKey } from './generator.js';
|
|
12
13
|
/** Cached validation result to avoid re-validating on every call */
|
|
13
14
|
let cachedResult = null;
|
|
@@ -62,13 +63,6 @@ export function isFeatureAvailable(requiredPlan, requiredScope, config) {
|
|
|
62
63
|
}
|
|
63
64
|
/**
|
|
64
65
|
* Validate license usage against org/user limits.
|
|
65
|
-
*
|
|
66
|
-
* Call this after validateLicense() to check if the current usage
|
|
67
|
-
* is within the license's limits. Returns a validation result with
|
|
68
|
-
* status 'org_limit_exceeded' or 'user_limit_exceeded' if over limit.
|
|
69
|
-
*
|
|
70
|
-
* If the license has no limits (maxOrganizations/maxUsersPerOrg undefined),
|
|
71
|
-
* this always returns valid.
|
|
72
66
|
*/
|
|
73
67
|
export function validateLicenseUsage(usage, config) {
|
|
74
68
|
const baseResult = validateLicense(config);
|
|
@@ -97,23 +91,22 @@ export function validateLicenseUsage(usage, config) {
|
|
|
97
91
|
}
|
|
98
92
|
// ─── Internal ────────────────────────────────────────────────
|
|
99
93
|
function performValidation(key, config) {
|
|
100
|
-
// Step 1:
|
|
101
|
-
|
|
102
|
-
if (separatorIndex === -1) {
|
|
94
|
+
// Step 1: Check minimum length (64 char hash + at least 1 char payload)
|
|
95
|
+
if (key.length <= HASH_LENGTH) {
|
|
103
96
|
return {
|
|
104
97
|
valid: false,
|
|
105
98
|
status: 'invalid',
|
|
106
99
|
message: 'Invalid license key format.',
|
|
107
100
|
};
|
|
108
101
|
}
|
|
109
|
-
const
|
|
110
|
-
const payloadBase64 = key.slice(
|
|
111
|
-
// Step 2: Verify
|
|
112
|
-
const
|
|
113
|
-
.
|
|
102
|
+
const hashProvided = key.slice(0, HASH_LENGTH);
|
|
103
|
+
const payloadBase64 = key.slice(HASH_LENGTH);
|
|
104
|
+
// Step 2: Verify hash integrity (sha256, no secret — MUI X model)
|
|
105
|
+
const hashComputed = crypto
|
|
106
|
+
.createHash(HASH_ALGORITHM)
|
|
114
107
|
.update(payloadBase64)
|
|
115
108
|
.digest('hex');
|
|
116
|
-
if (
|
|
109
|
+
if (hashProvided !== hashComputed) {
|
|
117
110
|
return {
|
|
118
111
|
valid: false,
|
|
119
112
|
status: 'invalid',
|
|
@@ -143,7 +136,6 @@ function performValidation(key, config) {
|
|
|
143
136
|
const now = Date.now();
|
|
144
137
|
const daysRemaining = Math.floor((payload.expiry - now) / (1000 * 60 * 60 * 24));
|
|
145
138
|
if (payload.model === 'perpetual') {
|
|
146
|
-
// Perpetual: key is valid for all releases before expiry date
|
|
147
139
|
const releaseDate = config?.releaseDate
|
|
148
140
|
? new Date(config.releaseDate).getTime()
|
|
149
141
|
: now;
|
|
@@ -158,7 +150,6 @@ function performValidation(key, config) {
|
|
|
158
150
|
}
|
|
159
151
|
}
|
|
160
152
|
else {
|
|
161
|
-
// Subscription: must be currently active
|
|
162
153
|
if (now > payload.expiry) {
|
|
163
154
|
return {
|
|
164
155
|
valid: false,
|
|
@@ -177,7 +168,7 @@ function performValidation(key, config) {
|
|
|
177
168
|
daysRemaining,
|
|
178
169
|
message: `Licensed to ${payload.customer} (${payload.plan}). ${daysRemaining > EXPIRY_WARNING_DAYS ? '' : `Expires in ${daysRemaining} days.`}`.trim(),
|
|
179
170
|
};
|
|
180
|
-
// Log expiry warning
|
|
171
|
+
// Log expiry warning
|
|
181
172
|
if (!config?.silent && daysRemaining <= EXPIRY_WARNING_DAYS && daysRemaining > 0) {
|
|
182
173
|
console.warn(`[Tetra] License expires in ${daysRemaining} days. Renew at https://tetra.soulbatical.com/renew`);
|
|
183
174
|
}
|