add relay admin and report viewer

This commit is contained in:
github-tijlxyz 2023-09-13 19:48:24 +02:00
parent feee34b683
commit ef03fff5a6
17 changed files with 2334 additions and 2059 deletions

8
.env Normal file
View File

@ -0,0 +1,8 @@
# Nostr Standard Relay Info
RELAY_NAME=""
RELAY_DESCRIPTION=""
RELAY_PUBKEY=""
RELAY_CONTACT=""
# Custom invite-relay settings
INVITE_MASTER="" # Master of the relay

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.20
require (
github.com/fiatjaf/khatru v0.0.2
github.com/joho/godotenv v1.5.1
github.com/nbd-wtf/go-nostr v0.20.0
)

2
go.sum
View File

@ -50,6 +50,8 @@ github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=

15
main.go
View File

@ -11,8 +11,12 @@ import (
"github.com/fiatjaf/khatru"
"github.com/fiatjaf/khatru/plugins/storage/badgern"
"github.com/joho/godotenv"
)
var relayMaster string
var db badgern.BadgerBackend
func main() {
// save whitelist on shutdown
var wg sync.WaitGroup
@ -30,16 +34,25 @@ func main() {
}
}()
// init env config
godotenv.Load(".env")
// init relay
relay := khatru.NewRelay()
relayMaster = os.Getenv("INVITE_MASTER")
relay.Name = os.Getenv("RELAY_NAME")
relay.Description = os.Getenv("RELAY_DESCRIPTION")
relay.PubKey = os.Getenv("RELAY_PUBKEY")
relay.Contact = os.Getenv("RELAY_CONTACT")
// load whitelist storage
if err := loadWhitelist(); err != nil {
panic(err)
}
// load db
db := badgern.BadgerBackend{Path: "/tmp/khatru-badgern-tmp"}
db = badgern.BadgerBackend{Path: "/tmp/khatru-badgern-tmp"}
if err := db.Init(); err != nil {
panic(err)
}

10
node_modules/.yarn-integrity generated vendored Normal file
View File

@ -0,0 +1,10 @@
{
"systemParams": "linux-x64-108",
"modulesFolders": [],
"flags": [],
"linkedModules": [],
"topLevelPatterns": [],
"lockfileEntries": {},
"files": [],
"artifacts": {}
}

File diff suppressed because one or more lines are too long

2051
ui/dist/assets/index-183b5ca2.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
ui/dist/assets/index-8d16ea5e.css vendored Normal file

File diff suppressed because one or more lines are too long

4
ui/dist/index.html vendored
View File

@ -6,8 +6,8 @@
<title>Invite Relay</title>
<link rel="icon" href="data:," />
<meta name="description" content="Invite Relay UI" />
<script type="module" crossorigin src="./assets/index-0320fb82.js"></script>
<link rel="stylesheet" href="./assets/index-815d9fe6.css">
<script type="module" crossorigin src="./assets/index-183b5ca2.js"></script>
<link rel="stylesheet" href="./assets/index-8d16ea5e.css">
</head>
<body>

View File

