Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nip 05 whitelist #22

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,9 @@ list:
tor:
enabled: false
proxy: ""

whitelist:
enabled: false
domain: "example.com" # Set the domain for the NIP-05 whitelist
errorMessage: "You are not authorized to upload." # Error message for non-whitelisted users
fetchDelay: 3600 # Delay between fetches in seconds
16 changes: 14 additions & 2 deletions src/api/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { config, Rule } from "../config.js";
import { hasUsedToken } from "../db/methods.js";
import { removeUpload, saveFromUploadRequest } from "../storage/upload.js";
import { blobDB } from "../db/db.js";
import { isWhitelisted, fetchWhitelist } from '../whitelist.js';

export type UploadState = CommonState & {
contentType: string;
Expand Down Expand Up @@ -80,6 +81,17 @@ router.head<UploadState>("/upload", async (ctx) => {
});

router.put<UploadState>("/upload", async (ctx) => {
if (!config.upload.enabled) throw new HttpErrors.NotFound("Uploads disabled");

const pubkey = ctx.state.auth?.pubkey;
if (!pubkey) throw new HttpErrors.Unauthorized("Missing public key");

await fetchWhitelist(); // Ensure the whitelist is up-to-date

if (config.whitelist.enabled && pubkey && !isWhitelisted(pubkey)) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code should be moved to the checkUpload method ( in the "check auth" block ) so it applies to both the /uploadand/media` endpoint

It also wont have to check if ctx.state.auth exists then ( there are some configurations that allow uploads without auth )

throw new HttpErrors.Forbidden(config.whitelist.errorMessage);
}

const { contentType } = ctx.state;

let upload = await saveFromUploadRequest(ctx.req);
Expand All @@ -97,8 +109,8 @@ router.put<UploadState>("/upload", async (ctx) => {
const blob = await addFromUpload(upload, type);

// add owner
if (ctx.state.auth?.pubkey && !blobDB.hasOwner(upload.sha256, ctx.state.auth.pubkey)) {
blobDB.addOwner(blob.sha256, ctx.state.auth.pubkey);
if (pubkey && !blobDB.hasOwner(upload.sha256, pubkey)) {
blobDB.addOwner(blob.sha256, pubkey);
}

if (ctx.state.auth) saveAuthToken(ctx.state.auth);
Expand Down
12 changes: 12 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ export type Config = {
enabled: boolean;
proxy: string;
};
whitelist: {
enabled: boolean;
domain: string;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
domain: string;
nip05Domain?: string;
pubkeys?: string[];

I would add a manual list of pubkeys in case someone wants a short hard-coded list. and I would rename domain to something like nip04Domian to make it clear what its fetching

errorMessage: string;
fetchDelay: number;
};
};

function loadYaml(filepath: string, content: string) {
Expand Down Expand Up @@ -92,6 +98,12 @@ const defaultConfig: Config = {
media: { enabled: false, requireAuth: true, requirePubkeyInRule: false },
list: { requireAuth: false, allowListOthers: false },
tor: { enabled: false, proxy: "" },
whitelist: {
enabled: true,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
enabled: true,
enabled: false,

Should default to false

domain: "",
errorMessage: "You are not authorized to upload.",
fetchDelay: 3600,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be renamed to something more intuitive like refreshIntervale

},
};

const searchPlaces = ["config.yaml", "config.yml", "config.json"];
Expand Down
35 changes: 35 additions & 0 deletions src/whitelist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import axios from 'axios';
import { config } from './config.js';

let whitelistCache: Set<string> = new Set();
let lastFetchTime = 0;

export async function fetchWhitelist() {
if (!config.whitelist.enabled) {
whitelistCache.clear();
return whitelistCache;
}

const now = Date.now();
if (now - lastFetchTime < config.whitelist.fetchDelay * 1000) {
return whitelistCache;
}

try {
const response = await axios.get(`https://${config.whitelist.domain}/.well-known/nostr.json`);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

axios and be easily replaced here with the built-in fetch method

const response  = await fetch(`https://${config.whitelist.domain}/.well-known/nostr.json`).then(res => res.json())

const data = response.data;
if (data && data.names) {
whitelistCache = new Set(Object.values(data.names));
lastFetchTime = now;
}
} catch (error) {
console.error("Failed to fetch whitelist:", error);
}

return whitelistCache;
}

export function isWhitelisted(pubkey: string): boolean {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would convert this method to async and make it dynamically fetch / refresh the whitelist when needed. that way the API endpoints themselves do not need to think about getting the whitelist

if (!config.whitelist.enabled) return true; // Allow all if whitelist is disabled
return whitelistCache.has(pubkey);
}