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:
parent
f31330a228
commit
4d24bdbccb
2 changed files with 121 additions and 2 deletions
79
ghost/ghost/src/core/activitypub/webfinger.service.test.ts
Normal file
79
ghost/ghost/src/core/activitypub/webfinger.service.test.ts
Normal 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);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue