initial commit

This commit is contained in:
Danny Morabito 2025-08-05 23:48:43 +02:00
commit 7ce36dfb37
Signed by: dannym
GPG key ID: 7CC8056A5A04557E
8 changed files with 298 additions and 0 deletions

81
src/main.ts Normal file
View file

@ -0,0 +1,81 @@
import type { ServerWebSocket } from "bun";
interface Event {
kind: number;
tags: string[][];
content: string;
created_at: number;
pubkey: string;
id: string;
sig: string;
}
type Nip42ProxySocketData = {
authenticated: boolean;
authToken: string;
remoteWs: WebSocket;
};
async function validateAuthEvent(event: Event, challenge: string): boolean {
if (event.kind !== 22242) return false;
const last30Seconds = Math.floor(Date.now() / 1000) - 30;
if (event.created_at < last30Seconds) return false;
const challengeTag = event.tags.find(tag => tag[0] === 'challange')?.[1];
if (challengeTag !== challenge) return false;
const file = Bun.file("./allowed-pubkeys.json");
if (!file.exists()) return true;
const allowedPubkeys = JSON.parse(await file.text());
if (!allowedPubkeys.includes(event.pubkey)) return false;
return true;
}
const sendMessage = (ws: ServerWebSocket<Nip42ProxySocketData>, message: any[]) => ws.send(JSON.stringify(message), true);
const sendAuth = (ws: ServerWebSocket<Nip42ProxySocketData>) => sendMessage(ws, ["AUTH", ws.data.authToken, "This is an authenticated relay."]);
export function main(mainRelayUrl: string) {
const server = Bun.serve<Nip42ProxySocketData, {}>({
fetch(req, server) {
const upgrade = server.upgrade(req, {
data: {
authenticated: false,
authToken: Bun.randomUUIDv7()
}
});
if (!upgrade) console.log(`http request`)
return new Response("this is a proxy. Use http to access it");
},
websocket: {
async message(ws, msg) {
const [command, ...data] = JSON.parse(msg);
if (!ws.data.authenticated) {
if (command === "REQ") {
const [name, ...filters] = data;
sendMessage(ws, ["CLOSED", name, 'auth-required: you must authenticate first']);
}
if (command === "EVENT") {
const [event] = data;
sendMessage(ws, ["OK", event.id, false, 'auth-required: you must authenticate first']);
}
if (command === "AUTH") {
const [event] = data;
const valid = await validateAuthEvent(event, ws.data.authToken);
if (!valid) return sendMessage(ws, ['NOTICE', "Invalid auth event"]);
ws.data.authenticated = true;
return;
}
return sendAuth(ws);
}
ws.data.remoteWs.send(msg);
},
open(ws) {
sendAuth(ws);
ws.data.remoteWs = new WebSocket(mainRelayUrl);
ws.data.remoteWs.onmessage = (data) => ws.send(data.data, true);
},
perMessageDeflate: true,
perMessageDeflateCompressionLevel: 9
}
})
console.log('Server listening @', server.url.host)
}