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 @@
1export 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
8const isRecord = (value: unknown): value is Record<string, unknown> =>
9 typeof value === "object" && value !== null && !Array.isArray(value);
10
11const decodeJsonPointerSegment = (value: string): string =>
12 value.replaceAll("~1", "/").replaceAll("~0", "~");
13
14const 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
31const 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
45const 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
70const 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
93export 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
106export 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
130export 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
4export * from "./tool-map";4export * from "./tool-map";
5export * from "./discovery";5export * from "./discovery";
6export * from "./system-tools";6export * from "./system-tools";
7export * from "./http-request-placements";
8export * 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
11const 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
22const rotr = (x: number, n: number): number => (x >>> n) | (x << (32 - n));
23
24const 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
58const 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
82const 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
119const 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 */
131export 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