Merge branch 'rm-activitypub' into 'main'
Remove ActivityPub actor endpoint, remove Webfinger/host-meta, remove deps.ts See merge request soapbox-pub/ditto!290
This commit is contained in:
commit
d0ab1d55ce
|
@ -36,7 +36,8 @@
|
||||||
"@std/media-types": "jsr:@std/media-types@^0.224.1",
|
"@std/media-types": "jsr:@std/media-types@^0.224.1",
|
||||||
"@std/streams": "jsr:@std/streams@^0.223.0",
|
"@std/streams": "jsr:@std/streams@^0.223.0",
|
||||||
"comlink": "npm:comlink@^4.4.1",
|
"comlink": "npm:comlink@^4.4.1",
|
||||||
"deno-safe-fetch": "https://gitlab.com/soapbox-pub/deno-safe-fetch/-/raw/v1.0.0/load.ts",
|
"deno-safe-fetch/load": "https://gitlab.com/soapbox-pub/deno-safe-fetch/-/raw/v1.0.0/load.ts",
|
||||||
|
"deno-sqlite": "https://raw.githubusercontent.com/alexgleason/deno-sqlite/325f66d8c395e7f6f5ee78ebfa42a0eeea4a942b/mod.ts",
|
||||||
"entities": "npm:entities@^4.5.0",
|
"entities": "npm:entities@^4.5.0",
|
||||||
"fast-stable-stringify": "npm:fast-stable-stringify@^1.0.0",
|
"fast-stable-stringify": "npm:fast-stable-stringify@^1.0.0",
|
||||||
"formdata-helper": "npm:formdata-helper@^0.3.0",
|
"formdata-helper": "npm:formdata-helper@^0.3.0",
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { Conf } from '@/config.ts';
|
||||||
import { startFirehose } from '@/firehose.ts';
|
import { startFirehose } from '@/firehose.ts';
|
||||||
import { Time } from '@/utils.ts';
|
import { Time } from '@/utils.ts';
|
||||||
|
|
||||||
import { actorController } from '@/controllers/activitypub/actor.ts';
|
|
||||||
import {
|
import {
|
||||||
accountController,
|
accountController,
|
||||||
accountLookupController,
|
accountLookupController,
|
||||||
|
@ -77,10 +76,8 @@ import {
|
||||||
} from '@/controllers/api/timelines.ts';
|
} from '@/controllers/api/timelines.ts';
|
||||||
import { trendingTagsController } from '@/controllers/api/trends.ts';
|
import { trendingTagsController } from '@/controllers/api/trends.ts';
|
||||||
import { indexController } from '@/controllers/site.ts';
|
import { indexController } from '@/controllers/site.ts';
|
||||||
import { hostMetaController } from '@/controllers/well-known/host-meta.ts';
|
|
||||||
import { nodeInfoController, nodeInfoSchemaController } from '@/controllers/well-known/nodeinfo.ts';
|
import { nodeInfoController, nodeInfoSchemaController } from '@/controllers/well-known/nodeinfo.ts';
|
||||||
import { nostrController } from '@/controllers/well-known/nostr.ts';
|
import { nostrController } from '@/controllers/well-known/nostr.ts';
|
||||||
import { webfingerController } from '@/controllers/well-known/webfinger.ts';
|
|
||||||
import { auth98Middleware, requireProof, requireRole } from '@/middleware/auth98Middleware.ts';
|
import { auth98Middleware, requireProof, requireRole } from '@/middleware/auth98Middleware.ts';
|
||||||
import { cacheMiddleware } from '@/middleware/cacheMiddleware.ts';
|
import { cacheMiddleware } from '@/middleware/cacheMiddleware.ts';
|
||||||
import { cspMiddleware } from '@/middleware/cspMiddleware.ts';
|
import { cspMiddleware } from '@/middleware/cspMiddleware.ts';
|
||||||
|
@ -137,13 +134,9 @@ app.use(
|
||||||
storeMiddleware,
|
storeMiddleware,
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get('/.well-known/webfinger', webfingerController);
|
|
||||||
app.get('/.well-known/host-meta', hostMetaController);
|
|
||||||
app.get('/.well-known/nodeinfo', nodeInfoController);
|
app.get('/.well-known/nodeinfo', nodeInfoController);
|
||||||
app.get('/.well-known/nostr.json', nostrController);
|
app.get('/.well-known/nostr.json', nostrController);
|
||||||
|
|
||||||
app.get('/users/:username', actorController);
|
|
||||||
|
|
||||||
app.get('/nodeinfo/:version', nodeInfoSchemaController);
|
app.get('/nodeinfo/:version', nodeInfoSchemaController);
|
||||||
|
|
||||||
app.get('/api/v1/instance', cacheMiddleware({ cacheName: 'web', expires: Time.minutes(5) }), instanceController);
|
app.get('/api/v1/instance', cacheMiddleware({ cacheName: 'web', expires: Time.minutes(5) }), instanceController);
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { getAuthor } from '@/queries.ts';
|
|
||||||
import { activityJson } from '@/utils/api.ts';
|
|
||||||
import { renderActor } from '@/views/activitypub/actor.ts';
|
|
||||||
import { localNip05Lookup } from '@/utils/nip05.ts';
|
|
||||||
|
|
||||||
import type { AppContext, AppController } from '@/app.ts';
|
|
||||||
|
|
||||||
const actorController: AppController = async (c) => {
|
|
||||||
const username = c.req.param('username');
|
|
||||||
const { signal } = c.req.raw;
|
|
||||||
|
|
||||||
const pointer = await localNip05Lookup(c.get('store'), username);
|
|
||||||
if (!pointer) return notFound(c);
|
|
||||||
|
|
||||||
const event = await getAuthor(pointer.pubkey, { signal });
|
|
||||||
if (!event) return notFound(c);
|
|
||||||
|
|
||||||
const actor = await renderActor(event, username);
|
|
||||||
if (!actor) return notFound(c);
|
|
||||||
|
|
||||||
return activityJson(c, actor);
|
|
||||||
};
|
|
||||||
|
|
||||||
function notFound(c: AppContext) {
|
|
||||||
return c.json({ error: 'Not found' }, 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { actorController };
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
|
|
||||||
import type { AppController } from '@/app.ts';
|
|
||||||
|
|
||||||
/** https://datatracker.ietf.org/doc/html/rfc6415 */
|
|
||||||
const hostMetaController: AppController = (c) => {
|
|
||||||
const template = Conf.local('/.well-known/webfinger?resource={uri}');
|
|
||||||
|
|
||||||
c.header('content-type', 'application/xrd+xml');
|
|
||||||
|
|
||||||
return c.body(
|
|
||||||
`<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
|
||||||
<Link rel="lrdd" template="${template}" type="application/xrd+xml" />
|
|
||||||
</XRD>
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { hostMetaController };
|
|
|
@ -1,97 +0,0 @@
|
||||||
import { nip19 } from 'nostr-tools';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { localNip05Lookup } from '@/utils/nip05.ts';
|
|
||||||
|
|
||||||
import type { AppContext, AppController } from '@/app.ts';
|
|
||||||
import type { Webfinger } from '@/schemas/webfinger.ts';
|
|
||||||
|
|
||||||
const webfingerQuerySchema = z.object({
|
|
||||||
resource: z.string().url(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const webfingerController: AppController = (c) => {
|
|
||||||
const query = webfingerQuerySchema.safeParse(c.req.query());
|
|
||||||
if (!query.success) {
|
|
||||||
return c.json({ error: 'Bad request', schema: query.error }, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resource = new URL(query.data.resource);
|
|
||||||
|
|
||||||
switch (resource.protocol) {
|
|
||||||
case 'acct:': {
|
|
||||||
return handleAcct(c, resource);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return c.json({ error: 'Unsupported URI scheme' }, 400);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Transforms the resource URI into a `[username, domain]` tuple. */
|
|
||||||
const acctSchema = z.custom<URL>((value) => value instanceof URL)
|
|
||||||
.transform((uri) => uri.pathname)
|
|
||||||
.pipe(z.string().email('Invalid acct'))
|
|
||||||
.transform((acct) => acct.split('@') as [username: string, host: string])
|
|
||||||
.refine(([_username, host]) => host === Conf.url.hostname, {
|
|
||||||
message: 'Host must be local',
|
|
||||||
path: ['resource', 'acct'],
|
|
||||||
});
|
|
||||||
|
|
||||||
async function handleAcct(c: AppContext, resource: URL): Promise<Response> {
|
|
||||||
const result = acctSchema.safeParse(resource);
|
|
||||||
if (!result.success) {
|
|
||||||
return c.json({ error: 'Invalid acct URI', schema: result.error }, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [username, host] = result.data;
|
|
||||||
const pointer = await localNip05Lookup(c.get('store'), username);
|
|
||||||
|
|
||||||
if (!pointer) {
|
|
||||||
return c.json({ error: 'Not found' }, 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
const json = renderWebfinger({
|
|
||||||
pubkey: pointer.pubkey,
|
|
||||||
username,
|
|
||||||
subject: `acct:${username}@${host}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
c.header('content-type', 'application/jrd+json');
|
|
||||||
return c.body(JSON.stringify(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RenderWebfingerOpts {
|
|
||||||
pubkey: string;
|
|
||||||
username: string;
|
|
||||||
subject: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Present Nostr user on Webfinger. */
|
|
||||||
function renderWebfinger({ pubkey, username, subject }: RenderWebfingerOpts): Webfinger {
|
|
||||||
const apId = Conf.local(`/users/${username}`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
subject,
|
|
||||||
aliases: [apId],
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
rel: 'self',
|
|
||||||
type: 'application/activity+json',
|
|
||||||
href: apId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rel: 'self',
|
|
||||||
type: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
|
||||||
href: apId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rel: 'self',
|
|
||||||
type: 'application/nostr+json',
|
|
||||||
href: `nostr:${nip19.npubEncode(pubkey)}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export { webfingerController };
|
|
12
src/deps.ts
12
src/deps.ts
|
@ -1,12 +0,0 @@
|
||||||
import 'deno-safe-fetch';
|
|
||||||
export {
|
|
||||||
type ParsedSignature,
|
|
||||||
pemToPublicKey,
|
|
||||||
publicKeyToPem,
|
|
||||||
signRequest,
|
|
||||||
verifyRequest,
|
|
||||||
} from 'https://gitlab.com/soapbox-pub/fedisign/-/raw/v0.2.1/mod.ts';
|
|
||||||
export { generateSeededRsa } from 'https://gitlab.com/soapbox-pub/seeded-rsa/-/raw/v1.0.0/mod.ts';
|
|
||||||
export {
|
|
||||||
DB as Sqlite,
|
|
||||||
} from 'https://raw.githubusercontent.com/alexgleason/deno-sqlite/325f66d8c395e7f6f5ee78ebfa42a0eeea4a942b/mod.ts';
|
|
|
@ -1,19 +0,0 @@
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
const linkSchema = z.object({
|
|
||||||
rel: z.string().optional(),
|
|
||||||
type: z.string().optional(),
|
|
||||||
href: z.string().optional(),
|
|
||||||
template: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const webfingerSchema = z.object({
|
|
||||||
subject: z.string(),
|
|
||||||
aliases: z.array(z.string()).catch([]),
|
|
||||||
links: z.array(linkSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
type Webfinger = z.infer<typeof webfingerSchema>;
|
|
||||||
|
|
||||||
export { webfingerSchema };
|
|
||||||
export type { Webfinger };
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'deno-safe-fetch/load';
|
||||||
|
|
||||||
import '@/precheck.ts';
|
import '@/precheck.ts';
|
||||||
import '@/sentry.ts';
|
import '@/sentry.ts';
|
||||||
import '@/nostr-wasm.ts';
|
import '@/nostr-wasm.ts';
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
import * as secp from '@noble/secp256k1';
|
|
||||||
import { LRUCache } from 'lru-cache';
|
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { generateSeededRsa, publicKeyToPem } from '@/deps.ts';
|
|
||||||
|
|
||||||
const opts = {
|
|
||||||
bits: 2048,
|
|
||||||
};
|
|
||||||
|
|
||||||
const rsaCache = new LRUCache<string, Promise<string>>({ max: 1000 });
|
|
||||||
|
|
||||||
async function buildSeed(pubkey: string): Promise<string> {
|
|
||||||
const key = await Conf.cryptoKey;
|
|
||||||
const data = new TextEncoder().encode(pubkey);
|
|
||||||
const signature = await window.crypto.subtle.sign('HMAC', key, data);
|
|
||||||
return secp.etc.bytesToHex(new Uint8Array(signature));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getPublicKeyPem(pubkey: string): Promise<string> {
|
|
||||||
const cached = await rsaCache.get(pubkey);
|
|
||||||
if (cached) return cached;
|
|
||||||
|
|
||||||
const seed = await buildSeed(pubkey);
|
|
||||||
const { publicKey } = await generateSeededRsa(seed, opts);
|
|
||||||
const promise = publicKeyToPem(publicKey);
|
|
||||||
|
|
||||||
rsaCache.set(pubkey, promise);
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export { getPublicKeyPem };
|
|
|
@ -1,48 +0,0 @@
|
||||||
import { NSchema as n } from '@nostrify/nostrify';
|
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { getPublicKeyPem } from '@/utils/rsa.ts';
|
|
||||||
|
|
||||||
import type { NostrEvent } from '@nostrify/nostrify';
|
|
||||||
import type { Actor } from '@/schemas/activitypub.ts';
|
|
||||||
|
|
||||||
/** Nostr metadata event to ActivityPub actor. */
|
|
||||||
async function renderActor(event: NostrEvent, username: string): Promise<Actor | undefined> {
|
|
||||||
const content = n.json().pipe(n.metadata()).catch({}).parse(event.content);
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'Person',
|
|
||||||
id: Conf.local(`/users/${username}`),
|
|
||||||
name: content?.name || '',
|
|
||||||
preferredUsername: username,
|
|
||||||
inbox: Conf.local(`/users/${username}/inbox`),
|
|
||||||
followers: Conf.local(`/users/${username}/followers`),
|
|
||||||
following: Conf.local(`/users/${username}/following`),
|
|
||||||
outbox: Conf.local(`/users/${username}/outbox`),
|
|
||||||
icon: content.picture
|
|
||||||
? {
|
|
||||||
type: 'Image',
|
|
||||||
url: content.picture,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
image: content.banner
|
|
||||||
? {
|
|
||||||
type: 'Image',
|
|
||||||
url: content.banner,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
summary: content.about ?? '',
|
|
||||||
attachment: [],
|
|
||||||
tag: [],
|
|
||||||
publicKey: {
|
|
||||||
id: Conf.local(`/users/${username}#main-key`),
|
|
||||||
owner: Conf.local(`/users/${username}`),
|
|
||||||
publicKeyPem: await getPublicKeyPem(event.pubkey),
|
|
||||||
},
|
|
||||||
endpoints: {
|
|
||||||
sharedInbox: Conf.local('/inbox'),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export { renderActor };
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'deno-safe-fetch';
|
import 'deno-safe-fetch/load';
|
||||||
import { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/nostrify';
|
import { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/nostrify';
|
||||||
import { ReadOnlyPolicy } from '@nostrify/nostrify/policies';
|
import { ReadOnlyPolicy } from '@nostrify/nostrify/policies';
|
||||||
import * as Comlink from 'comlink';
|
import * as Comlink from 'comlink';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { NSchema } from '@nostrify/nostrify';
|
import { NSchema } from '@nostrify/nostrify';
|
||||||
import * as Comlink from 'comlink';
|
import * as Comlink from 'comlink';
|
||||||
|
import { DB as Sqlite } from 'deno-sqlite';
|
||||||
|
|
||||||
import { Sqlite } from '@/deps.ts';
|
|
||||||
import { hashtagSchema } from '@/schema.ts';
|
import { hashtagSchema } from '@/schema.ts';
|
||||||
import { generateDateRange, Time } from '@/utils/time.ts';
|
import { generateDateRange, Time } from '@/utils/time.ts';
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue