@huloglobal/vendure-licence-sdk 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/retention.d.ts +46 -0
- package/dist/retention.d.ts.map +1 -0
- package/dist/retention.js +76 -0
- package/dist/retention.js.map +1 -0
- package/dist/security.d.ts +85 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +228 -0
- package/dist/security.js.map +1 -0
- package/dist/update-check.d.ts +40 -0
- package/dist/update-check.d.ts.map +1 -0
- package/dist/update-check.js +139 -0
- package/dist/update-check.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,49 @@ All notable changes to `@huloglobal/vendure-licence-sdk` are documented here. Th
|
|
|
4
4
|
format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and
|
|
5
5
|
this project adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
## [0.
|
|
7
|
+
## [0.3.0]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `startRetentionSweeper({ ... })` — schedules a recurring
|
|
11
|
+
`DELETE FROM <table> WHERE createdAt < ?` sweep that prunes rows
|
|
12
|
+
older than `options.days`. Optional `options.maxRows` adds a hard cap
|
|
13
|
+
on total rows (oldest pruned first when exceeded). `options.days = 0`
|
|
14
|
+
or `null` keeps everything (no pruning). Sweeper interval defaults to
|
|
15
|
+
24h, is `.unref()`d so it doesn't block shutdown, and runs first 60s
|
|
16
|
+
after start so boot stays snappy.
|
|
17
|
+
- Shared security primitives module (`security.ts`), exported from the
|
|
18
|
+
package root:
|
|
19
|
+
- `verifyHmacSha256(body, signature, secret)` — timing-safe HMAC-SHA256
|
|
20
|
+
verification for webhooks. Tolerates the GitHub-style `sha256=`
|
|
21
|
+
prefix.
|
|
22
|
+
- `signValue(value, secret)` / `verifySignedValue(signed, secret)` —
|
|
23
|
+
HMAC tag a string so we can detect tampering when it round-trips
|
|
24
|
+
through a cookie or URL parameter. 64-bit tag keeps cookies short.
|
|
25
|
+
- `RateLimiter` — token-bucket rate limiter with an LRU cap on the
|
|
26
|
+
keyspace so a flood of keys can't grow memory.
|
|
27
|
+
- `applySecurityHeaders(res, { strict })` — recommended baseline of
|
|
28
|
+
security headers (X-Content-Type-Options, X-Frame-Options,
|
|
29
|
+
Referrer-Policy, Permissions-Policy, Cross-Origin-Resource-Policy).
|
|
30
|
+
`strict` adds a tight CSP for JSON / API endpoints.
|
|
31
|
+
- `isUrlOnAllowlist(url, allowedDomains)` — guards click redirectors
|
|
32
|
+
against being used as an open redirector. Supports wildcard suffixes
|
|
33
|
+
(`*.example.com`).
|
|
34
|
+
- `hashIp(ip, salt)` — sha256 IP hashing with a per-install salt.
|
|
35
|
+
- `randomToken(bytes)` — URL-safe random tokens for secrets / one-shot
|
|
36
|
+
nonces.
|
|
37
|
+
|
|
38
|
+
## [0.2.0]
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
- **`UpdateChecker`** — polls the public npm registry every 24h for the
|
|
42
|
+
package's latest version. Exposes `current`, `latest`, `updateAvailable`,
|
|
43
|
+
`isMajor`, `lastCheckedAt`, `lastError`. Soft-fails on network errors
|
|
44
|
+
(keeps the previous cached value in memory). Each consuming plugin
|
|
45
|
+
surfaces its status via a `/status` endpoint so the admin dashboard
|
|
46
|
+
can render an "update available" banner.
|
|
47
|
+
- `UpdateStatus` exported type.
|
|
48
|
+
|
|
49
|
+
## [0.1.0]
|
|
8
50
|
|
|
9
51
|
### Added
|
|
10
52
|
- Offline RSA-SHA256 JWT licence verification (`verifyLicence`).
|
package/dist/index.d.ts
CHANGED
|
@@ -21,4 +21,7 @@
|
|
|
21
21
|
export { verifyLicence } from './verify';
|
|
22
22
|
export { LicenceStatus, LicencePayload, VerifyLicenceOptions } from './types';
|
|
23
23
|
export { RevocationChecker } from './revocation';
|
|
24
|
+
export { UpdateChecker, UpdateStatus } from './update-check';
|
|
25
|
+
export { verifyHmacSha256, signValue, verifySignedValue, RateLimiter, RateLimiterOptions, applySecurityHeaders, isUrlOnAllowlist, hashIp, randomToken, } from './security';
|
|
26
|
+
export { startRetentionSweeper, RetentionOptions } from './retention';
|
|
24
27
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EACH,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EAChB,MAAM,EACN,WAAW,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -20,9 +20,22 @@
|
|
|
20
20
|
* tier. The end-user can always upgrade by supplying a valid key.
|
|
21
21
|
*/
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
-
exports.RevocationChecker = exports.verifyLicence = void 0;
|
|
23
|
+
exports.startRetentionSweeper = exports.randomToken = exports.hashIp = exports.isUrlOnAllowlist = exports.applySecurityHeaders = exports.RateLimiter = exports.verifySignedValue = exports.signValue = exports.verifyHmacSha256 = exports.UpdateChecker = exports.RevocationChecker = exports.verifyLicence = void 0;
|
|
24
24
|
var verify_1 = require("./verify");
|
|
25
25
|
Object.defineProperty(exports, "verifyLicence", { enumerable: true, get: function () { return verify_1.verifyLicence; } });
|
|
26
26
|
var revocation_1 = require("./revocation");
|
|
27
27
|
Object.defineProperty(exports, "RevocationChecker", { enumerable: true, get: function () { return revocation_1.RevocationChecker; } });
|
|
28
|
+
var update_check_1 = require("./update-check");
|
|
29
|
+
Object.defineProperty(exports, "UpdateChecker", { enumerable: true, get: function () { return update_check_1.UpdateChecker; } });
|
|
30
|
+
var security_1 = require("./security");
|
|
31
|
+
Object.defineProperty(exports, "verifyHmacSha256", { enumerable: true, get: function () { return security_1.verifyHmacSha256; } });
|
|
32
|
+
Object.defineProperty(exports, "signValue", { enumerable: true, get: function () { return security_1.signValue; } });
|
|
33
|
+
Object.defineProperty(exports, "verifySignedValue", { enumerable: true, get: function () { return security_1.verifySignedValue; } });
|
|
34
|
+
Object.defineProperty(exports, "RateLimiter", { enumerable: true, get: function () { return security_1.RateLimiter; } });
|
|
35
|
+
Object.defineProperty(exports, "applySecurityHeaders", { enumerable: true, get: function () { return security_1.applySecurityHeaders; } });
|
|
36
|
+
Object.defineProperty(exports, "isUrlOnAllowlist", { enumerable: true, get: function () { return security_1.isUrlOnAllowlist; } });
|
|
37
|
+
Object.defineProperty(exports, "hashIp", { enumerable: true, get: function () { return security_1.hashIp; } });
|
|
38
|
+
Object.defineProperty(exports, "randomToken", { enumerable: true, get: function () { return security_1.randomToken; } });
|
|
39
|
+
var retention_1 = require("./retention");
|
|
40
|
+
Object.defineProperty(exports, "startRetentionSweeper", { enumerable: true, get: function () { return retention_1.startRetentionSweeper; } });
|
|
28
41
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AAEH,mCAAyC;AAAhC,uGAAA,aAAa,OAAA;AAEtB,2CAAiD;AAAxC,+GAAA,iBAAiB,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AAEH,mCAAyC;AAAhC,uGAAA,aAAa,OAAA;AAEtB,2CAAiD;AAAxC,+GAAA,iBAAiB,OAAA;AAC1B,+CAA6D;AAApD,6GAAA,aAAa,OAAA;AACtB,uCAUoB;AAThB,4GAAA,gBAAgB,OAAA;AAChB,qGAAA,SAAS,OAAA;AACT,6GAAA,iBAAiB,OAAA;AACjB,uGAAA,WAAW,OAAA;AAEX,gHAAA,oBAAoB,OAAA;AACpB,4GAAA,gBAAgB,OAAA;AAChB,kGAAA,MAAM,OAAA;AACN,uGAAA,WAAW,OAAA;AAEf,yCAAsE;AAA7D,kHAAA,qBAAqB,OAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface RetentionOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Maximum age in days. Rows older than this are deleted by the
|
|
4
|
+
* scheduled sweeper. `null` or `0` keeps everything (no pruning).
|
|
5
|
+
*/
|
|
6
|
+
days: number | null;
|
|
7
|
+
/**
|
|
8
|
+
* Optional hard cap on total rows. When the table exceeds this,
|
|
9
|
+
* the oldest rows are pruned regardless of age. `null` disables
|
|
10
|
+
* this safety valve.
|
|
11
|
+
*/
|
|
12
|
+
maxRows?: number | null;
|
|
13
|
+
/**
|
|
14
|
+
* How often the sweeper runs, in milliseconds. Default 24h. The
|
|
15
|
+
* sweeper is debounced to one run per process and is `.unref()`d
|
|
16
|
+
* so it doesn't keep the Node event loop alive.
|
|
17
|
+
*/
|
|
18
|
+
sweepIntervalMs?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Schedule a recurring `DELETE FROM <table> WHERE createdAt < ?` sweep
|
|
22
|
+
* that prunes rows older than `opts.days`. Optionally also caps total
|
|
23
|
+
* row count by `opts.maxRows`.
|
|
24
|
+
*
|
|
25
|
+
* startRetentionSweeper({
|
|
26
|
+
* getQueryRunner: () => connection.rawConnection,
|
|
27
|
+
* table: 'email_log',
|
|
28
|
+
* dateColumn: 'createdAt',
|
|
29
|
+
* options: { days: 180 },
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* Returns a stop function so tests can shut the sweeper down.
|
|
33
|
+
*/
|
|
34
|
+
export declare function startRetentionSweeper(input: {
|
|
35
|
+
/** Run a raw SQL query; returns a Promise of any result. Plugins
|
|
36
|
+
* typically pass `() => connection.rawConnection`. */
|
|
37
|
+
getConnection: () => {
|
|
38
|
+
query: (sql: string, params?: any[]) => Promise<any>;
|
|
39
|
+
} | null;
|
|
40
|
+
table: string;
|
|
41
|
+
dateColumn?: string;
|
|
42
|
+
options: RetentionOptions;
|
|
43
|
+
/** Optional human-readable plugin name used in log lines. */
|
|
44
|
+
label?: string;
|
|
45
|
+
}): () => void;
|
|
46
|
+
//# sourceMappingURL=retention.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retention.d.ts","sourceRoot":"","sources":["../src/retention.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC7B;;;OAGG;IACH,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IACzC;2DACuD;IACvD,aAAa,EAAE,MAAM;QAAE,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC;IACrF,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,IAAI,CA4Db"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startRetentionSweeper = startRetentionSweeper;
|
|
4
|
+
const core_1 = require("@vendure/core");
|
|
5
|
+
const loggerCtx = 'LicenceSdk:Retention';
|
|
6
|
+
/**
|
|
7
|
+
* Schedule a recurring `DELETE FROM <table> WHERE createdAt < ?` sweep
|
|
8
|
+
* that prunes rows older than `opts.days`. Optionally also caps total
|
|
9
|
+
* row count by `opts.maxRows`.
|
|
10
|
+
*
|
|
11
|
+
* startRetentionSweeper({
|
|
12
|
+
* getQueryRunner: () => connection.rawConnection,
|
|
13
|
+
* table: 'email_log',
|
|
14
|
+
* dateColumn: 'createdAt',
|
|
15
|
+
* options: { days: 180 },
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* Returns a stop function so tests can shut the sweeper down.
|
|
19
|
+
*/
|
|
20
|
+
function startRetentionSweeper(input) {
|
|
21
|
+
var _a;
|
|
22
|
+
const opts = input.options;
|
|
23
|
+
const interval = Math.max(60000, (_a = opts.sweepIntervalMs) !== null && _a !== void 0 ? _a : 24 * 60 * 60 * 1000);
|
|
24
|
+
const dateColumn = input.dateColumn || 'createdAt';
|
|
25
|
+
const label = input.label || input.table;
|
|
26
|
+
if ((!opts.days || opts.days <= 0) && !opts.maxRows) {
|
|
27
|
+
// Nothing to do — caller wants infinite retention.
|
|
28
|
+
return () => undefined;
|
|
29
|
+
}
|
|
30
|
+
let stopped = false;
|
|
31
|
+
let timer = null;
|
|
32
|
+
const sweep = async () => {
|
|
33
|
+
var _a, _b, _c, _d, _e, _f;
|
|
34
|
+
if (stopped)
|
|
35
|
+
return;
|
|
36
|
+
const conn = input.getConnection();
|
|
37
|
+
if (!conn)
|
|
38
|
+
return;
|
|
39
|
+
try {
|
|
40
|
+
if (opts.days && opts.days > 0) {
|
|
41
|
+
const res = await conn.query(`DELETE FROM \`${input.table}\` WHERE \`${dateColumn}\` < DATE_SUB(NOW(), INTERVAL ? DAY)`, [opts.days]);
|
|
42
|
+
const affected = ((_b = (_a = res === null || res === void 0 ? void 0 : res.affectedRows) !== null && _a !== void 0 ? _a : res === null || res === void 0 ? void 0 : res[1]) !== null && _b !== void 0 ? _b : 0);
|
|
43
|
+
if (affected) {
|
|
44
|
+
core_1.Logger.info(`[${label}] pruned ${affected} row(s) older than ${opts.days}d`, loggerCtx);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (opts.maxRows && opts.maxRows > 0) {
|
|
48
|
+
const countRows = await conn.query(`SELECT COUNT(*) AS n FROM \`${input.table}\``);
|
|
49
|
+
const total = Number((_d = (_c = countRows === null || countRows === void 0 ? void 0 : countRows[0]) === null || _c === void 0 ? void 0 : _c.n) !== null && _d !== void 0 ? _d : 0);
|
|
50
|
+
const over = total - opts.maxRows;
|
|
51
|
+
if (over > 0) {
|
|
52
|
+
// Delete the `over` oldest rows.
|
|
53
|
+
const res = await conn.query(`DELETE FROM \`${input.table}\` ORDER BY \`${dateColumn}\` ASC LIMIT ?`, [over]);
|
|
54
|
+
const affected = ((_f = (_e = res === null || res === void 0 ? void 0 : res.affectedRows) !== null && _e !== void 0 ? _e : res === null || res === void 0 ? void 0 : res[1]) !== null && _f !== void 0 ? _f : 0);
|
|
55
|
+
core_1.Logger.info(`[${label}] pruned ${affected} oldest row(s) — cap=${opts.maxRows}`, loggerCtx);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
core_1.Logger.warn(`[${label}] retention sweep failed: ${e === null || e === void 0 ? void 0 : e.message}`, loggerCtx);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
// First sweep 60s after start (don't slow boot), then on the interval.
|
|
64
|
+
setTimeout(() => { void sweep(); }, 60000);
|
|
65
|
+
timer = setInterval(() => { void sweep(); }, interval);
|
|
66
|
+
if (typeof timer.unref === 'function')
|
|
67
|
+
timer.unref();
|
|
68
|
+
return () => {
|
|
69
|
+
stopped = true;
|
|
70
|
+
if (timer) {
|
|
71
|
+
clearInterval(timer);
|
|
72
|
+
timer = null;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=retention.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retention.js","sourceRoot":"","sources":["../src/retention.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify an HMAC-SHA256 hex signature against `body` using `secret`.
|
|
3
|
+
*
|
|
4
|
+
* const ok = verifyHmacSha256(req.rawBody, req.header('x-signature'), secret);
|
|
5
|
+
*
|
|
6
|
+
* Constant-time comparison so the signature can't be brute-forced via
|
|
7
|
+
* timing. Returns `false` on any malformed input.
|
|
8
|
+
*/
|
|
9
|
+
export declare function verifyHmacSha256(body: string | Buffer, providedSignature: string | undefined | null, secret: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Append a short HMAC tag to a value so we can detect tampering when the
|
|
12
|
+
* value round-trips through a cookie / URL parameter / form field.
|
|
13
|
+
*
|
|
14
|
+
* const signed = signValue('visitor-abc', SECRET);
|
|
15
|
+
* // -> 'visitor-abc.QRwy3...'
|
|
16
|
+
*
|
|
17
|
+
* The tag is 16 hex characters (64 bits) — enough to defeat brute-force
|
|
18
|
+
* for any value we sign here, while keeping cookies short.
|
|
19
|
+
*/
|
|
20
|
+
export declare function signValue(value: string, secret: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Verify the tag and return the original value, or `null` if the tag
|
|
23
|
+
* doesn't match or the format is malformed.
|
|
24
|
+
*/
|
|
25
|
+
export declare function verifySignedValue(signed: string | undefined | null, secret: string): string | null;
|
|
26
|
+
export interface RateLimiterOptions {
|
|
27
|
+
/** Allowed events per window. */
|
|
28
|
+
capacity: number;
|
|
29
|
+
/** Window length in milliseconds. */
|
|
30
|
+
windowMs: number;
|
|
31
|
+
/** Maximum tracked keys (LRU-evicted past this). Default 10 000. */
|
|
32
|
+
maxKeys?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Token-bucket rate limiter with an LRU cap on the keyspace so we can't
|
|
36
|
+
* be DoS'd by an attacker pushing keys to grow memory. `allow(key)`
|
|
37
|
+
* returns `true` if the request fits in the bucket. Keys are arbitrary
|
|
38
|
+
* strings — typically `${ip}|${route}`.
|
|
39
|
+
*/
|
|
40
|
+
export declare class RateLimiter {
|
|
41
|
+
private readonly capacity;
|
|
42
|
+
private readonly windowMs;
|
|
43
|
+
private readonly maxKeys;
|
|
44
|
+
/** Map<key, [tokensRemaining, lastRefillEpochMs]>. JS Map preserves
|
|
45
|
+
* insertion order, which is good enough LRU for this use. */
|
|
46
|
+
private readonly buckets;
|
|
47
|
+
constructor(opts: RateLimiterOptions);
|
|
48
|
+
allow(key: string, cost?: number): boolean;
|
|
49
|
+
private touchLru;
|
|
50
|
+
/** Reset all buckets — useful in tests. */
|
|
51
|
+
clear(): void;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Apply a recommended security-headers baseline to an Express response.
|
|
55
|
+
*
|
|
56
|
+
* import { applySecurityHeaders } from '@huloglobal/vendure-licence-sdk';
|
|
57
|
+
* applySecurityHeaders(res, { strict: true });
|
|
58
|
+
*
|
|
59
|
+
* `strict` adds a tight Content-Security-Policy suitable for JSON / API
|
|
60
|
+
* endpoints that never embed third-party content. For HTML responses
|
|
61
|
+
* that need styling / scripts, leave `strict` off and set your own CSP.
|
|
62
|
+
*/
|
|
63
|
+
export declare function applySecurityHeaders(res: {
|
|
64
|
+
setHeader: (name: string, value: string) => unknown;
|
|
65
|
+
}, opts?: {
|
|
66
|
+
strict?: boolean;
|
|
67
|
+
}): void;
|
|
68
|
+
/**
|
|
69
|
+
* Returns `true` only when `url` parses as an http(s) URL and its
|
|
70
|
+
* hostname matches one of `allowedDomains`. Wildcard suffixes are
|
|
71
|
+
* supported — `*.example.com` matches `foo.example.com` and
|
|
72
|
+
* `bar.foo.example.com` but not `example.com`.
|
|
73
|
+
*
|
|
74
|
+
* Empty `allowedDomains` means "allow any http(s) URL" — the function
|
|
75
|
+
* still rejects `javascript:`, `data:`, `file:` and similar.
|
|
76
|
+
*/
|
|
77
|
+
export declare function isUrlOnAllowlist(url: string | undefined | null, allowedDomains: string[]): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Hash an IP with a per-install salt so we can store it for unique-visitor
|
|
80
|
+
* counts without exposing the raw address. 32-char output (128 bits).
|
|
81
|
+
*/
|
|
82
|
+
export declare function hashIp(ip: string | null | undefined, salt: string): string | null;
|
|
83
|
+
/** Generate a URL-safe random token of N bytes (base64url-encoded). */
|
|
84
|
+
export declare function randomToken(bytes?: number): string;
|
|
85
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAsBA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC5B,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,iBAAiB,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAC5C,MAAM,EAAE,MAAM,GACf,OAAO,CAcT;AAID;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYlG;AAID,MAAM,WAAW,kBAAkB;IAC/B,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC;kEAC8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;gBAEnD,IAAI,EAAE,kBAAkB;IAMpC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,MAAU,GAAG,OAAO;IA4B7C,OAAO,CAAC,QAAQ;IAShB,2CAA2C;IAC3C,KAAK,IAAI,IAAI;CAChB;AAID;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE;IAAE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;CAAE,EAAE,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,IAAI,CASxI;AAID;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAuBlG;AAID;;;GAGG;AACH,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGjF;AAID,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,KAAK,GAAE,MAAW,GAAG,MAAM,CAEtD"}
|
package/dist/security.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimiter = void 0;
|
|
4
|
+
exports.verifyHmacSha256 = verifyHmacSha256;
|
|
5
|
+
exports.signValue = signValue;
|
|
6
|
+
exports.verifySignedValue = verifySignedValue;
|
|
7
|
+
exports.applySecurityHeaders = applySecurityHeaders;
|
|
8
|
+
exports.isUrlOnAllowlist = isUrlOnAllowlist;
|
|
9
|
+
exports.hashIp = hashIp;
|
|
10
|
+
exports.randomToken = randomToken;
|
|
11
|
+
/**
|
|
12
|
+
* Shared security primitives for HULO Vendure plugins.
|
|
13
|
+
*
|
|
14
|
+
* - HMAC-SHA256 verification (timing-safe) for webhooks
|
|
15
|
+
* - Signed-value helpers (sign / verify) — used to seal cookies,
|
|
16
|
+
* query parameters and any other untrusted-channel value
|
|
17
|
+
* - In-memory token-bucket rate limiter keyed by an arbitrary string
|
|
18
|
+
* - Recommended security-headers helper for Express responses
|
|
19
|
+
* - URL allowlist verifier — defends the click redirector from being
|
|
20
|
+
* used as an open redirector
|
|
21
|
+
* - One-shot IP hashing helper (sha256 with a per-install salt)
|
|
22
|
+
*
|
|
23
|
+
* All helpers are dependency-free (Node built-ins only) and run in
|
|
24
|
+
* sub-millisecond time on a modern CPU. They never throw — errors bubble
|
|
25
|
+
* out as `false` / `null` returns so the calling code never crashes a
|
|
26
|
+
* request on a hostile input.
|
|
27
|
+
*/
|
|
28
|
+
const crypto_1 = require("crypto");
|
|
29
|
+
const url_1 = require("url");
|
|
30
|
+
// ── HMAC ────────────────────────────────────────────────────────────────
|
|
31
|
+
/**
|
|
32
|
+
* Verify an HMAC-SHA256 hex signature against `body` using `secret`.
|
|
33
|
+
*
|
|
34
|
+
* const ok = verifyHmacSha256(req.rawBody, req.header('x-signature'), secret);
|
|
35
|
+
*
|
|
36
|
+
* Constant-time comparison so the signature can't be brute-forced via
|
|
37
|
+
* timing. Returns `false` on any malformed input.
|
|
38
|
+
*/
|
|
39
|
+
function verifyHmacSha256(body, providedSignature, secret) {
|
|
40
|
+
if (!secret || !providedSignature)
|
|
41
|
+
return false;
|
|
42
|
+
try {
|
|
43
|
+
// Tolerate both bare hex and the `sha256=<hex>` GitHub-style prefix.
|
|
44
|
+
const provided = providedSignature.replace(/^sha256=/, '').trim().toLowerCase();
|
|
45
|
+
if (!/^[a-f0-9]{64}$/.test(provided))
|
|
46
|
+
return false;
|
|
47
|
+
const expected = (0, crypto_1.createHmac)('sha256', secret).update(body).digest('hex');
|
|
48
|
+
const a = Buffer.from(expected, 'utf8');
|
|
49
|
+
const b = Buffer.from(provided, 'utf8');
|
|
50
|
+
if (a.length !== b.length)
|
|
51
|
+
return false;
|
|
52
|
+
return (0, crypto_1.timingSafeEqual)(a, b);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// ── Signed values ──────────────────────────────────────────────────────
|
|
59
|
+
/**
|
|
60
|
+
* Append a short HMAC tag to a value so we can detect tampering when the
|
|
61
|
+
* value round-trips through a cookie / URL parameter / form field.
|
|
62
|
+
*
|
|
63
|
+
* const signed = signValue('visitor-abc', SECRET);
|
|
64
|
+
* // -> 'visitor-abc.QRwy3...'
|
|
65
|
+
*
|
|
66
|
+
* The tag is 16 hex characters (64 bits) — enough to defeat brute-force
|
|
67
|
+
* for any value we sign here, while keeping cookies short.
|
|
68
|
+
*/
|
|
69
|
+
function signValue(value, secret) {
|
|
70
|
+
const tag = (0, crypto_1.createHmac)('sha256', secret).update(value).digest('hex').slice(0, 16);
|
|
71
|
+
return `${value}.${tag}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Verify the tag and return the original value, or `null` if the tag
|
|
75
|
+
* doesn't match or the format is malformed.
|
|
76
|
+
*/
|
|
77
|
+
function verifySignedValue(signed, secret) {
|
|
78
|
+
if (!signed || typeof signed !== 'string')
|
|
79
|
+
return null;
|
|
80
|
+
const lastDot = signed.lastIndexOf('.');
|
|
81
|
+
if (lastDot <= 0 || lastDot >= signed.length - 1)
|
|
82
|
+
return null;
|
|
83
|
+
const value = signed.slice(0, lastDot);
|
|
84
|
+
const tag = signed.slice(lastDot + 1).toLowerCase();
|
|
85
|
+
if (!/^[a-f0-9]{16}$/.test(tag))
|
|
86
|
+
return null;
|
|
87
|
+
const expected = (0, crypto_1.createHmac)('sha256', secret).update(value).digest('hex').slice(0, 16);
|
|
88
|
+
const a = Buffer.from(expected, 'utf8');
|
|
89
|
+
const b = Buffer.from(tag, 'utf8');
|
|
90
|
+
if (a.length !== b.length)
|
|
91
|
+
return null;
|
|
92
|
+
return (0, crypto_1.timingSafeEqual)(a, b) ? value : null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Token-bucket rate limiter with an LRU cap on the keyspace so we can't
|
|
96
|
+
* be DoS'd by an attacker pushing keys to grow memory. `allow(key)`
|
|
97
|
+
* returns `true` if the request fits in the bucket. Keys are arbitrary
|
|
98
|
+
* strings — typically `${ip}|${route}`.
|
|
99
|
+
*/
|
|
100
|
+
class RateLimiter {
|
|
101
|
+
constructor(opts) {
|
|
102
|
+
/** Map<key, [tokensRemaining, lastRefillEpochMs]>. JS Map preserves
|
|
103
|
+
* insertion order, which is good enough LRU for this use. */
|
|
104
|
+
this.buckets = new Map();
|
|
105
|
+
this.capacity = Math.max(1, opts.capacity);
|
|
106
|
+
this.windowMs = Math.max(1, opts.windowMs);
|
|
107
|
+
this.maxKeys = Math.max(100, opts.maxKeys || 10000);
|
|
108
|
+
}
|
|
109
|
+
allow(key, cost = 1) {
|
|
110
|
+
if (!key)
|
|
111
|
+
return true;
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
const ratePerMs = this.capacity / this.windowMs;
|
|
114
|
+
let entry = this.buckets.get(key);
|
|
115
|
+
if (entry) {
|
|
116
|
+
const [tokens, last] = entry;
|
|
117
|
+
const refilled = Math.min(this.capacity, tokens + (now - last) * ratePerMs);
|
|
118
|
+
if (refilled < cost) {
|
|
119
|
+
// Update last so timing leaks aren't useful.
|
|
120
|
+
this.buckets.set(key, [refilled, now]);
|
|
121
|
+
this.touchLru(key);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
this.buckets.set(key, [refilled - cost, now]);
|
|
125
|
+
this.touchLru(key);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
// New key — start with a full bucket minus this request.
|
|
129
|
+
if (this.buckets.size >= this.maxKeys) {
|
|
130
|
+
// Drop the oldest entry (Map.keys() is insertion-ordered).
|
|
131
|
+
const oldest = this.buckets.keys().next().value;
|
|
132
|
+
if (oldest !== undefined)
|
|
133
|
+
this.buckets.delete(oldest);
|
|
134
|
+
}
|
|
135
|
+
this.buckets.set(key, [this.capacity - cost, now]);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
touchLru(key) {
|
|
139
|
+
// Move to "most recently used" by re-inserting.
|
|
140
|
+
const entry = this.buckets.get(key);
|
|
141
|
+
if (entry) {
|
|
142
|
+
this.buckets.delete(key);
|
|
143
|
+
this.buckets.set(key, entry);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** Reset all buckets — useful in tests. */
|
|
147
|
+
clear() { this.buckets.clear(); }
|
|
148
|
+
}
|
|
149
|
+
exports.RateLimiter = RateLimiter;
|
|
150
|
+
// ── Security headers ───────────────────────────────────────────────────
|
|
151
|
+
/**
|
|
152
|
+
* Apply a recommended security-headers baseline to an Express response.
|
|
153
|
+
*
|
|
154
|
+
* import { applySecurityHeaders } from '@huloglobal/vendure-licence-sdk';
|
|
155
|
+
* applySecurityHeaders(res, { strict: true });
|
|
156
|
+
*
|
|
157
|
+
* `strict` adds a tight Content-Security-Policy suitable for JSON / API
|
|
158
|
+
* endpoints that never embed third-party content. For HTML responses
|
|
159
|
+
* that need styling / scripts, leave `strict` off and set your own CSP.
|
|
160
|
+
*/
|
|
161
|
+
function applySecurityHeaders(res, opts = {}) {
|
|
162
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
163
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
164
|
+
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
165
|
+
res.setHeader('Permissions-Policy', 'geolocation=(), camera=(), microphone=(), payment=()');
|
|
166
|
+
res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
|
|
167
|
+
if (opts.strict) {
|
|
168
|
+
res.setHeader('Content-Security-Policy', "default-src 'none'; frame-ancestors 'none'");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// ── URL allowlist ──────────────────────────────────────────────────────
|
|
172
|
+
/**
|
|
173
|
+
* Returns `true` only when `url` parses as an http(s) URL and its
|
|
174
|
+
* hostname matches one of `allowedDomains`. Wildcard suffixes are
|
|
175
|
+
* supported — `*.example.com` matches `foo.example.com` and
|
|
176
|
+
* `bar.foo.example.com` but not `example.com`.
|
|
177
|
+
*
|
|
178
|
+
* Empty `allowedDomains` means "allow any http(s) URL" — the function
|
|
179
|
+
* still rejects `javascript:`, `data:`, `file:` and similar.
|
|
180
|
+
*/
|
|
181
|
+
function isUrlOnAllowlist(url, allowedDomains) {
|
|
182
|
+
if (!url || typeof url !== 'string')
|
|
183
|
+
return false;
|
|
184
|
+
let parsed;
|
|
185
|
+
try {
|
|
186
|
+
parsed = new url_1.URL(url);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
192
|
+
return false;
|
|
193
|
+
if (!(allowedDomains === null || allowedDomains === void 0 ? void 0 : allowedDomains.length))
|
|
194
|
+
return true;
|
|
195
|
+
const host = parsed.hostname.toLowerCase();
|
|
196
|
+
for (const allowed of allowedDomains) {
|
|
197
|
+
const norm = allowed.trim().toLowerCase();
|
|
198
|
+
if (!norm)
|
|
199
|
+
continue;
|
|
200
|
+
if (norm.startsWith('*.')) {
|
|
201
|
+
const suffix = norm.slice(2);
|
|
202
|
+
if (host === suffix)
|
|
203
|
+
continue; // *.example.com must NOT match example.com
|
|
204
|
+
if (host.endsWith('.' + suffix))
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
else if (host === norm) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
// ── IP hashing ─────────────────────────────────────────────────────────
|
|
214
|
+
/**
|
|
215
|
+
* Hash an IP with a per-install salt so we can store it for unique-visitor
|
|
216
|
+
* counts without exposing the raw address. 32-char output (128 bits).
|
|
217
|
+
*/
|
|
218
|
+
function hashIp(ip, salt) {
|
|
219
|
+
if (!ip)
|
|
220
|
+
return null;
|
|
221
|
+
return (0, crypto_1.createHash)('sha256').update(salt + '|' + ip).digest('hex').slice(0, 32);
|
|
222
|
+
}
|
|
223
|
+
// ── Random ─────────────────────────────────────────────────────────────
|
|
224
|
+
/** Generate a URL-safe random token of N bytes (base64url-encoded). */
|
|
225
|
+
function randomToken(bytes = 32) {
|
|
226
|
+
return (0, crypto_1.randomBytes)(bytes).toString('base64url');
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface UpdateStatus {
|
|
2
|
+
packageName: string;
|
|
3
|
+
current: string;
|
|
4
|
+
latest: string | null;
|
|
5
|
+
updateAvailable: boolean;
|
|
6
|
+
/** A "major"-level update is one where the first non-zero version
|
|
7
|
+
* component changes (semver-friendly for 0.x packages). */
|
|
8
|
+
isMajor: boolean;
|
|
9
|
+
lastCheckedAt: Date | null;
|
|
10
|
+
lastError: string | null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Polls the public npm registry for the latest version of a plugin's
|
|
14
|
+
* package and exposes a small status object. Each plugin instantiates
|
|
15
|
+
* one UpdateChecker at boot and surfaces its status via its `/status`
|
|
16
|
+
* endpoint — the admin UI then shows an "update available" banner.
|
|
17
|
+
*
|
|
18
|
+
* Failures (network, 404) keep the previous cached `latest` value in
|
|
19
|
+
* memory so a brief npm outage doesn't make the dashboard misreport.
|
|
20
|
+
* The cache is wiped only when a fresh value arrives.
|
|
21
|
+
*/
|
|
22
|
+
export declare class UpdateChecker {
|
|
23
|
+
private readonly packageName;
|
|
24
|
+
private readonly currentVersion;
|
|
25
|
+
private readonly pollMs;
|
|
26
|
+
private readonly registryUrl;
|
|
27
|
+
private latest;
|
|
28
|
+
private timer;
|
|
29
|
+
private lastCheckedAt;
|
|
30
|
+
private lastError;
|
|
31
|
+
constructor(packageName: string, currentVersion: string, pollMs?: number, // 24h
|
|
32
|
+
registryUrl?: string);
|
|
33
|
+
/** Start the background poll. Idempotent. */
|
|
34
|
+
start(): void;
|
|
35
|
+
stop(): void;
|
|
36
|
+
/** Force a fresh check. Returns the new status. */
|
|
37
|
+
refresh(): Promise<UpdateStatus>;
|
|
38
|
+
getStatus(): UpdateStatus;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=update-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-check.d.ts","sourceRoot":"","sources":["../src/update-check.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;IACzB;gEAC4D;IAC5D,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,qBAAa,aAAa;IAOlB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAThC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,SAAS,CAAuB;gBAGnB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,MAAM,GAAE,MAA4B,EAAE,MAAM;IAC5C,WAAW,GAAE,MAAqC;IAGvE,6CAA6C;IAC7C,KAAK,IAAI,IAAI;IAQb,IAAI,IAAI,IAAI;IAOZ,mDAAmD;IAC7C,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC;IAkCtC,SAAS,IAAI,YAAY;CAe5B"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UpdateChecker = void 0;
|
|
4
|
+
const core_1 = require("@vendure/core");
|
|
5
|
+
const loggerCtx = 'LicenceSdk:UpdateCheck';
|
|
6
|
+
/**
|
|
7
|
+
* Polls the public npm registry for the latest version of a plugin's
|
|
8
|
+
* package and exposes a small status object. Each plugin instantiates
|
|
9
|
+
* one UpdateChecker at boot and surfaces its status via its `/status`
|
|
10
|
+
* endpoint — the admin UI then shows an "update available" banner.
|
|
11
|
+
*
|
|
12
|
+
* Failures (network, 404) keep the previous cached `latest` value in
|
|
13
|
+
* memory so a brief npm outage doesn't make the dashboard misreport.
|
|
14
|
+
* The cache is wiped only when a fresh value arrives.
|
|
15
|
+
*/
|
|
16
|
+
class UpdateChecker {
|
|
17
|
+
constructor(packageName, currentVersion, pollMs = 24 * 60 * 60 * 1000, // 24h
|
|
18
|
+
registryUrl = 'https://registry.npmjs.org') {
|
|
19
|
+
this.packageName = packageName;
|
|
20
|
+
this.currentVersion = currentVersion;
|
|
21
|
+
this.pollMs = pollMs;
|
|
22
|
+
this.registryUrl = registryUrl;
|
|
23
|
+
this.latest = null;
|
|
24
|
+
this.timer = null;
|
|
25
|
+
this.lastCheckedAt = null;
|
|
26
|
+
this.lastError = null;
|
|
27
|
+
}
|
|
28
|
+
/** Start the background poll. Idempotent. */
|
|
29
|
+
start() {
|
|
30
|
+
if (this.timer)
|
|
31
|
+
return;
|
|
32
|
+
// First check runs 30s after boot so we don't slow down startup.
|
|
33
|
+
setTimeout(() => this.refresh().catch(() => undefined), 30000);
|
|
34
|
+
this.timer = setInterval(() => this.refresh().catch(() => undefined), this.pollMs);
|
|
35
|
+
if (typeof this.timer.unref === 'function')
|
|
36
|
+
this.timer.unref();
|
|
37
|
+
}
|
|
38
|
+
stop() {
|
|
39
|
+
if (this.timer) {
|
|
40
|
+
clearInterval(this.timer);
|
|
41
|
+
this.timer = null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Force a fresh check. Returns the new status. */
|
|
45
|
+
async refresh() {
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const t = setTimeout(() => controller.abort(), 8000);
|
|
48
|
+
try {
|
|
49
|
+
// The `latest` endpoint is small (a few hundred bytes) and
|
|
50
|
+
// cache-friendly — the registry returns a fresh dist-tags
|
|
51
|
+
// entry without the full package metadata payload.
|
|
52
|
+
const res = await fetch(`${this.registryUrl}/${encodeURIComponent(this.packageName)}/latest`, { signal: controller.signal, headers: { accept: 'application/json' } });
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
this.lastError = `npm registry ${res.status}`;
|
|
55
|
+
core_1.Logger.warn(`Update check for ${this.packageName} failed: ${this.lastError}`, loggerCtx);
|
|
56
|
+
return this.getStatus();
|
|
57
|
+
}
|
|
58
|
+
const body = await res.json();
|
|
59
|
+
const next = String((body === null || body === void 0 ? void 0 : body.version) || '').trim();
|
|
60
|
+
if (!next) {
|
|
61
|
+
this.lastError = 'no version in response';
|
|
62
|
+
return this.getStatus();
|
|
63
|
+
}
|
|
64
|
+
this.latest = next;
|
|
65
|
+
this.lastCheckedAt = new Date();
|
|
66
|
+
this.lastError = null;
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
this.lastError = (e === null || e === void 0 ? void 0 : e.message) || 'fetch failed';
|
|
70
|
+
core_1.Logger.warn(`Update check for ${this.packageName} failed: ${this.lastError}`, loggerCtx);
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
clearTimeout(t);
|
|
74
|
+
}
|
|
75
|
+
return this.getStatus();
|
|
76
|
+
}
|
|
77
|
+
getStatus() {
|
|
78
|
+
const updateAvailable = !!this.latest && compareVersions(this.latest, this.currentVersion) > 0;
|
|
79
|
+
const isMajor = updateAvailable
|
|
80
|
+
? isMajorBump(this.currentVersion, this.latest)
|
|
81
|
+
: false;
|
|
82
|
+
return {
|
|
83
|
+
packageName: this.packageName,
|
|
84
|
+
current: this.currentVersion,
|
|
85
|
+
latest: this.latest,
|
|
86
|
+
updateAvailable,
|
|
87
|
+
isMajor,
|
|
88
|
+
lastCheckedAt: this.lastCheckedAt,
|
|
89
|
+
lastError: this.lastError,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.UpdateChecker = UpdateChecker;
|
|
94
|
+
/**
|
|
95
|
+
* Tiny semver-ish compare. Treats versions as dot-separated number
|
|
96
|
+
* components; suffix tags (`-rc.1`, `-beta`) sort before the release.
|
|
97
|
+
* Sufficient for our case — both sides come from npm dist-tags.
|
|
98
|
+
*/
|
|
99
|
+
function compareVersions(a, b) {
|
|
100
|
+
const [ah, at] = splitTag(a);
|
|
101
|
+
const [bh, bt] = splitTag(b);
|
|
102
|
+
const ap = ah.split('.').map(n => parseInt(n, 10) || 0);
|
|
103
|
+
const bp = bh.split('.').map(n => parseInt(n, 10) || 0);
|
|
104
|
+
const len = Math.max(ap.length, bp.length);
|
|
105
|
+
for (let i = 0; i < len; i++) {
|
|
106
|
+
const av = ap[i] || 0;
|
|
107
|
+
const bv = bp[i] || 0;
|
|
108
|
+
if (av !== bv)
|
|
109
|
+
return av - bv;
|
|
110
|
+
}
|
|
111
|
+
// Pre-release sorts BEFORE release (0.2.0-rc.1 < 0.2.0).
|
|
112
|
+
if (at && !bt)
|
|
113
|
+
return -1;
|
|
114
|
+
if (!at && bt)
|
|
115
|
+
return 1;
|
|
116
|
+
if (at && bt)
|
|
117
|
+
return at.localeCompare(bt);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
function splitTag(v) {
|
|
121
|
+
const dash = v.indexOf('-');
|
|
122
|
+
return dash === -1 ? [v, ''] : [v.slice(0, dash), v.slice(dash + 1)];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* "Major bump" for the dashboard banner — uses the first non-zero
|
|
126
|
+
* version component as the major identifier (so 0.2.x → 0.3.x is major
|
|
127
|
+
* for 0.x packages, where the user expects breaking changes in 0.y
|
|
128
|
+
* bumps).
|
|
129
|
+
*/
|
|
130
|
+
function isMajorBump(current, next) {
|
|
131
|
+
const cp = current.split('.').map(n => parseInt(n, 10) || 0);
|
|
132
|
+
const np = next.split('.').map(n => parseInt(n, 10) || 0);
|
|
133
|
+
// Find the first non-zero index of current.
|
|
134
|
+
let i = 0;
|
|
135
|
+
while (i < cp.length && cp[i] === 0)
|
|
136
|
+
i++;
|
|
137
|
+
return (cp[i] || 0) !== (np[i] || 0);
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=update-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-check.js","sourceRoot":"","sources":["../src/update-check.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huloglobal/vendure-licence-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Licence validation helpers for HULO Vendure plugins. JWT verification, revocation polling and an admin status check used by paid HULO plugins to confirm the host installation is licensed.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Wayne Garrison <wayne@garrison.me.uk>",
|