two-stroke 7.8.3 → 7.10.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/package.json +3 -5
- package/src/index.ts +15 -7
- package/src/once.ts +8 -1
- package/src/util.ts +77 -0
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@cloudflare/vitest-pool-workers": "^0.18.0",
|
|
14
14
|
"@sentry/cli": "^3.6.0",
|
|
15
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
15
|
+
"@typescript-eslint/eslint-plugin": "^8.63.0",
|
|
16
16
|
"@typescript-eslint/parser": "^8.62.1",
|
|
17
17
|
"@vitest/runner": "^4.1.8",
|
|
18
18
|
"@vitest/snapshot": "^4.1.8",
|
|
@@ -20,13 +20,11 @@
|
|
|
20
20
|
"eslint-config-two-stroke": "^1.5.7",
|
|
21
21
|
"eslint-config-typescript": "^3.0.0",
|
|
22
22
|
"jose": "^6.2.3",
|
|
23
|
-
"jwk-subtle": "^1.1.8",
|
|
24
23
|
"mime": "^4.1.0",
|
|
25
24
|
"miniflare": "^4.20260701.0",
|
|
26
25
|
"msw": "^2.14.6",
|
|
27
26
|
"openapi-fetch": "^0.17.0",
|
|
28
27
|
"openapi-typescript": "^7.13.0",
|
|
29
|
-
"pbkdf-subtle": "^1.1.8",
|
|
30
28
|
"toucan-js": "^4.1.1",
|
|
31
29
|
"zod": "^4.4.3"
|
|
32
30
|
},
|
|
@@ -58,12 +56,12 @@
|
|
|
58
56
|
"url": "https://github.com/change-engine/two-stroke"
|
|
59
57
|
},
|
|
60
58
|
"type": "module",
|
|
61
|
-
"version": "7.
|
|
59
|
+
"version": "7.10.0",
|
|
62
60
|
"devDependencies": {
|
|
63
61
|
"@types/eslint": "^9.6.1",
|
|
64
62
|
"eslint": "^10.6.0",
|
|
65
63
|
"prettier": "^3.9.4",
|
|
66
64
|
"typescript": "^6.0.3",
|
|
67
|
-
"vitest": "^4.1.
|
|
65
|
+
"vitest": "^4.1.10"
|
|
68
66
|
}
|
|
69
67
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { verify as jwkVerify } from "jwk-subtle";
|
|
2
|
-
import { verify as pbkdfVerify } from "pbkdf-subtle";
|
|
3
1
|
import { Toucan } from "toucan-js";
|
|
4
2
|
import { type ZodSafeParseResult, z, ZodObject, ZodType } from "zod/v4";
|
|
5
3
|
import { openAPI } from "./open-api";
|
|
6
4
|
import { type Handler, type Route } from "./types";
|
|
5
|
+
import type { JSONWebKeySet } from "jose";
|
|
6
|
+
import { pbkdfVerify, jwkVerifyMulti } from "./util";
|
|
7
7
|
|
|
8
8
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
9
9
|
const noAuth = async () => null;
|
|
@@ -301,7 +301,7 @@ export function twoStroke<T>(
|
|
|
301
301
|
},
|
|
302
302
|
noAuth,
|
|
303
303
|
pbkdf:
|
|
304
|
-
(k: keyof T, customHeaderName
|
|
304
|
+
(k: keyof T, customHeaderName = "Authorization") =>
|
|
305
305
|
async ({ req, env }: { req: Request; env: T }) => {
|
|
306
306
|
const [scheme, token] = (req.headers.get(customHeaderName) ?? " ").split(" ");
|
|
307
307
|
if (
|
|
@@ -309,20 +309,28 @@ export function twoStroke<T>(
|
|
|
309
309
|
(await pbkdfVerify(env[k] as string, token ?? ""))
|
|
310
310
|
)
|
|
311
311
|
return;
|
|
312
|
-
throw Error("Invalid");
|
|
312
|
+
throw new Error("Invalid");
|
|
313
313
|
},
|
|
314
314
|
jwt:
|
|
315
315
|
<J>(k: keyof T, ak: keyof T) =>
|
|
316
316
|
async ({ req, env }: { req: Request; env: T }) => {
|
|
317
317
|
const [scheme, token] = (req.headers.get("Authorization") ?? " ").split(" ");
|
|
318
318
|
if (scheme === "Bearer") {
|
|
319
|
-
const
|
|
319
|
+
const rawIssuer = env[k] as string;
|
|
320
|
+
const rawAudience = env[ak] as string;
|
|
321
|
+
const issuer = rawIssuer.startsWith("{")
|
|
322
|
+
? (JSON.parse(rawIssuer) as Record<string, JSONWebKeySet | true>)
|
|
323
|
+
: { [rawIssuer]: true as const };
|
|
324
|
+
const audience = rawAudience.startsWith("[")
|
|
325
|
+
? (JSON.parse(rawAudience) as string[])
|
|
326
|
+
: [rawAudience];
|
|
327
|
+
const claims = await jwkVerifyMulti<J>(token ?? "", issuer, audience);
|
|
320
328
|
if (!claims) {
|
|
321
|
-
throw Error("Invalid");
|
|
329
|
+
throw new Error("Invalid");
|
|
322
330
|
}
|
|
323
331
|
return claims;
|
|
324
332
|
}
|
|
325
|
-
throw Error("Invalid");
|
|
333
|
+
throw new Error("Invalid");
|
|
326
334
|
},
|
|
327
335
|
queueHandler<I extends ZodType>(
|
|
328
336
|
input: I,
|
package/src/once.ts
CHANGED
|
@@ -14,7 +14,14 @@ export const once = async <T extends { retry_count: number }>(
|
|
|
14
14
|
if (retry_count > numRetries) throw new Error("Too many retries");
|
|
15
15
|
await queue.send(
|
|
16
16
|
{ ...m, retry_count },
|
|
17
|
-
{
|
|
17
|
+
{
|
|
18
|
+
delaySeconds: Math.min(
|
|
19
|
+
Math.round(
|
|
20
|
+
(0.5 + Math.random() / 2) * Math.pow(retry_count, backoffExponent) + Math.random() * 4,
|
|
21
|
+
),
|
|
22
|
+
900,
|
|
23
|
+
),
|
|
24
|
+
},
|
|
18
25
|
);
|
|
19
26
|
};
|
|
20
27
|
if (!(await isDuplicateMessage(key, bucket)))
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FlattenedJWSInput,
|
|
3
|
+
type JSONWebKeySet,
|
|
4
|
+
type JWSHeaderParameters,
|
|
5
|
+
createLocalJWKSet,
|
|
6
|
+
createRemoteJWKSet,
|
|
7
|
+
decodeJwt,
|
|
8
|
+
jwtVerify,
|
|
9
|
+
} from "jose";
|
|
10
|
+
|
|
11
|
+
export async function pbkdfVerify(key: string, password: string) {
|
|
12
|
+
return (
|
|
13
|
+
[
|
|
14
|
+
...new Uint8Array(
|
|
15
|
+
await crypto.subtle.deriveBits(
|
|
16
|
+
{
|
|
17
|
+
name: "PBKDF2",
|
|
18
|
+
hash: "SHA-256",
|
|
19
|
+
salt: new Uint8Array([...atob(key).slice(3, 19)].map((ch) => ch.charCodeAt(0))),
|
|
20
|
+
iterations: parseInt(
|
|
21
|
+
[...atob(key).slice(19, 22)].map((ch) => ch.charCodeAt(0).toString(16)).join(""),
|
|
22
|
+
16,
|
|
23
|
+
),
|
|
24
|
+
},
|
|
25
|
+
await crypto.subtle.importKey(
|
|
26
|
+
"raw",
|
|
27
|
+
new TextEncoder().encode(password),
|
|
28
|
+
"PBKDF2",
|
|
29
|
+
false,
|
|
30
|
+
["deriveBits"],
|
|
31
|
+
),
|
|
32
|
+
256,
|
|
33
|
+
),
|
|
34
|
+
),
|
|
35
|
+
]
|
|
36
|
+
.map((byte) => String.fromCharCode(byte))
|
|
37
|
+
.join("") === atob(key).slice(22, 54)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function jwkVerifyMulti<J>(
|
|
42
|
+
token: string,
|
|
43
|
+
issuers: Record<string, JSONWebKeySet | true>,
|
|
44
|
+
audiences: string[],
|
|
45
|
+
) {
|
|
46
|
+
const unverifiedClaims = decodeJwt(token);
|
|
47
|
+
if (!unverifiedClaims.iss) throw new Error("Invalid");
|
|
48
|
+
const issuer = issuers[unverifiedClaims.iss];
|
|
49
|
+
if (!issuer) throw new Error("Invalid");
|
|
50
|
+
|
|
51
|
+
let JWKS: (
|
|
52
|
+
protectedHeader?: JWSHeaderParameters,
|
|
53
|
+
token?: FlattenedJWSInput,
|
|
54
|
+
) => Promise<CryptoKey>;
|
|
55
|
+
|
|
56
|
+
if (issuer === true) {
|
|
57
|
+
// OIDC
|
|
58
|
+
const oidcRequest = await fetch(
|
|
59
|
+
`${unverifiedClaims.iss}${unverifiedClaims.iss.endsWith("/") ? "" : "/"}.well-known/openid-configuration`,
|
|
60
|
+
);
|
|
61
|
+
const oidc = await oidcRequest.json<{ jwks_uri: string }>();
|
|
62
|
+
JWKS = createRemoteJWKSet(new URL(oidc.jwks_uri));
|
|
63
|
+
} else {
|
|
64
|
+
JWKS = createLocalJWKSet(issuer);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const claims = (
|
|
68
|
+
await jwtVerify<J>(token ?? "", JWKS, {
|
|
69
|
+
algorithms: ["RS256", "ES256"],
|
|
70
|
+
audience: audiences,
|
|
71
|
+
})
|
|
72
|
+
).payload;
|
|
73
|
+
if (!claims) {
|
|
74
|
+
throw new Error("Invalid");
|
|
75
|
+
}
|
|
76
|
+
return claims;
|
|
77
|
+
}
|