@ -7,6 +7,9 @@
import { NDKNip07Signer } from "@nostr-dev-kit/ndk";
import { buildHierarchy } from "./lib/utils";
import { nip19 } from "nostr-tools";
import AdminView from "./components/AdminView.svelte";
let adminView = false;
async function login() {
const signer = new NDKNip07Signer();
@ -33,8 +36,17 @@
});
</script>
<article class="prose font-sans px-4 py-6 lg:max-w-7xl lg:pt-6 lg:pb-28">
<article class="font-sans px-4 py-6 lg:max-w-7xl lg:pt-6 lg:pb-28">
<h1>Invite Relay</h1>
{#if adminView == true}
<button
on:click={() => adminView = false}
type="button"
class="inline-flex mr-2 items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
>Leave Admin View</button
>
<AdminView />
{:else if adminView == false}
<div>
{#if $userPublickey === undefined}
<button
@ -45,6 +57,14 @@
>
{/if}
</div>
<div>
<button
on:click={() => adminView = true}
type="button"
class="inline-flex mr-2 items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
>Open Admin View</button
>
</div>
{#if invitedata.find((p) => $userPublickey == nip19.npubEncode(p.pk))}
<div>
<h3>Invite Someone</h3>
@ -59,4 +79,5 @@
<Hieracy {hierarchy} reload={fetchData} />
</div>
</div>
{/if}
</article>

View File

@ -0,0 +1,43 @@
<script lang="js">
import {
NDKRelay,
NDKRelaySet,
} from "@nostr-dev-kit/ndk";
import { ndk } from "../lib/nostr";
import { onMount } from "svelte";
import ReportEvent from "./ReportEvent.svelte";
import { relayUrl } from '../lib/consts';
let events = [];
onMount(async () => {
try {
// dont know why this needs so much code
let specificRelay = [new NDKRelay(relayUrl)];
const relaySet = new NDKRelaySet(specificRelay, $ndk);
relaySet.relays.forEach(async (relay) => {
await relay.connect().catch((err) => {
console.log("RELAY CONNECT ERROR");
console.error(err);
});
relay.on("connect", () => {
console.log(relay.url, "connected");
});
});
let filter = { kinds: [1984], limit: 250 };
let es = await $ndk.fetchEvents(filter/*, relaySet*/);
events = Array.from(es);
} catch {
console.log("error while getting feed", error);
}
});
</script>
<div>
{#each events as event}
{#if event.relay.url == relayUrl}
<ReportEvent {event} />
{/if}
{/each}
</div>

View File

@ -0,0 +1,153 @@
<script>
import { NDKEvent, NDKRelay, NDKRelaySet } from "@nostr-dev-kit/ndk";
import { relayUrl } from "../lib/consts";
import { ndk } from "../lib/nostr";
export let event;
let show = true;
async function removeUser(username, pk) {
let confirmation = confirm(
`Are you sure you want to remove ${
username ? username : pk
}? All people they invited will also be removed. (you can only do this if you invited this user or are the relay admin)`
);
if (confirmation) {
try {
// only publish to the relay in question, dont know why this needs so much code
let specificRelay = [new NDKRelay(relayUrl)];
const relaySet = new NDKRelaySet(specificRelay, $ndk);
relaySet.relays.forEach(async (relay) => {
await relay.connect().catch((err) => {
console.log("error while connecting to relay", err);
});
relay.on("connect", () => {
console.log("connected");
});
});
const event = new NDKEvent($ndk);
event.kind = 20202;
event.tags.push(["p", pk]);
await event.publish(relaySet).then(() => reload());
} catch (error) {
console.log("error while publishing", error);
}
}
}
</script>
{#if show}
<div class="rounded-lg border border-slate-500 bg-slate-50 w-full p-4 mt-8">
<div class="columns-2 p-0 m-0">
<div class="bg-white rounded-lg px-4 py-2">
from
{#await event.author?.fetchProfile()}
<a href={`nostr:${event.author.npub}`}>...</a>
{:then profile}
<img
class="h-7 w-7 m-0 p-0 rounded-full inline"
src={profile &&
JSON.parse(Array.from(profile)[0]?.content)?.picture}
alt=""
/>
<a href={`nostr:${event.author.npub}`}
>{profile && JSON.parse(Array.from(profile)[0]?.content)?.name}</a
>
{/await}
</div>
<div class="bg-white rounded-lg px-4 py-2">
{#if event?.tags.find((e) => e[0] == "p")?.[0] && event?.tags.find((e) => e[0] == "p")?.[1]}
to
{#await $ndk
.getUser({ hexpubkey: event.tags.find((e) => e[0] == "p")?.[1] })
.fetchProfile()}
<img class="h-7 w-7 m-0 p-0 rounded-full inline" src="" alt="" />
<a href={`nostr:${event.tags.find((e) => e[0] == "p")?.[1]}`}>...</a
>
{:then profile}
<img
class="h-7 w-7 m-0 p-0 rounded-full inline"
src={profile &&
JSON.parse(Array.from(profile)[0]?.content)?.picture}
alt=""
/>
<a href={`nostr:${event.tags.find((e) => e[0] == "p")?.[1]}`}
>{profile &&
JSON.parse(Array.from(profile)[0]?.content)?.name.length <= 16
? JSON.parse(Array.from(profile)[0]?.content)?.name
: JSON.parse(Array.from(profile)[0]?.content)?.name.substring(
0,
13
) + "..."}</a
>
{/await}
{:else}
huh, nothing here
{/if}
</div>
</div>
<div class="bg-white max-h-64 scroll-auto rounded-lg m-0 p-4 mt-2">
{#if event?.tags.find((e) => e[0] == "e")?.[0] && event?.tags.find((e) => e[0] == "e")?.[1] && event?.tags.find((e) => e[0] == "e")?.[2]}
{event?.tags.find((e) => e[0] == "e")?.[2]}
{:else if event?.tags.find((e) => e[0] == "p")?.[0] && event?.tags.find((e) => e[0] == "p")?.[1] && event?.tags.find((e) => e[0] == "p")?.[2]}
{event?.tags.find((e) => e[0] == "p")?.[2]}
{/if}
{#if event?.content}
<p class="text-gray-500 m-0 p-0">{event.content}</p>
{/if}
</div>
{#if event?.tags.find((e) => e[0] == "e")?.[0] && event?.tags.find((e) => e[0] == "e")?.[1]}
<div
class="bg-white max-h-64 max-w-full overflow-y-scroll rounded-lg m-0 p-4 mt-2"
>
{#await $ndk.fetchEvent( { ids: [event?.tags.find((e) => e[0] == "e")?.[1]] } )}
<p class="text-gray-500 p-0 m-0">... (can we find this event?)</p>
{:then theevent}
<p class="m-0 p-0">{theevent.content}</p>
<p class="text-gray-500 m-0 p-0">
kind: <span class="text-black inline">{theevent.kind}</span>
</p>
<p class="text-gray-500 m-0 p-0">
tags: {#each theevent.tags as tag}<p>
{tag[0]}: <span class="text-black">{tag[1]}</span>
</p>{/each}
</p>
{/await}
</div>
{/if}
<div class="p-0 mb-0 mx-0 mt-2">
<div class="columns-4 inline">
<a
href={`nostr:${
event?.tags.find((e) => e[0] == "e")?.[0] &&
event?.tags.find((e) => e[0] == "e")?.[1]
? event?.tags.find((e) => e[0] == "e")?.[1]
: event?.tags.find((e) => e[0] == "p")?.[1]
}`}
class="rounded-lg inline bg-slate-100 p-2 cursor-pointer hover:bg-white"
>
Open in client
</a>
<button
class="rounded-lg inline bg-slate-100 p-2 cursor-pointer hover:bg-white"
>
View in invite tree
</button>
<button
on:click={() => (show = false)}
class="rounded-lg inline bg-green-500 p-2 cursor-pointer hover:bg-green-400"
>
Dismiss <!-- TO BE IMPLEMENTED: Remove report event? -->
</button>
<button
on:click={() =>
removeUser("user", event.tags.find((e) => e[0] == "p")?.[1])}
class="rounded-lg inline bg-red-500 p-2 cursor-pointer hover:bg-red-400"
>
Exlude this user
</button>
</div>
</div>
</div>
{/if}

View File

@ -25,7 +25,7 @@
let confirmation = confirm(
`Are you sure you want to remove ${
username ? username : person.pk
}? All people they invited will also be removed.`,
}? All people they invited will also be removed. (you can only do this if you invited this user or are the relay admin)`,
);
if (confirmation) {
try {
@ -34,8 +34,7 @@
const relaySet = new NDKRelaySet(specificRelay, $ndk);
relaySet.relays.forEach(async (relay) => {
await relay.connect().catch((err) => {
console.log("RELAY CONNECT ERROR");
console.error(err);
console.log("error while connecting to relay", err);
});
relay.on("connect", () => {
console.log("connected");

View File

@ -1,5 +1,11 @@
package main
import (
"context"
"github.com/nbd-wtf/go-nostr"
)
func isPkInWhitelist(targetPk string) bool {
for i := 0; i < len(whitelist); i++ {
if whitelist[i].Pk == targetPk {
@ -13,6 +19,7 @@ func deleteFromWhitelistRecursively (target string) {
var updatedWhitelist []User
var queue []string
// Remove from whitelist
for _, user := range whitelist {
if user.Pk != target {
updatedWhitelist = append(updatedWhitelist, user)
@ -21,8 +28,20 @@ func deleteFromWhitelistRecursively (target string) {
queue = append(queue, user.Pk);
}
}
whitelist = updatedWhitelist
// Remove all events
go func () {
var filter nostr.Filter = nostr.Filter{
Authors: []string{target},
}
events, _ := db.QueryEvents(context.TODO(), filter)
for ev := range events {
db.DeleteEvent(context.TODO(), ev)
}
}()
// Recursive
for _, pk := range queue {
deleteFromWhitelistRecursively(pk)
}

View File

@ -37,7 +37,7 @@ func whitelistRejecter(ctx context.Context, evt *nostr.Event) (reject bool, msg
pTags := evt.Tags.GetAll([]string{"p"})
for _, tag := range pTags {
for _, user := range whitelist {
if user.Pk == tag.Value() && user.InvitedBy == evt.PubKey {
if user.Pk == tag.Value() && (user.InvitedBy == evt.PubKey || evt.PubKey == relayMaster) {
deleteFromWhitelistRecursively(tag.Value())
}
}

4
yarn.lock Normal file
View File

@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1