Remove need for WebServer in a lot of areas, oputil ap condition functional

* Getting domain, URLs, etc. for local web server do not need a web server instance themselves
* fix up oputil
This commit is contained in:
Bryan Ashby 2023-03-18 14:30:37 -06:00
parent e915527427
commit fb02fc599a
No known key found for this signature in database
GPG Key ID: C2C1B501E4EFD994
19 changed files with 187 additions and 191 deletions

View File

@ -115,14 +115,14 @@ module.exports = class Actor extends ActivityPubObject {
? user.getSanitizedName('real') ? user.getSanitizedName('real')
: user.username, : user.username,
endpoints: { endpoints: {
sharedInbox: Endpoints.sharedInbox(webServer), sharedInbox: Endpoints.sharedInbox(),
}, },
inbox: Endpoints.inbox(webServer, user), inbox: Endpoints.inbox(user),
outbox: Endpoints.outbox(webServer, user), outbox: Endpoints.outbox(user),
followers: Endpoints.followers(webServer, user), followers: Endpoints.followers(user),
following: Endpoints.following(webServer, user), following: Endpoints.following(user),
summary: user.getProperty(UserProps.AutoSignature) || '', summary: user.getProperty(UserProps.AutoSignature) || '',
url: Endpoints.profile(webServer, user), url: Endpoints.profile(user),
manuallyApprovesFollowers: userSettings.manuallyApprovesFollowers, manuallyApprovesFollowers: userSettings.manuallyApprovesFollowers,
discoverable: userSettings.discoverable, discoverable: userSettings.discoverable,
// :TODO: we can start to define BBS related stuff with the community perhaps // :TODO: we can start to define BBS related stuff with the community perhaps

View File

@ -84,7 +84,7 @@ module.exports = class Collection extends ActivityPubObject {
} }
static addFollower(owningUser, followingActor, webServer, ignoreDupes, cb) { static addFollower(owningUser, followingActor, webServer, ignoreDupes, cb) {
const collectionId = Endpoints.followers(webServer, owningUser); const collectionId = Endpoints.followers(owningUser);
return Collection.addToCollection( return Collection.addToCollection(
Collections.Followers, Collections.Followers,
owningUser, owningUser,
@ -98,8 +98,7 @@ module.exports = class Collection extends ActivityPubObject {
} }
static addFollowRequest(owningUser, requestingActor, webServer, ignoreDupes, cb) { static addFollowRequest(owningUser, requestingActor, webServer, ignoreDupes, cb) {
const collectionId = const collectionId = Endpoints.makeUserUrl(owningUser) + 'follow-requests';
Endpoints.makeUserUrl(webServer, owningUser) + 'follow-requests';
return Collection.addToCollection( return Collection.addToCollection(
Collections.FollowRequests, Collections.FollowRequests,
owningUser, owningUser,
@ -113,7 +112,7 @@ module.exports = class Collection extends ActivityPubObject {
} }
static addFollowing(owningUser, followingActor, webServer, ignoreDupes, cb) { static addFollowing(owningUser, followingActor, webServer, ignoreDupes, cb) {
const collectionId = Endpoints.following(webServer, owningUser); const collectionId = Endpoints.following(owningUser);
return Collection.addToCollection( return Collection.addToCollection(
Collections.Following, Collections.Following,
owningUser, owningUser,
@ -127,7 +126,7 @@ module.exports = class Collection extends ActivityPubObject {
} }
static addOutboxItem(owningUser, outboxItem, isPrivate, webServer, ignoreDupes, cb) { static addOutboxItem(owningUser, outboxItem, isPrivate, webServer, ignoreDupes, cb) {
const collectionId = Endpoints.outbox(webServer, owningUser); const collectionId = Endpoints.outbox(owningUser);
return Collection.addToCollection( return Collection.addToCollection(
Collections.Outbox, Collections.Outbox,
owningUser, owningUser,
@ -141,7 +140,7 @@ module.exports = class Collection extends ActivityPubObject {
} }
static addInboxItem(inboxItem, owningUser, webServer, ignoreDupes, cb) { static addInboxItem(inboxItem, owningUser, webServer, ignoreDupes, cb) {
const collectionId = Endpoints.inbox(webServer, owningUser); const collectionId = Endpoints.inbox(owningUser);
return Collection.addToCollection( return Collection.addToCollection(
Collections.Inbox, Collections.Inbox,
owningUser, owningUser,
@ -498,8 +497,7 @@ module.exports = class Collection extends ActivityPubObject {
} }
// e.g. http://somewhere.com/_enig/ap/users/NuSkooler/followers // e.g. http://somewhere.com/_enig/ap/users/NuSkooler/followers
const collectionId = const collectionId = Endpoints.makeUserUrl(owningUser) + `/${collectionName}`;
Endpoints.makeUserUrl(webServer, owningUser) + `/${collectionName}`;
if (!page) { if (!page) {
return apDb.get( return apDb.get(

View File

@ -1,4 +1,5 @@
const { WellKnownLocations } = require('../servers/content/web'); const { WellKnownLocations } = require('../servers/content/web');
const { buildUrl } = require('../web_util');
// deps // deps
const { v4: UUIDv4 } = require('uuid'); const { v4: UUIDv4 } = require('uuid');
@ -16,46 +17,42 @@ exports.objectId = objectId;
const ActivityPubUsersPrefix = '/ap/users/'; const ActivityPubUsersPrefix = '/ap/users/';
function makeUserUrl(webServer, user, relPrefix = ActivityPubUsersPrefix) { function makeUserUrl(user, relPrefix = ActivityPubUsersPrefix) {
return webServer.buildUrl( return buildUrl(WellKnownLocations.Internal + `${relPrefix}${user.username}`);
WellKnownLocations.Internal + `${relPrefix}${user.username}`
);
} }
function inbox(webServer, user) { function inbox(user) {
return makeUserUrl(webServer, user, ActivityPubUsersPrefix) + '/inbox'; return makeUserUrl(user, ActivityPubUsersPrefix) + '/inbox';
} }
function outbox(webServer, user) { function outbox(user) {
return makeUserUrl(webServer, user, ActivityPubUsersPrefix) + '/outbox'; return makeUserUrl(user, ActivityPubUsersPrefix) + '/outbox';
} }
function followers(webServer, user) { function followers(user) {
return makeUserUrl(webServer, user, ActivityPubUsersPrefix) + '/followers'; return makeUserUrl(user, ActivityPubUsersPrefix) + '/followers';
} }
function following(webServer, user) { function following(user) {
return makeUserUrl(webServer, user, ActivityPubUsersPrefix) + '/following'; return makeUserUrl(user, ActivityPubUsersPrefix) + '/following';
} }
function actorId(webServer, user) { function actorId(user) {
return makeUserUrl(webServer, user, ActivityPubUsersPrefix); return makeUserUrl(user, ActivityPubUsersPrefix);
} }
function profile(webServer, user) { function profile(user) {
return webServer.buildUrl(WellKnownLocations.Internal + `/wf/@${user.username}`); return buildUrl(WellKnownLocations.Internal + `/wf/@${user.username}`);
} }
function avatar(webServer, user, filename) { function avatar(user, filename) {
return makeUserUrl(webServer, user, '/users/') + `/avatar/${filename}`; return makeUserUrl(user, '/users/') + `/avatar/${filename}`;
} }
function sharedInbox(webServer) { function sharedInbox() {
return webServer.buildUrl(WellKnownLocations.Internal + '/ap/shared-inbox'); return buildUrl(WellKnownLocations.Internal + '/ap/shared-inbox');
} }
function objectId(webServer, objectType) { function objectId(objectType) {
return webServer.buildUrl( return buildUrl(WellKnownLocations.Internal + `/ap/${UUIDv4()}/${objectType}`);
WellKnownLocations.Internal + `/ap/${UUIDv4()}/${objectType}`
);
} }

View File

@ -84,7 +84,7 @@ module.exports = class ActivityPubObject {
} }
static makeObjectId(webServer, objectType) { static makeObjectId(webServer, objectType) {
return Endpoints.objectId(webServer, objectType); return Endpoints.objectId(objectType);
} }
sendTo(inboxEndpoint, fromUser, webServer, cb) { sendTo(inboxEndpoint, fromUser, webServer, cb) {
@ -103,7 +103,7 @@ module.exports = class ActivityPubObject {
}, },
sign: { sign: {
key: privateKey, key: privateKey,
keyId: Endpoints.actorId(webServer, fromUser) + '#main-key', keyId: Endpoints.actorId(fromUser) + '#main-key',
authorizationHeaderName: 'Signature', authorizationHeaderName: 'Signature',
headers: HttpSignatureSignHeaders, headers: HttpSignatureSignHeaders,
}, },

View File

@ -6,6 +6,7 @@ const { stripAnsiControlCodes } = require('../string_util');
const { WellKnownRecipientFields } = require('./const'); const { WellKnownRecipientFields } = require('./const');
const Log = require('../logger').log; const Log = require('../logger').log;
const { getWebDomain } = require('../web_util'); const { getWebDomain } = require('../web_util');
const Endpoints = require('./endpoint');
// deps // deps
const _ = require('lodash'); const _ = require('lodash');
@ -30,6 +31,7 @@ exports.userNameFromSubject = userNameFromSubject;
exports.userNameToSubject = userNameToSubject; exports.userNameToSubject = userNameToSubject;
exports.extractMessageMetadata = extractMessageMetadata; exports.extractMessageMetadata = extractMessageMetadata;
exports.recipientIdsFromObject = recipientIdsFromObject; exports.recipientIdsFromObject = recipientIdsFromObject;
exports.prepareLocalUserAsActor = prepareLocalUserAsActor;
// :TODO: more info in default // :TODO: more info in default
// this profile template is the *default* for both WebFinger // this profile template is the *default* for both WebFinger
@ -262,3 +264,43 @@ function recipientIdsFromObject(obj) {
return Array.from(new Set(ids)); return Array.from(new Set(ids));
} }
function prepareLocalUserAsActor(user, options = { force: false }, cb) {
const hasProps =
user.getProperty(UserProps.ActivityPubActorId) &&
user.getProperty(UserProps.PrivateActivityPubSigningKey) &&
user.getProperty(UserProps.PublicActivityPubSigningKey);
if (hasProps && !options.force) {
return cb(null);
}
const actorId = Endpoints.actorId(user);
user.setProperty(UserProps.ActivityPubActorId, actorId);
user.updateActivityPubKeyPairProperties(err => {
if (err) {
return cb(err);
}
user.generateNewRandomAvatar((err, outPath) => {
if (err) {
return err;
}
// :TODO: fetch over +op default overrides here, e.g. 'enabled'
const apSettings = ActivityPubSettings.fromUser(user);
const filename = paths.basename(outPath);
const avatarUrl = Endpoints.avatar(user, filename);
apSettings.image = avatarUrl;
apSettings.icon = avatarUrl;
user.setProperty(UserProps.AvatarImageUrl, avatarUrl);
user.setProperty(UserProps.ActivityPubSettings, JSON.stringify(apSettings));
return cb(null);
});
});
}

View File

@ -17,6 +17,7 @@ const webServerPackageName = require('./servers/content/web.js').moduleInfo.pack
const Events = require('./events.js'); const Events = require('./events.js');
const UserProps = require('./user_property.js'); const UserProps = require('./user_property.js');
const SysProps = require('./system_menu_method.js'); const SysProps = require('./system_menu_method.js');
const { buildUrl } = require('./web_util');
// deps // deps
const hashids = require('hashids/cjs'); const hashids = require('hashids/cjs');
@ -202,11 +203,11 @@ class FileAreaWebAccess {
buildSingleFileTempDownloadLink(client, fileEntry, hashId) { buildSingleFileTempDownloadLink(client, fileEntry, hashId) {
hashId = hashId || this.getSingleFileHashId(client, fileEntry); hashId = hashId || this.getSingleFileHashId(client, fileEntry);
return this.webServer.instance.buildUrl(`${Config().fileBase.web.path}${hashId}`); return buildUrl(`${Config().fileBase.web.path}${hashId}`);
} }
buildBatchArchiveTempDownloadLink(client, hashId) { buildBatchArchiveTempDownloadLink(client, hashId) {
return this.webServer.instance.buildUrl(`${Config().fileBase.web.path}${hashId}`); return buildUrl(`${Config().fileBase.web.path}${hashId}`);
} }
getExistingTempDownloadServeItem(client, fileEntry, cb) { getExistingTempDownloadServeItem(client, fileEntry, cb) {

View File

@ -5,7 +5,6 @@ const {
initConfigAndDatabases, initConfigAndDatabases,
} = require('./oputil_common'); } = require('./oputil_common');
const getHelpFor = require('./oputil_help.js').getHelpFor; const getHelpFor = require('./oputil_help.js').getHelpFor;
const UserProps = require('../user_property');
const { Errors } = require('../enig_error'); const { Errors } = require('../enig_error');
// deps // deps
@ -48,24 +47,30 @@ function applyAction(username, actionFunc, cb) {
}); });
} }
function conditionSingleUser(User, username, userId, settings, cb) { function conditionSingleUser(user, cb) {
const { userNameToSubject } = require('../activitypub/util'); const { userNameToSubject, prepareLocalUserAsActor } = require('../activitypub/util');
const subject = userNameToSubject(username);
const subject = userNameToSubject(user.username);
if (!subject) { if (!subject) {
return cb(Errors.General(`Failed to get subject for ${username}`)); return cb(Errors.General(`Failed to get subject for ${user.username}`));
} }
console.info(`Conditioning ${username} (${userId}) -> ${subject}...`); console.info(`Conditioning ${user.username} (${user.userId}) -> ${subject}...`);
prepareLocalUserAsActor(user, { force: argv.force }, err => {
User.persistPropertyByUserId(userId, UserProps.ActivityPubSettings, settings, err => { if (err) {
return cb(err); return cb(err);
}
user.persistProperties(user.properties, err => {
if (err) {
return cb(err);
}
});
}); });
} }
function actionConditionAllUsers(_, cb) { function actionConditionAllUsers(_, cb) {
const User = require('../../core/user.js'); const User = require('../../core/user.js');
const ActivityPubSettings = require('../activitypub/settings');
const defaultSettings = JSON.stringify(new ActivityPubSettings());
User.getUserList({}, (err, userList) => { User.getUserList({}, (err, userList) => {
if (err) { if (err) {
@ -75,13 +80,12 @@ function actionConditionAllUsers(_, cb) {
async.each( async.each(
userList, userList,
(entry, next) => { (entry, next) => {
conditionSingleUser( User.getUser(entry.userId, (err, user) => {
User, if (err) {
entry.userName, return next(err);
entry.userId, }
defaultSettings, return conditionSingleUser(user, next);
next });
);
}, },
err => { err => {
return cb(err); return cb(err);
@ -90,13 +94,6 @@ function actionConditionAllUsers(_, cb) {
}); });
} }
function actionConditionUser(user, cb) {
const User = require('../../core/user.js');
const ActivityPubSettings = require('../activitypub/settings');
const defaultSettings = JSON.stringify(new ActivityPubSettings());
return conditionSingleUser(User, user.username, user.userId, defaultSettings, cb);
}
function validateActivityPub() { function validateActivityPub() {
// //
// Web Server, and ActivityPub both must be enabled // Web Server, and ActivityPub both must be enabled
@ -116,7 +113,7 @@ function validateActivityPub() {
function conditionUser(action, username) { function conditionUser(action, username) {
return applyAction( return applyAction(
username, username,
'*' === username ? actionConditionAllUsers : actionConditionUser, '*' === username ? actionConditionAllUsers : conditionSingleUser,
err => { err => {
if (err) { if (err) {
console.error(err.message); console.error(err.message);

View File

@ -10,6 +10,7 @@ const async = require('async');
const inq = require('inquirer'); const inq = require('inquirer');
const fs = require('fs'); const fs = require('fs');
const hjson = require('hjson'); const hjson = require('hjson');
const log = require('../../core/logger');
const packageJson = require('../../package.json'); const packageJson = require('../../package.json');
@ -81,6 +82,7 @@ function initConfigAndDatabases(cb) {
initConfig(callback); initConfig(callback);
}, },
function initDb(callback) { function initDb(callback) {
log.init();
db.initializeDatabases(callback); db.initializeDatabases(callback);
}, },
function initArchiveUtil(callback) { function initArchiveUtil(callback) {

View File

@ -215,6 +215,9 @@ Actions:
condition USERNAME Condition user with system ActivityPub defaults condition USERNAME Condition user with system ActivityPub defaults
Instead of an actual USERNAME, the '*' character may be substituted. Instead of an actual USERNAME, the '*' character may be substituted.
condition arguments:
--force Force condition; overrides any existing settings
`, `,
}); });

View File

@ -299,7 +299,7 @@ exports.getModule = class ActivityPubScannerTosser extends MessageScanTossModule
} }
_collectFollowersSharedInboxEndpoints(localUser, cb) { _collectFollowersSharedInboxEndpoints(localUser, cb) {
const localFollowersEndpoint = Endpoints.followers(this._webServer(), localUser); const localFollowersEndpoint = Endpoints.followers(localUser);
Collection.followers(localFollowersEndpoint, 'all', (err, collection) => { Collection.followers(localFollowersEndpoint, 'all', (err, collection) => {
if (err) { if (err) {

View File

@ -5,7 +5,6 @@ const Config = require('../../config.js').get;
const { Errors } = require('../../enig_error.js'); const { Errors } = require('../../enig_error.js');
const { loadModulesForCategory, moduleCategories } = require('../../module_util'); const { loadModulesForCategory, moduleCategories } = require('../../module_util');
const WebHandlerModule = require('../../web_handler_module'); const WebHandlerModule = require('../../web_handler_module');
const { getWebDomain } = require('../../web_util');
// deps // deps
const http = require('http'); const http = require('http');
@ -90,53 +89,6 @@ exports.getModule = class WebServerModule extends ServerModule {
return this.log; return this.log;
} }
getDomain() {
return getWebDomain();
}
baseUrl() {
const config = Config();
const overridePrefix = _.get(config, 'contentServers.web.overrideUrlPrefix');
if (overridePrefix) {
return overridePrefix;
}
let schema;
let port;
if (config.contentServers.web.https.enabled) {
schema = 'https://';
port =
443 === config.contentServers.web.https.port
? ''
: `:${config.contentServers.web.https.port}`;
} else {
schema = 'http://';
port =
80 === config.contentServers.web.http.port
? ''
: `:${config.contentServers.web.http.port}`;
}
return `${schema}${config.contentServers.web.domain}${port}`;
}
fullUrl(req) {
const base = this.baseUrl();
return new URL(`${base}${req.url}`);
}
buildUrl(pathAndQuery) {
//
// Create a URL such as
// https://l33t.codes:44512/ + |pathAndQuery|
//
// Prefer HTTPS over HTTP. Be explicit about the port
// only if non-standard. Allow users to override full prefix in config.
//
const base = this.baseUrl();
return `${base}${pathAndQuery}`;
}
isEnabled() { isEnabled() {
return this.enableHttp || this.enableHttps; return this.enableHttp || this.enableHttps;
} }

View File

@ -4,8 +4,8 @@ const {
getUserProfileTemplatedBody, getUserProfileTemplatedBody,
DefaultProfileTemplate, DefaultProfileTemplate,
getActorId, getActorId,
prepareLocalUserAsActor,
} = require('../../../activitypub/util'); } = require('../../../activitypub/util');
const Endpoints = require('../../../activitypub/endpoint');
const { const {
ActivityStreamMediaType, ActivityStreamMediaType,
WellKnownActivity, WellKnownActivity,
@ -21,15 +21,14 @@ const Note = require('../../../activitypub/note');
const EnigAssert = require('../../../enigma_assert'); const EnigAssert = require('../../../enigma_assert');
const Message = require('../../../message'); const Message = require('../../../message');
const Events = require('../../../events'); const Events = require('../../../events');
const UserProps = require('../../../user_property');
const { Errors } = require('../../../enig_error'); const { Errors } = require('../../../enig_error');
const { getFullUrl } = require('../../../web_util');
// deps // deps
const _ = require('lodash'); const _ = require('lodash');
const enigma_assert = require('../../../enigma_assert'); const enigma_assert = require('../../../enigma_assert');
const httpSignature = require('http-signature'); const httpSignature = require('http-signature');
const async = require('async'); const async = require('async');
const paths = require('path');
exports.moduleInfo = { exports.moduleInfo = {
name: 'ActivityPub', name: 'ActivityPub',
@ -175,7 +174,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
_selfUrlRequestHandler(req, resp) { _selfUrlRequestHandler(req, resp) {
this.log.trace({ url: req.url }, 'Request for "self"'); this.log.trace({ url: req.url }, 'Request for "self"');
let actorId = this.webServer.fullUrl(req).toString(); let actorId = getFullUrl(req).toString();
let sendActor = false; let sendActor = false;
if (actorId.endsWith('.json')) { if (actorId.endsWith('.json')) {
sendActor = true; sendActor = true;
@ -783,7 +782,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
_localUserFromCollectionEndpoint(req, collectionName, cb) { _localUserFromCollectionEndpoint(req, collectionName, cb) {
// turn a collection URL to a Actor ID // turn a collection URL to a Actor ID
let actorId = this.webServer.fullUrl(req).toString(); let actorId = getFullUrl(req).toString();
const suffix = `/${collectionName}`; const suffix = `/${collectionName}`;
if (actorId.endsWith(suffix)) { if (actorId.endsWith(suffix)) {
actorId = actorId.slice(0, -suffix.length); actorId = actorId.slice(0, -suffix.length);
@ -986,7 +985,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
return this.webServer.resourceNotFound(resp); return this.webServer.resourceNotFound(resp);
} }
const url = this.webServer.fullUrl(req); const url = getFullUrl(req);
const page = url.searchParams.get('page'); const page = url.searchParams.get('page');
const collectionId = url.toString(); const collectionId = url.toString();
@ -1037,7 +1036,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
_singlePublicNoteGetHandler(req, resp) { _singlePublicNoteGetHandler(req, resp) {
this.log.debug({ url: req.url }, 'Request for "Note"'); this.log.debug({ url: req.url }, 'Request for "Note"');
const noteId = this.webServer.fullUrl(req).toString(); const noteId = getFullUrl(req).toString();
Note.fromPublicNoteId(noteId, (err, note) => { Note.fromPublicNoteId(noteId, (err, note) => {
if (err) { if (err) {
return this.webServer.internalServerError(resp, err); return this.webServer.internalServerError(resp, err);
@ -1180,42 +1179,6 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
`Preparing ActivityPub settings for "${user.username}"` `Preparing ActivityPub settings for "${user.username}"`
); );
const actorId = Endpoints.actorId(this.webServer, user); return prepareLocalUserAsActor(user, cb);
user.setProperty(UserProps.ActivityPubActorId, actorId);
user.updateActivityPubKeyPairProperties(err => {
if (err) {
return cb(err);
}
user.generateNewRandomAvatar((err, outPath) => {
if (err) {
this.log.warn(
{
username: user.username,
userId: user.userId,
error: err.message,
},
`Failed to generate random avatar for "${user.username}"`
);
}
// :TODO: fetch over +op default overrides here, e.g. 'enabled'
const apSettings = ActivityPubSettings.fromUser(user);
const filename = paths.basename(outPath);
const avatarUrl = Endpoints.avatar(this.webServer, user, filename);
apSettings.image = avatarUrl;
apSettings.icon = avatarUrl;
user.setProperty(
UserProps.ActivityPubSettings,
JSON.stringify(apSettings)
);
return cb(null);
});
});
} }
}; };

View File

@ -6,6 +6,7 @@ const packageJson = require('../../../../package.json');
const StatLog = require('../../../stat_log'); const StatLog = require('../../../stat_log');
const SysProps = require('../../../system_property'); const SysProps = require('../../../system_property');
const SysLogKeys = require('../../../system_log'); const SysLogKeys = require('../../../system_log');
const { getBaseUrl, getWebDomain } = require('../../../web_util');
// deps // deps
const moment = require('moment'); const moment = require('moment');
@ -30,7 +31,7 @@ exports.getModule = class NodeInfo2WebHandler extends WebHandlerModule {
this.log = webServer.logger().child({ webHandler: 'NodeInfo2' }); this.log = webServer.logger().child({ webHandler: 'NodeInfo2' });
const domain = this.webServer.getDomain(); const domain = getWebDomain();
if (!domain) { if (!domain) {
return cb(Errors.UnexpectedState('Web server does not have "domain" set')); return cb(Errors.UnexpectedState('Web server does not have "domain" set'));
} }
@ -65,7 +66,7 @@ exports.getModule = class NodeInfo2WebHandler extends WebHandlerModule {
const nodeInfo = { const nodeInfo = {
version: '1.0', version: '1.0',
server: { server: {
baseUrl: this.webServer.baseUrl(), baseUrl: getBaseUrl(),
name: config.general.boardName, name: config.general.boardName,
software: 'ENiGMA½ Bulletin Board Software', software: 'ENiGMA½ Bulletin Board Software',
version: packageJson.version, version: packageJson.version,

View File

@ -2,6 +2,7 @@ const WebHandlerModule = require('../../../web_handler_module');
const { Errors } = require('../../../enig_error'); const { Errors } = require('../../../enig_error');
const EngiAssert = require('../../../enigma_assert'); const EngiAssert = require('../../../enigma_assert');
const Config = require('../../../config').get; const Config = require('../../../config').get;
const { getFullUrl, getWebDomain } = require('../../../web_util');
// deps // deps
const paths = require('path'); const paths = require('path');
@ -28,7 +29,7 @@ exports.getModule = class SystemGeneralWebHandler extends WebHandlerModule {
this.log = webServer.logger().child({ webHandler: 'SysGeneral' }); this.log = webServer.logger().child({ webHandler: 'SysGeneral' });
const domain = this.webServer.getDomain(); const domain = getWebDomain();
if (!domain) { if (!domain) {
return cb(Errors.UnexpectedState('Web server does not have "domain" set')); return cb(Errors.UnexpectedState('Web server does not have "domain" set'));
} }
@ -44,7 +45,7 @@ exports.getModule = class SystemGeneralWebHandler extends WebHandlerModule {
} }
_avatarGetHandler(req, resp) { _avatarGetHandler(req, resp) {
const url = this.webServer.fullUrl(req); const url = getFullUrl(req);
const filename = paths.basename(url.pathname); const filename = paths.basename(url.pathname);
if (!filename) { if (!filename) {
return this.webServer.fileNotFound(resp); return this.webServer.fileNotFound(resp);

View File

@ -11,6 +11,7 @@ const EngiAssert = require('../../../enigma_assert');
const User = require('../../../user'); const User = require('../../../user');
const UserProps = require('../../../user_property'); const UserProps = require('../../../user_property');
const ActivityPubSettings = require('../../../activitypub/settings'); const ActivityPubSettings = require('../../../activitypub/settings');
const { getFullUrl, buildUrl, getWebDomain } = require('../../../web_util');
// deps // deps
const _ = require('lodash'); const _ = require('lodash');
@ -38,7 +39,7 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule {
this.log = webServer.logger().child({ webHandler: 'WebFinger' }); this.log = webServer.logger().child({ webHandler: 'WebFinger' });
const domain = this.webServer.getDomain(); const domain = getWebDomain();
if (!domain) { if (!domain) {
return cb(Errors.UnexpectedState('Web server does not have "domain" set')); return cb(Errors.UnexpectedState('Web server does not have "domain" set'));
} }
@ -49,15 +50,9 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule {
new RegExp(`^acct:(.+)@${domain}$`), new RegExp(`^acct:(.+)@${domain}$`),
// profile page // profile page
// https://webfinger.net/rel/profile-page/ // https://webfinger.net/rel/profile-page/
new RegExp( new RegExp(`^${buildUrl(WellKnownLocations.Internal + '/wf/@')}(.+)$`),
`^${this.webServer.buildUrl(WellKnownLocations.Internal + '/wf/@')}(.+)$`
),
// self URL // self URL
new RegExp( new RegExp(`^${buildUrl(WellKnownLocations.Internal + '/ap/users/')}(.+)$`),
`^${this.webServer.buildUrl(
WellKnownLocations.Internal + '/ap/users/'
)}(.+)$`
),
]; ];
this.webServer.addRoute({ this.webServer.addRoute({
@ -78,7 +73,7 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule {
_profileRequestHandler(req, resp) { _profileRequestHandler(req, resp) {
// Profile requests do not have an Actor ID available // Profile requests do not have an Actor ID available
const profileQuery = this.webServer.fullUrl(req).toString(); const profileQuery = getFullUrl(req).toString();
const accountName = this._getAccountName(profileQuery); const accountName = this._getAccountName(profileQuery);
if (!accountName) { if (!accountName) {
this.log.warn( this.log.warn(
@ -135,7 +130,7 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule {
} }
_webFingerRequestHandler(req, resp) { _webFingerRequestHandler(req, resp) {
const url = this.webServer.fullUrl(req); const url = getFullUrl(req);
const resource = url.searchParams.get('resource'); const resource = url.searchParams.get('resource');
if (!resource) { if (!resource) {
return this.webServer.respondWithError( return this.webServer.respondWithError(
@ -161,11 +156,10 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule {
return this.webServer.resourceNotFound(resp); return this.webServer.resourceNotFound(resp);
} }
const domain = this.webServer.getDomain(); const domain = getWebDomain();
const body = JSON.stringify({ const body = JSON.stringify({
subject: `acct:${localUser.username}@${domain}`, subject: `acct:${localUser.username}@${domain}`,
aliases: [this._profileUrl(localUser), this._userActorId(localUser)], aliases: [Endpoints.profile(localUser), Endpoints.actorId(localUser)],
links: [ links: [
this._profilePageLink(localUser), this._profilePageLink(localUser),
this._selfLink(localUser), this._selfLink(localUser),
@ -218,12 +212,8 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule {
}); });
} }
_profileUrl(user) {
return Endpoints.profile(this.webServer, user);
}
_profilePageLink(user) { _profilePageLink(user) {
const href = this._profileUrl(user); const href = Endpoints.profile(user);
return { return {
rel: 'http://webfinger.net/rel/profile-page', rel: 'http://webfinger.net/rel/profile-page',
type: 'text/plain', type: 'text/plain',
@ -232,12 +222,12 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule {
} }
_userActorId(user) { _userActorId(user) {
return Endpoints.actorId(this.webServer, user); return Endpoints.actorId(user);
} }
// :TODO: only if ActivityPub is enabled // :TODO: only if ActivityPub is enabled
_selfLink(user) { _selfLink(user) {
const href = this._userActorId(user); const href = Endpoints.actorId(user);
return { return {
rel: 'self', rel: 'self',
type: 'application/activity+json', type: 'application/activity+json',
@ -249,7 +239,7 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule {
_subscribeLink() { _subscribeLink() {
return { return {
rel: 'http://ostatus.org/schema/1.0/subscribe', rel: 'http://ostatus.org/schema/1.0/subscribe',
template: this.webServer.buildUrl( template: buildUrl(
WellKnownLocations.Internal + '/ap/authorize_interaction?uri={uri}' WellKnownLocations.Internal + '/ap/authorize_interaction?uri={uri}'
), ),
}; };

View File

@ -17,6 +17,7 @@ const { sendMail } = require('./email.js');
const UserProps = require('./user_property.js'); const UserProps = require('./user_property.js');
const Log = require('./logger.js').log; const Log = require('./logger.js').log;
const { getConnectionByUserId } = require('./client_connections.js'); const { getConnectionByUserId } = require('./client_connections.js');
const { buildUrl } = require('./web_util');
// deps // deps
const async = require('async'); const async = require('async');
@ -75,8 +76,7 @@ module.exports = class User2FA_OTPWebRegister {
}); });
}, },
(token, textTemplate, htmlTemplate, callback) => { (token, textTemplate, htmlTemplate, callback) => {
const webServer = getWebServer(); const registerUrl = buildUrl(
const registerUrl = webServer.instance.buildUrl(
WellKnownLocations.Internal + WellKnownLocations.Internal +
`/2fa/enable_2fa_otp?token=${token}&otpType=${otpType}` `/2fa/enable_2fa_otp?token=${token}&otpType=${otpType}`
); );
@ -172,7 +172,7 @@ module.exports = class User2FA_OTPWebRegister {
return User2FA_OTPWebRegister.accessDenied(webServer, resp); return User2FA_OTPWebRegister.accessDenied(webServer, resp);
} }
const postUrl = webServer.instance.buildUrl( const postUrl = buildUrl(
WellKnownLocations.Internal + '/2fa/enable_2fa_otp' WellKnownLocations.Internal + '/2fa/enable_2fa_otp'
); );
const config = Config(); const config = Config();

View File

@ -70,6 +70,8 @@ module.exports = {
PublicActivityPubSigningKey: 'public_key_activitypub_sign_rsa_pem', // RSA public key for ActivityPub signing PublicActivityPubSigningKey: 'public_key_activitypub_sign_rsa_pem', // RSA public key for ActivityPub signing
PrivateActivityPubSigningKey: 'private_key_activitypub_sign_rsa_pem', // RSA private key (corresponding to PublicActivityPubSigningKey) PrivateActivityPubSigningKey: 'private_key_activitypub_sign_rsa_pem', // RSA private key (corresponding to PublicActivityPubSigningKey)
AvatarImageUrl: 'user_avatar_image',
ActivityPubSettings: 'activitypub_settings', // JSON object (above); see ActivityPubSettings in activitypub/settings.js ActivityPubSettings: 'activitypub_settings', // JSON object (above); see ActivityPubSettings in activitypub/settings.js
ActivityPubActorId: 'activitypub_actor_id', // Actor ID representing this users ActivityPubActorId: 'activitypub_actor_id', // Actor ID representing this users
}; };

View File

@ -12,6 +12,7 @@ const userDb = require('./database.js').dbs.user;
const getISOTimestampString = require('./database.js').getISOTimestampString; const getISOTimestampString = require('./database.js').getISOTimestampString;
const Log = require('./logger.js').log; const Log = require('./logger.js').log;
const UserProps = require('./user_property.js'); const UserProps = require('./user_property.js');
const { buildUrl } = require('./web_util');
// deps // deps
const async = require('async'); const async = require('async');
@ -121,7 +122,7 @@ class WebPasswordReset {
function buildAndSendEmail(user, textTemplate, htmlTemplate, callback) { function buildAndSendEmail(user, textTemplate, htmlTemplate, callback) {
const sendMail = require('./email.js').sendMail; const sendMail = require('./email.js').sendMail;
const resetUrl = webServer.instance.buildUrl( const resetUrl = buildUrl(
WellKnownLocations.Internal + WellKnownLocations.Internal +
`/sec/reset_password?token=${ `/sec/reset_password?token=${
user.properties[UserProps.EmailPwResetToken] user.properties[UserProps.EmailPwResetToken]
@ -271,7 +272,7 @@ class WebPasswordReset {
); );
} }
const postResetUrl = webServer.instance.buildUrl( const postResetUrl = buildUrl(
WellKnownLocations.Internal + '/sec/reset_password' WellKnownLocations.Internal + '/sec/reset_password'
); );

View File

@ -4,6 +4,9 @@ const Config = require('./config').get;
const { get, isString } = require('lodash'); const { get, isString } = require('lodash');
exports.getWebDomain = getWebDomain; exports.getWebDomain = getWebDomain;
exports.getBaseUrl = getBaseUrl;
exports.getFullUrl = getFullUrl;
exports.buildUrl = buildUrl;
function getWebDomain() { function getWebDomain() {
const config = Config(); const config = Config();
@ -15,3 +18,46 @@ function getWebDomain() {
return config.contentServers.web.domain; return config.contentServers.web.domain;
} }
function getBaseUrl() {
const config = Config();
const overridePrefix = get(config, 'contentServers.web.overrideUrlPrefix');
if (overridePrefix) {
return overridePrefix;
}
let schema;
let port;
if (config.contentServers.web.https.enabled) {
schema = 'https://';
port =
443 === config.contentServers.web.https.port
? ''
: `:${config.contentServers.web.https.port}`;
} else {
schema = 'http://';
port =
80 === config.contentServers.web.http.port
? ''
: `:${config.contentServers.web.http.port}`;
}
return `${schema}${config.contentServers.web.domain}${port}`;
}
function getFullUrl(req) {
const base = getBaseUrl();
return new URL(`${base}${req.url}`);
}
function buildUrl(pathAndQuery) {
//
// Create a URL such as
// https://l33t.codes:44512/ + |pathAndQuery|
//
// Prefer HTTPS over HTTP. Be explicit about the port
// only if non-standard. Allow users to override full prefix in config.
//
const base = getBaseUrl();
return `${base}${pathAndQuery}`;
}