| 
									
										
										
										
											2023-05-22 23:38:11 -04:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | const assert = require('assert'); | 
					
						
							| 
									
										
										
										
											2023-06-21 15:45:29 -04:00
										 |  |  | const { createHash } = require('crypto'); | 
					
						
							| 
									
										
										
										
											2023-05-22 23:38:11 -04:00
										 |  |  | const nconf = require('nconf'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const db = require('./mocks/databasemock'); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | const slugify = require('../src/slugify'); | 
					
						
							|  |  |  | const utils = require('../src/utils'); | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | const request = require('../src/request'); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-24 14:00:41 -04:00
										 |  |  | const meta = require('../src/meta'); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | const user = require('../src/user'); | 
					
						
							|  |  |  | const privileges = require('../src/privileges'); | 
					
						
							| 
									
										
										
										
											2023-06-21 15:45:29 -04:00
										 |  |  | const activitypub = require('../src/activitypub'); | 
					
						
							| 
									
										
										
										
											2023-05-22 23:38:11 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | describe('ActivityPub integration', () => { | 
					
						
							| 
									
										
										
										
											2023-05-24 14:00:41 -04:00
										 |  |  | 	before(() => { | 
					
						
							| 
									
										
										
										
											2023-06-16 10:57:34 -04:00
										 |  |  | 		meta.config.activitypubEnabled = 1; | 
					
						
							| 
									
										
										
										
											2023-05-24 14:00:41 -04:00
										 |  |  | 	}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	after(() => { | 
					
						
							| 
									
										
										
										
											2023-06-16 10:57:34 -04:00
										 |  |  | 		delete meta.config.activitypubEnabled; | 
					
						
							| 
									
										
										
										
											2023-05-24 14:00:41 -04:00
										 |  |  | 	}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-22 23:38:11 -04:00
										 |  |  | 	describe('WebFinger endpoint', () => { | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 		let uid; | 
					
						
							|  |  |  | 		let slug; | 
					
						
							| 
									
										
										
										
											2023-06-26 15:09:47 -04:00
										 |  |  | 		const { host } = nconf.get('url_parsed'); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		beforeEach(async () => { | 
					
						
							|  |  |  | 			slug = slugify(utils.generateUUID().slice(0, 8)); | 
					
						
							|  |  |  | 			uid = await user.create({ username: slug }); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-22 23:38:11 -04:00
										 |  |  | 		it('should return a 404 Not Found if no user exists by that username', async () => { | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			const { response } = await request.get(`${nconf.get('url')}/.well-known/webfinger?resource=acct:foobar@${host}`); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			assert(response); | 
					
						
							|  |  |  | 			assert.strictEqual(response.statusCode, 404); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it('should return a 400 Bad Request if the request is malformed', async () => { | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			const { response } = await request.get(`${nconf.get('url')}/.well-known/webfinger?resource=acct:foobar`); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			assert(response); | 
					
						
							|  |  |  | 			assert.strictEqual(response.statusCode, 400); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it('should return 403 Forbidden if the calling user is not allowed to view the user list/profiles', async () => { | 
					
						
							|  |  |  | 			await privileges.global.rescind(['groups:view:users'], 'guests'); | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			const { response } = await request.get(`${nconf.get('url')}/.well-known/webfinger?resource=acct:${slug}@${host}`); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			assert(response); | 
					
						
							|  |  |  | 			assert.strictEqual(response.statusCode, 403); | 
					
						
							|  |  |  | 			await privileges.global.give(['groups:view:users'], 'guests'); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it('should return a valid WebFinger response otherwise', async () => { | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			const { response, body } = await request.get(`${nconf.get('url')}/.well-known/webfinger?resource=acct:${slug}@${host}`); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			assert(response); | 
					
						
							|  |  |  | 			assert.strictEqual(response.statusCode, 200); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			['subject', 'aliases', 'links'].forEach((prop) => { | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 				assert(body.hasOwnProperty(prop)); | 
					
						
							|  |  |  | 				assert(body[prop]); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			assert.strictEqual(body.subject, `acct:${slug}@${host}`); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			assert(Array.isArray(body.aliases)); | 
					
						
							|  |  |  | 			assert([`${nconf.get('url')}/uid/${uid}`, `${nconf.get('url')}/user/${slug}`].every(url => body.aliases.includes(url))); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			assert(Array.isArray(body.links)); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 		}); | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-13 13:38:52 -05:00
										 |  |  | 	describe('Helpers', () => { | 
					
						
							| 
									
										
										
										
											2023-12-11 14:35:04 -05:00
										 |  |  | 		describe('.query()', () => { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		describe('.generateKeys()', () => { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		describe('.resolveLocalUid()', () => { | 
					
						
							|  |  |  | 			let uid; | 
					
						
							|  |  |  | 			let slug; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			beforeEach(async () => { | 
					
						
							|  |  |  | 				slug = slugify(utils.generateUUID().slice(0, 8)); | 
					
						
							|  |  |  | 				uid = await user.create({ username: slug }); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it('should throw when an invalid input is passed in', async () => { | 
					
						
							|  |  |  | 				await assert.rejects( | 
					
						
							|  |  |  | 					activitypub.helpers.resolveLocalUid('ncl28h3qwhoiclwnevoinw3u'), | 
					
						
							| 
									
										
										
										
											2024-01-08 15:03:46 -05:00
										 |  |  | 					{ message: '[[error:activitypub.invalid-id]]' } | 
					
						
							| 
									
										
										
										
											2023-12-11 14:35:04 -05:00
										 |  |  | 				); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it('should return null when valid input is passed but does not resolve', async () => { | 
					
						
							|  |  |  | 				const uid = await activitypub.helpers.resolveLocalUid(`acct:foobar@${nconf.get('url_parsed').host}`); | 
					
						
							|  |  |  | 				assert.strictEqual(uid, null); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it('should resolve to a local uid when given a webfinger-style string', async () => { | 
					
						
							|  |  |  | 				const found = await activitypub.helpers.resolveLocalUid(`acct:${slug}@${nconf.get('url_parsed').host}`); | 
					
						
							|  |  |  | 				assert.strictEqual(found, uid); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it('should resolve even without the "acct:" prefix', async () => { | 
					
						
							|  |  |  | 				const found = await activitypub.helpers.resolveLocalUid(`${slug}@${nconf.get('url_parsed').host}`); | 
					
						
							|  |  |  | 				assert.strictEqual(found, uid); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it('should resolve when passed a full URL', async () => { | 
					
						
							|  |  |  | 				const found = await activitypub.helpers.resolveLocalUid(`${nconf.get('url')}/user/${slug}`); | 
					
						
							|  |  |  | 				assert.strictEqual(found, uid); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 	describe('ActivityPub screener middleware', () => { | 
					
						
							|  |  |  | 		let uid; | 
					
						
							|  |  |  | 		let slug; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		beforeEach(async () => { | 
					
						
							|  |  |  | 			slug = slugify(utils.generateUUID().slice(0, 8)); | 
					
						
							|  |  |  | 			uid = await user.create({ username: slug }); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-24 14:00:41 -04:00
										 |  |  | 		it('should return regular user profile html if federation is disabled', async () => { | 
					
						
							| 
									
										
										
										
											2023-06-16 10:57:34 -04:00
										 |  |  | 			delete meta.config.activitypubEnabled; | 
					
						
							| 
									
										
										
										
											2023-05-24 14:00:41 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			const { response, body } = await request.get(`${nconf.get('url')}/user/${slug}`, { | 
					
						
							| 
									
										
										
										
											2023-05-24 14:00:41 -04:00
										 |  |  | 				headers: { | 
					
						
							|  |  |  | 					Accept: 'text/html', | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert(response); | 
					
						
							|  |  |  | 			assert.strictEqual(response.statusCode, 200); | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			assert(body.startsWith('<!DOCTYPE html>')); | 
					
						
							| 
									
										
										
										
											2023-05-24 14:00:41 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-16 10:57:34 -04:00
										 |  |  | 			meta.config.activitypubEnabled = 1; | 
					
						
							| 
									
										
										
										
											2023-05-24 14:00:41 -04:00
										 |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 		it('should return regular user profile html if Accept header is not ActivityPub-related', async () => { | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			const { response, body } = await request.get(`${nconf.get('url')}/user/${slug}`, { | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 				headers: { | 
					
						
							|  |  |  | 					Accept: 'text/html', | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert(response); | 
					
						
							|  |  |  | 			assert.strictEqual(response.statusCode, 200); | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			assert(body.startsWith('<!DOCTYPE html>')); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it('should return the ActivityPub Actor JSON-LD payload if the correct Accept header is provided', async () => { | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			const { response, body } = await request.get(`${nconf.get('url')}/user/${slug}`, { | 
					
						
							| 
									
										
										
										
											2023-05-22 23:38:11 -04:00
										 |  |  | 				headers: { | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 					Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', | 
					
						
							| 
									
										
										
										
											2023-05-22 23:38:11 -04:00
										 |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert(response); | 
					
						
							|  |  |  | 			assert.strictEqual(response.statusCode, 200); | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			assert(body.hasOwnProperty('@context')); | 
					
						
							|  |  |  | 			assert(body['@context'].includes('https://www.w3.org/ns/activitystreams')); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 		}); | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-22 16:18:49 -05:00
										 |  |  | 	describe('User Actor endpoint', () => { | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 		let uid; | 
					
						
							|  |  |  | 		let slug; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		beforeEach(async () => { | 
					
						
							|  |  |  | 			slug = slugify(utils.generateUUID().slice(0, 8)); | 
					
						
							|  |  |  | 			uid = await user.create({ username: slug }); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it('should return a valid ActivityPub Actor JSON-LD payload', async () => { | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			const { response, body } = await request.get(`${nconf.get('url')}/user/${slug}`, { | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 				headers: { | 
					
						
							|  |  |  | 					Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert(response); | 
					
						
							|  |  |  | 			assert.strictEqual(response.statusCode, 200); | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			assert(body.hasOwnProperty('@context')); | 
					
						
							|  |  |  | 			assert(body['@context'].includes('https://www.w3.org/ns/activitystreams')); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			['id', 'url', 'followers', 'following', 'inbox', 'outbox'].forEach((prop) => { | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 				assert(body.hasOwnProperty(prop)); | 
					
						
							|  |  |  | 				assert(body[prop]); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			assert.strictEqual(body.id, body.url); | 
					
						
							|  |  |  | 			assert.strictEqual(body.type, 'Person'); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it('should contain a `publicKey` property with a public key', async () => { | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			const { response, body } = await request.get(`${nconf.get('url')}/user/${slug}`, { | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 				headers: { | 
					
						
							|  |  |  | 					Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', | 
					
						
							| 
									
										
										
										
											2023-05-22 23:38:11 -04:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}); | 
					
						
							| 
									
										
										
										
											2023-05-23 16:13:16 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-21 14:38:16 -05:00
										 |  |  | 			assert(body.hasOwnProperty('publicKey')); | 
					
						
							|  |  |  | 			assert(['id', 'owner', 'publicKeyPem'].every(prop => body.publicKey.hasOwnProperty(prop))); | 
					
						
							| 
									
										
										
										
											2023-05-22 23:38:11 -04:00
										 |  |  | 		}); | 
					
						
							|  |  |  | 	}); | 
					
						
							| 
									
										
										
										
											2023-06-21 15:45:29 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-22 16:18:49 -05:00
										 |  |  | 	describe('Instance Actor endpoint', () => { | 
					
						
							|  |  |  | 		let response; | 
					
						
							|  |  |  | 		let body; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		before(async () => { | 
					
						
							|  |  |  | 			({ response, body } = await request.get(nconf.get('url'), { | 
					
						
							|  |  |  | 				headers: { | 
					
						
							|  |  |  | 					Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			})); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it('should respond properly', async () => { | 
					
						
							|  |  |  | 			assert(response); | 
					
						
							|  |  |  | 			assert.strictEqual(response.statusCode, 200); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it('should return a valid ActivityPub Actor JSON-LD payload', async () => { | 
					
						
							|  |  |  | 			assert(body.hasOwnProperty('@context')); | 
					
						
							|  |  |  | 			assert(body['@context'].includes('https://www.w3.org/ns/activitystreams')); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-23 11:22:18 -05:00
										 |  |  | 			['id', 'url', 'inbox', 'outbox', 'name', 'preferredUsername'].forEach((prop) => { | 
					
						
							| 
									
										
										
										
											2024-01-22 16:18:49 -05:00
										 |  |  | 				assert(body.hasOwnProperty(prop)); | 
					
						
							|  |  |  | 				assert(body[prop]); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert.strictEqual(body.id, body.url); | 
					
						
							|  |  |  | 			assert.strictEqual(body.type, 'Application'); | 
					
						
							| 
									
										
										
										
											2024-01-23 11:22:18 -05:00
										 |  |  | 			assert.strictEqual(body.name, meta.config.site_title || 'NodeBB'); | 
					
						
							|  |  |  | 			assert.strictEqual(body.preferredUsername, nconf.get('url_parsed').hostname); | 
					
						
							| 
									
										
										
										
											2024-01-22 16:18:49 -05:00
										 |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it('should contain a `publicKey` property with a public key', async () => { | 
					
						
							|  |  |  | 			assert(body.hasOwnProperty('publicKey')); | 
					
						
							|  |  |  | 			assert(['id', 'owner', 'publicKeyPem'].every(prop => body.publicKey.hasOwnProperty(prop))); | 
					
						
							|  |  |  | 		}); | 
					
						
							| 
									
										
										
										
											2024-01-23 11:22:18 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		it('should also have a valid WebFinger response tied to `preferredUsername`', async () => { | 
					
						
							|  |  |  | 			const { response, body: body2 } = await request.get(`${nconf.get('url')}/.well-known/webfinger?resource=acct:${body.preferredUsername}@${nconf.get('url_parsed').host}`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert.strictEqual(response.statusCode, 200); | 
					
						
							|  |  |  | 			assert(body2 && body2.aliases && body2.links); | 
					
						
							|  |  |  | 			assert(body2.aliases.includes(nconf.get('url'))); | 
					
						
							|  |  |  | 			assert(body2.links.some(item => item.rel === 'self' && item.type === 'application/activity+json' && item.href === nconf.get('url'))); | 
					
						
							|  |  |  | 		}); | 
					
						
							| 
									
										
										
										
											2024-01-22 16:18:49 -05:00
										 |  |  | 	}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-26 15:09:47 -04:00
										 |  |  | 	describe('http signature signing and verification', () => { | 
					
						
							| 
									
										
										
										
											2023-06-21 15:45:29 -04:00
										 |  |  | 		describe('.sign()', () => { | 
					
						
							|  |  |  | 			let uid; | 
					
						
							|  |  |  | 			let username; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			before(async () => { | 
					
						
							|  |  |  | 				username = utils.generateUUID().slice(0, 10); | 
					
						
							|  |  |  | 				uid = await user.create({ username }); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it('should create a key-pair for a user if the user does not have one already', async () => { | 
					
						
							|  |  |  | 				const endpoint = `${nconf.get('url')}/user/${username}/inbox`; | 
					
						
							|  |  |  | 				await activitypub.sign(uid, endpoint); | 
					
						
							|  |  |  | 				const { publicKey, privateKey } = await db.getObject(`uid:${uid}:keys`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				assert(publicKey); | 
					
						
							|  |  |  | 				assert(privateKey); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it('should return an object with date, a null digest, and signature, if no payload is passed in', async () => { | 
					
						
							|  |  |  | 				const endpoint = `${nconf.get('url')}/user/${username}/inbox`; | 
					
						
							|  |  |  | 				const { date, digest, signature } = await activitypub.sign(uid, endpoint); | 
					
						
							|  |  |  | 				const dateObj = new Date(date); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				assert(signature); | 
					
						
							|  |  |  | 				assert(dateObj); | 
					
						
							|  |  |  | 				assert.strictEqual(digest, null); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it('should also return a digest hash if payload is passed in', async () => { | 
					
						
							|  |  |  | 				const endpoint = `${nconf.get('url')}/user/${username}/inbox`; | 
					
						
							|  |  |  | 				const payload = { foo: 'bar' }; | 
					
						
							|  |  |  | 				const { digest } = await activitypub.sign(uid, endpoint, payload); | 
					
						
							|  |  |  | 				const hash = createHash('sha256'); | 
					
						
							|  |  |  | 				hash.update(JSON.stringify(payload)); | 
					
						
							| 
									
										
										
										
											2023-06-23 14:59:47 -04:00
										 |  |  | 				const checksum = hash.digest('base64'); | 
					
						
							| 
									
										
										
										
											2023-06-21 15:45:29 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				assert(digest); | 
					
						
							| 
									
										
										
										
											2023-06-23 14:59:47 -04:00
										 |  |  | 				assert.strictEqual(digest, `sha-256=${checksum}`); | 
					
						
							| 
									
										
										
										
											2023-06-21 15:45:29 -04:00
										 |  |  | 			}); | 
					
						
							| 
									
										
										
										
											2024-01-22 16:18:49 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			it('should create a key for NodeBB itself if a uid of 0 is passed in', async () => { | 
					
						
							|  |  |  | 				const endpoint = `${nconf.get('url')}/user/${username}/inbox`; | 
					
						
							|  |  |  | 				await activitypub.sign(0, endpoint); | 
					
						
							|  |  |  | 				const { publicKey, privateKey } = await db.getObject(`uid:0:keys`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				assert(publicKey); | 
					
						
							|  |  |  | 				assert(privateKey); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it('should return headers with an appropriate key id uri', async () => { | 
					
						
							|  |  |  | 				const endpoint = `${nconf.get('url')}/user/${username}/inbox`; | 
					
						
							|  |  |  | 				const { signature } = await activitypub.sign(uid, endpoint); | 
					
						
							|  |  |  | 				const [keyId] = signature.split(','); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				assert(signature); | 
					
						
							|  |  |  | 				assert.strictEqual(keyId, `keyId="${nconf.get('url')}/user/${username}#key"`); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it('should return the instance key id when uid is 0', async () => { | 
					
						
							|  |  |  | 				const endpoint = `${nconf.get('url')}/user/${username}/inbox`; | 
					
						
							|  |  |  | 				const { signature } = await activitypub.sign(0, endpoint); | 
					
						
							|  |  |  | 				const [keyId] = signature.split(','); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				assert(signature); | 
					
						
							|  |  |  | 				assert.strictEqual(keyId, `keyId="${nconf.get('url')}#key"`); | 
					
						
							|  |  |  | 			}); | 
					
						
							| 
									
										
										
										
											2023-06-21 15:45:29 -04:00
										 |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-23 14:59:47 -04:00
										 |  |  | 		describe('.verify()', () => { | 
					
						
							| 
									
										
										
										
											2023-06-21 15:45:29 -04:00
										 |  |  | 			let uid; | 
					
						
							|  |  |  | 			let username; | 
					
						
							| 
									
										
										
										
											2023-08-08 15:33:35 -04:00
										 |  |  | 			const baseUrl = nconf.get('relative_path'); | 
					
						
							| 
									
										
										
										
											2023-06-21 15:45:29 -04:00
										 |  |  | 			const mockReqBase = { | 
					
						
							|  |  |  | 				method: 'GET', | 
					
						
							|  |  |  | 				// path: ...
 | 
					
						
							| 
									
										
										
										
											2023-08-08 15:33:35 -04:00
										 |  |  | 				baseUrl, | 
					
						
							| 
									
										
										
										
											2023-06-21 15:45:29 -04:00
										 |  |  | 				headers: { | 
					
						
							|  |  |  | 					// host: ...
 | 
					
						
							|  |  |  | 					// date: ...
 | 
					
						
							|  |  |  | 					// signature: ...
 | 
					
						
							|  |  |  | 					// digest: ...
 | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			before(async () => { | 
					
						
							|  |  |  | 				username = utils.generateUUID().slice(0, 10); | 
					
						
							|  |  |  | 				uid = await user.create({ username }); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it('should return true when the proper signature and relevant headers are passed in', async () => { | 
					
						
							|  |  |  | 				const endpoint = `${nconf.get('url')}/user/${username}/inbox`; | 
					
						
							|  |  |  | 				const path = `/user/${username}/inbox`; | 
					
						
							|  |  |  | 				const signature = await activitypub.sign(uid, endpoint); | 
					
						
							|  |  |  | 				const { host } = nconf.get('url_parsed'); | 
					
						
							|  |  |  | 				const req = { | 
					
						
							|  |  |  | 					...mockReqBase, | 
					
						
							|  |  |  | 					...{ | 
					
						
							|  |  |  | 						path, | 
					
						
							|  |  |  | 						headers: { ...signature, host }, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				const verified = await activitypub.verify(req); | 
					
						
							|  |  |  | 				assert.strictEqual(verified, true); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it('should return true when a digest is also passed in', async () => { | 
					
						
							|  |  |  | 				const endpoint = `${nconf.get('url')}/user/${username}/inbox`; | 
					
						
							|  |  |  | 				const path = `/user/${username}/inbox`; | 
					
						
							|  |  |  | 				const signature = await activitypub.sign(uid, endpoint, { foo: 'bar' }); | 
					
						
							|  |  |  | 				const { host } = nconf.get('url_parsed'); | 
					
						
							|  |  |  | 				const req = { | 
					
						
							|  |  |  | 					...mockReqBase, | 
					
						
							|  |  |  | 					...{ | 
					
						
							|  |  |  | 						method: 'POST', | 
					
						
							|  |  |  | 						path, | 
					
						
							|  |  |  | 						headers: { ...signature, host }, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				const verified = await activitypub.verify(req); | 
					
						
							|  |  |  | 				assert.strictEqual(verified, true); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	}); | 
					
						
							| 
									
										
										
										
											2023-05-22 23:38:11 -04:00
										 |  |  | }); |