@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.
Files changed (47) hide show
  1. package/dist/frontend/index.d.ts +0 -2
  2. package/dist/frontend/index.d.ts.map +1 -1
  3. package/dist/frontend/index.js +0 -1
  4. package/dist/frontend/index.js.map +1 -1
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/middleware/securityMiddleware.js +10 -2
  10. package/dist/middleware/securityMiddleware.js.map +1 -1
  11. package/dist/shared/billing/SeatBillingService.d.ts +106 -0
  12. package/dist/shared/billing/SeatBillingService.d.ts.map +1 -0
  13. package/dist/shared/billing/SeatBillingService.js +292 -0
  14. package/dist/shared/billing/SeatBillingService.js.map +1 -0
  15. package/dist/shared/billing/index.d.ts +4 -0
  16. package/dist/shared/billing/index.d.ts.map +1 -1
  17. package/dist/shared/billing/index.js +2 -0
  18. package/dist/shared/billing/index.js.map +1 -1
  19. package/dist/shared/billing/seat-pricing.d.ts +53 -0
  20. package/dist/shared/billing/seat-pricing.d.ts.map +1 -0
  21. package/dist/shared/billing/seat-pricing.js +81 -0
  22. package/dist/shared/billing/seat-pricing.js.map +1 -0
  23. package/dist/shared/email/index.d.ts +1 -1
  24. package/dist/shared/email/index.d.ts.map +1 -1
  25. package/dist/shared/email/index.js +1 -1
  26. package/dist/shared/email/index.js.map +1 -1
  27. package/dist/shared/email/sanitize.d.ts +14 -0
  28. package/dist/shared/email/sanitize.d.ts.map +1 -1
  29. package/dist/shared/email/sanitize.js +20 -0
  30. package/dist/shared/email/sanitize.js.map +1 -1
  31. package/dist/shared/license/constants.d.ts +12 -10
  32. package/dist/shared/license/constants.d.ts.map +1 -1
  33. package/dist/shared/license/constants.js +12 -10
  34. package/dist/shared/license/constants.js.map +1 -1
  35. package/dist/shared/license/generator.d.ts +9 -6
  36. package/dist/shared/license/generator.d.ts.map +1 -1
  37. package/dist/shared/license/generator.js +15 -13
  38. package/dist/shared/license/generator.js.map +1 -1
  39. package/dist/shared/license/index.d.ts +2 -2
  40. package/dist/shared/license/index.d.ts.map +1 -1
  41. package/dist/shared/license/index.js +4 -3
  42. package/dist/shared/license/index.js.map +1 -1
  43. package/dist/shared/license/validator.d.ts +2 -8
  44. package/dist/shared/license/validator.d.ts.map +1 -1
  45. package/dist/shared/license/validator.js +12 -21
  46. package/dist/shared/license/validator.js.map +1 -1
  47. 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":"AAAA;;;;;;;GAOG;AAaH,uCAAuC;AACvC,MAAM,CAAC,MAAM,kBAAkB,GAAe;IAC5C,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;IAChD,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE;IACrD,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;IACxD,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;IACxD,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;CAC1D,CAAC;AAqBF;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAa,EACb,QAAoB,kBAAkB;IAEtC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACf,OAAO;YACL,YAAY,EAAE,CAAC;YACf,WAAW,EAAE,CAAC;YACd,SAAS,EAAE,CAAC;YACZ,SAAS,EAAE,CAAC;YACZ,qBAAqB,EAAE,CAAC;YACxB,SAAS,EAAE,CAAC;YACZ,gBAAgB,EAAE,CAAC;YACnB,cAAc,EAAE,KAAK;SACtB,CAAC;IACJ,CAAC;IAED,gBAAgB;IAChB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,gBAAgB,GAAG,CAAC,CAAC;IAEzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YACnD,SAAS,GAAG,CAAC,CAAC;YACd,gBAAgB,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;YACzC,MAAM;QACR,CAAC;IACH,CAAC;IAED,yCAAyC;IACzC,IAAI,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;QACxC,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACzC,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QAC7B,gBAAgB,GAAG,QAAQ,CAAC,YAAY,CAAC;IAC3C,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEpD,sBAAsB;IACtB,MAAM,QAAQ,GAAG,KAAK,GAAG,gBAAgB,CAAC;IAE1C,gBAAgB;IAChB,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,CAAC,CAAC;IACnC,MAAM,cAAc,GAAG,QAAQ,GAAG,OAAO,IAAI,OAAO,GAAG,CAAC,CAAC;IACzD,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAEjD,OAAO;QACL,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,GAAG,CAAC,GAAG,GAAG;QAClD,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,EAAE,GAAG,GAAG,CAAC,GAAG,GAAG;QACtD,SAAS;QACT,SAAS;QACT,qBAAqB,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACrF,SAAS;QACT,gBAAgB;QAChB,cAAc;KACf,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,YAAoB;IACnD,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,GAAG,CAAC,CAAC;AACxC,CAAC"}
@@ -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;AAG3C,YAAY,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACb,aAAa,EACb,aAAa,EACb,eAAe,EACf,YAAY,EACZ,iBAAiB,GAClB,MAAM,YAAY,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, '&quot;')
17
17
  .replace(/'/g, '&#x27;');
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
- /** HMAC algorithm */
15
- export declare const HMAC_ALGORITHM = "sha256";
16
- /** Length of the HMAC hex digest */
17
- export declare const HMAC_LENGTH = 64;
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
- * Signing secret embedded in the package.
19
+ * License integrity model (MUI X style).
20
20
  *
21
- * This is NOT a security mechanism. Like MUI X and AG Grid, the license system
22
- * relies on legal compliance, not technical protection. The key is self-validating
23
- * and the algorithm is public. This secret simply prevents casual key forging
24
- * while keeping validation 100% offline.
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,qBAAqB;AACrB,eAAO,MAAM,cAAc,WAAW,CAAC;AAEvC,oCAAoC;AACpC,eAAO,MAAM,WAAW,KAAK,CAAC;AAE9B;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,yCAAyC,CAAC"}
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
- /** HMAC algorithm */
19
- export const HMAC_ALGORITHM = 'sha256';
20
- /** Length of the HMAC hex digest */
21
- export const HMAC_LENGTH = 64; // sha256 = 32 bytes = 64 hex chars
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
- * Signing secret embedded in the package.
23
+ * License integrity model (MUI X style).
24
24
  *
25
- * This is NOT a security mechanism. Like MUI X and AG Grid, the license system
26
- * relies on legal compliance, not technical protection. The key is self-validating
27
- * and the algorithm is public. This secret simply prevents casual key forging
28
- * while keeping validation 100% offline.
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,qBAAqB;AACrB,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAC;AAEvC,oCAAoC;AACpC,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC,CAAC,mCAAmC;AAElE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,sCAAsC,CAAC"}
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: {HMAC_HEX}.{BASE64_PAYLOAD}
5
+ * Format: {SHA256_HEX}{BASE64_PAYLOAD} (no separator, first 64 chars = hash)
6
6
  *
7
- * The HMAC ensures integrity (not security the signing secret is embedded
8
- * in the package source, just like MUI X and AG Grid).
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: {HMAC}.{BASE64_PAYLOAD}
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;;;;;;;;;;GAUG;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,CAkBnE"}
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: {HMAC_HEX}.{BASE64_PAYLOAD}
5
+ * Format: {SHA256_HEX}{BASE64_PAYLOAD} (no separator, first 64 chars = hash)
6
6
  *
7
- * The HMAC ensures integrity (not security the signing secret is embedded
8
- * in the package source, just like MUI X and AG Grid).
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, KEY_SEPARATOR, HMAC_ALGORITHM, SIGNING_SECRET } from './constants.js';
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: {HMAC}.{BASE64_PAYLOAD}
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 hmac = crypto
38
- .createHmac(HMAC_ALGORITHM, SIGNING_SECRET)
40
+ const hash = crypto
41
+ .createHash(HASH_ALGORITHM)
39
42
  .update(payloadBase64)
40
43
  .digest('hex');
41
- return `${hmac}${KEY_SEPARATOR}${payloadBase64}`;
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
- const separatorIndex = key.indexOf(KEY_SEPARATOR);
50
- if (separatorIndex === -1)
52
+ if (key.length <= HASH_LENGTH)
51
53
  return null;
52
- const payloadBase64 = key.slice(separatorIndex + 1);
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;;;;;;;;;;GAUG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAuB5F;;;;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,EAAE,cAAc,CAAC;SAC1C,MAAM,CAAC,aAAa,CAAC;SACrB,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjB,OAAO,GAAG,IAAI,GAAG,aAAa,GAAG,aAAa,EAAE,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,CAAC;QACH,MAAM,cAAc,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QAClD,IAAI,cAAc,KAAK,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAEvC,MAAM,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC;QACpD,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"}
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
- * Like MUI X and AG Grid: HMAC integrity check, self-validating keys,
6
- * zero network calls. Security relies on legal compliance, not technical protection.
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;AAGpB,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"}
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
- * Like MUI X and AG Grid: HMAC integrity check, self-validating keys,
6
- * zero network calls. Security relies on legal compliance, not technical protection.
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 (for admin/CLI use — not typically used in consumer projects)
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,0EAA0E;AAC1E,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"}
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
- * Like MUI X and AG Grid: integrity check via HMAC, then decode metadata.
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;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAkB,YAAY,EAAE,uBAAuB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAcrH;;;;;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;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,MAAM,CAAC,EAAE,aAAa,GACrB,uBAAuB,CA2BzB"}
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
- * Like MUI X and AG Grid: integrity check via HMAC, then decode metadata.
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 { KEY_SEPARATOR, HMAC_ALGORITHM, SIGNING_SECRET, PLAN_HIERARCHY, EXPIRY_WARNING_DAYS, } from './constants.js';
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: Parse key format
101
- const separatorIndex = key.indexOf(KEY_SEPARATOR);
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 hmacProvided = key.slice(0, separatorIndex);
110
- const payloadBase64 = key.slice(separatorIndex + 1);
111
- // Step 2: Verify HMAC integrity
112
- const hmacComputed = crypto
113
- .createHmac(HMAC_ALGORITHM, SIGNING_SECRET)
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 (!crypto.timingSafeEqual(Buffer.from(hmacProvided, 'hex'), Buffer.from(hmacComputed, 'hex'))) {
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 (not an error, just a heads up)
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
  }