Updates to WebFinger resource parsing
This commit is contained in:
parent
3db35bc5b6
commit
7b5cb165ee
|
@ -28,9 +28,10 @@ function getServer(packageName) {
|
||||||
|
|
||||||
function startListening(cb) {
|
function startListening(cb) {
|
||||||
const moduleUtil = require('./module_util.js'); // late load so we get Config
|
const moduleUtil = require('./module_util.js'); // late load so we get Config
|
||||||
|
const cats = moduleUtil.moduleCategories;
|
||||||
|
|
||||||
async.each(
|
async.each(
|
||||||
['login', 'content', 'chat'],
|
[cats.Login, cats.Content, cats.Chat],
|
||||||
(category, next) => {
|
(category, next) => {
|
||||||
moduleUtil.loadModulesForCategory(
|
moduleUtil.loadModulesForCategory(
|
||||||
`${category}Servers`,
|
`${category}Servers`,
|
||||||
|
|
|
@ -21,6 +21,14 @@ exports.loadModulesForCategory = loadModulesForCategory;
|
||||||
exports.getModulePaths = getModulePaths;
|
exports.getModulePaths = getModulePaths;
|
||||||
exports.initializeModules = initializeModules;
|
exports.initializeModules = initializeModules;
|
||||||
|
|
||||||
|
exports.moduleCategories = {
|
||||||
|
Login: 'login',
|
||||||
|
Content: 'content',
|
||||||
|
Chat: 'chat',
|
||||||
|
ScannerTossers: 'scannerTossers',
|
||||||
|
WebHandlers: 'webHandlers',
|
||||||
|
};
|
||||||
|
|
||||||
function loadModuleEx(options, cb) {
|
function loadModuleEx(options, cb) {
|
||||||
assert(_.isObject(options));
|
assert(_.isObject(options));
|
||||||
assert(_.isString(options.name));
|
assert(_.isString(options.name));
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const loadModulesForCategory = require('./module_util.js').loadModulesForCategory;
|
const { loadModulesForCategory, moduleCategories } = require('./module_util');
|
||||||
|
|
||||||
// standard/deps
|
// standard/deps
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
@ -18,7 +18,7 @@ function startup(cb) {
|
||||||
[
|
[
|
||||||
function loadModules(callback) {
|
function loadModules(callback) {
|
||||||
loadModulesForCategory(
|
loadModulesForCategory(
|
||||||
'scannerTossers',
|
moduleCategories.ScannerTossers,
|
||||||
(module, nextModule) => {
|
(module, nextModule) => {
|
||||||
const modInst = new module.getModule();
|
const modInst = new module.getModule();
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ const mimeTypes = require('mime-types');
|
||||||
const forEachSeries = require('async/forEachSeries');
|
const forEachSeries = require('async/forEachSeries');
|
||||||
const findSeries = require('async/findSeries');
|
const findSeries = require('async/findSeries');
|
||||||
|
|
||||||
const { loadModulesForCategory } = require('../../module_util');
|
const { loadModulesForCategory, moduleCategories } = require('../../module_util');
|
||||||
|
|
||||||
const ModuleInfo = (exports.moduleInfo = {
|
const ModuleInfo = (exports.moduleInfo = {
|
||||||
name: 'Web',
|
name: 'Web',
|
||||||
|
@ -26,6 +26,11 @@ const ModuleInfo = (exports.moduleInfo = {
|
||||||
packageName: 'codes.l33t.enigma.web.server',
|
packageName: 'codes.l33t.enigma.web.server',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
exports.WellKnownLocations = {
|
||||||
|
Rfc5785: '/.well-known',
|
||||||
|
Internal: '/_enig',
|
||||||
|
};
|
||||||
|
|
||||||
class Route {
|
class Route {
|
||||||
constructor(route) {
|
constructor(route) {
|
||||||
Object.assign(this, route);
|
Object.assign(this, route);
|
||||||
|
@ -79,6 +84,17 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||||
this.routes = {};
|
this.routes = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDomain() {
|
||||||
|
const config = Config();
|
||||||
|
const overridePrefix = _.get(config.contentServers.web.overrideUrlPrefix);
|
||||||
|
if (_.isString(overridePrefix)) {
|
||||||
|
const url = new URL(overridePrefix);
|
||||||
|
return url.hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.contentServers.web.domain;
|
||||||
|
}
|
||||||
|
|
||||||
buildUrl(pathAndQuery) {
|
buildUrl(pathAndQuery) {
|
||||||
//
|
//
|
||||||
// Create a URL such as
|
// Create a URL such as
|
||||||
|
@ -146,7 +162,7 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadModulesForCategory(
|
loadModulesForCategory(
|
||||||
'webHandlers',
|
moduleCategories.WebHandlers,
|
||||||
(module, nextModule) => {
|
(module, nextModule) => {
|
||||||
const moduleInst = new module.getModule();
|
const moduleInst = new module.getModule();
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -3,9 +3,11 @@ const Config = require('../../../config').get;
|
||||||
const { Errors } = require('../../../enig_error');
|
const { Errors } = require('../../../enig_error');
|
||||||
|
|
||||||
const WebServerPackageName = require('../web').moduleInfo.packageName;
|
const WebServerPackageName = require('../web').moduleInfo.packageName;
|
||||||
|
const { WellKnownLocations } = require('../web');
|
||||||
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const User = require('../../../user');
|
const User = require('../../../user');
|
||||||
|
const EnigAssert = require('../../../enigma_assert');
|
||||||
const Log = require('../../../logger').log;
|
const Log = require('../../../logger').log;
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
|
@ -21,7 +23,9 @@ exports.getModule = class WebFingerServerModule extends ServerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
init(cb) {
|
init(cb) {
|
||||||
if (!_.get(Config(), 'contentServers.web.handlers.webFinger.enabled')) {
|
const config = Config();
|
||||||
|
|
||||||
|
if (!_.get(config, 'contentServers.web.handlers.webFinger.enabled')) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,12 +33,30 @@ exports.getModule = class WebFingerServerModule extends ServerModule {
|
||||||
|
|
||||||
// we rely on the web server
|
// we rely on the web server
|
||||||
this.webServer = getServer(WebServerPackageName);
|
this.webServer = getServer(WebServerPackageName);
|
||||||
if (!this.webServer || !this.webServer.instance.isEnabled()) {
|
const ws = this._webServer();
|
||||||
|
if (!ws || !ws.isEnabled()) {
|
||||||
return cb(Errors.UnexpectedState('Cannot access web server!'));
|
return cb(Errors.UnexpectedState('Cannot access web server!'));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.webServer.instance.addRoute({
|
const domain = ws.getDomain();
|
||||||
|
if (!domain) {
|
||||||
|
return cb(Errors.UnexpectedState('Web server does not have "domain" set'));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.acceptedResourceRegExps = [
|
||||||
|
// acct:NAME@our.domain.tld
|
||||||
|
new RegExp(`^acct:(.+)@${domain}$`),
|
||||||
|
// profile URL
|
||||||
|
new RegExp(`^${ws.buildUrl(WellKnownLocations.Internal + '/wf/@')}(.+)$`),
|
||||||
|
// self URL
|
||||||
|
new RegExp(
|
||||||
|
`^${ws.buildUrl(WellKnownLocations.Internal + '/ap/users/')}(.+)$`
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
ws.addRoute({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7033.html#section-10.1
|
||||||
path: /^\/\.well-known\/webfinger\/?\?/,
|
path: /^\/\.well-known\/webfinger\/?\?/,
|
||||||
handler: this._webFingerRequestHandler.bind(this),
|
handler: this._webFingerRequestHandler.bind(this),
|
||||||
});
|
});
|
||||||
|
@ -42,12 +64,16 @@ exports.getModule = class WebFingerServerModule extends ServerModule {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_webServer() {
|
||||||
|
return this.webServer.instance;
|
||||||
|
}
|
||||||
|
|
||||||
_webFingerRequestHandler(req, resp) {
|
_webFingerRequestHandler(req, resp) {
|
||||||
const url = new URL(req.url, `https://${req.headers.host}`);
|
const url = new URL(req.url, `https://${req.headers.host}`);
|
||||||
|
|
||||||
const resource = url.searchParams.get('resource');
|
const resource = url.searchParams.get('resource');
|
||||||
if (!resource) {
|
if (!resource) {
|
||||||
return this.webServer.instance.respondWithError(
|
return this._webServer().respondWithError(
|
||||||
resp,
|
resp,
|
||||||
400,
|
400,
|
||||||
'"resource" is required',
|
'"resource" is required',
|
||||||
|
@ -55,16 +81,22 @@ exports.getModule = class WebFingerServerModule extends ServerModule {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._getUser(resource, resp, (err, user, accountName) => {
|
this._getUser(resource, resp, (err, user) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
// |resp| already written to
|
// |resp| already written to
|
||||||
return Log.warn({ error: err.message }, `WebFinger failed: ${req.url}`);
|
return Log.warn({ error: err.message }, `WebFinger failed: ${req.url}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const domain = this._webServer().getDomain();
|
||||||
|
|
||||||
const body = JSON.stringify({
|
const body = JSON.stringify({
|
||||||
subject: `acct:${accountName}`,
|
subject: `acct:${user.username}@${domain}`,
|
||||||
aliases: [this._profileUrl(user), this._selfUrl(user)],
|
aliases: [this._profileUrl(user), this._selfUrl(user)],
|
||||||
links: [this._profilePageLink(user), this._selfLink(user), this._subscribeLink()],
|
links: [
|
||||||
|
this._profilePageLink(user),
|
||||||
|
this._selfLink(user),
|
||||||
|
this._subscribeLink(),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
|
@ -78,7 +110,9 @@ exports.getModule = class WebFingerServerModule extends ServerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
_profileUrl(user) {
|
_profileUrl(user) {
|
||||||
return this.webServer.instance.buildUrl(`/wf/@${user.username}`);
|
return this._webServer().buildUrl(
|
||||||
|
WellKnownLocations.Internal + `/wf/@${user.username}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_profilePageLink(user) {
|
_profilePageLink(user) {
|
||||||
|
@ -91,9 +125,12 @@ exports.getModule = class WebFingerServerModule extends ServerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
_selfUrl(user) {
|
_selfUrl(user) {
|
||||||
return this.webServer.instance.buildUrl(`/users/${user.username}`);
|
return this._webServer().buildUrl(
|
||||||
|
WellKnownLocations.Internal + `/ap/users/${user.username}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// :TODO: only if ActivityPub is enabled
|
||||||
_selfLink(user) {
|
_selfLink(user) {
|
||||||
const href = this._selfUrl(user);
|
const href = this._selfUrl(user);
|
||||||
return {
|
return {
|
||||||
|
@ -103,18 +140,28 @@ exports.getModule = class WebFingerServerModule extends ServerModule {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// :TODO: only if ActivityPub is enabled
|
||||||
_subscribeLink() {
|
_subscribeLink() {
|
||||||
return {
|
return {
|
||||||
rel: 'http://ostatus.org/schema/1.0/subscribe',
|
rel: 'http://ostatus.org/schema/1.0/subscribe',
|
||||||
template: this.webServer.instance.buildUrl('/authorize_interaction?uri={uri}'),
|
template: this._webServer().buildUrl(
|
||||||
|
WellKnownLocations.Internal + '/ap/authorize_interaction?uri={uri}'
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_getUser(resource, resp, cb) {
|
_getAccountName(resource) {
|
||||||
// we only handle "acct:NAME" URIs
|
for (const re of this.acceptedResourceRegExps) {
|
||||||
|
const m = resource.match(re);
|
||||||
|
if (m && m.length === 2) {
|
||||||
|
return m[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getUser(resource, resp, cb) {
|
||||||
const notFound = () => {
|
const notFound = () => {
|
||||||
this.webServer.instance.respondWithError(
|
this._webServer().respondWithError(
|
||||||
resp,
|
resp,
|
||||||
404,
|
404,
|
||||||
'Resource not found',
|
'Resource not found',
|
||||||
|
@ -122,25 +169,17 @@ exports.getModule = class WebFingerServerModule extends ServerModule {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const acctIndex = resource.indexOf('acct:', 0);
|
const accountName = this._getAccountName(resource);
|
||||||
if (0 != acctIndex) {
|
if (!accountName || accountName.length < 1) {
|
||||||
notFound();
|
notFound();
|
||||||
return cb(Errors.DoesNotExist('"acct:" missing'));
|
return cb(
|
||||||
|
Errors.DoesNotExist(
|
||||||
|
`Failed to parse "account name" for resource: ${resource}`
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountName = resource.substring(acctIndex + 5);
|
User.getUserIdAndName(accountName, (err, userId) => {
|
||||||
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) {
|
if (err) {
|
||||||
notFound();
|
notFound();
|
||||||
return cb(err);
|
return cb(err);
|
||||||
|
@ -152,7 +191,7 @@ exports.getModule = class WebFingerServerModule extends ServerModule {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null, user, accountName);
|
return cb(null, user);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue