badmfck-api-server 5.0.6 → 5.0.8
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.
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { IInterceptor } from "../APIService";
|
|
2
2
|
import { HTTPRequestVO } from "../structures/Interfaces";
|
|
3
3
|
export declare class InterceptorPayAuth implements IInterceptor<any> {
|
|
4
|
-
|
|
4
|
+
private static readonly TS_PAST_SEC;
|
|
5
|
+
private static readonly TS_FUTURE_SEC;
|
|
6
|
+
private static readonly NONCE_TTL_MS;
|
|
7
|
+
private static readonly NONCE_MAX;
|
|
8
|
+
private nonceSeen;
|
|
9
|
+
private authorizationsPreset;
|
|
10
|
+
constructor(authorizations?: {
|
|
11
|
+
key: string;
|
|
12
|
+
kid: string;
|
|
13
|
+
}[]);
|
|
14
|
+
private purgeNonces;
|
|
15
|
+
intercept(req: HTTPRequestVO, authorizations?: {
|
|
5
16
|
key: string;
|
|
6
17
|
kid: string;
|
|
7
18
|
}[]): Promise<any>;
|
|
@@ -17,6 +17,28 @@ const _TAuthParams = {
|
|
|
17
17
|
sig: ""
|
|
18
18
|
};
|
|
19
19
|
class InterceptorPayAuth {
|
|
20
|
+
static TS_PAST_SEC = 300;
|
|
21
|
+
static TS_FUTURE_SEC = 60;
|
|
22
|
+
static NONCE_TTL_MS = (InterceptorPayAuth.TS_PAST_SEC + InterceptorPayAuth.TS_FUTURE_SEC) * 1000;
|
|
23
|
+
static NONCE_MAX = 100_000;
|
|
24
|
+
nonceSeen = new Map();
|
|
25
|
+
authorizationsPreset;
|
|
26
|
+
constructor(authorizations) {
|
|
27
|
+
this.authorizationsPreset = authorizations;
|
|
28
|
+
}
|
|
29
|
+
purgeNonces(nowMs) {
|
|
30
|
+
for (const [nonce, expiresAt] of this.nonceSeen) {
|
|
31
|
+
if (expiresAt > nowMs)
|
|
32
|
+
break;
|
|
33
|
+
this.nonceSeen.delete(nonce);
|
|
34
|
+
}
|
|
35
|
+
while (this.nonceSeen.size > InterceptorPayAuth.NONCE_MAX) {
|
|
36
|
+
const oldest = this.nonceSeen.keys().next().value;
|
|
37
|
+
if (oldest === undefined)
|
|
38
|
+
break;
|
|
39
|
+
this.nonceSeen.delete(oldest);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
20
42
|
async intercept(req, authorizations) {
|
|
21
43
|
const authScheme = "payauth ";
|
|
22
44
|
let headerData = req.headers["authorization"];
|
|
@@ -39,11 +61,13 @@ class InterceptorPayAuth {
|
|
|
39
61
|
const params = {};
|
|
40
62
|
headerData.substring(authScheme.length).trim().split(",").forEach(v => {
|
|
41
63
|
v = v.trim();
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
const key =
|
|
45
|
-
let value =
|
|
46
|
-
if (value.
|
|
64
|
+
const eq = v.indexOf("=");
|
|
65
|
+
if (eq > 0) {
|
|
66
|
+
const key = v.substring(0, eq).trim().toLowerCase();
|
|
67
|
+
let value = v.substring(eq + 1).trim();
|
|
68
|
+
if (value.length > 1 &&
|
|
69
|
+
((value.startsWith("'") && value.endsWith("'")) ||
|
|
70
|
+
(value.startsWith('"') && value.endsWith('"')))) {
|
|
47
71
|
value = value.substring(1, value.length - 1);
|
|
48
72
|
}
|
|
49
73
|
params[key] = value;
|
|
@@ -52,15 +76,38 @@ class InterceptorPayAuth {
|
|
|
52
76
|
const errors = await Validator_1.Validator.validateStructure(_TAuthParams, params);
|
|
53
77
|
if (errors)
|
|
54
78
|
throw { ...DefaultErrors_1.default.UNAUTHORIZED, details: "Malformed authorization header", stack: errors };
|
|
79
|
+
if (!/^\d{10}$/.test(params.ts) && !/^\d{13}$/.test(params.ts))
|
|
80
|
+
throw { ...DefaultErrors_1.default.UNAUTHORIZED, details: "ts must be Unix epoch seconds (10 digits) or milliseconds (13 digits), UTC" };
|
|
81
|
+
const nowMs = Date.now();
|
|
82
|
+
const nowSec = Math.floor(nowMs / 1000);
|
|
83
|
+
const tsSec = params.ts.length === 13
|
|
84
|
+
? Math.floor(parseInt(params.ts, 10) / 1000)
|
|
85
|
+
: parseInt(params.ts, 10);
|
|
86
|
+
const delta = tsSec - nowSec;
|
|
87
|
+
if (delta < -InterceptorPayAuth.TS_PAST_SEC || delta > InterceptorPayAuth.TS_FUTURE_SEC)
|
|
88
|
+
throw { ...DefaultErrors_1.default.UNAUTHORIZED, details: `ts out of window: off by ${delta}s (server epoch ${nowSec})` };
|
|
89
|
+
this.purgeNonces(nowMs);
|
|
90
|
+
if (this.nonceSeen.has(params.nonce))
|
|
91
|
+
throw { ...DefaultErrors_1.default.UNAUTHORIZED, details: "Duplicate nonce" };
|
|
92
|
+
if (!authorizations || authorizations.length === 0) {
|
|
93
|
+
if (this.authorizationsPreset && this.authorizationsPreset.length > 0)
|
|
94
|
+
authorizations = this.authorizationsPreset;
|
|
95
|
+
else
|
|
96
|
+
throw { ...DefaultErrors_1.default.UNAUTHORIZED, details: "No authorizations provided" };
|
|
97
|
+
}
|
|
55
98
|
for (let i of authorizations) {
|
|
56
99
|
if (i.kid === params.kid) {
|
|
57
|
-
if (params.alg.toLowerCase() === "hmac-sha256") {
|
|
100
|
+
if (params.alg && params.alg.toLowerCase() === "hmac-sha256") {
|
|
58
101
|
if (b64) {
|
|
59
102
|
const key = Buffer.from(i.key, "hex");
|
|
60
103
|
const sig = crypto_1.default.createHmac("sha256", key).update(b64 + params.ts + params.nonce).digest("hex");
|
|
61
|
-
|
|
104
|
+
const got = Buffer.from(String(params.sig), "utf-8");
|
|
105
|
+
const want = Buffer.from(sig, "utf-8");
|
|
106
|
+
if (got.length === want.length && crypto_1.default.timingSafeEqual(got, want)) {
|
|
107
|
+
this.nonceSeen.set(params.nonce, nowMs + InterceptorPayAuth.NONCE_TTL_MS);
|
|
62
108
|
return i;
|
|
63
|
-
|
|
109
|
+
}
|
|
110
|
+
throw { ...DefaultErrors_1.default.UNAUTHORIZED, details: "Signature mismatch" };
|
|
64
111
|
}
|
|
65
112
|
else {
|
|
66
113
|
throw { ...DefaultErrors_1.default.UNAUTHORIZED, details: "Can't calc signature, no b64" };
|