0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Added finger functionality to WebFingerService

ref https://linear.app/tryghost/issue/MOM-120

We need to do this to lookup the inbox which we need to send a Follow activity
This commit is contained in:
Fabien O'Carroll 2024-05-14 11:23:40 +07:00 committed by Fabien 'egg' O'Carroll
parent f31330a228
commit 4d24bdbccb
2 changed files with 121 additions and 2 deletions

View file

@ -0,0 +1,79 @@
import assert from 'assert';
import {ActorRepository} from './actor.repository';
import {WebFingerService} from './webfinger.service';
import {Actor} from './actor.entity';
describe('WebFingerService', function () {
describe('getResource', function () {
it('Throws with invalid resource', async function () {
const repository: ActorRepository = {} as ActorRepository;
const service = new WebFingerService(repository, new URL('https://activitypub.server'));
await service.getResource('invalid').then(
() => {
throw new Error('Should have thrown');
},
(err) => {
assert.ok(err);
}
);
});
it('Throws with missing actor', async function () {
const repository: ActorRepository = {
async getOne() {
return null;
}
} as unknown as ActorRepository;
const service = new WebFingerService(repository, new URL('https://activitypub.server'));
await service.getResource('invalid').then(
() => {
throw new Error('Should have thrown');
},
(err) => {
assert.ok(err);
}
);
});
it('Responds with webfinger for found actors', async function () {
const actor = Actor.create({username: 'c00ld00d'});
const repository: ActorRepository = {
async getOne() {
return actor;
}
} as unknown as ActorRepository;
const url = new URL('https://activitypub.server');
const service = new WebFingerService(repository, url);
const subject = 'acct:c00ld00d@activitypub.server';
const result = await service.getResource(subject);
assert.deepEqual(result, {
subject,
links: [{
rel: 'self',
type: 'application/activity+json',
href: actor.getJSONLD(url).id
}]
});
});
});
describe('#finger', function () {
it('Throws with invalid usernames', async function () {
const repository: ActorRepository = {} as ActorRepository;
const service = new WebFingerService(repository, new URL('https://activitypub.server'));
await service.finger('invalid').then(
() => {
throw new Error('Should have thrown');
},
(err) => {
assert.ok(err);
}
);
});
});
});

View file

@ -1,7 +1,8 @@
import {Inject} from '@nestjs/common';
import {ActorRepository} from './actor.repository';
const accountResource = /acct:(\w+)@(\w+)/;
const accountResourceMatcher = /acct:(\w+)@(\w+)/;
const usernameMatcher = /@?(.+)@(.+)/;
export class WebFingerService {
constructor(
@Inject('ActorRepository') private repository: ActorRepository,
@ -10,7 +11,7 @@ export class WebFingerService {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getResource(resource: string, rel?: string[]) {
const match = resource.match(accountResource);
const match = resource.match(accountResourceMatcher);
if (!match) {
throw new Error('Invalid Resource');
}
@ -36,4 +37,43 @@ export class WebFingerService {
return result;
}
async finger(handle: string) {
const match = handle.match(usernameMatcher);
if (!match) {
throw new Error('Invalid username');
}
const username = match[1];
const host = match[2];
let protocol = 'https';
// TODO Never in prod
if (host.startsWith('localhost') || host.startsWith('127.0.0.1')) {
protocol = 'http';
}
const res = await fetch(`${protocol}://${host}/.well-known/webfinger?resource=acct:${username}@${host}`);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const json: any = await res.json();
if (json.subject !== `acct:${username}@${host}`) {
throw new Error('Subject does not match - not jumping thru hoops');
}
const self = json.links.find((link: any) => link.rel === 'self');
const selfRes = await fetch(self.href, {
headers: {
accept: self.type
}
});
const data = await selfRes.json();
return data as any;
}
}