Hand back a profile for self

This commit is contained in:
Bryan Ashby 2023-01-04 20:29:18 -07:00
parent d4f74447ec
commit 5055337eff
No known key found for this signature in database
GPG Key ID: C2C1B501E4EFD994
4 changed files with 195 additions and 123 deletions

View File

@ -3,10 +3,32 @@ const User = require('./user');
const { Errors } = require('./enig_error');
const UserProps = require('./user_property');
// deps
const _ = require('lodash');
const mimeTypes = require('mime-types');
const waterfall = require('async/waterfall');
const fs = require('graceful-fs');
const paths = require('path');
const moment = require('moment');
exports.makeUserUrl = makeUserUrl;
exports.webFingerProfileUrl = webFingerProfileUrl;
exports.selfUrl = selfUrl;
exports.userFromAccount = userFromAccount;
exports.getUserProfileTemplatedBody = getUserProfileTemplatedBody;
// :TODO: more info in default
// this profile template is the *default* for both WebFinger
// profiles and 'self' requests without the
// Accept: application/activity+json headers present
exports.DefaultProfileTemplate = `
User information for: %USERNAME%
Real Name: %REAL_NAME%
Login Count: %LOGIN_COUNT%
Affiliations: %AFFILIATIONS%
Achievement Points: %ACHIEVEMENT_POINTS%
`;
function makeUserUrl(webServer, user, relPrefix) {
return webServer.buildUrl(
@ -45,3 +67,73 @@ function userFromAccount(accountName, cb) {
});
});
}
function getUserProfileTemplatedBody(
templateFile,
user,
defaultTemplate,
defaultContentType,
cb
) {
const Log = require('./logger').log;
const Config = require('./config').get;
waterfall(
[
callback => {
return fs.readFile(templateFile || '', 'utf8', (err, template) => {
return callback(null, template);
});
},
(template, callback) => {
if (!template) {
if (templateFile) {
Log.warn(`Failed to load profile template "${templateFile}"`);
}
return callback(null, defaultTemplate, defaultContentType);
}
const contentType = mimeTypes.contentType(paths.basename(templateFile));
return callback(null, template, contentType);
},
(template, contentType, callback) => {
const up = (p, na = 'N/A') => {
return user.getProperty(p) || na;
};
let birthDate = up(UserProps.Birthdate);
if (moment.isDate(birthDate)) {
birthDate = moment(birthDate);
}
const varMap = {
USERNAME: user.username,
REAL_NAME: user.getSanitizedName('real'),
SEX: up(UserProps.Sex),
BIRTHDATE: birthDate,
AGE: user.getAge(),
LOCATION: up(UserProps.Location),
AFFILIATIONS: up(UserProps.Affiliations),
EMAIL: up(UserProps.EmailAddress),
WEB_ADDRESS: up(UserProps.WebAddress),
ACCOUNT_CREATED: moment(user.getProperty(UserProps.AccountCreated)),
LAST_LOGIN: moment(user.getProperty(UserProps.LastLoginTs)),
LOGIN_COUNT: up(UserProps.LoginCount),
ACHIEVEMENT_COUNT: up(UserProps.AchievementTotalCount, '0'),
ACHIEVEMENT_POINTS: up(UserProps.AchievementTotalPoints, '0'),
BOARDNAME: Config().general.boardName,
};
let body = template;
_.each(varMap, (val, varName) => {
body = body.replace(new RegExp(`%${varName}%`, 'g'), val);
});
return callback(null, body, contentType);
},
],
(err, data, contentType) => {
return cb(err, data, contentType);
}
);
}

View File

@ -317,8 +317,8 @@ exports.getModule = class WebServerModule extends ServerModule {
req.url.substr(req.url.lastIndexOf('/', 1)),
tryFile
);
const filePath = this.resolveStaticPath(fileName);
const filePath = this.resolveStaticPath(fileName);
fs.stat(filePath, (err, stats) => {
if (err || !stats.isFile()) {
return nextTryFile(null, false);

View File

@ -4,9 +4,15 @@ const {
webFingerProfileUrl,
selfUrl,
userFromAccount,
getUserProfileTemplatedBody,
DefaultProfileTemplate,
} = require('../../../activitypub_util');
const UserProps = require('../../../user_property');
const { Errors } = require('../../../enig_error');
const Config = require('../../../config').get;
// deps
const _ = require('lodash');
exports.moduleInfo = {
name: 'ActivityPub',
@ -36,15 +42,6 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
}
_selfUrlRequestHandler(req, resp) {
const accept = req.headers['accept'] || '*/*';
if (accept === 'application/activity+json') {
return this._selfAsActorHandler(req, resp);
}
return this._standardSelfHandler(req, resp);
}
_selfAsActorHandler(req, resp) {
const url = new URL(req.url, `https://${req.headers.host}`);
const accountName = url.pathname.substring(url.pathname.lastIndexOf('/') + 1);
@ -53,6 +50,16 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
return this._notFound(resp);
}
const accept = req.headers['accept'] || '*/*';
if (accept === 'application/activity+json') {
return this._selfAsActorHandler(user, req, resp);
}
return this._standardSelfHandler(user, req, resp);
});
}
_selfAsActorHandler(user, req, resp) {
const body = JSON.stringify({
'@context': [
'https://www.w3.org/ns/activitystreams',
@ -83,14 +90,39 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
resp.writeHead(200, headers);
return resp.end(body);
});
}
_standardSelfHandler(req, resp) {
// :TODO: this should also be their profile page?! Perhaps that should also be shared...
_standardSelfHandler(user, req, resp) {
let templateFile = _.get(
Config(),
'contentServers.web.handlers.activityPub.selfTemplate'
);
if (templateFile) {
templateFile = this.webServer.resolveTemplatePath(templateFile);
}
// we'll fall back to the same default profile info as the WebFinger profile
getUserProfileTemplatedBody(
templateFile,
user,
DefaultProfileTemplate,
'text/plain',
(err, body, contentType) => {
if (err) {
return this._notFound(resp);
}
const headers = {
'Content-Type': contentType,
'Content-Length': body.length,
};
resp.writeHead(200, headers);
return resp.end(body);
}
);
}
_notFound(resp) {
this.webServer.respondWithError(
resp,

View File

@ -1,22 +1,17 @@
const WebHandlerModule = require('../../../web_handler_module');
const Config = require('../../../config').get;
const { Errors, ErrorReasons } = require('../../../enig_error');
const { Errors } = require('../../../enig_error');
const { WellKnownLocations } = require('../web');
const {
selfUrl,
webFingerProfileUrl,
userFromAccount,
getUserProfileTemplatedBody,
DefaultProfileTemplate,
} = require('../../../activitypub_util');
const _ = require('lodash');
const User = require('../../../user');
const UserProps = require('../../../user_property');
const Log = require('../../../logger').log;
const mimeTypes = require('mime-types');
const fs = require('graceful-fs');
const paths = require('path');
const moment = require('moment');
exports.moduleInfo = {
name: 'WebFinger',
@ -25,16 +20,6 @@ exports.moduleInfo = {
packageName: 'codes.l33t.enigma.web.handler.webfinger',
};
// :TODO: more info in default
const DefaultProfileTemplate = `
User information for: %USERNAME%
Real Name: %REAL_NAME%
Login Count: %LOGIN_COUNT%
Affiliations: %AFFILIATIONS%
Achievement Points: %ACHIEVEMENT_POINTS%
`;
//
// WebFinger: https://www.rfc-editor.org/rfc/rfc7033
//
@ -117,51 +102,6 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule {
return this._notFound(resp);
}
this._getProfileTemplate((template, mimeType) => {
const up = (p, na = 'N/A') => {
return user.getProperty(p) || na;
};
let birthDate = up(UserProps.Birthdate);
if (moment.isDate(birthDate)) {
birthDate = moment(birthDate);
}
const varMap = {
USERNAME: user.username,
REAL_NAME: user.getSanitizedName('real'),
SEX: up(UserProps.Sex),
BIRTHDATE: birthDate,
AGE: user.getAge(),
LOCATION: up(UserProps.Location),
AFFILIATIONS: up(UserProps.Affiliations),
EMAIL: up(UserProps.EmailAddress),
WEB_ADDRESS: up(UserProps.WebAddress),
ACCOUNT_CREATED: moment(user.getProperty(UserProps.AccountCreated)),
LAST_LOGIN: moment(user.getProperty(UserProps.LastLoginTs)),
LOGIN_COUNT: up(UserProps.LoginCount),
ACHIEVEMENT_COUNT: up(UserProps.AchievementTotalCount, '0'),
ACHIEVEMENT_POINTS: up(UserProps.AchievementTotalPoints, '0'),
BOARDNAME: Config().general.boardName,
};
let body = template;
_.each(varMap, (val, varName) => {
body = body.replace(new RegExp(`%${varName}%`, 'g'), val);
});
const headers = {
'Content-Type': mimeType,
'Content-Length': body.length,
};
resp.writeHead(200, headers);
return resp.end(body);
});
});
}
_getProfileTemplate(cb) {
let templateFile = _.get(
Config(),
'contentServers.web.handlers.webFinger.profileTemplate'
@ -169,18 +109,26 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule {
if (templateFile) {
templateFile = this.webServer.resolveTemplatePath(templateFile);
}
fs.readFile(templateFile || '', 'utf8', (err, data) => {
getUserProfileTemplatedBody(
templateFile,
user,
DefaultProfileTemplate,
'text/plain',
(err, body, contentType) => {
if (err) {
if (templateFile) {
Log.warn(
{ error: err.message },
`Failed to load profile template "${templateFile}"`
);
return this._notFound(resp);
}
return cb(DefaultProfileTemplate, 'text/plain');
const headers = {
'Content-Type': contentType,
'Content-Length': body.length,
};
resp.writeHead(200, headers);
return resp.end(body);
}
return cb(data, mimeTypes.contentType(paths.basename(templateFile)));
);
});
}