Skip to content

Commit

Permalink
relayed_pubkey
Browse files Browse the repository at this point in the history
  • Loading branch information
bob2402 committed Apr 29, 2024
1 parent e2fe1b6 commit 25e9e3b
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 77 deletions.
15 changes: 14 additions & 1 deletion deploy/example.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { PublicKey } from "../_libs.ts";
import { run } from "../main.tsx";

const relayed_pubkey = Deno.env.get("relayed_pubkey");
if (!relayed_pubkey) {
console.error("Please set the environment variable 'relayed_pubkey'");
Deno.exit(1);
}

const pubkey = PublicKey.FromString(relayed_pubkey);
if (pubkey instanceof Error) {
console.error(pubkey);
Deno.exit(1);
}

const relay = await run({
port: 8080,
default_policy: {
Expand All @@ -8,7 +21,7 @@ const relay = await run({
default_information: {
name: "Relayed Example",
description: "A lightweight relay written in Deno.",
pubkey: Deno.env.get("relayed_pubkey"),
pubkey,
contact: "",
icon: "",
},
Expand Down
2 changes: 1 addition & 1 deletion graphql-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const typeDefs = gql`
type RelayInformation {
name: String
description: String
pubkey: String
pubkey: PublicKey!
contact: String
supported_nips: [Int!]
software: String
Expand Down
97 changes: 38 additions & 59 deletions main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { RootResolver } from "./resolvers/root.ts";
import * as gql from "https://esm.sh/[email protected]";
import { Policy } from "./resolvers/policy.ts";
import { func_ResolvePolicyByKind } from "./resolvers/policy.ts";
import { NostrKind, PublicKey, verifyEvent } from "./_libs.ts";
import { NostrEvent, NostrKind, PublicKey, verifyEvent } from "./_libs.ts";
import { PolicyStore } from "./resolvers/policy.ts";
import { Policies } from "./resolvers/policy.ts";
import {
Expand All @@ -24,7 +24,7 @@ import {
func_MarkEventDeleted,
func_WriteRegularEvent,
} from "./resolvers/event.ts";
import { getCookies } from "https://deno.land/[email protected]/http/cookie.ts";
import { Cookie, getCookies, setCookie } from "https://deno.land/[email protected]/http/cookie.ts";

const schema = gql.buildSchema(gql.print(typeDefs));

Expand All @@ -43,15 +43,17 @@ export type Relay = {
block?: Set<string>;
}) => Promise<Policy | Error>;
get_policy: (kind: NostrKind) => Promise<Policy>;
set_relay_information: (args: RelayInformation) => Promise<RelayInformation>;
set_relay_information: (
args: { name?: string; description?: string; pubkey?: string; contact?: string; icon?: string },
) => Promise<RelayInformation>;
get_relay_information: () => Promise<RelayInformation>;
default_policy: DefaultPolicy;
};

export async function run(args: {
port: number;
admin?: PublicKey;
default_information?: RelayInformation;
default_information: RelayInformation;
default_policy: DefaultPolicy;
kv?: Deno.Kv;
}): Promise<Error | Relay> {
Expand All @@ -62,24 +64,6 @@ export async function run(args: {

const { port, default_policy, default_information } = args;

if (default_information) {
if (!default_information.pubkey) {
const env_pubkey = Deno.env.get("relayed_pubkey");
if (env_pubkey) {
default_information.pubkey = env_pubkey;
}
}
if (default_information.pubkey) {
const pubkeyArray = default_information.pubkey.split(",");
for (const pubkey of pubkeyArray) {
const pubkeyObj = PublicKey.FromString(pubkey);
if (pubkeyObj instanceof Error) {
return pubkeyObj;
}
}
}
}

let resolve_hostname;
const hostname = new Promise<string>((resolve) => {
resolve_hostname = resolve;
Expand Down Expand Up @@ -148,7 +132,6 @@ export type EventReadWriter = {

const root_handler = (
args: {
information?: RelayInformation;
connections: Map<WebSocket, SubscriptionMap>;
default_policy: DefaultPolicy;
resolvePolicyByKind: func_ResolvePolicyByKind;
Expand All @@ -162,11 +145,28 @@ async (req: Request, info: Deno.ServeHandlerInfo) => {

const { pathname, protocol } = new URL(req.url);
if (pathname === "/api/auth/login") {
const auth = req.headers.get("authorization");
const body = await verifyToken(auth, args.relayInformationStore);
const resp = new Response(JSON.stringify(body), { status: 200 });
resp.headers.set("set-cookie", `token=${auth}; Path=/; Secure; HttpOnly; SameSite=Strict;`);
return resp;
const body = await req.json();
if (!body) {
return new Response(`{"errors":"request body is null"}`, { status: 400 });
}
const result = await verifyToken(body, args.relayInformationStore);
if (!result.success) {
return new Response(JSON.stringify(result), { status: 400 });
} else {
const auth = btoa(JSON.stringify(body));
const headers = new Headers();
const cookie: Cookie = {
name: "token",
value: auth,
path: "/",
secure: true,
httpOnly: true,
sameSite: "Strict",
};
setCookie(headers, cookie);
const resp = new Response("", { status: 200, headers });
return resp;
}
}
if (pathname == "/api") {
return graphql_handler(args)(req);
Expand Down Expand Up @@ -200,7 +200,11 @@ async (req: Request) => {
const query = await req.json();
const cookies = getCookies(req.headers);
const token = cookies.token;
const body = await verifyToken(token, args.relayInformationStore);
if (!token) {
return new Response(`{"errors":"no token"}`);
}
const event = JSON.parse(atob(token));
const body = await verifyToken(event, args.relayInformationStore);
if (!body.success) {
return new Response(JSON.stringify(body), { status: 200 });
}
Expand Down Expand Up @@ -245,41 +249,17 @@ const information_handler = async (args: { relayInformationStore: RelayInformati
return resp;
};

async function verifyToken(token: string | null, relayInformationStore: RelayInformationStore) {
async function verifyToken(event: NostrEvent, relayInformationStore: RelayInformationStore) {
try {
if (!token) {
throw new Error("token not found");
}
const [prefix, eventBase64] = token.split(" ");
if (prefix !== "Nostr") {
throw new Error("token not Nostr");
}
const event = JSON.parse(atob(eventBase64));
if (!event) {
throw new Error("no auth event");
}
if (!await verifyEvent(event)) {
throw new Error("token not verified");
}
const { pubkey: relayPubkey } = await relayInformationStore.resolveRelayInformation();
if (!relayPubkey) {
throw new Error("relay pubkey not set");
}
const relayPubkeyArr = relayPubkey.split(",");
const relayPubkeyHexArr: string[] = [];
for (const pubkey of relayPubkeyArr) {
const relayPubkeyObj = PublicKey.FromString(pubkey);
if (relayPubkeyObj instanceof Error) {
throw new Error(`relay pubkey:${pubkey} not valid`);
}
relayPubkeyHexArr.push(relayPubkeyObj.hex);
}
const pubkey = PublicKey.FromString(event.pubkey);
if (pubkey instanceof Error) {
throw new Error("pubkey not valid");
}
if (!relayPubkeyHexArr.includes(pubkey.hex)) {
throw new Error("pubkey not in relay");
if (pubkey.hex !== (await relayInformationStore.resolveRelayInformation()).pubkey.hex) {
throw new Error("not admin");
}
return {
success: true,
Expand Down Expand Up @@ -388,9 +368,8 @@ const graphiql = `
}
const event = await ext.signEvent(unsigned_event);
const response = await fetch('/api/auth/login', {
headers: {
authorization: "Nostr " + btoa(JSON.stringify(event)),
},
method: 'POST',
body: JSON.stringify(event),
})
const data = await response.json();
if(data.success) {
Expand Down
28 changes: 21 additions & 7 deletions resolvers/nip11.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { PublicKey } from "../_libs.ts";

export type RelayInformation = {
name?: string;
description?: string;
pubkey?: string;
pubkey: PublicKey;
contact?: string;
supported_nips?: number[];
software?: string;
Expand All @@ -24,16 +26,15 @@ export class RelayInformationStore {

constructor(
private kv: Deno.Kv,
default_information?: RelayInformation,
default_information: RelayInformation,
) {
this.default_information = default_information ? default_information : {};
this.default_information = default_information;
}

resolveRelayInformation = async (): Promise<RelayInformation> => {
const get_relay_information = (await this.kv.get<RelayInformation>(["relay_information"])).value ||
{};
const get_relay_information = (await this.kv.get<RelayInformation>(["relay_information"])).value;
// if pubkey is set in default_information, it will be used as the pubkey
if (this.default_information.pubkey) {
if (get_relay_information && this.default_information.pubkey) {
get_relay_information.pubkey = this.default_information.pubkey;
}
return { ...this.default_information, ...get_relay_information, ...not_modifiable_information };
Expand All @@ -49,7 +50,20 @@ export class RelayInformationStore {
},
) => {
const old_information = await this.resolveRelayInformation();
const new_information = { ...old_information, ...args };
const new_information = {
...old_information,
name: args.name,
description: args.description,
contact: args.contact,
icon: args.icon,
};
if (args.pubkey) {
const pubkey = PublicKey.FromString(args.pubkey);
if (pubkey instanceof Error) {
throw new Error("Invalid pubkey");
}
new_information.pubkey = pubkey;
}
await this.kv.set(["relay_information"], new_information);
return { ...new_information, ...not_modifiable_information };
};
Expand Down
18 changes: 9 additions & 9 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Deno.test("main", async (t) => {
const relay = await run({
port: 8080,
default_information: {
pubkey: test_ctx.publicKey.bech32(),
pubkey: test_ctx.publicKey,
},
default_policy: {
allowed_kinds: [NostrKind.Long_Form, NostrKind.Encrypted_Custom_App_Data],
Expand Down Expand Up @@ -168,7 +168,7 @@ Deno.test("replaceable events", async (t) => {
const relay = await run({
port: 8080,
default_information: {
pubkey: test_ctx.publicKey.bech32(),
pubkey: test_ctx.publicKey,
},
default_policy: {
allowed_kinds: "all",
Expand All @@ -188,7 +188,7 @@ Deno.test("NIP-9: Deletion", async () => {
const relay = await run({
port: 8080,
default_information: {
pubkey: test_ctx.publicKey.bech32(),
pubkey: test_ctx.publicKey,
},
default_policy: {
allowed_kinds: "all",
Expand All @@ -212,7 +212,7 @@ Deno.test("NIP-11: Relay Information Document", async (t) => {
},
default_information: {
name: "Nostr Relay",
pubkey: test_ctx.publicKey.bech32(),
pubkey: test_ctx.publicKey,
},
kv: await test_kv(),
}) as Relay;
Expand All @@ -221,7 +221,7 @@ Deno.test("NIP-11: Relay Information Document", async (t) => {
const information = await relay.get_relay_information();
assertEquals(information, {
name: "Nostr Relay",
pubkey: test_ctx.publicKey.bech32(),
pubkey: test_ctx.publicKey,
...not_modifiable_information,
});
});
Expand All @@ -234,7 +234,7 @@ Deno.test("NIP-11: Relay Information Document", async (t) => {
const information2 = await relay.get_relay_information();
assertEquals(information2, {
name: "Nostr Relay2",
pubkey: test_ctx.publicKey.bech32(),
pubkey: test_ctx.publicKey,
...not_modifiable_information,
});
});
Expand All @@ -247,7 +247,7 @@ Deno.test("NIP-11: Relay Information Document", async (t) => {
icon: null,
contact: null,
description: null,
pubkey: test_ctx.publicKey.bech32(),
pubkey: test_ctx.publicKey,
...not_modifiable_information,
});
});
Expand All @@ -263,7 +263,7 @@ Deno.test("NIP-11: Relay Information Document", async (t) => {
icon: null,
contact: null,
description: null,
pubkey: test_ctx.publicKey.bech32(),
pubkey: test_ctx.publicKey,
...not_modifiable_information,
});
});
Expand All @@ -284,7 +284,7 @@ async function queryGql(relay: Relay, query: string, variables?: object) {
const res = await fetch(`http://${hostname}:${port}/api`, {
method: "POST",
headers: {
"cookie": `token="nostr ${test_auth_event()}`,
"cookie": `token="${test_auth_event()}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ query, variables }),
Expand Down

0 comments on commit 25e9e3b

Please sign in to comment.