Open History to see the last commit
1import * as Effect from "effect/Effect";
2import * as Option from "effect/Option";
3import * as Schema from "effect/Schema";
4
5const Keys = Schema.fromJsonString(Schema.Record(Schema.String, Schema.String));
6
7const decodeKeys = Schema.decodeEffect(Keys);
8
9export const developerIdentity = Effect.fn("TaskCache.developerIdentity")(function* (
10 request: Request,
11 encodedKeys: string | undefined,
12) {
13 const authorization = request.headers.get("authorization");
14 if (!authorization?.toLowerCase().startsWith("basic ")) return undefined;
15 if (!encodedKeys || authorization.length > 1024) return null;
16
17 const decoded = Option.liftThrowable(atob)(authorization.slice(6));
18 if (decoded._tag === "None") return null;
19 const keys = yield* decodeKeys(encodedKeys).pipe(Effect.option);
20 if (keys._tag === "None") return null;
21 const separator = decoded.value.indexOf(":");
22 const name = decoded.value.slice(0, separator);
23 const token = decoded.value.slice(separator + 1);
24 if (separator < 1 || !/^[a-z0-9-]{1,64}$/.test(name) || token.length < 32) return null;
25 const expected = Object.hasOwn(keys.value, name) ? keys.value[name] : undefined;
26 if (typeof expected !== "string" || !/^[a-f0-9]{64}$/.test(expected)) return null;
27 const digest = new Uint8Array(
28 yield* Effect.promise(() => crypto.subtle.digest("SHA-256", new TextEncoder().encode(token))),
29 );
30 const actual = Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("");
31 let difference = 0;
32 for (let index = 0; index < actual.length; index++) {
33 difference |= actual.charCodeAt(index) ^ expected.charCodeAt(index);
34 }
35 return difference === 0 ? name : null;
36});