History

google oauth works

Rhys Sullivan committed 8 months ago

Commit 2e328ab1 parent 38327c077 files · +7814 −866Browse files

Showing the changes to packages/codemode-core/src (3 of 77 files). Show all

Unified · Split

3 files
  1. packages/codemode-core/src/http-request-placements.ts +164 −0
  2. packages/codemode-core/src/index.ts +2 −0
  3. packages/codemode-core/src/isomorphic-hash.ts +132 −0
packages/codemode-core/src/http-request-placements.ts+164−0
@@ -0,0 +1,164 @@
1+export type HttpRequestPlacements = {
2+ headers?: Readonly<Record<string, string>>;
3+ queryParams?: Readonly<Record<string, string>>;
4+ cookies?: Readonly<Record<string, string>>;
5+ bodyValues?: Readonly<Record<string, string>>;
6+};
7+
8+const isRecord = (value: unknown): value is Record<string, unknown> =>
9+ typeof value === "object" && value !== null && !Array.isArray(value);
10+
11+const decodeJsonPointerSegment = (value: string): string =>
12+ value.replaceAll("~1", "/").replaceAll("~0", "~");
13+
14+const pathSegmentsFromPlacementPath = (value: string): ReadonlyArray<string> => {
15+ const trimmed = value.trim();
16+ if (trimmed.length === 0) {
17+ return [];
18+ }
19+
20+ if (trimmed.startsWith("/")) {
21+ return trimmed
22+ .split("/")
23+ .slice(1)
24+ .map(decodeJsonPointerSegment)
25+ .filter((segment) => segment.length > 0);
26+ }
27+
28+ return trimmed.split(".").filter((segment) => segment.length > 0);
29+};
30+
31+const findHeaderKey = (
32+ headers: Readonly<Record<string, string>>,
33+ name: string,
34+): string | null => {
35+ const lowered = name.toLowerCase();
36+ for (const key of Object.keys(headers)) {
37+ if (key.toLowerCase() === lowered) {
38+ return key;
39+ }
40+ }
41+
42+ return null;
43+};
44+
45+const parseCookieHeader = (value: string): Record<string, string> => {
46+ const parsed: Record<string, string> = {};
47+
48+ for (const part of value.split(";")) {
49+ const trimmed = part.trim();
50+ if (trimmed.length === 0) {
51+ continue;
52+ }
53+
54+ const separatorIndex = trimmed.indexOf("=");
55+ if (separatorIndex === -1) {
56+ parsed[trimmed] = "";
57+ continue;
58+ }
59+
60+ const name = trimmed.slice(0, separatorIndex).trim();
61+ const cookieValue = trimmed.slice(separatorIndex + 1).trim();
62+ if (name.length > 0) {
63+ parsed[name] = cookieValue;
64+ }
65+ }
66+
67+ return parsed;
68+};
69+
70+const setNestedRecordValue = (
71+ target: Record<string, unknown>,
72+ path: ReadonlyArray<string>,
73+ value: string,
74+) => {
75+ let current = target;
76+
77+ for (let index = 0; index < path.length - 1; index += 1) {
78+ const segment = path[index]!;
79+ const next = current[segment];
80+ if (isRecord(next)) {
81+ current = next;
82+ continue;
83+ }
84+
85+ const replacement: Record<string, unknown> = {};
86+ current[segment] = replacement;
87+ current = replacement;
88+ }
89+
90+ current[path[path.length - 1]!] = value;
91+};
92+
93+export const applyHttpQueryPlacementsToUrl = (input: {
94+ url: string | URL;
95+ queryParams?: Readonly<Record<string, string>>;
96+}): URL => {
97+ const url = input.url instanceof URL ? new URL(input.url.toString()) : new URL(input.url);
98+
99+ for (const [key, value] of Object.entries(input.queryParams ?? {})) {
100+ url.searchParams.set(key, value);
101+ }
102+
103+ return url;
104+};
105+
106+export const applyCookiePlacementsToHeaders = (input: {
107+ headers: Readonly<Record<string, string>>;
108+ cookies?: Readonly<Record<string, string>>;
109+}): Record<string, string> => {
110+ const cookies = input.cookies ?? {};
111+ if (Object.keys(cookies).length === 0) {
112+ return { ...input.headers };
113+ }
114+
115+ const cookieHeaderKey = findHeaderKey(input.headers, "cookie");
116+ const cookieHeaderValue = cookieHeaderKey ? input.headers[cookieHeaderKey] : null;
117+ const mergedCookies = {
118+ ...(cookieHeaderValue ? parseCookieHeader(cookieHeaderValue) : {}),
119+ ...cookies,
120+ };
121+ const headers = { ...input.headers };
122+
123+ headers[cookieHeaderKey ?? "cookie"] = Object.entries(mergedCookies)
124+ .map(([name, value]) => `${name}=${encodeURIComponent(value)}`)
125+ .join("; ");
126+
127+ return headers;
128+};
129+
130+export const applyJsonBodyPlacements = (input: {
131+ body: unknown;
132+ bodyValues?: Readonly<Record<string, string>>;
133+ label?: string;
134+}): unknown => {
135+ const bodyValues = input.bodyValues ?? {};
136+ if (Object.keys(bodyValues).length === 0) {
137+ return input.body;
138+ }
139+
140+ const root =
141+ input.body == null
142+ ? {}
143+ : isRecord(input.body)
144+ ? structuredClone(input.body)
145+ : null;
146+
147+ if (root === null) {
148+ throw new Error(
149+ `${input.label ?? "HTTP request"} auth body placements require an object JSON body`,
150+ );
151+ }
152+
153+ for (const [rawPath, value] of Object.entries(bodyValues)) {
154+ const path = pathSegmentsFromPlacementPath(rawPath);
155+ if (path.length === 0) {
156+ throw new Error(
157+ `${input.label ?? "HTTP request"} auth body placement path cannot be empty`,
158+ );
159+ }
160+ setNestedRecordValue(root, path, value);
161+ }
162+
163+ return root;
164+};
packages/codemode-core/src/index.ts+2−0
@@ -4,3 +4,5 @@ export * from "./schema-types"; · 3 unmodified lines
44 export * from "./tool-map";
55 export * from "./discovery";
66 export * from "./system-tools";
7+export * from "./http-request-placements";
8+export * from "./isomorphic-hash";
packages/codemode-core/src/isomorphic-hash.ts+132−0
@@ -0,0 +1,132 @@
1+/**
2+ * Isomorphic SHA-256 implementation that works in both Node.js and the browser.
3+ *
4+ * Avoids importing `node:crypto`, which Vite externalizes for browser builds,
5+ * causing runtime errors when this module is pulled into client bundles.
6+ *
7+ * Uses the same SHA-256 algorithm as `node:crypto`, producing identical hex output.
8+ */
9+
10+// SHA-256 constants: first 32 bits of the fractional parts of the cube roots of the first 64 primes
11+const K = new Uint32Array([
12+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
13+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
14+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
15+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
16+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
17+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
18+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
19+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
20+]);
21+
22+const rotr = (x: number, n: number): number => (x >>> n) | (x << (32 - n));
23+
24+const sha256Compress = (state: Uint32Array, block: Uint32Array): void => {
25+ const w = new Uint32Array(64);
26+ for (let i = 0; i < 16; i++) w[i] = block[i];
27+ for (let i = 16; i < 64; i++) {
28+ const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >>> 3);
29+ const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >>> 10);
30+ w[i] = (w[i - 16] + s0 + w[i - 7] + s1) | 0;
31+ }
32+
33+ let a = state[0], b = state[1], c = state[2], d = state[3];
34+ let e = state[4], f = state[5], g = state[6], h = state[7];
35+
36+ for (let i = 0; i < 64; i++) {
37+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
38+ const ch = (e & f) ^ (~e & g);
39+ const temp1 = (h + S1 + ch + K[i] + w[i]) | 0;
40+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
41+ const maj = (a & b) ^ (a & c) ^ (b & c);
42+ const temp2 = (S0 + maj) | 0;
43+
44+ h = g; g = f; f = e; e = (d + temp1) | 0;
45+ d = c; c = b; b = a; a = (temp1 + temp2) | 0;
46+ }
47+
48+ state[0] = (state[0] + a) | 0;
49+ state[1] = (state[1] + b) | 0;
50+ state[2] = (state[2] + c) | 0;
51+ state[3] = (state[3] + d) | 0;
52+ state[4] = (state[4] + e) | 0;
53+ state[5] = (state[5] + f) | 0;
54+ state[6] = (state[6] + g) | 0;
55+ state[7] = (state[7] + h) | 0;
56+};
57+
58+const utf8Encode = (str: string): Uint8Array => {
59+ if (typeof TextEncoder !== "undefined") {
60+ return new TextEncoder().encode(str);
61+ }
62+
63+ // Fallback for environments without TextEncoder
64+ const bytes: number[] = [];
65+ for (let i = 0; i < str.length; i++) {
66+ let c = str.charCodeAt(i);
67+ if (c < 0x80) {
68+ bytes.push(c);
69+ } else if (c < 0x800) {
70+ bytes.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f));
71+ } else if (c >= 0xd800 && c < 0xdc00 && i + 1 < str.length) {
72+ const next = str.charCodeAt(++i);
73+ c = 0x10000 + ((c - 0xd800) << 10) + (next - 0xdc00);
74+ bytes.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
75+ } else {
76+ bytes.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
77+ }
78+ }
79+ return new Uint8Array(bytes);
80+};
81+
82+const sha256 = (data: Uint8Array): Uint8Array => {
83+ const state = new Uint32Array([
84+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
85+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
86+ ]);
87+
88+ const bitLen = data.length * 8;
89+
90+ // Padding: append 1 bit, then zeros, then 64-bit big-endian length
91+ const padded = new Uint8Array(Math.ceil((data.length + 9) / 64) * 64);
92+ padded.set(data);
93+ padded[data.length] = 0x80;
94+ // Write the 64-bit big-endian bit length at the end
95+ const view = new DataView(padded.buffer);
96+ // For strings up to ~256 MB, the high 32 bits of the bit length are zero
97+ view.setUint32(padded.length - 4, bitLen, false);
98+ if (bitLen > 0xffffffff) {
99+ view.setUint32(padded.length - 8, Math.floor(bitLen / 0x100000000), false);
100+ }
101+
102+ // Process each 64-byte block
103+ const block = new Uint32Array(16);
104+ for (let offset = 0; offset < padded.length; offset += 64) {
105+ for (let i = 0; i < 16; i++) {
106+ block[i] = view.getUint32(offset + i * 4, false);
107+ }
108+ sha256Compress(state, block);
109+ }
110+
111+ const result = new Uint8Array(32);
112+ const resultView = new DataView(result.buffer);
113+ for (let i = 0; i < 8; i++) {
114+ resultView.setUint32(i * 4, state[i], false);
115+ }
116+ return result;
117+};
118+
119+const hexFromBytes = (bytes: Uint8Array): string => {
120+ let hex = "";
121+ for (let i = 0; i < bytes.length; i++) {
122+ hex += bytes[i].toString(16).padStart(2, "0");
123+ }
124+ return hex;
125+};
126+
127+/**
128+ * Compute a SHA-256 hex digest of a UTF-8 string.
129+ * Drop-in replacement for `createHash("sha256").update(value).digest("hex")`.
130+ */
131+export const sha256Hex = (value: string): string =>
132+ hexFromBytes(sha256(utf8Encode(value)));

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t