enigma-bbs/core/servers/content/web_handlers/webfinger.js

160 lines
4.5 KiB
JavaScript
Raw Normal View History

2022-12-31 05:39:39 +00:00
const { ServerModule } = require('../../../server_module');
const Config = require('../../../config').get;
const { Errors } = require('../../../enig_error');
2022-12-31 05:39:39 +00:00
const WebServerPackageName = require('../web').moduleInfo.packageName;
const _ = require('lodash');
const User = require('../../../user');
const Log = require('../../../logger').log;
2022-12-31 05:39:39 +00:00
exports.moduleInfo = {
name: 'WebFinger',
desc: 'A simple WebFinger Handler',
2022-12-31 05:39:39 +00:00
author: 'NuSkooler',
packageName: 'codes.l33t.enigma.web.handler.finger',
2022-12-31 05:39:39 +00:00
};
exports.getModule = class WebFingerServerModule extends ServerModule {
constructor() {
super();
}
init(cb) {
if (!_.get(Config(), 'contentServers.web.handlers.webFinger.enabled')) {
return cb(null);
}
const { getServer } = require('../../../listening_server');
// we rely on the web server
this.webServer = getServer(WebServerPackageName);
if (!this.webServer || !this.webServer.instance.isEnabled()) {
return cb(Errors.UnexpectedState('Cannot access web server!'));
2022-12-31 05:39:39 +00:00
}
this.webServer.instance.addRoute({
method: 'GET',
path: /^\/\.well-known\/webfinger\/?\?/,
handler: this._webFingerRequestHandler.bind(this),
});
return cb(null);
}
_webFingerRequestHandler(req, resp) {
const url = new URL(req.url, `https://${req.headers.host}`);
const resource = url.searchParams.get('resource');
if (!resource) {
return this.webServer.instance.respondWithError(
resp,
400,
'"resource" is required',
'Missing "resource"'
);
}
this._getUser(resource, resp, (err, user, accountName) => {
if (err) {
// |resp| already written to
return Log.warn({ error: err.message }, `WebFinger failed: ${req.url}`);
}
const body = JSON.stringify({
subject: `acct:${accountName}`,
2022-12-31 19:05:59 +00:00
aliases: [this._profileUrl(user), this._selfUrl(user)],
links: [this._profilePageLink(user), this._selfLink(user), this._subscribeLink()],
});
const headers = {
'Content-Type': 'application/jrd+json',
'Content-Length': body.length,
};
resp.writeHead(200, headers);
return resp.end(body);
});
}
2022-12-31 19:05:59 +00:00
_profileUrl(user) {
return this.webServer.instance.buildUrl(`/wf/@${user.username}`);
}
_profilePageLink(user) {
2022-12-31 19:05:59 +00:00
const href = this._profileUrl(user);
return {
rel: 'http://webfinger.net/rel/profile-page',
type: 'text/plain',
href,
};
}
2022-12-31 19:05:59 +00:00
_selfUrl(user) {
return this.webServer.instance.buildUrl(`/users/${user.username}`);
}
_selfLink(user) {
const href = this._selfUrl(user);
return {
rel: 'self',
type: 'application/activity+json',
href,
};
}
_subscribeLink() {
return {
rel: 'http://ostatus.org/schema/1.0/subscribe',
template: this.webServer.instance.buildUrl('/authorize_interaction?uri={uri}'),
};
}
_getUser(resource, resp, cb) {
2022-12-31 19:05:59 +00:00
// we only handle "acct:NAME" URIs
const notFound = () => {
this.webServer.instance.respondWithError(
resp,
404,
'Resource not found',
'Resource Not Found'
);
};
const acctIndex = resource.indexOf('acct:', 0);
if (0 != acctIndex) {
notFound();
return cb(Errors.DoesNotExist('"acct:" missing'));
}
const accountName = resource.substring(acctIndex + 5);
const domain = _.get(Config(), 'contentServers.web.domain', 'localhost');
if (!accountName.endsWith(`@${domain}`)) {
notFound();
return cb(Errors.Invalid(`Invalid "acct" value: ${accountName}`));
}
const searchQuery = accountName.substring(
0,
accountName.length - (domain.length + 1)
);
User.getUserIdAndName(searchQuery, (err, userId) => {
if (err) {
notFound();
return cb(err);
}
User.getUser(userId, (err, user) => {
if (err) {
notFound();
return cb(err);
}
return cb(null, user, accountName);
});
});
2022-12-31 05:39:39 +00:00
}
};