@tumbaland/backend-core 1.27.0 → 1.28.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/dist/entitlements/index.d.ts +1 -1
- package/dist/entitlements/index.d.ts.map +1 -1
- package/dist/entitlements/index.js +2 -1
- package/dist/entitlements/index.js.map +1 -1
- package/dist/entitlements/usage.d.ts +21 -0
- package/dist/entitlements/usage.d.ts.map +1 -1
- package/dist/entitlements/usage.js +36 -0
- package/dist/entitlements/usage.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware/authMiddleware.d.ts +14 -0
- package/dist/middleware/authMiddleware.d.ts.map +1 -1
- package/dist/middleware/authMiddleware.js +29 -0
- package/dist/middleware/authMiddleware.js.map +1 -1
- package/package.json +1 -1
- package/src/entitlements/index.ts +1 -1
- package/src/entitlements/usage.test.ts +48 -1
- package/src/entitlements/usage.ts +44 -0
- package/src/index.ts +1 -1
- package/src/middleware/authMiddleware.test.ts +70 -1
- package/src/middleware/authMiddleware.ts +29 -1
|
@@ -3,7 +3,7 @@ export type { MeterKey, FeatureKey, MeterPeriod, MeterDefinition, FeatureDefinit
|
|
|
3
3
|
export type { Entitlements } from './types';
|
|
4
4
|
export { UsageMeter, currentPeriod } from './UsageMeter';
|
|
5
5
|
export type { IUsageMeter } from './UsageMeter';
|
|
6
|
-
export { getUsage, getUsageSnapshot, checkQuota, consumeQuota, releaseQuota, setUsage } from './usage';
|
|
6
|
+
export { getUsage, getUsageSnapshot, checkQuota, consumeQuota, recordUsage, releaseQuota, setUsage } from './usage';
|
|
7
7
|
export type { QuotaRequest } from './usage';
|
|
8
8
|
export { reconcileUsageMeters, runUsageReconciliation, RECONCILED_METERS } from './reconcile';
|
|
9
9
|
export type { ReconcileOptions, ReconcileReport, MeterChange, OrphanedMeter } from './reconcile';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/entitlements/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,aAAa,EACb,UAAU,EACV,WAAW,EACX,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,WAAW,EACX,UAAU,EACV,YAAY,EACZ,UAAU,EACX,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,QAAQ,EACR,UAAU,EACV,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EAClB,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/entitlements/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,aAAa,EACb,UAAU,EACV,WAAW,EACX,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,WAAW,EACX,UAAU,EACV,YAAY,EACZ,UAAU,EACX,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,QAAQ,EACR,UAAU,EACV,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EAClB,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACpH,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC9F,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjG,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAE3F,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClG,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.retentionFloor = exports.requireQuota = exports.requireEntitlement = exports.loadEntitlements = exports.clearEntitlementsCache = exports.invalidateEntitlements = exports.getEntitlements = exports.RECONCILED_METERS = exports.runUsageReconciliation = exports.reconcileUsageMeters = exports.setUsage = exports.releaseQuota = exports.consumeQuota = exports.checkQuota = exports.getUsageSnapshot = exports.getUsage = exports.currentPeriod = exports.UsageMeter = exports.fitsWithin = exports.isFeatureKey = exports.isMeterKey = exports.meterPeriod = exports.normalizeLimits = exports.resolveLimits = exports.ENTITLEMENT_CATALOG = exports.DEFAULT_PLAN_LIMITS = exports.FEATURE_DEFINITIONS = exports.METER_DEFINITIONS = exports.FEATURE_KEYS = exports.METER_KEYS = exports.applyLimits = exports.STAFF_PLAN = exports.FALLBACK_PLAN = exports.UNLIMITED = void 0;
|
|
3
|
+
exports.retentionFloor = exports.requireQuota = exports.requireEntitlement = exports.loadEntitlements = exports.clearEntitlementsCache = exports.invalidateEntitlements = exports.getEntitlements = exports.RECONCILED_METERS = exports.runUsageReconciliation = exports.reconcileUsageMeters = exports.setUsage = exports.releaseQuota = exports.recordUsage = exports.consumeQuota = exports.checkQuota = exports.getUsageSnapshot = exports.getUsage = exports.currentPeriod = exports.UsageMeter = exports.fitsWithin = exports.isFeatureKey = exports.isMeterKey = exports.meterPeriod = exports.normalizeLimits = exports.resolveLimits = exports.ENTITLEMENT_CATALOG = exports.DEFAULT_PLAN_LIMITS = exports.FEATURE_DEFINITIONS = exports.METER_DEFINITIONS = exports.FEATURE_KEYS = exports.METER_KEYS = exports.applyLimits = exports.STAFF_PLAN = exports.FALLBACK_PLAN = exports.UNLIMITED = void 0;
|
|
4
4
|
var definitions_1 = require("./definitions");
|
|
5
5
|
Object.defineProperty(exports, "UNLIMITED", { enumerable: true, get: function () { return definitions_1.UNLIMITED; } });
|
|
6
6
|
Object.defineProperty(exports, "FALLBACK_PLAN", { enumerable: true, get: function () { return definitions_1.FALLBACK_PLAN; } });
|
|
@@ -26,6 +26,7 @@ Object.defineProperty(exports, "getUsage", { enumerable: true, get: function ()
|
|
|
26
26
|
Object.defineProperty(exports, "getUsageSnapshot", { enumerable: true, get: function () { return usage_1.getUsageSnapshot; } });
|
|
27
27
|
Object.defineProperty(exports, "checkQuota", { enumerable: true, get: function () { return usage_1.checkQuota; } });
|
|
28
28
|
Object.defineProperty(exports, "consumeQuota", { enumerable: true, get: function () { return usage_1.consumeQuota; } });
|
|
29
|
+
Object.defineProperty(exports, "recordUsage", { enumerable: true, get: function () { return usage_1.recordUsage; } });
|
|
29
30
|
Object.defineProperty(exports, "releaseQuota", { enumerable: true, get: function () { return usage_1.releaseQuota; } });
|
|
30
31
|
Object.defineProperty(exports, "setUsage", { enumerable: true, get: function () { return usage_1.setUsage; } });
|
|
31
32
|
var reconcile_1 = require("./reconcile");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/entitlements/index.ts"],"names":[],"mappings":";;;AAAA,6CAiBuB;AAhBrB,wGAAA,SAAS,OAAA;AACT,4GAAA,aAAa,OAAA;AACb,yGAAA,UAAU,OAAA;AACV,0GAAA,WAAW,OAAA;AACX,yGAAA,UAAU,OAAA;AACV,2GAAA,YAAY,OAAA;AACZ,gHAAA,iBAAiB,OAAA;AACjB,kHAAA,mBAAmB,OAAA;AACnB,kHAAA,mBAAmB,OAAA;AACnB,kHAAA,mBAAmB,OAAA;AACnB,4GAAA,aAAa,OAAA;AACb,8GAAA,eAAe,OAAA;AACf,0GAAA,WAAW,OAAA;AACX,yGAAA,UAAU,OAAA;AACV,2GAAA,YAAY,OAAA;AACZ,yGAAA,UAAU,OAAA;AAcZ,2CAAyD;AAAhD,wGAAA,UAAU,OAAA;AAAE,2GAAA,aAAa,OAAA;AAElC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/entitlements/index.ts"],"names":[],"mappings":";;;AAAA,6CAiBuB;AAhBrB,wGAAA,SAAS,OAAA;AACT,4GAAA,aAAa,OAAA;AACb,yGAAA,UAAU,OAAA;AACV,0GAAA,WAAW,OAAA;AACX,yGAAA,UAAU,OAAA;AACV,2GAAA,YAAY,OAAA;AACZ,gHAAA,iBAAiB,OAAA;AACjB,kHAAA,mBAAmB,OAAA;AACnB,kHAAA,mBAAmB,OAAA;AACnB,kHAAA,mBAAmB,OAAA;AACnB,4GAAA,aAAa,OAAA;AACb,8GAAA,eAAe,OAAA;AACf,0GAAA,WAAW,OAAA;AACX,yGAAA,UAAU,OAAA;AACV,2GAAA,YAAY,OAAA;AACZ,yGAAA,UAAU,OAAA;AAcZ,2CAAyD;AAAhD,wGAAA,UAAU,OAAA;AAAE,2GAAA,aAAa,OAAA;AAElC,iCAAoH;AAA3G,iGAAA,QAAQ,OAAA;AAAE,yGAAA,gBAAgB,OAAA;AAAE,mGAAA,UAAU,OAAA;AAAE,qGAAA,YAAY,OAAA;AAAE,oGAAA,WAAW,OAAA;AAAE,qGAAA,YAAY,OAAA;AAAE,iGAAA,QAAQ,OAAA;AAGlG,yCAA8F;AAArF,iHAAA,oBAAoB,OAAA;AAAE,mHAAA,sBAAsB,OAAA;AAAE,8GAAA,iBAAiB,OAAA;AAGxE,mCAA2F;AAAlF,yGAAA,eAAe,OAAA;AAAE,gHAAA,sBAAsB,OAAA;AAAE,gHAAA,sBAAsB,OAAA;AAExE,2CAAkG;AAAzF,8GAAA,gBAAgB,OAAA;AAAE,gHAAA,kBAAkB,OAAA;AAAE,0GAAA,YAAY,OAAA;AAAE,4GAAA,cAAc,OAAA"}
|
|
@@ -53,4 +53,25 @@ export declare function releaseQuota(subjectId: string, meter: MeterKey, amount?
|
|
|
53
53
|
* for periodic reconciliation against the source of truth.
|
|
54
54
|
*/
|
|
55
55
|
export declare function setUsage(subjectId: string, meter: MeterKey, count: number, now?: Date): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Record usage that has already happened, without a limit check.
|
|
58
|
+
*
|
|
59
|
+
* The storage gate runs on the *signed-URL request* — the only point where a
|
|
60
|
+
* file's size is known before its bytes are written — so by the time the photo
|
|
61
|
+
* record is created the object is already in MinIO. Neither of the existing
|
|
62
|
+
* primitives fits that moment: `checkQuota` records nothing, and `consumeQuota`
|
|
63
|
+
* would reject and roll back, leaving the meter understating bytes that are
|
|
64
|
+
* genuinely stored while the user is refused a record for storage they have
|
|
65
|
+
* already paid for.
|
|
66
|
+
*
|
|
67
|
+
* So this increments past the limit deliberately. A cumulative meter can only
|
|
68
|
+
* be pushed over by a race or a stale entitlement, both bounded by one upload,
|
|
69
|
+
* and the gate then refuses the *next* one — which is the correct behaviour:
|
|
70
|
+
* the ceiling stops new writes, it does not un-store bytes.
|
|
71
|
+
*
|
|
72
|
+
* Never throws, for the same reason `releaseQuota` does not: the resource
|
|
73
|
+
* exists, the caller is on its success path, and a failed counter update is
|
|
74
|
+
* repaired by the nightly reconciliation this meter is derived for.
|
|
75
|
+
*/
|
|
76
|
+
export declare function recordUsage(subjectId: string, meter: MeterKey, amount?: number, now?: Date): Promise<void>;
|
|
56
77
|
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/entitlements/usage.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAqC,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,CAAC;IAChB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,YAAY,CAAC;IAC3B,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,sDAAsD;AACtD,wBAAsB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAO9F;AAED,sFAAsF;AACtF,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAYvG;AAiCD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CASjE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBnE;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,QAAQ,EACf,MAAM,SAAI,EACV,GAAG,CAAC,EAAE,IAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,IAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/entitlements/usage.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAqC,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,CAAC;IAChB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,YAAY,CAAC;IAC3B,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,sDAAsD;AACtD,wBAAsB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAO9F;AAED,sFAAsF;AACtF,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAYvG;AAiCD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CASjE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBnE;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,QAAQ,EACf,MAAM,SAAI,EACV,GAAG,CAAC,EAAE,IAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,IAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,QAAQ,EACf,MAAM,SAAI,EACV,GAAG,CAAC,EAAE,IAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAiBf"}
|
|
@@ -9,6 +9,7 @@ exports.checkQuota = checkQuota;
|
|
|
9
9
|
exports.consumeQuota = consumeQuota;
|
|
10
10
|
exports.releaseQuota = releaseQuota;
|
|
11
11
|
exports.setUsage = setUsage;
|
|
12
|
+
exports.recordUsage = recordUsage;
|
|
12
13
|
const logger_1 = __importDefault(require("../logging/logger"));
|
|
13
14
|
const HttpError_1 = require("../errors/HttpError");
|
|
14
15
|
const UsageMeter_1 = require("./UsageMeter");
|
|
@@ -147,4 +148,39 @@ async function releaseQuota(subjectId, meter, amount = 1, now) {
|
|
|
147
148
|
async function setUsage(subjectId, meter, count, now) {
|
|
148
149
|
await UsageMeter_1.UsageMeter.updateOne({ subjectId: subjectId.toLowerCase(), meter, period: (0, UsageMeter_1.currentPeriod)(meter, now) }, { $set: { count: Math.max(0, Math.floor(count)) } }, { upsert: true, setDefaultsOnInsert: true });
|
|
149
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Record usage that has already happened, without a limit check.
|
|
153
|
+
*
|
|
154
|
+
* The storage gate runs on the *signed-URL request* — the only point where a
|
|
155
|
+
* file's size is known before its bytes are written — so by the time the photo
|
|
156
|
+
* record is created the object is already in MinIO. Neither of the existing
|
|
157
|
+
* primitives fits that moment: `checkQuota` records nothing, and `consumeQuota`
|
|
158
|
+
* would reject and roll back, leaving the meter understating bytes that are
|
|
159
|
+
* genuinely stored while the user is refused a record for storage they have
|
|
160
|
+
* already paid for.
|
|
161
|
+
*
|
|
162
|
+
* So this increments past the limit deliberately. A cumulative meter can only
|
|
163
|
+
* be pushed over by a race or a stale entitlement, both bounded by one upload,
|
|
164
|
+
* and the gate then refuses the *next* one — which is the correct behaviour:
|
|
165
|
+
* the ceiling stops new writes, it does not un-store bytes.
|
|
166
|
+
*
|
|
167
|
+
* Never throws, for the same reason `releaseQuota` does not: the resource
|
|
168
|
+
* exists, the caller is on its success path, and a failed counter update is
|
|
169
|
+
* repaired by the nightly reconciliation this meter is derived for.
|
|
170
|
+
*/
|
|
171
|
+
async function recordUsage(subjectId, meter, amount = 1, now) {
|
|
172
|
+
if (amount <= 0)
|
|
173
|
+
return;
|
|
174
|
+
try {
|
|
175
|
+
await UsageMeter_1.UsageMeter.updateOne({ subjectId: subjectId.toLowerCase(), meter, period: (0, UsageMeter_1.currentPeriod)(meter, now) }, { $inc: { count: amount } }, { upsert: true, setDefaultsOnInsert: true });
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
logger_1.default.warn('Failed to record usage', {
|
|
179
|
+
subjectId,
|
|
180
|
+
meter,
|
|
181
|
+
amount,
|
|
182
|
+
error: err?.message
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
150
186
|
//# sourceMappingURL=usage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/entitlements/usage.ts"],"names":[],"mappings":";;;;;AAkBA,4BAOC;AAGD,4CAYC;AAyCD,gCASC;AAgBD,oCAmBC;AAUD,oCAyBC;AAOD,4BAWC;
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/entitlements/usage.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { HttpError, BadRequestError, UnauthorizedError, ForbiddenError, NotFound
|
|
|
12
12
|
export type { PaymentRequiredDetails } from './errors/HttpError';
|
|
13
13
|
export * from './entitlements';
|
|
14
14
|
export * from './groups';
|
|
15
|
-
export { authenticateToken } from './middleware/authMiddleware';
|
|
15
|
+
export { authenticateToken, optionalAuth } from './middleware/authMiddleware';
|
|
16
16
|
export { requireInternalServiceToken, internalServiceTokenBypass, allowUserOrInternalService, verifyInternalToken, INTERNAL_TOKEN_HEADER, SERVICE_ID_HEADER } from './middleware/internalServiceAuth';
|
|
17
17
|
export { createCorsMiddleware } from './middleware/corsMiddleware';
|
|
18
18
|
export { errorHandler } from './middleware/errorHandler';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,YAAY,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAG9D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,iBAAiB,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC7K,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAGjE,cAAc,gBAAgB,CAAC;AAG/B,cAAc,UAAU,CAAC;AAGzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,YAAY,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAG9D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,iBAAiB,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC7K,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAGjE,cAAc,gBAAgB,CAAC;AAG/B,cAAc,UAAU,CAAC;AAGzB,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC9E,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EAClB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC1G,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACnH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGjD,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7E,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAG9E,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EACf,QAAQ,EACT,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,UAAU,EACV,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,SAAS,EACT,UAAU,EACV,aAAa,EACb,kBAAkB,EACnB,MAAM,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -18,8 +18,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.
|
|
22
|
-
exports.extractSpanContext = exports.injectHeaders = exports.setSpanTag = exports.logToSpan = exports.createChildSpan = exports.tracingMiddleware = exports.startSpan = exports.getTracer = void 0;
|
|
21
|
+
exports.register = exports.businessMetrics = exports.databaseQueriesTotal = exports.databaseQueryDuration = exports.httpRequestsTotal = exports.httpRequestDuration = exports.metricsMiddleware = exports.canAccessResource = exports.buildAccessQuery = exports.sendError = exports.sendSuccess = exports.createApiResponse = exports.correlationMiddleware = exports.generateCorrelationId = exports.validate = exports.strictRateLimiter = exports.standardRateLimiter = exports.createRateLimiter = exports.securityHeaders = exports.simpleRequestLogger = exports.requestLoggerWithMetrics = exports.requestLogger = exports.errorHandler = exports.createCorsMiddleware = exports.SERVICE_ID_HEADER = exports.INTERNAL_TOKEN_HEADER = exports.verifyInternalToken = exports.allowUserOrInternalService = exports.internalServiceTokenBypass = exports.requireInternalServiceToken = exports.optionalAuth = exports.authenticateToken = exports.PaymentRequiredError = exports.TooManyRequestsError = exports.ConflictError = exports.NotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.BadRequestError = exports.HttpError = exports.requireEnv = exports.disconnectDB = exports.connectDB = exports.createHealthCheck = exports.metricsHandler = exports.healthLive = exports.healthCheck = exports.logger = exports.registerShutdown = exports.createBaseApp = void 0;
|
|
22
|
+
exports.extractSpanContext = exports.injectHeaders = exports.setSpanTag = exports.logToSpan = exports.createChildSpan = exports.tracingMiddleware = exports.startSpan = exports.getTracer = exports.initTracer = void 0;
|
|
23
23
|
// App bootstrap
|
|
24
24
|
var createBaseApp_1 = require("./app/createBaseApp");
|
|
25
25
|
Object.defineProperty(exports, "createBaseApp", { enumerable: true, get: function () { return createBaseApp_1.createBaseApp; } });
|
|
@@ -59,6 +59,7 @@ __exportStar(require("./groups"), exports);
|
|
|
59
59
|
// Middleware
|
|
60
60
|
var authMiddleware_1 = require("./middleware/authMiddleware");
|
|
61
61
|
Object.defineProperty(exports, "authenticateToken", { enumerable: true, get: function () { return authMiddleware_1.authenticateToken; } });
|
|
62
|
+
Object.defineProperty(exports, "optionalAuth", { enumerable: true, get: function () { return authMiddleware_1.optionalAuth; } });
|
|
62
63
|
var internalServiceAuth_1 = require("./middleware/internalServiceAuth");
|
|
63
64
|
Object.defineProperty(exports, "requireInternalServiceToken", { enumerable: true, get: function () { return internalServiceAuth_1.requireInternalServiceToken; } });
|
|
64
65
|
Object.defineProperty(exports, "internalServiceTokenBypass", { enumerable: true, get: function () { return internalServiceAuth_1.internalServiceTokenBypass; } });
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,qFAAqF;;;;;;;;;;;;;;;;;;;;;AAErF,gBAAgB;AAChB,qDAAoD;AAA3C,8GAAA,aAAa,OAAA;AAEtB,2CAAkD;AAAzC,4GAAA,gBAAgB,OAAA;AAGzB,UAAU;AACV,2CAAqD;AAA5C,iHAAA,OAAO,OAAU;AAE1B,gBAAgB;AAChB,8DAAoF;AAA3E,+GAAA,WAAW,OAAA;AAAE,8GAAA,UAAU,OAAA;AAAE,kHAAA,cAAc,OAAA;AAChD,gEAA+D;AAAtD,sHAAA,iBAAiB,OAAA;AAG1B,WAAW;AACX,oDAAgE;AAAvD,uGAAA,SAAS,OAAA;AAAE,0GAAA,YAAY,OAAA;AAEhC,SAAS;AACT,oCAA0C;AAAjC,iGAAA,UAAU,OAAA;AAEnB,SAAS;AACT,gDAA6K;AAApK,sGAAA,SAAS,OAAA;AAAE,4GAAA,eAAe,OAAA;AAAE,8GAAA,iBAAiB,OAAA;AAAE,2GAAA,cAAc,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,iHAAA,oBAAoB,OAAA;AAAE,iHAAA,oBAAoB,OAAA;AAGhJ,wDAAwD;AACxD,iDAA+B;AAE/B,0DAA0D;AAC1D,2CAAyB;AAEzB,aAAa;AACb,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,qFAAqF;;;;;;;;;;;;;;;;;;;;;AAErF,gBAAgB;AAChB,qDAAoD;AAA3C,8GAAA,aAAa,OAAA;AAEtB,2CAAkD;AAAzC,4GAAA,gBAAgB,OAAA;AAGzB,UAAU;AACV,2CAAqD;AAA5C,iHAAA,OAAO,OAAU;AAE1B,gBAAgB;AAChB,8DAAoF;AAA3E,+GAAA,WAAW,OAAA;AAAE,8GAAA,UAAU,OAAA;AAAE,kHAAA,cAAc,OAAA;AAChD,gEAA+D;AAAtD,sHAAA,iBAAiB,OAAA;AAG1B,WAAW;AACX,oDAAgE;AAAvD,uGAAA,SAAS,OAAA;AAAE,0GAAA,YAAY,OAAA;AAEhC,SAAS;AACT,oCAA0C;AAAjC,iGAAA,UAAU,OAAA;AAEnB,SAAS;AACT,gDAA6K;AAApK,sGAAA,SAAS,OAAA;AAAE,4GAAA,eAAe,OAAA;AAAE,8GAAA,iBAAiB,OAAA;AAAE,2GAAA,cAAc,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,iHAAA,oBAAoB,OAAA;AAAE,iHAAA,oBAAoB,OAAA;AAGhJ,wDAAwD;AACxD,iDAA+B;AAE/B,0DAA0D;AAC1D,2CAAyB;AAEzB,aAAa;AACb,8DAA8E;AAArE,mHAAA,iBAAiB,OAAA;AAAE,8GAAA,YAAY,OAAA;AACxC,wEAO0C;AANxC,kIAAA,2BAA2B,OAAA;AAC3B,iIAAA,0BAA0B,OAAA;AAC1B,iIAAA,0BAA0B,OAAA;AAC1B,0HAAA,mBAAmB,OAAA;AACnB,4HAAA,qBAAqB,OAAA;AACrB,wHAAA,iBAAiB,OAAA;AAEnB,8DAAmE;AAA1D,sHAAA,oBAAoB,OAAA;AAC7B,0DAAyD;AAAhD,4GAAA,YAAY,OAAA;AACrB,4DAA0G;AAAjG,8GAAA,aAAa,OAAA;AAAE,yHAAA,wBAAwB,OAAA;AAAE,oHAAA,mBAAmB,OAAA;AACrE,kDAAmH;AAA1G,2GAAA,eAAe,OAAA;AAAE,6GAAA,iBAAiB,OAAA;AAAE,+GAAA,mBAAmB,OAAA;AAAE,6GAAA,iBAAiB,OAAA;AACnF,kDAAiD;AAAxC,oGAAA,QAAQ,OAAA;AAKjB,QAAQ;AACR,mDAAmF;AAA1E,oHAAA,qBAAqB,OAAA;AAAE,oHAAA,qBAAqB,OAAA;AACrD,6CAA6E;AAApE,6GAAA,iBAAiB,OAAA;AAAE,uGAAA,WAAW,OAAA;AAAE,qGAAA,SAAS,OAAA;AAElD,2DAA8E;AAArE,mHAAA,gBAAgB,OAAA;AAAE,oHAAA,iBAAiB,OAAA;AAE5C,UAAU;AACV,qCAQmB;AAPjB,4GAAA,iBAAiB,OAAA;AACjB,8GAAA,mBAAmB,OAAA;AACnB,4GAAA,iBAAiB,OAAA;AACjB,gHAAA,qBAAqB,OAAA;AACrB,+GAAA,oBAAoB,OAAA;AACpB,0GAAA,eAAe,OAAA;AACf,mGAAA,QAAQ,OAAA;AAGV,UAAU;AACV,qCAUmB;AATjB,qGAAA,UAAU,OAAA;AACV,oGAAA,SAAS,OAAA;AACT,oGAAA,SAAS,OAAA;AACT,4GAAA,iBAAiB,OAAA;AACjB,0GAAA,eAAe,OAAA;AACf,oGAAA,SAAS,OAAA;AACT,qGAAA,UAAU,OAAA;AACV,wGAAA,aAAa,OAAA;AACb,6GAAA,kBAAkB,OAAA"}
|
|
@@ -6,4 +6,18 @@ import { Request, Response, NextFunction } from 'express';
|
|
|
6
6
|
* `../types/auth`, no `(req as any)` cast needed.
|
|
7
7
|
*/
|
|
8
8
|
export declare function authenticateToken(req: Request, res: Response, next: NextFunction): void;
|
|
9
|
+
/**
|
|
10
|
+
* Attaches the user when a valid token is present, and never rejects.
|
|
11
|
+
*
|
|
12
|
+
* For routes whose response is public but *richer* when signed in — the plan
|
|
13
|
+
* catalog is the case this exists for: a signed-out visitor on the landing
|
|
14
|
+
* page must be able to read prices, while a signed-in one additionally gets
|
|
15
|
+
* the hidden plan they are actually on.
|
|
16
|
+
*
|
|
17
|
+
* An invalid or expired token is treated as no token rather than as a 401. The
|
|
18
|
+
* caller asked for public data; refusing it because a stale cookie is lying
|
|
19
|
+
* around would break the landing page for exactly the people most likely to
|
|
20
|
+
* have one.
|
|
21
|
+
*/
|
|
22
|
+
export declare function optionalAuth(req: Request, _res: Response, next: NextFunction): void;
|
|
9
23
|
//# sourceMappingURL=authMiddleware.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authMiddleware.d.ts","sourceRoot":"","sources":["../../src/middleware/authMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAK1D;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAiBvF"}
|
|
1
|
+
{"version":3,"file":"authMiddleware.d.ts","sourceRoot":"","sources":["../../src/middleware/authMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAK1D;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAiBvF;AACD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAcnF"}
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.authenticateToken = authenticateToken;
|
|
7
|
+
exports.optionalAuth = optionalAuth;
|
|
7
8
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
8
9
|
const env_1 = require("../config/env");
|
|
9
10
|
/**
|
|
@@ -29,4 +30,32 @@ function authenticateToken(req, res, next) {
|
|
|
29
30
|
res.status(401).json({ success: false, message: 'Invalid or expired token' });
|
|
30
31
|
}
|
|
31
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Attaches the user when a valid token is present, and never rejects.
|
|
35
|
+
*
|
|
36
|
+
* For routes whose response is public but *richer* when signed in — the plan
|
|
37
|
+
* catalog is the case this exists for: a signed-out visitor on the landing
|
|
38
|
+
* page must be able to read prices, while a signed-in one additionally gets
|
|
39
|
+
* the hidden plan they are actually on.
|
|
40
|
+
*
|
|
41
|
+
* An invalid or expired token is treated as no token rather than as a 401. The
|
|
42
|
+
* caller asked for public data; refusing it because a stale cookie is lying
|
|
43
|
+
* around would break the landing page for exactly the people most likely to
|
|
44
|
+
* have one.
|
|
45
|
+
*/
|
|
46
|
+
function optionalAuth(req, _res, next) {
|
|
47
|
+
try {
|
|
48
|
+
const JWT_SECRET = (0, env_1.requireEnv)('JWT_SECRET');
|
|
49
|
+
const token = req.cookies?.access_token || req.headers.authorization?.replace('Bearer ', '');
|
|
50
|
+
if (token) {
|
|
51
|
+
const user = jsonwebtoken_1.default.verify(token, JWT_SECRET);
|
|
52
|
+
req.user = user;
|
|
53
|
+
req.userGroups = user.groups ?? [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Deliberately ignored — see above.
|
|
58
|
+
}
|
|
59
|
+
next();
|
|
60
|
+
}
|
|
32
61
|
//# sourceMappingURL=authMiddleware.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authMiddleware.js","sourceRoot":"","sources":["../../src/middleware/authMiddleware.ts"],"names":[],"mappings":";;;;;AAWA,8CAiBC;
|
|
1
|
+
{"version":3,"file":"authMiddleware.js","sourceRoot":"","sources":["../../src/middleware/authMiddleware.ts"],"names":[],"mappings":";;;;;AAWA,8CAiBC;AAcD,oCAcC;AAvDD,gEAA+B;AAC/B,uCAA2C;AAG3C;;;;;GAKG;AACH,SAAgB,iBAAiB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;IAC/E,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAA,gBAAU,EAAC,YAAY,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,YAAY,IAAI,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QAE7F,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC,CAAC;YAC3E,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,sBAAG,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,CAAgB,CAAC;QAC1D,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;QAChB,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;QACnC,IAAI,EAAE,CAAC;IACT,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC,CAAC;IAChF,CAAC;AACH,CAAC;AACD;;;;;;;;;;;;GAYG;AACH,SAAgB,YAAY,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB;IAC3E,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAA,gBAAU,EAAC,YAAY,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,YAAY,IAAI,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QAE7F,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,IAAI,GAAG,sBAAG,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,CAAgB,CAAC;YAC1D,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YAChB,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;QACrC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oCAAoC;IACtC,CAAC;IACD,IAAI,EAAE,CAAC;AACT,CAAC"}
|
package/package.json
CHANGED
|
@@ -30,7 +30,7 @@ export type { Entitlements } from './types';
|
|
|
30
30
|
|
|
31
31
|
export { UsageMeter, currentPeriod } from './UsageMeter';
|
|
32
32
|
export type { IUsageMeter } from './UsageMeter';
|
|
33
|
-
export { getUsage, getUsageSnapshot, checkQuota, consumeQuota, releaseQuota, setUsage } from './usage';
|
|
33
|
+
export { getUsage, getUsageSnapshot, checkQuota, consumeQuota, recordUsage, releaseQuota, setUsage } from './usage';
|
|
34
34
|
export type { QuotaRequest } from './usage';
|
|
35
35
|
|
|
36
36
|
export { reconcileUsageMeters, runUsageReconciliation, RECONCILED_METERS } from './reconcile';
|
|
@@ -18,7 +18,7 @@ jest.mock('../logging/logger', () => ({
|
|
|
18
18
|
|
|
19
19
|
import logger from '../logging/logger';
|
|
20
20
|
import { UsageMeter, currentPeriod } from './UsageMeter';
|
|
21
|
-
import { checkQuota, consumeQuota, getUsage, getUsageSnapshot, releaseQuota, setUsage } from './usage';
|
|
21
|
+
import { checkQuota, consumeQuota, getUsage, getUsageSnapshot, recordUsage, releaseQuota, setUsage } from './usage';
|
|
22
22
|
import { Entitlements } from './types';
|
|
23
23
|
import { UNLIMITED, normalizeLimits } from './definitions';
|
|
24
24
|
|
|
@@ -241,6 +241,53 @@ describe('releaseQuota', () => {
|
|
|
241
241
|
});
|
|
242
242
|
});
|
|
243
243
|
|
|
244
|
+
describe('recordUsage', () => {
|
|
245
|
+
it('increments a meter that does not exist yet', async () => {
|
|
246
|
+
await recordUsage('user@example.com', 'storageBytes', 4_096);
|
|
247
|
+
|
|
248
|
+
expect(await getUsage('user@example.com', 'storageBytes')).toBe(4_096);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('records past the limit rather than refusing bytes already on disk', async () => {
|
|
252
|
+
// The gate ran at the signed-URL request; these bytes are in MinIO whatever
|
|
253
|
+
// the counter says. Rolling the increment back — what consumeQuota would do
|
|
254
|
+
// — would understate real storage and let the next upload through too.
|
|
255
|
+
await setUsage('user@example.com', 'storageBytes', 900);
|
|
256
|
+
|
|
257
|
+
await recordUsage('user@example.com', 'storageBytes', 500);
|
|
258
|
+
|
|
259
|
+
expect(await getUsage('user@example.com', 'storageBytes')).toBe(1_400);
|
|
260
|
+
// And the next request is the one that is refused.
|
|
261
|
+
await expect(
|
|
262
|
+
checkQuota({ subjectId: 'user@example.com', meter: 'storageBytes', amount: 1, entitlements: entitlements() })
|
|
263
|
+
).rejects.toMatchObject({ statusCode: 402 });
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('does not lose concurrent increments', async () => {
|
|
267
|
+
await Promise.all(Array.from({ length: 10 }, () => recordUsage('user@example.com', 'storageBytes', 10)));
|
|
268
|
+
|
|
269
|
+
expect(await getUsage('user@example.com', 'storageBytes')).toBe(100);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('ignores a non-positive amount instead of writing a no-op row', async () => {
|
|
273
|
+
await recordUsage('user@example.com', 'storageBytes', 0);
|
|
274
|
+
await recordUsage('user@example.com', 'storageBytes', -50);
|
|
275
|
+
|
|
276
|
+
expect(await UsageMeter.countDocuments({ subjectId: 'user@example.com' })).toBe(0);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('never throws — the resource exists and the caller is on its success path', async () => {
|
|
280
|
+
const failing = jest
|
|
281
|
+
.spyOn(UsageMeter, 'updateOne')
|
|
282
|
+
.mockRejectedValueOnce(new Error('connection lost') as never);
|
|
283
|
+
|
|
284
|
+
await expect(recordUsage('user@example.com', 'storageBytes', 10)).resolves.toBeUndefined();
|
|
285
|
+
expect(logger.warn).toHaveBeenCalledWith('Failed to record usage', expect.objectContaining({ meter: 'storageBytes' }));
|
|
286
|
+
|
|
287
|
+
failing.mockRestore();
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
244
291
|
describe('getUsageSnapshot', () => {
|
|
245
292
|
it('reports every meter, defaulting the untouched ones to zero', async () => {
|
|
246
293
|
await setUsage('user@example.com', 'storageBytes', 42);
|
|
@@ -177,3 +177,47 @@ export async function setUsage(
|
|
|
177
177
|
{ upsert: true, setDefaultsOnInsert: true }
|
|
178
178
|
);
|
|
179
179
|
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Record usage that has already happened, without a limit check.
|
|
183
|
+
*
|
|
184
|
+
* The storage gate runs on the *signed-URL request* — the only point where a
|
|
185
|
+
* file's size is known before its bytes are written — so by the time the photo
|
|
186
|
+
* record is created the object is already in MinIO. Neither of the existing
|
|
187
|
+
* primitives fits that moment: `checkQuota` records nothing, and `consumeQuota`
|
|
188
|
+
* would reject and roll back, leaving the meter understating bytes that are
|
|
189
|
+
* genuinely stored while the user is refused a record for storage they have
|
|
190
|
+
* already paid for.
|
|
191
|
+
*
|
|
192
|
+
* So this increments past the limit deliberately. A cumulative meter can only
|
|
193
|
+
* be pushed over by a race or a stale entitlement, both bounded by one upload,
|
|
194
|
+
* and the gate then refuses the *next* one — which is the correct behaviour:
|
|
195
|
+
* the ceiling stops new writes, it does not un-store bytes.
|
|
196
|
+
*
|
|
197
|
+
* Never throws, for the same reason `releaseQuota` does not: the resource
|
|
198
|
+
* exists, the caller is on its success path, and a failed counter update is
|
|
199
|
+
* repaired by the nightly reconciliation this meter is derived for.
|
|
200
|
+
*/
|
|
201
|
+
export async function recordUsage(
|
|
202
|
+
subjectId: string,
|
|
203
|
+
meter: MeterKey,
|
|
204
|
+
amount = 1,
|
|
205
|
+
now?: Date
|
|
206
|
+
): Promise<void> {
|
|
207
|
+
if (amount <= 0) return;
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
await UsageMeter.updateOne(
|
|
211
|
+
{ subjectId: subjectId.toLowerCase(), meter, period: currentPeriod(meter, now) },
|
|
212
|
+
{ $inc: { count: amount } },
|
|
213
|
+
{ upsert: true, setDefaultsOnInsert: true }
|
|
214
|
+
);
|
|
215
|
+
} catch (err) {
|
|
216
|
+
logger.warn('Failed to record usage', {
|
|
217
|
+
subjectId,
|
|
218
|
+
meter,
|
|
219
|
+
amount,
|
|
220
|
+
error: (err as Error)?.message
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -31,7 +31,7 @@ export * from './entitlements';
|
|
|
31
31
|
export * from './groups';
|
|
32
32
|
|
|
33
33
|
// Middleware
|
|
34
|
-
export { authenticateToken } from './middleware/authMiddleware';
|
|
34
|
+
export { authenticateToken, optionalAuth } from './middleware/authMiddleware';
|
|
35
35
|
export {
|
|
36
36
|
requireInternalServiceToken,
|
|
37
37
|
internalServiceTokenBypass,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Request, Response } from 'express';
|
|
2
2
|
import jwt from 'jsonwebtoken';
|
|
3
|
-
import { authenticateToken } from './authMiddleware';
|
|
3
|
+
import { authenticateToken, optionalAuth } from './authMiddleware';
|
|
4
4
|
import { UserPayload } from '../types/auth';
|
|
5
5
|
|
|
6
6
|
function mockReq(overrides: Partial<Request> = {}): Request {
|
|
@@ -102,3 +102,72 @@ describe('authenticateToken', () => {
|
|
|
102
102
|
expect(res.status).toHaveBeenCalledWith(401);
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
|
+
|
|
106
|
+
describe('optionalAuth', () => {
|
|
107
|
+
const ORIGINAL_ENV = process.env;
|
|
108
|
+
const next = jest.fn();
|
|
109
|
+
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
process.env = { ...ORIGINAL_ENV, JWT_SECRET: 'test-secret' };
|
|
112
|
+
next.mockClear();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
afterAll(() => {
|
|
116
|
+
process.env = ORIGINAL_ENV;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const payload: UserPayload = { id: '1', email: 'user@example.com', name: 'Test User' };
|
|
120
|
+
|
|
121
|
+
it('attaches the user when the token is valid', () => {
|
|
122
|
+
const req = mockReq({ cookies: { access_token: jwt.sign(payload, 'test-secret') } });
|
|
123
|
+
|
|
124
|
+
optionalAuth(req, mockRes(), next);
|
|
125
|
+
|
|
126
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
127
|
+
expect(req.user).toMatchObject({ email: 'user@example.com' });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('reads a bearer token too', () => {
|
|
131
|
+
const req = mockReq({ headers: { authorization: `Bearer ${jwt.sign(payload, 'test-secret')}` } });
|
|
132
|
+
|
|
133
|
+
optionalAuth(req, mockRes(), next);
|
|
134
|
+
|
|
135
|
+
expect(req.user).toMatchObject({ email: 'user@example.com' });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('continues anonymously when there is no token', () => {
|
|
139
|
+
const req = mockReq();
|
|
140
|
+
const res = mockRes();
|
|
141
|
+
|
|
142
|
+
optionalAuth(req, res, next);
|
|
143
|
+
|
|
144
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
145
|
+
expect(req.user).toBeUndefined();
|
|
146
|
+
expect(res.status).not.toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('treats an expired or forged token as no token rather than as a 401', () => {
|
|
150
|
+
// The caller asked for public data. Refusing it because a stale cookie is
|
|
151
|
+
// lying around would break the landing page for exactly the people most
|
|
152
|
+
// likely to have one.
|
|
153
|
+
const req = mockReq({ cookies: { access_token: jwt.sign(payload, 'wrong-secret') } });
|
|
154
|
+
const res = mockRes();
|
|
155
|
+
|
|
156
|
+
optionalAuth(req, res, next);
|
|
157
|
+
|
|
158
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
159
|
+
expect(req.user).toBeUndefined();
|
|
160
|
+
expect(res.status).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('continues anonymously when JWT_SECRET is not configured', () => {
|
|
164
|
+
delete process.env.JWT_SECRET;
|
|
165
|
+
const req = mockReq({ cookies: { access_token: 'anything' } });
|
|
166
|
+
const res = mockRes();
|
|
167
|
+
|
|
168
|
+
optionalAuth(req, res, next);
|
|
169
|
+
|
|
170
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
171
|
+
expect(res.status).not.toHaveBeenCalled();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -26,4 +26,32 @@ export function authenticateToken(req: Request, res: Response, next: NextFunctio
|
|
|
26
26
|
} catch (error) {
|
|
27
27
|
res.status(401).json({ success: false, message: 'Invalid or expired token' });
|
|
28
28
|
}
|
|
29
|
-
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Attaches the user when a valid token is present, and never rejects.
|
|
32
|
+
*
|
|
33
|
+
* For routes whose response is public but *richer* when signed in — the plan
|
|
34
|
+
* catalog is the case this exists for: a signed-out visitor on the landing
|
|
35
|
+
* page must be able to read prices, while a signed-in one additionally gets
|
|
36
|
+
* the hidden plan they are actually on.
|
|
37
|
+
*
|
|
38
|
+
* An invalid or expired token is treated as no token rather than as a 401. The
|
|
39
|
+
* caller asked for public data; refusing it because a stale cookie is lying
|
|
40
|
+
* around would break the landing page for exactly the people most likely to
|
|
41
|
+
* have one.
|
|
42
|
+
*/
|
|
43
|
+
export function optionalAuth(req: Request, _res: Response, next: NextFunction): void {
|
|
44
|
+
try {
|
|
45
|
+
const JWT_SECRET = requireEnv('JWT_SECRET');
|
|
46
|
+
const token = req.cookies?.access_token || req.headers.authorization?.replace('Bearer ', '');
|
|
47
|
+
|
|
48
|
+
if (token) {
|
|
49
|
+
const user = jwt.verify(token, JWT_SECRET) as UserPayload;
|
|
50
|
+
req.user = user;
|
|
51
|
+
req.userGroups = user.groups ?? [];
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// Deliberately ignored — see above.
|
|
55
|
+
}
|
|
56
|
+
next();
|
|
57
|
+
}
|