Implement Webfinger
This commit is contained in:
parent
215ff85b6d
commit
cf78b721f5
|
@ -28,6 +28,7 @@ import {
|
||||||
import { streamingController } from './controllers/api/streaming.ts';
|
import { streamingController } from './controllers/api/streaming.ts';
|
||||||
import { indexController } from './controllers/site.ts';
|
import { indexController } from './controllers/site.ts';
|
||||||
import { nostrController } from './controllers/well-known/nostr.ts';
|
import { nostrController } from './controllers/well-known/nostr.ts';
|
||||||
|
import { hostMetaController, webfingerController } from './controllers/well-known/webfinger.ts';
|
||||||
import { auth19, requireAuth } from './middleware/auth19.ts';
|
import { auth19, requireAuth } from './middleware/auth19.ts';
|
||||||
import { auth98 } from './middleware/auth98.ts';
|
import { auth98 } from './middleware/auth98.ts';
|
||||||
|
|
||||||
|
@ -57,6 +58,8 @@ app.get('/api/v1/streaming/', streamingController);
|
||||||
|
|
||||||
app.use('*', cors({ origin: '*', exposeHeaders: ['link'] }), auth19, auth98());
|
app.use('*', cors({ origin: '*', exposeHeaders: ['link'] }), auth19, auth98());
|
||||||
|
|
||||||
|
app.get('/.well-known/webfinger', webfingerController);
|
||||||
|
app.get('/.well-known/host-meta', hostMetaController);
|
||||||
app.get('/.well-known/nostr.json', nostrController);
|
app.get('/.well-known/nostr.json', nostrController);
|
||||||
|
|
||||||
app.get('/api/v1/instance', instanceController);
|
app.get('/api/v1/instance', instanceController);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
import { Conf } from '@/config.ts';
|
||||||
import { db } from '@/db.ts';
|
import { db } from '@/db.ts';
|
||||||
import { z } from '@/deps.ts';
|
import { z } from '@/deps.ts';
|
||||||
|
|
||||||
import type { AppController } from '@/app.ts';
|
import type { AppController } from '@/app.ts';
|
||||||
import { Conf } from '../../config.ts';
|
|
||||||
|
|
||||||
const nameSchema = z.string().min(1).regex(/^\w+$/);
|
const nameSchema = z.string().min(1).regex(/^\w+$/);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { Conf } from '@/config.ts';
|
||||||
|
import { db } from '@/db.ts';
|
||||||
|
import { nip19, z } from '@/deps.ts';
|
||||||
|
import { urlTransformSchema } from '@/schema.ts';
|
||||||
|
|
||||||
|
import type { AppController } from '@/app.ts';
|
||||||
|
import type { Webfinger } from '@/schemas/webfinger.ts';
|
||||||
|
|
||||||
|
const webfingerController: AppController = async (c) => {
|
||||||
|
const { hostname } = new URL(Conf.localDomain);
|
||||||
|
|
||||||
|
/** Transforms the resource URI into a `[username, domain]` tuple. */
|
||||||
|
const acctSchema = urlTransformSchema
|
||||||
|
.refine((uri) => uri.protocol === 'acct:', 'Protocol must be `acct:`')
|
||||||
|
.refine((uri) => z.string().email().safeParse(uri.pathname).success, 'Invalid acct')
|
||||||
|
.transform((uri) => uri.pathname.split('@') as [username: string, host: string])
|
||||||
|
.refine(([_username, host]) => host === hostname, 'Host must be local');
|
||||||
|
|
||||||
|
const result = acctSchema.safeParse(c.req.query('resource'));
|
||||||
|
if (!result.success) {
|
||||||
|
return c.json({ error: 'Bad request', schema: result.error }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await db.users.findFirst({ where: { username: result.data[0] } });
|
||||||
|
c.header('content-type', 'application/jrd+json');
|
||||||
|
return c.body(JSON.stringify(renderWebfinger(user)));
|
||||||
|
} catch (_e) {
|
||||||
|
return c.json({ error: 'Not found' }, 404);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hostMetaController: AppController = (c) => {
|
||||||
|
const template = Conf.url('/.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>`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface RenderWebfingerOpts {
|
||||||
|
pubkey: string;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Present Nostr user on Webfinger. */
|
||||||
|
function renderWebfinger({ pubkey, username }: RenderWebfingerOpts): Webfinger {
|
||||||
|
const { host } = new URL(Conf.localDomain);
|
||||||
|
const apId = Conf.url(`/users/${username}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subject: `acct:${username}@${host}`,
|
||||||
|
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 { hostMetaController, webfingerController };
|
|
@ -95,6 +95,20 @@ const decode64Schema = z.string().transform((value, ctx) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** Transforms a string into a `URL` object. */
|
||||||
|
const urlTransformSchema = z.string().transform((val, ctx) => {
|
||||||
|
try {
|
||||||
|
return new URL(val);
|
||||||
|
} catch (_e) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'Invalid URI',
|
||||||
|
fatal: true,
|
||||||
|
});
|
||||||
|
return z.NEVER;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export {
|
export {
|
||||||
decode64Schema,
|
decode64Schema,
|
||||||
emojiTagSchema,
|
emojiTagSchema,
|
||||||
|
@ -106,4 +120,5 @@ export {
|
||||||
parseRelay,
|
parseRelay,
|
||||||
relaySchema,
|
relaySchema,
|
||||||
signedEventSchema,
|
signedEventSchema,
|
||||||
|
urlTransformSchema,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { z } from '@/deps.ts';
|
||||||
|
|
||||||
|
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 };
|
Loading…
Reference in New Issue