From c88c2e0dd0fc4ad675b02aeeab04586a4160d4f4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Erik=20Hedenstr=C3=B6m?= <erik@hedenstroem.com>
Date: Fri, 1 Mar 2024 15:34:46 +0000
Subject: [PATCH] Added checks for authorized keys

---
 .gitlab-ci.yml | 12 ++++++++++
 src/index.ts   | 59 +++++++++++++++++++++++---------------------------
 wrangler.toml  | 49 ++---------------------------------------
 3 files changed, 41 insertions(+), 79 deletions(-)
 create mode 100644 .gitlab-ci.yml

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..7143263
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,12 @@
+image: node:lts-alpine
+
+stages:
+  - publish
+
+publish:
+  stage: publish
+  only:
+    - main
+  script:
+    - npm ci
+    - npm run deploy
diff --git a/src/index.ts b/src/index.ts
index c9599f2..b18da2a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,28 +1,4 @@
-/**
- * Welcome to Cloudflare Workers! This is your first worker.
- *
- * - Run `npm run dev` in your terminal to start a development server
- * - Open a browser tab at http://localhost:8787/ to see your worker in action
- * - Run `npm run deploy` to publish your worker
- *
- * Learn more at https://developers.cloudflare.com/workers/
- */
-
 export interface Env {
-	// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/
-	// MY_KV_NAMESPACE: KVNamespace;
-	//
-	// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/
-	// MY_DURABLE_OBJECT: DurableObjectNamespace;
-	//
-	// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/
-	// MY_BUCKET: R2Bucket;
-	//
-	// Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/
-	// MY_SERVICE: Fetcher;
-	//
-	// Example binding to a Queue. Learn more at https://developers.cloudflare.com/queues/javascript-apis/
-	// MY_QUEUE: Queue;
 	ALLOWED_OPENAI_KEYS: string;
 	HELICONE_KEY: string;
 }
@@ -33,23 +9,42 @@ export default {
 		// The URL you want to proxy the requests to
 		const url = new URL(request.url);
 
-		// Modify the request to point to the target URL
-		// For example, if you're proxying to https://example.com
+		// Modify the request to point to the Helicone URL
 		url.hostname = "oai.hconeai.com";
-		
+
+		if (request.method === "OPTIONS") {
+			// Send OPTIONS request to the OpenAI API
+			url.hostname = "api.openai.com";
+		} else {
+			// Check that there is a bearer token in the Authorization header
+			const authHeader = request.headers.get("Authorization");
+			if (!authHeader || !authHeader.startsWith("Bearer ")) {
+				return new Response("Unauthorized", { status: 401 });
+			}
+			// Check that the token is one of the allowed OpenAI keys
+			const token = authHeader.substring(7);
+			if (!env.ALLOWED_OPENAI_KEYS.split(",").includes(token)) {
+				return new Response("Forbidden", { status: 403 });
+			}
+		}
+
+		// Remove the /helicone-proxy prefix from the URL
+		if (url.pathname.startsWith('/helicone-proxy')) {
+			url.pathname = url.pathname.substring('/helicone-proxy'.length);
+		}
+
 		// Clone the request to modify it
 		const modifiedRequest = new Request(url.toString(), request);
-		
+
 		// Add your custom header to the request
 		modifiedRequest.headers.set("Host", url.hostname);
 		modifiedRequest.headers.set("Helicone-Auth", "Bearer " + env.HELICONE_KEY);
-		console.log(modifiedRequest);
-		
+
 		// Make the fetch request with the modified request
 		const response = await fetch(modifiedRequest);
-		
+
 		// Return the response from the target server
 		return response;
-		//return new Response('Hello World!');
+
 	},
 };
diff --git a/wrangler.toml b/wrangler.toml
index 476900f..b5e7b8f 100644
--- a/wrangler.toml
+++ b/wrangler.toml
@@ -2,50 +2,5 @@ name = "helicone-proxy"
 main = "src/index.ts"
 compatibility_date = "2024-02-23"
 
-# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
-# Note: Use secrets to store sensitive data.
-# Docs: https://developers.cloudflare.com/workers/platform/environment-variables
-# [vars]
-# MY_VARIABLE = "production_value"
-
-# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs.
-# Docs: https://developers.cloudflare.com/workers/runtime-apis/kv
-# [[kv_namespaces]]
-# binding = "MY_KV_NAMESPACE"
-# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
-
-# Bind an R2 Bucket. Use R2 to store arbitrarily large blobs of data, such as files.
-# Docs: https://developers.cloudflare.com/r2/api/workers/workers-api-usage/
-# [[r2_buckets]]
-# binding = "MY_BUCKET"
-# bucket_name = "my-bucket"
-
-# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer.
-# Docs: https://developers.cloudflare.com/queues/get-started
-# [[queues.producers]]
-# binding = "MY_QUEUE"
-# queue = "my-queue"
-
-# Bind a Queue consumer. Queue Consumers can retrieve tasks scheduled by Producers to act on them.
-# Docs: https://developers.cloudflare.com/queues/get-started
-# [[queues.consumers]]
-# queue = "my-queue"
-
-# Bind another Worker service. Use this binding to call another Worker without network overhead.
-# Docs: https://developers.cloudflare.com/workers/platform/services
-# [[services]]
-# binding = "MY_SERVICE"
-# service = "my-service"
-
-# Bind a Durable Object. Durable objects are a scale-to-zero compute primitive based on the actor model.
-# Durable Objects can live for as long as needed. Use these when you need a long-running "server", such as in realtime apps.
-# Docs: https://developers.cloudflare.com/workers/runtime-apis/durable-objects
-# [[durable_objects.bindings]]
-# name = "MY_DURABLE_OBJECT"
-# class_name = "MyDurableObject"
-
-# Durable Object migrations.
-# Docs: https://developers.cloudflare.com/workers/learning/using-durable-objects#configure-durable-object-classes-with-migrations
-# [[migrations]]
-# tag = "v1"
-# new_classes = ["MyDurableObject"]
+[dev]
+  port = 3000
-- 
GitLab