Hand back a profile for self
This commit is contained in:
parent
d4f74447ec
commit
5055337eff
|
@ -3,10 +3,32 @@ const User = require('./user');
|
||||||
const { Errors } = require('./enig_error');
|
const { Errors } = require('./enig_error');
|
||||||
const UserProps = require('./user_property');
|
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.makeUserUrl = makeUserUrl;
|
||||||
exports.webFingerProfileUrl = webFingerProfileUrl;
|
exports.webFingerProfileUrl = webFingerProfileUrl;
|
||||||
exports.selfUrl = selfUrl;
|
exports.selfUrl = selfUrl;
|
||||||
exports.userFromAccount = userFromAccount;
|
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) {
|
function makeUserUrl(webServer, user, relPrefix) {
|
||||||
return webServer.buildUrl(
|
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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -317,8 +317,8 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||||
req.url.substr(req.url.lastIndexOf('/', 1)),
|
req.url.substr(req.url.lastIndexOf('/', 1)),
|
||||||
tryFile
|
tryFile
|
||||||
);
|
);
|
||||||
const filePath = this.resolveStaticPath(fileName);
|
|
||||||
|
|
||||||
|
const filePath = this.resolveStaticPath(fileName);
|
||||||
fs.stat(filePath, (err, stats) => {
|
fs.stat(filePath, (err, stats) => {
|
||||||
if (err || !stats.isFile()) {
|
if (err || !stats.isFile()) {
|
||||||
return nextTryFile(null, false);
|
return nextTryFile(null, false);
|
||||||
|
|
|
@ -4,9 +4,15 @@ const {
|
||||||
webFingerProfileUrl,
|
webFingerProfileUrl,
|
||||||
selfUrl,
|
selfUrl,
|
||||||
userFromAccount,
|
userFromAccount,
|
||||||
|
getUserProfileTemplatedBody,
|
||||||
|
DefaultProfileTemplate,
|
||||||
} = require('../../../activitypub_util');
|
} = require('../../../activitypub_util');
|
||||||
const UserProps = require('../../../user_property');
|
const UserProps = require('../../../user_property');
|
||||||
const { Errors } = require('../../../enig_error');
|
const { Errors } = require('../../../enig_error');
|
||||||
|
const Config = require('../../../config').get;
|
||||||
|
|
||||||
|
// deps
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name: 'ActivityPub',
|
name: 'ActivityPub',
|
||||||
|
@ -36,15 +42,6 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
_selfUrlRequestHandler(req, resp) {
|
_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 url = new URL(req.url, `https://${req.headers.host}`);
|
||||||
const accountName = url.pathname.substring(url.pathname.lastIndexOf('/') + 1);
|
const accountName = url.pathname.substring(url.pathname.lastIndexOf('/') + 1);
|
||||||
|
|
||||||
|
@ -53,42 +50,77 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
return this._notFound(resp);
|
return this._notFound(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = JSON.stringify({
|
const accept = req.headers['accept'] || '*/*';
|
||||||
'@context': [
|
if (accept === 'application/activity+json') {
|
||||||
'https://www.w3.org/ns/activitystreams',
|
return this._selfAsActorHandler(user, req, resp);
|
||||||
'https://w3id.org/security/v1',
|
}
|
||||||
],
|
|
||||||
id: selfUrl(this.webServer, user),
|
|
||||||
type: 'Person',
|
|
||||||
preferredUsername: user.username,
|
|
||||||
name: user.getSanitizedName('real'),
|
|
||||||
endpoints: {
|
|
||||||
sharedInbox: 'TODO',
|
|
||||||
},
|
|
||||||
inbox: makeUserUrl(this.webServer, user, '/ap/users') + '/outbox',
|
|
||||||
outbox: makeUserUrl(this.webServer, user, '/ap/users') + '/inbox',
|
|
||||||
followers: makeUserUrl(this.webServer, user, '/ap/users') + '/followers',
|
|
||||||
following: makeUserUrl(this.webServer, user, '/ap/users') + '/following',
|
|
||||||
summary: user.getProperty(UserProps.AutoSignature) || '',
|
|
||||||
url: webFingerProfileUrl(this.webServer, user),
|
|
||||||
publicKey: {},
|
|
||||||
|
|
||||||
// :TODO: we can start to define BBS related stuff with the community perhaps
|
return this._standardSelfHandler(user, req, resp);
|
||||||
});
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
'Content-Type': 'application/activity+json',
|
|
||||||
'Content-Length': body.length,
|
|
||||||
};
|
|
||||||
|
|
||||||
resp.writeHead(200, headers);
|
|
||||||
return resp.end(body);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_standardSelfHandler(req, resp) {
|
_selfAsActorHandler(user, req, resp) {
|
||||||
// :TODO: this should also be their profile page?! Perhaps that should also be shared...
|
const body = JSON.stringify({
|
||||||
return this._notFound(resp);
|
'@context': [
|
||||||
|
'https://www.w3.org/ns/activitystreams',
|
||||||
|
'https://w3id.org/security/v1',
|
||||||
|
],
|
||||||
|
id: selfUrl(this.webServer, user),
|
||||||
|
type: 'Person',
|
||||||
|
preferredUsername: user.username,
|
||||||
|
name: user.getSanitizedName('real'),
|
||||||
|
endpoints: {
|
||||||
|
sharedInbox: 'TODO',
|
||||||
|
},
|
||||||
|
inbox: makeUserUrl(this.webServer, user, '/ap/users') + '/outbox',
|
||||||
|
outbox: makeUserUrl(this.webServer, user, '/ap/users') + '/inbox',
|
||||||
|
followers: makeUserUrl(this.webServer, user, '/ap/users') + '/followers',
|
||||||
|
following: makeUserUrl(this.webServer, user, '/ap/users') + '/following',
|
||||||
|
summary: user.getProperty(UserProps.AutoSignature) || '',
|
||||||
|
url: webFingerProfileUrl(this.webServer, user),
|
||||||
|
publicKey: {},
|
||||||
|
|
||||||
|
// :TODO: we can start to define BBS related stuff with the community perhaps
|
||||||
|
});
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/activity+json',
|
||||||
|
'Content-Length': body.length,
|
||||||
|
};
|
||||||
|
|
||||||
|
resp.writeHead(200, headers);
|
||||||
|
return resp.end(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
_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) {
|
_notFound(resp) {
|
||||||
|
|
|
@ -1,22 +1,17 @@
|
||||||
const WebHandlerModule = require('../../../web_handler_module');
|
const WebHandlerModule = require('../../../web_handler_module');
|
||||||
const Config = require('../../../config').get;
|
const Config = require('../../../config').get;
|
||||||
const { Errors, ErrorReasons } = require('../../../enig_error');
|
const { Errors } = require('../../../enig_error');
|
||||||
const { WellKnownLocations } = require('../web');
|
const { WellKnownLocations } = require('../web');
|
||||||
const {
|
const {
|
||||||
selfUrl,
|
selfUrl,
|
||||||
webFingerProfileUrl,
|
webFingerProfileUrl,
|
||||||
userFromAccount,
|
userFromAccount,
|
||||||
|
getUserProfileTemplatedBody,
|
||||||
|
DefaultProfileTemplate,
|
||||||
} = require('../../../activitypub_util');
|
} = require('../../../activitypub_util');
|
||||||
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const User = require('../../../user');
|
|
||||||
const UserProps = require('../../../user_property');
|
|
||||||
const Log = require('../../../logger').log;
|
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 = {
|
exports.moduleInfo = {
|
||||||
name: 'WebFinger',
|
name: 'WebFinger',
|
||||||
|
@ -25,16 +20,6 @@ exports.moduleInfo = {
|
||||||
packageName: 'codes.l33t.enigma.web.handler.webfinger',
|
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
|
// WebFinger: https://www.rfc-editor.org/rfc/rfc7033
|
||||||
//
|
//
|
||||||
|
@ -117,70 +102,33 @@ exports.getModule = class WebFingerWebHandler extends WebHandlerModule {
|
||||||
return this._notFound(resp);
|
return this._notFound(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._getProfileTemplate((template, mimeType) => {
|
let templateFile = _.get(
|
||||||
const up = (p, na = 'N/A') => {
|
Config(),
|
||||||
return user.getProperty(p) || na;
|
'contentServers.web.handlers.webFinger.profileTemplate'
|
||||||
};
|
);
|
||||||
|
if (templateFile) {
|
||||||
let birthDate = up(UserProps.Birthdate);
|
templateFile = this.webServer.resolveTemplatePath(templateFile);
|
||||||
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'
|
|
||||||
);
|
|
||||||
if (templateFile) {
|
|
||||||
templateFile = this.webServer.resolveTemplatePath(templateFile);
|
|
||||||
}
|
|
||||||
fs.readFile(templateFile || '', 'utf8', (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
if (templateFile) {
|
|
||||||
Log.warn(
|
|
||||||
{ error: err.message },
|
|
||||||
`Failed to load profile template "${templateFile}"`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cb(DefaultProfileTemplate, 'text/plain');
|
|
||||||
}
|
}
|
||||||
return cb(data, mimeTypes.contentType(paths.basename(templateFile)));
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue