From fb02fc599a39671e3f038e1cf738d0df551a719b Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sat, 18 Mar 2023 14:30:37 -0600 Subject: [PATCH] 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 --- core/activitypub/actor.js | 12 ++--- core/activitypub/collection.js | 14 +++-- core/activitypub/endpoint.js | 45 ++++++++-------- core/activitypub/object.js | 4 +- core/activitypub/util.js | 42 +++++++++++++++ core/file_area_web.js | 5 +- core/oputil/activitypub.js | 47 ++++++++--------- core/oputil/oputil_common.js | 2 + core/oputil/oputil_help.js | 3 ++ core/scanner_tossers/activitypub.js | 2 +- core/servers/content/web.js | 48 ----------------- .../content/web_handlers/activitypub.js | 51 +++---------------- .../servers/content/web_handlers/nodeinfo2.js | 5 +- .../content/web_handlers/system_general.js | 5 +- .../servers/content/web_handlers/webfinger.js | 34 +++++-------- core/user_2fa_otp_web_register.js | 6 +-- core/user_property.js | 2 + core/web_password_reset.js | 5 +- core/web_util.js | 46 +++++++++++++++++ 19 files changed, 187 insertions(+), 191 deletions(-) diff --git a/core/activitypub/actor.js b/core/activitypub/actor.js index 998aa6f5..2d7d82e6 100644 --- a/core/activitypub/actor.js +++ b/core/activitypub/actor.js @@ -115,14 +115,14 @@ module.exports = class Actor extends ActivityPubObject { ? user.getSanitizedName('real') : user.username, endpoints: { - sharedInbox: Endpoints.sharedInbox(webServer), + sharedInbox: Endpoints.sharedInbox(), }, - inbox: Endpoints.inbox(webServer, user), - outbox: Endpoints.outbox(webServer, user), - followers: Endpoints.followers(webServer, user), - following: Endpoints.following(webServer, user), + inbox: Endpoints.inbox(user), + outbox: Endpoints.outbox(user), + followers: Endpoints.followers(user), + following: Endpoints.following(user), summary: user.getProperty(UserProps.AutoSignature) || '', - url: Endpoints.profile(webServer, user), + url: Endpoints.profile(user), manuallyApprovesFollowers: userSettings.manuallyApprovesFollowers, discoverable: userSettings.discoverable, // :TODO: we can start to define BBS related stuff with the community perhaps diff --git a/core/activitypub/collection.js b/core/activitypub/collection.js index 02129291..096451ff 100644 --- a/core/activitypub/collection.js +++ b/core/activitypub/collection.js @@ -84,7 +84,7 @@ module.exports = class Collection extends ActivityPubObject { } static addFollower(owningUser, followingActor, webServer, ignoreDupes, cb) { - const collectionId = Endpoints.followers(webServer, owningUser); + const collectionId = Endpoints.followers(owningUser); return Collection.addToCollection( Collections.Followers, owningUser, @@ -98,8 +98,7 @@ module.exports = class Collection extends ActivityPubObject { } static addFollowRequest(owningUser, requestingActor, webServer, ignoreDupes, cb) { - const collectionId = - Endpoints.makeUserUrl(webServer, owningUser) + 'follow-requests'; + const collectionId = Endpoints.makeUserUrl(owningUser) + 'follow-requests'; return Collection.addToCollection( Collections.FollowRequests, owningUser, @@ -113,7 +112,7 @@ module.exports = class Collection extends ActivityPubObject { } static addFollowing(owningUser, followingActor, webServer, ignoreDupes, cb) { - const collectionId = Endpoints.following(webServer, owningUser); + const collectionId = Endpoints.following(owningUser); return Collection.addToCollection( Collections.Following, owningUser, @@ -127,7 +126,7 @@ module.exports = class Collection extends ActivityPubObject { } static addOutboxItem(owningUser, outboxItem, isPrivate, webServer, ignoreDupes, cb) { - const collectionId = Endpoints.outbox(webServer, owningUser); + const collectionId = Endpoints.outbox(owningUser); return Collection.addToCollection( Collections.Outbox, owningUser, @@ -141,7 +140,7 @@ module.exports = class Collection extends ActivityPubObject { } static addInboxItem(inboxItem, owningUser, webServer, ignoreDupes, cb) { - const collectionId = Endpoints.inbox(webServer, owningUser); + const collectionId = Endpoints.inbox(owningUser); return Collection.addToCollection( Collections.Inbox, owningUser, @@ -498,8 +497,7 @@ module.exports = class Collection extends ActivityPubObject { } // e.g. http://somewhere.com/_enig/ap/users/NuSkooler/followers - const collectionId = - Endpoints.makeUserUrl(webServer, owningUser) + `/${collectionName}`; + const collectionId = Endpoints.makeUserUrl(owningUser) + `/${collectionName}`; if (!page) { return apDb.get( diff --git a/core/activitypub/endpoint.js b/core/activitypub/endpoint.js index 53dabadb..e4429a54 100644 --- a/core/activitypub/endpoint.js +++ b/core/activitypub/endpoint.js @@ -1,4 +1,5 @@ const { WellKnownLocations } = require('../servers/content/web'); +const { buildUrl } = require('../web_util'); // deps const { v4: UUIDv4 } = require('uuid'); @@ -16,46 +17,42 @@ exports.objectId = objectId; const ActivityPubUsersPrefix = '/ap/users/'; -function makeUserUrl(webServer, user, relPrefix = ActivityPubUsersPrefix) { - return webServer.buildUrl( - WellKnownLocations.Internal + `${relPrefix}${user.username}` - ); +function makeUserUrl(user, relPrefix = ActivityPubUsersPrefix) { + return buildUrl(WellKnownLocations.Internal + `${relPrefix}${user.username}`); } -function inbox(webServer, user) { - return makeUserUrl(webServer, user, ActivityPubUsersPrefix) + '/inbox'; +function inbox(user) { + return makeUserUrl(user, ActivityPubUsersPrefix) + '/inbox'; } -function outbox(webServer, user) { - return makeUserUrl(webServer, user, ActivityPubUsersPrefix) + '/outbox'; +function outbox(user) { + return makeUserUrl(user, ActivityPubUsersPrefix) + '/outbox'; } -function followers(webServer, user) { - return makeUserUrl(webServer, user, ActivityPubUsersPrefix) + '/followers'; +function followers(user) { + return makeUserUrl(user, ActivityPubUsersPrefix) + '/followers'; } -function following(webServer, user) { - return makeUserUrl(webServer, user, ActivityPubUsersPrefix) + '/following'; +function following(user) { + return makeUserUrl(user, ActivityPubUsersPrefix) + '/following'; } -function actorId(webServer, user) { - return makeUserUrl(webServer, user, ActivityPubUsersPrefix); +function actorId(user) { + return makeUserUrl(user, ActivityPubUsersPrefix); } -function profile(webServer, user) { - return webServer.buildUrl(WellKnownLocations.Internal + `/wf/@${user.username}`); +function profile(user) { + return buildUrl(WellKnownLocations.Internal + `/wf/@${user.username}`); } -function avatar(webServer, user, filename) { - return makeUserUrl(webServer, user, '/users/') + `/avatar/${filename}`; +function avatar(user, filename) { + return makeUserUrl(user, '/users/') + `/avatar/${filename}`; } -function sharedInbox(webServer) { - return webServer.buildUrl(WellKnownLocations.Internal + '/ap/shared-inbox'); +function sharedInbox() { + return buildUrl(WellKnownLocations.Internal + '/ap/shared-inbox'); } -function objectId(webServer, objectType) { - return webServer.buildUrl( - WellKnownLocations.Internal + `/ap/${UUIDv4()}/${objectType}` - ); +function objectId(objectType) { + return buildUrl(WellKnownLocations.Internal + `/ap/${UUIDv4()}/${objectType}`); } diff --git a/core/activitypub/object.js b/core/activitypub/object.js index 21e4217b..10dd7a3c 100644 --- a/core/activitypub/object.js +++ b/core/activitypub/object.js @@ -84,7 +84,7 @@ module.exports = class ActivityPubObject { } static makeObjectId(webServer, objectType) { - return Endpoints.objectId(webServer, objectType); + return Endpoints.objectId(objectType); } sendTo(inboxEndpoint, fromUser, webServer, cb) { @@ -103,7 +103,7 @@ module.exports = class ActivityPubObject { }, sign: { key: privateKey, - keyId: Endpoints.actorId(webServer, fromUser) + '#main-key', + keyId: Endpoints.actorId(fromUser) + '#main-key', authorizationHeaderName: 'Signature', headers: HttpSignatureSignHeaders, }, diff --git a/core/activitypub/util.js b/core/activitypub/util.js index d66d657e..8f19d0fe 100644 --- a/core/activitypub/util.js +++ b/core/activitypub/util.js @@ -6,6 +6,7 @@ const { stripAnsiControlCodes } = require('../string_util'); const { WellKnownRecipientFields } = require('./const'); const Log = require('../logger').log; const { getWebDomain } = require('../web_util'); +const Endpoints = require('./endpoint'); // deps const _ = require('lodash'); @@ -30,6 +31,7 @@ exports.userNameFromSubject = userNameFromSubject; exports.userNameToSubject = userNameToSubject; exports.extractMessageMetadata = extractMessageMetadata; exports.recipientIdsFromObject = recipientIdsFromObject; +exports.prepareLocalUserAsActor = prepareLocalUserAsActor; // :TODO: more info in default // this profile template is the *default* for both WebFinger @@ -262,3 +264,43 @@ function recipientIdsFromObject(obj) { 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); + }); + }); +} diff --git a/core/file_area_web.js b/core/file_area_web.js index 4ffe2491..6c04f59e 100644 --- a/core/file_area_web.js +++ b/core/file_area_web.js @@ -17,6 +17,7 @@ const webServerPackageName = require('./servers/content/web.js').moduleInfo.pack const Events = require('./events.js'); const UserProps = require('./user_property.js'); const SysProps = require('./system_menu_method.js'); +const { buildUrl } = require('./web_util'); // deps const hashids = require('hashids/cjs'); @@ -202,11 +203,11 @@ class FileAreaWebAccess { buildSingleFileTempDownloadLink(client, fileEntry, hashId) { 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) { - return this.webServer.instance.buildUrl(`${Config().fileBase.web.path}${hashId}`); + return buildUrl(`${Config().fileBase.web.path}${hashId}`); } getExistingTempDownloadServeItem(client, fileEntry, cb) { diff --git a/core/oputil/activitypub.js b/core/oputil/activitypub.js index 11858d5d..a0a2c280 100644 --- a/core/oputil/activitypub.js +++ b/core/oputil/activitypub.js @@ -5,7 +5,6 @@ const { initConfigAndDatabases, } = require('./oputil_common'); const getHelpFor = require('./oputil_help.js').getHelpFor; -const UserProps = require('../user_property'); const { Errors } = require('../enig_error'); // deps @@ -48,24 +47,30 @@ function applyAction(username, actionFunc, cb) { }); } -function conditionSingleUser(User, username, userId, settings, cb) { - const { userNameToSubject } = require('../activitypub/util'); - const subject = userNameToSubject(username); +function conditionSingleUser(user, cb) { + const { userNameToSubject, prepareLocalUserAsActor } = require('../activitypub/util'); + + const subject = userNameToSubject(user.username); 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 => { + if (err) { + return cb(err); + } - User.persistPropertyByUserId(userId, UserProps.ActivityPubSettings, settings, err => { - return cb(err); + user.persistProperties(user.properties, err => { + if (err) { + return cb(err); + } + }); }); } function actionConditionAllUsers(_, cb) { const User = require('../../core/user.js'); - const ActivityPubSettings = require('../activitypub/settings'); - const defaultSettings = JSON.stringify(new ActivityPubSettings()); User.getUserList({}, (err, userList) => { if (err) { @@ -75,13 +80,12 @@ function actionConditionAllUsers(_, cb) { async.each( userList, (entry, next) => { - conditionSingleUser( - User, - entry.userName, - entry.userId, - defaultSettings, - next - ); + User.getUser(entry.userId, (err, user) => { + if (err) { + return next(err); + } + return conditionSingleUser(user, next); + }); }, 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() { // // Web Server, and ActivityPub both must be enabled @@ -116,7 +113,7 @@ function validateActivityPub() { function conditionUser(action, username) { return applyAction( username, - '*' === username ? actionConditionAllUsers : actionConditionUser, + '*' === username ? actionConditionAllUsers : conditionSingleUser, err => { if (err) { console.error(err.message); diff --git a/core/oputil/oputil_common.js b/core/oputil/oputil_common.js index bbc6c500..3e56734b 100644 --- a/core/oputil/oputil_common.js +++ b/core/oputil/oputil_common.js @@ -10,6 +10,7 @@ const async = require('async'); const inq = require('inquirer'); const fs = require('fs'); const hjson = require('hjson'); +const log = require('../../core/logger'); const packageJson = require('../../package.json'); @@ -81,6 +82,7 @@ function initConfigAndDatabases(cb) { initConfig(callback); }, function initDb(callback) { + log.init(); db.initializeDatabases(callback); }, function initArchiveUtil(callback) { diff --git a/core/oputil/oputil_help.js b/core/oputil/oputil_help.js index c7a17d09..6a5ee7d9 100644 --- a/core/oputil/oputil_help.js +++ b/core/oputil/oputil_help.js @@ -215,6 +215,9 @@ Actions: condition USERNAME Condition user with system ActivityPub defaults Instead of an actual USERNAME, the '*' character may be substituted. + +condition arguments: + --force Force condition; overrides any existing settings `, }); diff --git a/core/scanner_tossers/activitypub.js b/core/scanner_tossers/activitypub.js index 35e23ed4..4c7b8383 100644 --- a/core/scanner_tossers/activitypub.js +++ b/core/scanner_tossers/activitypub.js @@ -299,7 +299,7 @@ exports.getModule = class ActivityPubScannerTosser extends MessageScanTossModule } _collectFollowersSharedInboxEndpoints(localUser, cb) { - const localFollowersEndpoint = Endpoints.followers(this._webServer(), localUser); + const localFollowersEndpoint = Endpoints.followers(localUser); Collection.followers(localFollowersEndpoint, 'all', (err, collection) => { if (err) { diff --git a/core/servers/content/web.js b/core/servers/content/web.js index cf9331a6..9bc8ebb2 100644 --- a/core/servers/content/web.js +++ b/core/servers/content/web.js @@ -5,7 +5,6 @@ const Config = require('../../config.js').get; const { Errors } = require('../../enig_error.js'); const { loadModulesForCategory, moduleCategories } = require('../../module_util'); const WebHandlerModule = require('../../web_handler_module'); -const { getWebDomain } = require('../../web_util'); // deps const http = require('http'); @@ -90,53 +89,6 @@ exports.getModule = class WebServerModule extends ServerModule { 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() { return this.enableHttp || this.enableHttps; } diff --git a/core/servers/content/web_handlers/activitypub.js b/core/servers/content/web_handlers/activitypub.js index daeb336a..c8f7caaf 100644 --- a/core/servers/content/web_handlers/activitypub.js +++ b/core/servers/content/web_handlers/activitypub.js @@ -4,8 +4,8 @@ const { getUserProfileTemplatedBody, DefaultProfileTemplate, getActorId, + prepareLocalUserAsActor, } = require('../../../activitypub/util'); -const Endpoints = require('../../../activitypub/endpoint'); const { ActivityStreamMediaType, WellKnownActivity, @@ -21,15 +21,14 @@ const Note = require('../../../activitypub/note'); const EnigAssert = require('../../../enigma_assert'); const Message = require('../../../message'); const Events = require('../../../events'); -const UserProps = require('../../../user_property'); const { Errors } = require('../../../enig_error'); +const { getFullUrl } = require('../../../web_util'); // deps const _ = require('lodash'); const enigma_assert = require('../../../enigma_assert'); const httpSignature = require('http-signature'); const async = require('async'); -const paths = require('path'); exports.moduleInfo = { name: 'ActivityPub', @@ -175,7 +174,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule { _selfUrlRequestHandler(req, resp) { this.log.trace({ url: req.url }, 'Request for "self"'); - let actorId = this.webServer.fullUrl(req).toString(); + let actorId = getFullUrl(req).toString(); let sendActor = false; if (actorId.endsWith('.json')) { sendActor = true; @@ -783,7 +782,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule { _localUserFromCollectionEndpoint(req, collectionName, cb) { // turn a collection URL to a Actor ID - let actorId = this.webServer.fullUrl(req).toString(); + let actorId = getFullUrl(req).toString(); const suffix = `/${collectionName}`; if (actorId.endsWith(suffix)) { actorId = actorId.slice(0, -suffix.length); @@ -986,7 +985,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule { return this.webServer.resourceNotFound(resp); } - const url = this.webServer.fullUrl(req); + const url = getFullUrl(req); const page = url.searchParams.get('page'); const collectionId = url.toString(); @@ -1037,7 +1036,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule { _singlePublicNoteGetHandler(req, resp) { 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) => { if (err) { return this.webServer.internalServerError(resp, err); @@ -1180,42 +1179,6 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule { `Preparing ActivityPub settings for "${user.username}"` ); - const actorId = Endpoints.actorId(this.webServer, user); - 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); - }); - }); + return prepareLocalUserAsActor(user, cb); } }; diff --git a/core/servers/content/web_handlers/nodeinfo2.js b/core/servers/content/web_handlers/nodeinfo2.js index 52bdf5f1..741b6665 100644 --- a/core/servers/content/web_handlers/nodeinfo2.js +++ b/core/servers/content/web_handlers/nodeinfo2.js @@ -6,6 +6,7 @@ const packageJson = require('../../../../package.json'); const StatLog = require('../../../stat_log'); const SysProps = require('../../../system_property'); const SysLogKeys = require('../../../system_log'); +const { getBaseUrl, getWebDomain } = require('../../../web_util'); // deps const moment = require('moment'); @@ -30,7 +31,7 @@ exports.getModule = class NodeInfo2WebHandler extends WebHandlerModule { this.log = webServer.logger().child({ webHandler: 'NodeInfo2' }); - const domain = this.webServer.getDomain(); + const domain = getWebDomain(); if (!domain) { return cb(Errors.UnexpectedState('Web server does not have "domain" set')); } @@ -65,7 +66,7 @@ exports.getModule = class NodeInfo2WebHandler extends WebHandlerModule { const nodeInfo = { version: '1.0', server: { - baseUrl: this.webServer.baseUrl(), + baseUrl: getBaseUrl(), name: config.general.boardName, software: 'ENiGMA½ Bulletin Board Software', version: packageJson.version, diff --git a/core/servers/content/web_handlers/system_general.js b/core/servers/content/web_handlers/system_general.js index 2b92b4fe..e5b79804 100644 --- a/core/servers/content/web_handlers/system_general.js +++ b/core/servers/content/web_handlers/system_general.js @@ -2,6 +2,7 @@ const WebHandlerModule = require('../../../web_handler_module'); const { Errors } = require('../../../enig_error'); const EngiAssert = require('../../../enigma_assert'); const Config = require('../../../config').get; +const { getFullUrl, getWebDomain } = require('../../../web_util'); // deps const paths = require('path'); @@ -28,7 +29,7 @@ exports.getModule = class SystemGeneralWebHandler extends WebHandlerModule { this.log = webServer.logger().child({ webHandler: 'SysGeneral' }); - const domain = this.webServer.getDomain(); + const domain = getWebDomain(); if (!domain) { return cb(Errors.UnexpectedState('Web server does not have "domain" set')); } @@ -44,7 +45,7 @@ exports.getModule = class SystemGeneralWebHandler extends WebHandlerModule { } _avatarGetHandler(req, resp) { - const url = this.webServer.fullUrl(req); + const url = getFullUrl(req); const filename = paths.basename(url.pathname); if (!filename) { return this.webServer.fileNotFound(resp); diff --git a/core/servers/content/web_handlers/webfinger.js b/core/servers/content/web_handlers/webfinger.js index 9326946b..05816c4c 100644 --- a/core/servers/content/web_handlers/webfinger.js +++ b/core/servers/content/web_handlers/webfinger.js @@ -11,6 +11,7 @@ const EngiAssert = require('../../../enigma_assert'); const User = require('../../../user'); const UserProps = require('../../../user_property'); const ActivityPubSettings = require('../../../activitypub/settings'); +const { getFullUrl, buildUrl, getWebDomain } = require('../../../web_util'); // deps const _ = require('lodash'); @@ -38,7 +39,7 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule { this.log = webServer.logger().child({ webHandler: 'WebFinger' }); - const domain = this.webServer.getDomain(); + const domain = getWebDomain(); if (!domain) { 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}$`), // profile page // https://webfinger.net/rel/profile-page/ - new RegExp( - `^${this.webServer.buildUrl(WellKnownLocations.Internal + '/wf/@')}(.+)$` - ), + new RegExp(`^${buildUrl(WellKnownLocations.Internal + '/wf/@')}(.+)$`), // self URL - new RegExp( - `^${this.webServer.buildUrl( - WellKnownLocations.Internal + '/ap/users/' - )}(.+)$` - ), + new RegExp(`^${buildUrl(WellKnownLocations.Internal + '/ap/users/')}(.+)$`), ]; this.webServer.addRoute({ @@ -78,7 +73,7 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule { _profileRequestHandler(req, resp) { // 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); if (!accountName) { this.log.warn( @@ -135,7 +130,7 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule { } _webFingerRequestHandler(req, resp) { - const url = this.webServer.fullUrl(req); + const url = getFullUrl(req); const resource = url.searchParams.get('resource'); if (!resource) { return this.webServer.respondWithError( @@ -161,11 +156,10 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule { return this.webServer.resourceNotFound(resp); } - const domain = this.webServer.getDomain(); - + const domain = getWebDomain(); const body = JSON.stringify({ subject: `acct:${localUser.username}@${domain}`, - aliases: [this._profileUrl(localUser), this._userActorId(localUser)], + aliases: [Endpoints.profile(localUser), Endpoints.actorId(localUser)], links: [ this._profilePageLink(localUser), this._selfLink(localUser), @@ -218,12 +212,8 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule { }); } - _profileUrl(user) { - return Endpoints.profile(this.webServer, user); - } - _profilePageLink(user) { - const href = this._profileUrl(user); + const href = Endpoints.profile(user); return { rel: 'http://webfinger.net/rel/profile-page', type: 'text/plain', @@ -232,12 +222,12 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule { } _userActorId(user) { - return Endpoints.actorId(this.webServer, user); + return Endpoints.actorId(user); } // :TODO: only if ActivityPub is enabled _selfLink(user) { - const href = this._userActorId(user); + const href = Endpoints.actorId(user); return { rel: 'self', type: 'application/activity+json', @@ -249,7 +239,7 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule { _subscribeLink() { return { rel: 'http://ostatus.org/schema/1.0/subscribe', - template: this.webServer.buildUrl( + template: buildUrl( WellKnownLocations.Internal + '/ap/authorize_interaction?uri={uri}' ), }; diff --git a/core/user_2fa_otp_web_register.js b/core/user_2fa_otp_web_register.js index 27944a5c..df6c14ee 100644 --- a/core/user_2fa_otp_web_register.js +++ b/core/user_2fa_otp_web_register.js @@ -17,6 +17,7 @@ const { sendMail } = require('./email.js'); const UserProps = require('./user_property.js'); const Log = require('./logger.js').log; const { getConnectionByUserId } = require('./client_connections.js'); +const { buildUrl } = require('./web_util'); // deps const async = require('async'); @@ -75,8 +76,7 @@ module.exports = class User2FA_OTPWebRegister { }); }, (token, textTemplate, htmlTemplate, callback) => { - const webServer = getWebServer(); - const registerUrl = webServer.instance.buildUrl( + const registerUrl = buildUrl( WellKnownLocations.Internal + `/2fa/enable_2fa_otp?token=${token}&otpType=${otpType}` ); @@ -172,7 +172,7 @@ module.exports = class User2FA_OTPWebRegister { return User2FA_OTPWebRegister.accessDenied(webServer, resp); } - const postUrl = webServer.instance.buildUrl( + const postUrl = buildUrl( WellKnownLocations.Internal + '/2fa/enable_2fa_otp' ); const config = Config(); diff --git a/core/user_property.js b/core/user_property.js index 3d40b6f5..5ddbf602 100644 --- a/core/user_property.js +++ b/core/user_property.js @@ -70,6 +70,8 @@ module.exports = { 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) + AvatarImageUrl: 'user_avatar_image', + ActivityPubSettings: 'activitypub_settings', // JSON object (above); see ActivityPubSettings in activitypub/settings.js ActivityPubActorId: 'activitypub_actor_id', // Actor ID representing this users }; diff --git a/core/web_password_reset.js b/core/web_password_reset.js index a1b46bf1..1e4f4fff 100644 --- a/core/web_password_reset.js +++ b/core/web_password_reset.js @@ -12,6 +12,7 @@ const userDb = require('./database.js').dbs.user; const getISOTimestampString = require('./database.js').getISOTimestampString; const Log = require('./logger.js').log; const UserProps = require('./user_property.js'); +const { buildUrl } = require('./web_util'); // deps const async = require('async'); @@ -121,7 +122,7 @@ class WebPasswordReset { function buildAndSendEmail(user, textTemplate, htmlTemplate, callback) { const sendMail = require('./email.js').sendMail; - const resetUrl = webServer.instance.buildUrl( + const resetUrl = buildUrl( WellKnownLocations.Internal + `/sec/reset_password?token=${ user.properties[UserProps.EmailPwResetToken] @@ -271,7 +272,7 @@ class WebPasswordReset { ); } - const postResetUrl = webServer.instance.buildUrl( + const postResetUrl = buildUrl( WellKnownLocations.Internal + '/sec/reset_password' ); diff --git a/core/web_util.js b/core/web_util.js index eb13a8eb..024193ba 100644 --- a/core/web_util.js +++ b/core/web_util.js @@ -4,6 +4,9 @@ const Config = require('./config').get; const { get, isString } = require('lodash'); exports.getWebDomain = getWebDomain; +exports.getBaseUrl = getBaseUrl; +exports.getFullUrl = getFullUrl; +exports.buildUrl = buildUrl; function getWebDomain() { const config = Config(); @@ -15,3 +18,46 @@ function getWebDomain() { 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}`; +}