style: format
parent
fe109f7fdb
commit
bf09166c51
|
@ -1,77 +1,98 @@
|
||||||
declare module '@peertube/http-signature' {
|
declare module "@peertube/http-signature" {
|
||||||
import type { IncomingMessage, ClientRequest } from 'node:http';
|
import type { IncomingMessage, ClientRequest } from "node:http";
|
||||||
|
|
||||||
interface ISignature {
|
interface ISignature {
|
||||||
keyId: string;
|
keyId: string;
|
||||||
algorithm: string;
|
algorithm: string;
|
||||||
headers: string[];
|
headers: string[];
|
||||||
signature: string;
|
signature: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IOptions {
|
interface IOptions {
|
||||||
headers?: string[];
|
headers?: string[];
|
||||||
algorithm?: string;
|
algorithm?: string;
|
||||||
strict?: boolean;
|
strict?: boolean;
|
||||||
authorizationHeaderName?: string;
|
authorizationHeaderName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IParseRequestOptions extends IOptions {
|
interface IParseRequestOptions extends IOptions {
|
||||||
clockSkew?: number;
|
clockSkew?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IParsedSignature {
|
interface IParsedSignature {
|
||||||
scheme: string;
|
scheme: string;
|
||||||
params: ISignature;
|
params: ISignature;
|
||||||
signingString: string;
|
signingString: string;
|
||||||
algorithm: string;
|
algorithm: string;
|
||||||
keyId: string;
|
keyId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestSignerConstructorOptions =
|
type RequestSignerConstructorOptions =
|
||||||
IRequestSignerConstructorOptionsFromProperties |
|
| IRequestSignerConstructorOptionsFromProperties
|
||||||
IRequestSignerConstructorOptionsFromFunction;
|
| IRequestSignerConstructorOptionsFromFunction;
|
||||||
|
|
||||||
interface IRequestSignerConstructorOptionsFromProperties {
|
interface IRequestSignerConstructorOptionsFromProperties {
|
||||||
keyId: string;
|
keyId: string;
|
||||||
key: string | Buffer;
|
key: string | Buffer;
|
||||||
algorithm?: string;
|
algorithm?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRequestSignerConstructorOptionsFromFunction {
|
interface IRequestSignerConstructorOptionsFromFunction {
|
||||||
sign?: (data: string, cb: (err: any, sig: ISignature) => void) => void;
|
sign?: (data: string, cb: (err: any, sig: ISignature) => void) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RequestSigner {
|
class RequestSigner {
|
||||||
constructor(options: RequestSignerConstructorOptions);
|
constructor(options: RequestSignerConstructorOptions);
|
||||||
|
|
||||||
public writeHeader(header: string, value: string): string;
|
public writeHeader(header: string, value: string): string;
|
||||||
|
|
||||||
public writeDateHeader(): string;
|
public writeDateHeader(): string;
|
||||||
|
|
||||||
public writeTarget(method: string, path: string): void;
|
public writeTarget(method: string, path: string): void;
|
||||||
|
|
||||||
public sign(cb: (err: any, authz: string) => void): void;
|
public sign(cb: (err: any, authz: string) => void): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISignRequestOptions extends IOptions {
|
interface ISignRequestOptions extends IOptions {
|
||||||
keyId: string;
|
keyId: string;
|
||||||
key: string;
|
key: string;
|
||||||
httpVersion?: string;
|
httpVersion?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parse(request: IncomingMessage, options?: IParseRequestOptions): IParsedSignature;
|
export function parse(
|
||||||
export function parseRequest(request: IncomingMessage, options?: IParseRequestOptions): IParsedSignature;
|
request: IncomingMessage,
|
||||||
|
options?: IParseRequestOptions
|
||||||
|
): IParsedSignature;
|
||||||
|
export function parseRequest(
|
||||||
|
request: IncomingMessage,
|
||||||
|
options?: IParseRequestOptions
|
||||||
|
): IParsedSignature;
|
||||||
|
|
||||||
export function sign(request: ClientRequest, options: ISignRequestOptions): boolean;
|
export function sign(
|
||||||
export function signRequest(request: ClientRequest, options: ISignRequestOptions): boolean;
|
request: ClientRequest,
|
||||||
export function createSigner(): RequestSigner;
|
options: ISignRequestOptions
|
||||||
export function isSigner(obj: any): obj is RequestSigner;
|
): boolean;
|
||||||
|
export function signRequest(
|
||||||
|
request: ClientRequest,
|
||||||
|
options: ISignRequestOptions
|
||||||
|
): boolean;
|
||||||
|
export function createSigner(): RequestSigner;
|
||||||
|
export function isSigner(obj: any): obj is RequestSigner;
|
||||||
|
|
||||||
export function sshKeyToPEM(key: string): string;
|
export function sshKeyToPEM(key: string): string;
|
||||||
export function sshKeyFingerprint(key: string): string;
|
export function sshKeyFingerprint(key: string): string;
|
||||||
export function pemToRsaSSHKey(pem: string, comment: string): string;
|
export function pemToRsaSSHKey(pem: string, comment: string): string;
|
||||||
|
|
||||||
export function verify(parsedSignature: IParsedSignature, pubkey: string | Buffer): boolean;
|
export function verify(
|
||||||
export function verifySignature(parsedSignature: IParsedSignature, pubkey: string | Buffer): boolean;
|
parsedSignature: IParsedSignature,
|
||||||
export function verifyHMAC(parsedSignature: IParsedSignature, secret: string): boolean;
|
pubkey: string | Buffer
|
||||||
}
|
): boolean;
|
||||||
|
export function verifySignature(
|
||||||
|
parsedSignature: IParsedSignature,
|
||||||
|
pubkey: string | Buffer
|
||||||
|
): boolean;
|
||||||
|
export function verifyHMAC(
|
||||||
|
parsedSignature: IParsedSignature,
|
||||||
|
secret: string
|
||||||
|
): boolean;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { env } from "process"
|
import { env } from "process";
|
||||||
|
|
||||||
export const ACCEPT_HEADER = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
export const ACCEPT_HEADER =
|
||||||
export const USER_AGENT = `xtex-home/1.0@${env['VERCEL_GIT_COMMIT_SHA']} (${env['NEXT_PUBLIC_VERCEL_URL']})`
|
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
||||||
|
export const USER_AGENT = `xtex-home/1.0@${env["VERCEL_GIT_COMMIT_SHA"]} (${env["NEXT_PUBLIC_VERCEL_URL"]})`;
|
||||||
|
|
|
@ -1,35 +1,35 @@
|
||||||
import { signRequest } from "@peertube/http-signature";
|
import { signRequest } from "@peertube/http-signature";
|
||||||
import { BaseEntity } from "activitypub-core-types/lib/activitypub/Core/Entity"
|
import { BaseEntity } from "activitypub-core-types/lib/activitypub/Core/Entity";
|
||||||
import { ClientRequest } from "http";
|
import { ClientRequest } from "http";
|
||||||
import { env } from "process";
|
import { env } from "process";
|
||||||
import { ACCEPT_HEADER, USER_AGENT } from "./consts"
|
import { ACCEPT_HEADER, USER_AGENT } from "./consts";
|
||||||
|
|
||||||
export async function deliveryAPActivity(url: URL, doc: BaseEntity) {
|
export async function deliveryAPActivity(url: URL, doc: BaseEntity) {
|
||||||
console.log(`deliverying AP document ${url}`)
|
console.log(`deliverying AP document ${url}`);
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
url: url.toString(),
|
url: url.toString(),
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Date': new Date().toUTCString(),
|
Date: new Date().toUTCString(),
|
||||||
'Host': url.hostname,
|
Host: url.hostname,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
const signature = signRequest(request as unknown as ClientRequest, {
|
const signature = signRequest(request as unknown as ClientRequest, {
|
||||||
key: env['XTEX_HOME_AP_PRIV_KEY']!!,
|
key: env["XTEX_HOME_AP_PRIV_KEY"]!!,
|
||||||
keyId: 'XTEX-HOME-AP-INSTANCE-ACTOR',
|
keyId: "XTEX-HOME-AP-INSTANCE-ACTOR",
|
||||||
})
|
});
|
||||||
console.log(signature)
|
console.log(signature);
|
||||||
|
|
||||||
const result = await fetch(url, {
|
const result = await fetch(url, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
body: JSON.stringify(doc),
|
body: JSON.stringify(doc),
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': USER_AGENT,
|
"User-Agent": USER_AGENT,
|
||||||
'Accept': ACCEPT_HEADER,
|
Accept: ACCEPT_HEADER,
|
||||||
'Content-Type': 'application/activity+json',
|
"Content-Type": "application/activity+json",
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
console.log(`delivered AP doc ${url}: ${result.status}`)
|
console.log(`delivered AP doc ${url}: ${result.status}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,40 @@
|
||||||
import { CoreObject, Entity, EntityReference, Link } from "activitypub-core-types/lib/activitypub";
|
import {
|
||||||
|
CoreObject,
|
||||||
|
Entity,
|
||||||
|
EntityReference,
|
||||||
|
Link,
|
||||||
|
} from "activitypub-core-types/lib/activitypub";
|
||||||
import { BaseEntity } from "activitypub-core-types/lib/activitypub/Core/Entity";
|
import { BaseEntity } from "activitypub-core-types/lib/activitypub/Core/Entity";
|
||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
import { ACCEPT_HEADER, USER_AGENT } from "./consts";
|
import { ACCEPT_HEADER, USER_AGENT } from "./consts";
|
||||||
|
|
||||||
export async function resolveApEntity(ref: EntityReference): Promise<Entity> {
|
export async function resolveApEntity(ref: EntityReference): Promise<Entity> {
|
||||||
if (typeof ref == 'string') {
|
if (typeof ref == "string") {
|
||||||
return await getApDocument(ref) as Entity
|
return (await getApDocument(ref)) as Entity;
|
||||||
} else if (typeof (ref as Link).href == 'string') {
|
} else if (typeof (ref as Link).href == "string") {
|
||||||
return resolveApEntity((ref as Link).href!!)
|
return resolveApEntity((ref as Link).href!!);
|
||||||
} else if (typeof (ref as CoreObject).type == 'string') {
|
} else if (typeof (ref as CoreObject).type == "string") {
|
||||||
return ref as CoreObject
|
return ref as CoreObject;
|
||||||
} else {
|
} else {
|
||||||
throw `${ref} cannot be resolved as a AP entity`
|
throw `${ref} cannot be resolved as a AP entity`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getApDocument(url: URL): Promise<BaseEntity> {
|
export async function getApDocument(url: URL): Promise<BaseEntity> {
|
||||||
console.log(`resolving AP document ${url}, UA: ${USER_AGENT}`)
|
console.log(`resolving AP document ${url}, UA: ${USER_AGENT}`);
|
||||||
const result = await fetch(url, {
|
const result = await fetch(url, {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': USER_AGENT,
|
"User-Agent": USER_AGENT,
|
||||||
'Accept': ACCEPT_HEADER,
|
Accept: ACCEPT_HEADER,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
console.log(`resolved AP doc ${url}: ${result.status}`)
|
console.log(`resolved AP doc ${url}: ${result.status}`);
|
||||||
const json = await result.text()
|
const json = await result.text();
|
||||||
try {
|
try {
|
||||||
return JSON.parse(json) as BaseEntity
|
return JSON.parse(json) as BaseEntity;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(json)
|
console.error(json);
|
||||||
throw e
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,71 +6,95 @@ const nextConfig = {
|
||||||
async redirects() {
|
async redirects() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: '/blog/:path*',
|
source: "/blog/:path*",
|
||||||
destination: 'https://blog.xtexx.ml/:path*',
|
destination: "https://blog.xtexx.ml/:path*",
|
||||||
permanent: true,
|
permanent: true,
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
},
|
},
|
||||||
async headers() {//application/ld+json; profile="https://www.w3.org/ns/activitystreams"
|
async headers() {
|
||||||
const nodeinfo = {
|
const nodeinfo = {
|
||||||
key: 'Content-Type',
|
key: "Content-Type",
|
||||||
value: 'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.1#"',
|
value:
|
||||||
}
|
'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.1#"',
|
||||||
|
};
|
||||||
|
const cors = [
|
||||||
|
{
|
||||||
|
key: "Access-Control-Allow-Origin",
|
||||||
|
value: "*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Access-Control-Expose-Headers",
|
||||||
|
value: "*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Access-Control-Max-Age",
|
||||||
|
value: "86400",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Access-Control-Allow-Methods",
|
||||||
|
value: "*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Access-Control-Allow-Headers",
|
||||||
|
value: "*",
|
||||||
|
},
|
||||||
|
];
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: '/.well-known/nodeinfo',
|
source: "/.well-known/nodeinfo",
|
||||||
headers: [nodeinfo],
|
headers: [nodeinfo],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: '/nodeinfo.json',
|
source: "/nodeinfo.json",
|
||||||
headers: [nodeinfo],
|
headers: [nodeinfo],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: '/.well-known/host-meta',
|
source: "/.well-known/host-meta",
|
||||||
headers: [
|
headers: [
|
||||||
{
|
{
|
||||||
key: 'Content-Type',
|
key: "Content-Type",
|
||||||
value: 'application/xrd+xml',
|
value: "application/xrd+xml",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: '/.well-known/matrix/:path*',
|
source: "/.well-known/matrix/:path*",
|
||||||
headers: [
|
headers: [
|
||||||
{
|
{
|
||||||
key: 'Content-Type',
|
key: "Content-Type",
|
||||||
value: 'application/json; charset=utf-8',
|
value: "application/json; charset=utf-8",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: '/ap/:path*',
|
source: "/ap/:path*",
|
||||||
headers: [
|
headers: [
|
||||||
{
|
{
|
||||||
key: 'Content-Type',
|
key: "Content-Type",
|
||||||
value: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8',
|
value:
|
||||||
|
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
},
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: '/.well-known/host-meta.json',
|
source: "/.well-known/host-meta.json",
|
||||||
destination: '/api/host-meta.json',
|
destination: "/api/host-meta.json",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: '/.well-known/webfinger',
|
source: "/.well-known/webfinger",
|
||||||
destination: '/api/webfinger',
|
destination: "/api/webfinger",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: '/ap/api/:path*',
|
source: "/ap/api/:path*",
|
||||||
destination: '/api/ap/:path*',
|
destination: "/api/ap/:path*",
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = nextConfig
|
module.exports = nextConfig;
|
||||||
|
|
|
@ -1,39 +1,60 @@
|
||||||
import * as httpSignature from '@peertube/http-signature'
|
import * as httpSignature from "@peertube/http-signature";
|
||||||
import { Activity, ActivityTypes, Actor, Reject } from 'activitypub-core-types/lib/activitypub'
|
import {
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
Activity,
|
||||||
import { deliveryAPActivity } from '../../../ap/delivery'
|
ActivityTypes,
|
||||||
import { resolveApEntity } from '../../../ap/resolver'
|
Actor,
|
||||||
|
Reject,
|
||||||
|
} from "activitypub-core-types/lib/activitypub";
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { deliveryAPActivity } from "../../../ap/delivery";
|
||||||
|
import { resolveApEntity } from "../../../ap/resolver";
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse
|
res: NextApiResponse
|
||||||
) {
|
) {
|
||||||
const signature = req.headers['signature']
|
const signature = req.headers["signature"];
|
||||||
const activity = req.body as Activity
|
const activity = req.body as Activity;
|
||||||
if (signature == null) {
|
if (signature == null) {
|
||||||
return res.status(400).send('no signature')
|
return res.status(400).send("no signature");
|
||||||
|
}
|
||||||
|
if (activity.actor instanceof Array || !activity.actor) {
|
||||||
|
return res.status(400).send("actor is more than one AS entity ref");
|
||||||
|
}
|
||||||
|
const actor = (await resolveApEntity(activity.actor)) as Actor;
|
||||||
|
if (
|
||||||
|
!httpSignature.verifySignature(
|
||||||
|
httpSignature.parseRequest(req),
|
||||||
|
actor.publicKey!!.publicKeyPem
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
console.error(
|
||||||
|
`signature check failed, actor: ${actor}, provided: ${signature}`
|
||||||
|
);
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.send(`signature check failed, expected: ${actor.publicKey}`);
|
||||||
|
}
|
||||||
|
if (activity.type == ActivityTypes.FOLLOW) {
|
||||||
|
// follow request
|
||||||
|
console.log(`sending follow Reject to ${actor.inbox}`);
|
||||||
|
if (actor.inbox! instanceof URL) {
|
||||||
|
return res.status(400).send("inbox is not a standalone doc");
|
||||||
}
|
}
|
||||||
if (activity.actor instanceof Array || !activity.actor) {
|
await deliveryAPActivity(
|
||||||
return res.status(400).send('actor is more than one AS entity ref')
|
actor.inbox as unknown as URL,
|
||||||
}
|
{
|
||||||
const actor = (await resolveApEntity(activity.actor) as Actor)
|
type: ActivityTypes.REJECT,
|
||||||
if (!httpSignature.verifySignature(httpSignature.parseRequest(req), actor.publicKey!!.publicKeyPem)) {
|
id: new URL(
|
||||||
console.error(`signature check failed, actor: ${actor}, provided: ${signature}`)
|
`https://xtexx.ml/ap/reject_follows/${encodeURI(
|
||||||
return res.status(400).send(`signature check failed, expected: ${actor.publicKey}`)
|
actor.id!!.toString()
|
||||||
}
|
)}`
|
||||||
if (activity.type == ActivityTypes.FOLLOW) {
|
),
|
||||||
// follow request
|
actor: new URL("https://xtexx.ml/ap/actor.json"),
|
||||||
console.log(`sending follow Reject to ${actor.inbox}`)
|
object: activity.id,
|
||||||
if (actor.inbox! instanceof URL) {
|
target: actor.id,
|
||||||
return res.status(400).send('inbox is not a standalone doc')
|
} as Reject
|
||||||
}
|
);
|
||||||
await deliveryAPActivity(actor.inbox as unknown as URL, {
|
}
|
||||||
type: ActivityTypes.REJECT,
|
res.status(200).end();
|
||||||
id: new URL(`https://xtexx.ml/ap/reject_follows/${encodeURI(actor.id!!.toString())}`),
|
|
||||||
actor: new URL('https://xtexx.ml/ap/actor.json'),
|
|
||||||
object: activity.id,
|
|
||||||
target: actor.id,
|
|
||||||
} as Reject)
|
|
||||||
}
|
|
||||||
res.status(200).end()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
export default function handler(
|
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
req: NextApiRequest,
|
console.log(req.body);
|
||||||
res: NextApiResponse
|
console.log(JSON.stringify(req.body));
|
||||||
) {
|
res.status(200).end();
|
||||||
console.log(req.body)
|
|
||||||
console.log(JSON.stringify(req.body))
|
|
||||||
res.status(200).end()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import { Link } from 'activitypub-core-types/lib/activitypub'
|
import { Link } from "activitypub-core-types/lib/activitypub";
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import site_lrs from '../../data/site_lrs.json'
|
import site_lrs from "../../data/site_lrs.json";
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
links: Link[],
|
links: Link[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function handler(
|
export default function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse<Data>
|
res: NextApiResponse<Data>
|
||||||
) {
|
) {
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
links: site_lrs as Link[],
|
links: site_lrs as Link[],
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +1,75 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import site_lrs from '../../data/site_lrs.json'
|
import site_lrs from "../../data/site_lrs.json";
|
||||||
import webfingerData from '../../data/webfinger.json'
|
import webfingerData from "../../data/webfinger.json";
|
||||||
import { collect as collectEcho } from './whoami'
|
import { collect as collectEcho } from "./whoami";
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
subject: string,
|
subject: string;
|
||||||
aliases: string[] | undefined,
|
aliases: string[] | undefined;
|
||||||
links: any[],
|
links: any[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export function lookupData(username: string): {
|
export function lookupData(username: string):
|
||||||
aliases: string[],
|
| {
|
||||||
links: any[]
|
aliases: string[];
|
||||||
} | undefined {
|
links: any[];
|
||||||
let data = (webfingerData as any[string])[username]
|
}
|
||||||
if (data != undefined && data.aliasTo != undefined)
|
| undefined {
|
||||||
return lookupData(data.aliasTo)
|
let data = (webfingerData as any[string])[username];
|
||||||
return data
|
if (data != undefined && data.aliasTo != undefined)
|
||||||
|
return lookupData(data.aliasTo);
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function handler(
|
export default function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse<Data>
|
res: NextApiResponse<Data>
|
||||||
) {
|
) {
|
||||||
let uri: string = req.query['resource'] as string
|
let uri: string = req.query["resource"] as string;
|
||||||
if (uri == undefined) {
|
if (uri == undefined) {
|
||||||
res.status(400).end('"resource" query param is not provided')
|
res.status(400).end('"resource" query param is not provided');
|
||||||
return
|
return;
|
||||||
|
}
|
||||||
|
if (!uri.startsWith("acct:")) {
|
||||||
|
res.status(404).end("Only acct urls are allowed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (uri.indexOf("@") == -1) {
|
||||||
|
res.status(404).end("Username is not provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let username = uri.substring(5, uri.indexOf("@")).toLowerCase();
|
||||||
|
if (username.startsWith("//")) username = username.substring(2);
|
||||||
|
let aliases: string[] = [];
|
||||||
|
let links: any[] = [];
|
||||||
|
switch (username) {
|
||||||
|
case "this": {
|
||||||
|
links = site_lrs;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (!uri.startsWith('acct:')) {
|
case "echo": {
|
||||||
res.status(404).end('Only acct urls are allowed')
|
links = [
|
||||||
return
|
{
|
||||||
|
rel: "contents",
|
||||||
|
href: JSON.stringify(collectEcho(req)),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (uri.indexOf('@') == -1) {
|
default: {
|
||||||
res.status(404).end('Username is not provided')
|
let result = lookupData(username);
|
||||||
return
|
if (result == undefined) {
|
||||||
|
res.status(404).end(`User "${username}" not found`);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
aliases = result.aliases;
|
||||||
|
links = result.links;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let username = uri.substring(5, uri.indexOf('@')).toLowerCase()
|
}
|
||||||
if (username.startsWith('//'))
|
res.status(200).json({
|
||||||
username = username.substring(2)
|
subject: uri,
|
||||||
let aliases: string[] = []
|
aliases: aliases != undefined && aliases.length == 0 ? undefined : aliases,
|
||||||
let links: any[] = []
|
links,
|
||||||
switch (username) {
|
});
|
||||||
case 'this': {
|
|
||||||
links = site_lrs
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'echo': {
|
|
||||||
links = [
|
|
||||||
{
|
|
||||||
rel: 'contents',
|
|
||||||
href: JSON.stringify(collectEcho(req)),
|
|
||||||
}
|
|
||||||
]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
let result = lookupData(username)
|
|
||||||
if (result == undefined) {
|
|
||||||
res.status(404).end(`User "${username}" not found`)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
aliases = result.aliases
|
|
||||||
links = result.links
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.status(200).json({
|
|
||||||
subject: uri,
|
|
||||||
aliases: (aliases != undefined && aliases.length == 0) ? undefined : aliases,
|
|
||||||
links,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import type { IncomingHttpHeaders } from 'node:http'
|
import type { IncomingHttpHeaders } from "node:http";
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
httpVersion: string,
|
httpVersion: string;
|
||||||
headers: IncomingHttpHeaders,
|
headers: IncomingHttpHeaders;
|
||||||
address: string,
|
address: string;
|
||||||
port: number,
|
port: number;
|
||||||
ipv6: boolean,
|
ipv6: boolean;
|
||||||
method: string,
|
method: string;
|
||||||
userAgent: string | undefined,
|
userAgent: string | undefined;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function collect(req: NextApiRequest): Data {
|
export function collect(req: NextApiRequest): Data {
|
||||||
return {
|
return {
|
||||||
|
@ -17,15 +17,15 @@ export function collect(req: NextApiRequest): Data {
|
||||||
headers: req.headers,
|
headers: req.headers,
|
||||||
address: req.socket.remoteAddress!,
|
address: req.socket.remoteAddress!,
|
||||||
port: req.socket.remotePort!,
|
port: req.socket.remotePort!,
|
||||||
ipv6: req.socket.remoteFamily == 'IPv6',
|
ipv6: req.socket.remoteFamily == "IPv6",
|
||||||
method: req.method!,
|
method: req.method!,
|
||||||
userAgent: req.headers["user-agent"],
|
userAgent: req.headers["user-agent"],
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function handler(
|
export default function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse<Data>
|
res: NextApiResponse<Data>
|
||||||
) {
|
) {
|
||||||
res.status(200).json(collect(req))
|
res.status(200).json(collect(req));
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue