UNPKG

webfinger-handler

Version:

A library that generates a handler for webfinger requests. The created handler works with Node JS HTTP request and response objects, and is otherwise framework agnostic.

187 lines (186 loc) 5.57 kB
/** * Webfinger * @module webfinger-handler * @see {@link module:webfinger-handler.default} */ /** * Create a handler for incoming webfinger requests * @param {getDescriptor} getDescriptor Returns a JSON Resource Descriptor object * @returns {webfingerHandler} Method for handling webifinger requests */ export default class WebfingerHandler { #getDescriptor; constructor(desc) { this.#getDescriptor = desc; } /** * Webfinger handler * @async * @param {IncomingMessage} req The incomming HTTP request * @param {OutgoingMessage} res The outgoing HTTP response * @returns {Boolean} True if the request was handled, false if the request path did not match the webfinger endpoint */ async handle(req, res) { try { const subject = parse(req); if (!subject) { return false; } const links = (await this.#getDescriptor(subject)) || []; const descriptor = Array.isArray(links) ? { subject: subject.uri, links } : links; send(res, descriptor); } catch (e) { if (e instanceof WebfingerError) { sendError(res, e); } else { throw e; } } return true; } ; static activitypubResponse = activitypubResponse; } /** * An error encountered while processing a webfinger request */ class WebfingerError extends Error { } /** * An error thrown when the incomming Webfinger request uses the wrong HTTP method */ class WebfingerMethodError extends WebfingerError { } /** * An error thrown when the webfinger `resource` paramter is incorrectly formatted */ class WebfingerResourceError extends WebfingerError { } const matcher = /^acct:([^@]+)@(.+)$/i; /** * An object representing an acct: URI */ export class AcctUri { user; host; /** * Construct an instance of AcctUri * @param {Object} options The properties to set on the AcctUri instance * @param {string} user The user part of the account name * @param {string} host The hostname part of the account name */ constructor(user, host) { this.user = user; this.host = host; } /** * Parse a string into an AcctUri object * @param {string} resource An acct: URI * @returns {AcctUri} */ static parse(resource) { const match = resource?.match(matcher); if (!match) { throw new WebfingerResourceError('Expected a resource of the form "acct:username@domain.example"'); } const [, user, host] = match; return new AcctUri(user, host); } /** * The scheme of the URI. This is always `acct:`. */ get scheme() { return 'acct:'; } /** * Alias for `user` */ get userpart() { return this.user; } /** * The account string * Made of the user and host concatenated with the `@` symbol */ get account() { return this.userpart + '@' + this.host; } /** * The full URI including the scheme */ get uri() { return this.scheme + this.account; } toString() { return this.uri; } } export const endpoint = '/.well-known/webfinger'; /** * Parses an incoming http request for the requested acct: URI * @param {IncomingMessage} req The incoming HTTP request * @returns {false} If the request is not for the webfinger endpoint * @returns {AcctUri} An object representing the requested account * @throws {WebfingerMethodError} If the GET method is not used in the request */ function parse(req) { const url = new URL(req.url, 'file://'); if (url.pathname !== endpoint) { return false; } if (req.method !== 'GET') { throw new WebfingerMethodError('This endpoint only accepts GET requests'); } const resource = url.searchParams.get('resource'); return AcctUri.parse(resource); } /** * Informs the user about an error that occured with the webfinger request * @param {HttpResponse} res The node HTTP response object * @param {WebfingerError} error The error to report back to the user */ function sendError(res, error) { res.setHeader('Access-Control-Allow-Origin', '*'); if (error instanceof WebfingerMethodError) { res.statusCode = 405; } else { res.statusCode = 400; } res.statusMessage = error.message; res.end(error.message); } /** * Sends a JSON RD response body to the client * @param {HttpResponse} res The node HTTP response object * @param {object} body The payload to return to the user */ function send(res, body) { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Content-Type', 'application/jrd+json'); res.end(JSON.stringify(body)); } /** * Get a link for an activitypub actor * @callback getActorLink * @async * @param {AcctUri} resource The acct: uri of the resource to fetch for * @returns {(string|object)} Either the href of the actor, or the JRD link representation of the actor */ /** * Generates a getDescriptor method specifically for activitypub resource * @param {getActorLink} getActivitypubLink Method that returns an href representing an activitypub actor * @returns {getDescriptor} Method that can be used as the Webfinger descriptor getter */ export function activitypubResponse(href) { return [ { rel: 'self', type: 'application/activity+json', href } ]; }