| 
									
										
										
										
											2023-05-29 17:42:44 -04:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const request = require('request-promise-native'); | 
					
						
							| 
									
										
										
										
											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-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; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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
 | 
					
						
							|  |  |  | 	const response = await request(`https://${hostname}/.well-known/webfinger?resource=acct:${id}`, { | 
					
						
							|  |  |  | 		simple: false, | 
					
						
							|  |  |  | 		resolveWithFullResponse: true, | 
					
						
							|  |  |  | 		json: true, | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (response.statusCode !== 200 || !response.body.hasOwnProperty('links')) { | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Parse links to find actor endpoint
 | 
					
						
							|  |  |  | 	let actorUri = response.body.links.filter(link => link.type === 'application/activity+json' && link.rel === 'self'); | 
					
						
							|  |  |  | 	if (actorUri.length) { | 
					
						
							|  |  |  | 		actorUri = actorUri.pop(); | 
					
						
							|  |  |  | 		({ href: actorUri } = actorUri); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-19 17:29:22 -04:00
										 |  |  | 	const { publicKey } = response.body; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (validator.isURL(input, { | 
					
						
							|  |  |  | 		require_protocol: true, | 
					
						
							|  |  |  | 		require_host: true, | 
					
						
							|  |  |  | 		require_tld: false, | 
					
						
							|  |  |  | 		protocols: ['https'], | 
					
						
							|  |  |  | 		require_valid_protocol: true, | 
					
						
							|  |  |  | 	})) { | 
					
						
							|  |  |  | 		const { host, pathname } = new URL(input); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (host === nconf.get('url_parsed').host) { | 
					
						
							|  |  |  | 			slug = pathname.split('/').filter(Boolean)[1]; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			throw new Error('[[activitypub:invalid-id]]'); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else if (input.indexOf('@') !== -1) { // Webfinger
 | 
					
						
							|  |  |  | 		([slug] = input.replace(/^acct:/, '').split('@')); | 
					
						
							|  |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2023-06-28 14:59:39 -04:00
										 |  |  | 		throw new Error('[[activitypub:invalid-id]]'); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return await user.getUidByUserslug(slug); | 
					
						
							|  |  |  | }; |