2023-05-29 17:42:44 -04:00
|
|
|
'use strict';
|
|
|
|
|
|
2023-06-23 14:59:47 -04:00
|
|
|
const { generateKeyPairSync } = require('crypto');
|
2023-06-19 17:29:22 -04:00
|
|
|
const winston = require('winston');
|
2023-06-28 14:59:39 -04:00
|
|
|
const nconf = require('nconf');
|
2023-12-11 14:35:04 -05:00
|
|
|
const validator = require('validator');
|
2023-05-29 17:42:44 -04:00
|
|
|
|
2023-12-19 14:33:38 -05:00
|
|
|
const request = require('../request');
|
2023-06-19 17:29:22 -04:00
|
|
|
const db = require('../database');
|
2023-06-16 11:26:25 -04:00
|
|
|
const ttl = require('../cache/ttl');
|
2023-06-28 14:59:39 -04:00
|
|
|
const user = require('../user');
|
2023-06-16 11:26:25 -04:00
|
|
|
|
|
|
|
|
const webfingerCache = ttl({ ttl: 1000 * 60 * 60 * 24 }); // 24 hours
|
|
|
|
|
|
2023-05-29 17:42:44 -04:00
|
|
|
const Helpers = module.exports;
|
|
|
|
|
|
2024-01-05 11:38:26 -05:00
|
|
|
Helpers.isUri = (value) => {
|
|
|
|
|
if (typeof value !== 'string') {
|
|
|
|
|
value = String(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const protocols = ['https'];
|
|
|
|
|
if (process.env.CI === 'true') {
|
|
|
|
|
protocols.push('http');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return validator.isURL(value, {
|
|
|
|
|
require_protocol: true,
|
|
|
|
|
require_host: true,
|
|
|
|
|
protocols,
|
|
|
|
|
require_valid_protocol: true,
|
|
|
|
|
require_tld: false, // temporary — for localhost
|
|
|
|
|
});
|
|
|
|
|
};
|
2024-01-04 16:23:09 -05:00
|
|
|
|
2023-05-29 17:42:44 -04:00
|
|
|
Helpers.query = async (id) => {
|
|
|
|
|
const [username, hostname] = id.split('@');
|
|
|
|
|
if (!username || !hostname) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-16 11:26:25 -04:00
|
|
|
if (webfingerCache.has(id)) {
|
|
|
|
|
return webfingerCache.get(id);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-29 17:42:44 -04:00
|
|
|
// Make a webfinger query to retrieve routing information
|
2023-12-19 14:33:38 -05:00
|
|
|
const { response, body } = await request.get(`https://${hostname}/.well-known/webfinger?resource=acct:${id}`);
|
2023-05-29 17:42:44 -04:00
|
|
|
|
2023-12-19 14:33:38 -05:00
|
|
|
if (response.statusCode !== 200 || !body.hasOwnProperty('links')) {
|
2023-05-29 17:42:44 -04:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse links to find actor endpoint
|
2023-12-19 14:33:38 -05:00
|
|
|
let actorUri = body.links.filter(link => link.type === 'application/activity+json' && link.rel === 'self');
|
2023-05-29 17:42:44 -04:00
|
|
|
if (actorUri.length) {
|
|
|
|
|
actorUri = actorUri.pop();
|
|
|
|
|
({ href: actorUri } = actorUri);
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-19 14:33:38 -05:00
|
|
|
const { publicKey } = body;
|
2023-06-19 17:29:22 -04:00
|
|
|
|
|
|
|
|
webfingerCache.set(id, { username, hostname, actorUri, publicKey });
|
|
|
|
|
return { username, hostname, actorUri, publicKey };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Helpers.generateKeys = async (uid) => {
|
|
|
|
|
winston.verbose(`[activitypub] Generating RSA key-pair for uid ${uid}`);
|
|
|
|
|
const {
|
|
|
|
|
publicKey,
|
|
|
|
|
privateKey,
|
|
|
|
|
} = generateKeyPairSync('rsa', {
|
|
|
|
|
modulusLength: 2048,
|
|
|
|
|
publicKeyEncoding: {
|
|
|
|
|
type: 'spki',
|
|
|
|
|
format: 'pem',
|
|
|
|
|
},
|
|
|
|
|
privateKeyEncoding: {
|
|
|
|
|
type: 'pkcs8',
|
|
|
|
|
format: 'pem',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await db.setObject(`uid:${uid}:keys`, { publicKey, privateKey });
|
|
|
|
|
return { publicKey, privateKey };
|
2023-05-29 17:42:44 -04:00
|
|
|
};
|
2023-06-28 14:59:39 -04:00
|
|
|
|
2023-12-11 14:35:04 -05:00
|
|
|
Helpers.resolveLocalUid = async (input) => {
|
|
|
|
|
let slug;
|
2023-12-13 13:38:52 -05:00
|
|
|
const protocols = ['https'];
|
|
|
|
|
if (process.env.CI === 'true') {
|
|
|
|
|
protocols.push('http');
|
|
|
|
|
}
|
2024-01-04 16:23:09 -05:00
|
|
|
if (Helpers.isUri(input)) {
|
2023-12-11 14:35:04 -05:00
|
|
|
const { host, pathname } = new URL(input);
|
|
|
|
|
|
|
|
|
|
if (host === nconf.get('url_parsed').host) {
|
2024-01-29 16:59:13 -05:00
|
|
|
const [type, value] = pathname.replace(nconf.get('relative_path'), '').split('/').filter(Boolean)[1];
|
|
|
|
|
if (type === 'uid') {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
slug = value;
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error('[[error:activitypub.invalid-id]]');
|
2023-12-11 14:35:04 -05:00
|
|
|
}
|
|
|
|
|
} else if (input.indexOf('@') !== -1) { // Webfinger
|
|
|
|
|
([slug] = input.replace(/^acct:/, '').split('@'));
|
|
|
|
|
} else {
|
2024-01-08 14:30:09 -05:00
|
|
|
throw new Error('[[error:activitypub.invalid-id]]');
|
2023-06-28 14:59:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await user.getUidByUserslug(slug);
|
|
|
|
|
};
|