mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-07 12:36:57 +00:00
fix type errors with JsonLdService and remove unused factory pattern
This commit is contained in:
parent
d0ae76214c
commit
424e163c6f
4 changed files with 54 additions and 44 deletions
|
@ -936,9 +936,7 @@ export class ApRendererService {
|
||||||
|
|
||||||
const keypair = await this.userKeypairService.getUserKeypair(user.id);
|
const keypair = await this.userKeypairService.getUserKeypair(user.id);
|
||||||
|
|
||||||
const jsonLd = this.jsonLdService.use();
|
activity = await this.jsonLdService.signRsaSignature2017(activity, keypair.privateKey, `${this.config.url}/users/${user.id}#main-key`);
|
||||||
jsonLd.debug = false;
|
|
||||||
activity = await jsonLd.signRsaSignature2017(activity, keypair.privateKey, `${this.config.url}/users/${user.id}#main-key`);
|
|
||||||
|
|
||||||
return activity;
|
return activity;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,23 +13,56 @@ import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { StatusError } from '@/misc/status-error.js';
|
import { StatusError } from '@/misc/status-error.js';
|
||||||
import { CONTEXT, PRELOADED_CONTEXTS } from './misc/contexts.js';
|
import { CONTEXT, PRELOADED_CONTEXTS } from './misc/contexts.js';
|
||||||
import { validateContentTypeSetAsJsonLD } from './misc/validator.js';
|
import { validateContentTypeSetAsJsonLD } from './misc/validator.js';
|
||||||
import type { JsonLdDocument } from 'jsonld';
|
import type { ContextDefinition, JsonLdDocument } from 'jsonld';
|
||||||
import type { JsonLd as JsonLdObject, RemoteDocument } from 'jsonld/jsonld-spec.js';
|
import type { JsonLd as JsonLdObject, RemoteDocument } from 'jsonld/jsonld-spec.js';
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/66252656
|
||||||
|
type RemoveIndex<T> = {
|
||||||
|
[ K in keyof T as string extends K
|
||||||
|
? never
|
||||||
|
: number extends K
|
||||||
|
? never
|
||||||
|
: symbol extends K
|
||||||
|
? never
|
||||||
|
: K
|
||||||
|
] : T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Document = RemoveIndex<JsonLdDocument>;
|
||||||
|
|
||||||
|
export type Signature = {
|
||||||
|
id?: string;
|
||||||
|
type: string;
|
||||||
|
creator: string;
|
||||||
|
domain?: string;
|
||||||
|
nonce: string;
|
||||||
|
created: string;
|
||||||
|
signatureValue: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Signed<T extends Document> = T & {
|
||||||
|
signature: Signature;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isSigned<T extends Document>(doc: T): doc is Signed<T> {
|
||||||
|
return 'signature' in doc && typeof(doc.signature) === 'object';
|
||||||
|
}
|
||||||
|
|
||||||
// RsaSignature2017 implementation is based on https://github.com/transmute-industries/RsaSignature2017
|
// RsaSignature2017 implementation is based on https://github.com/transmute-industries/RsaSignature2017
|
||||||
|
|
||||||
class JsonLd {
|
@Injectable()
|
||||||
public preLoad = true;
|
export class JsonLdService {
|
||||||
public loderTimeout = 5000;
|
private readonly logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private readonly logger: Logger,
|
loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
|
this.logger = loggerService.getLogger('json-ld');
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async signRsaSignature2017(data: any, privateKey: string, creator: string, domain?: string, created?: Date): Promise<any> {
|
public async signRsaSignature2017<T extends Document>(data: T, privateKey: string, creator: string, domain?: string, created?: Date): Promise<Signed<T>> {
|
||||||
const options: {
|
const options: {
|
||||||
type: string;
|
type: string;
|
||||||
creator: string;
|
creator: string;
|
||||||
|
@ -65,7 +98,7 @@ class JsonLd {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async verifyRsaSignature2017(data: any, publicKey: string): Promise<boolean> {
|
public async verifyRsaSignature2017(data: Signed<Document>, publicKey: string): Promise<boolean> {
|
||||||
const toBeSigned = await this.createVerifyData(data, data.signature);
|
const toBeSigned = await this.createVerifyData(data, data.signature);
|
||||||
const verifier = crypto.createVerify('sha256');
|
const verifier = crypto.createVerify('sha256');
|
||||||
verifier.update(toBeSigned);
|
verifier.update(toBeSigned);
|
||||||
|
@ -73,7 +106,7 @@ class JsonLd {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async createVerifyData(data: any, options: any): Promise<string> {
|
public async createVerifyData<T extends Document>(data: T, options: Partial<Signature>): Promise<string> {
|
||||||
const transformedOptions = {
|
const transformedOptions = {
|
||||||
...options,
|
...options,
|
||||||
'@context': 'https://w3id.org/identity/v1',
|
'@context': 'https://w3id.org/identity/v1',
|
||||||
|
@ -83,7 +116,7 @@ class JsonLd {
|
||||||
delete transformedOptions['signatureValue'];
|
delete transformedOptions['signatureValue'];
|
||||||
const canonizedOptions = await this.normalize(transformedOptions);
|
const canonizedOptions = await this.normalize(transformedOptions);
|
||||||
const optionsHash = this.sha256(canonizedOptions.toString());
|
const optionsHash = this.sha256(canonizedOptions.toString());
|
||||||
const transformedData = { ...data };
|
const transformedData = { ...data } as T & { signature?: unknown };
|
||||||
delete transformedData['signature'];
|
delete transformedData['signature'];
|
||||||
const cannonidedData = await this.normalize(transformedData);
|
const cannonidedData = await this.normalize(transformedData);
|
||||||
this.logger.debug('cannonidedData', cannonidedData);
|
this.logger.debug('cannonidedData', cannonidedData);
|
||||||
|
@ -93,7 +126,8 @@ class JsonLd {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async compact(data: any, context: any = CONTEXT): Promise<JsonLdDocument> {
|
// TODO our default CONTEXT isn't valid for the library, is this a bug?
|
||||||
|
public async compact(data: Document, context: ContextDefinition = CONTEXT as unknown as ContextDefinition): Promise<Document> {
|
||||||
const customLoader = this.getLoader();
|
const customLoader = this.getLoader();
|
||||||
// XXX: Importing jsonld dynamically since Jest frequently fails to import it statically
|
// XXX: Importing jsonld dynamically since Jest frequently fails to import it statically
|
||||||
// https://github.com/misskey-dev/misskey/pull/9894#discussion_r1103753595
|
// https://github.com/misskey-dev/misskey/pull/9894#discussion_r1103753595
|
||||||
|
@ -103,7 +137,7 @@ class JsonLd {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async normalize(data: JsonLdDocument): Promise<string> {
|
public async normalize(data: Document): Promise<string> {
|
||||||
const customLoader = this.getLoader();
|
const customLoader = this.getLoader();
|
||||||
return (await import('jsonld')).default.normalize(data, {
|
return (await import('jsonld')).default.normalize(data, {
|
||||||
documentLoader: customLoader,
|
documentLoader: customLoader,
|
||||||
|
@ -115,7 +149,7 @@ class JsonLd {
|
||||||
return async (url: string): Promise<RemoteDocument> => {
|
return async (url: string): Promise<RemoteDocument> => {
|
||||||
if (!/^https?:\/\//.test(url)) throw new UnrecoverableError(`Invalid URL: ${url}`);
|
if (!/^https?:\/\//.test(url)) throw new UnrecoverableError(`Invalid URL: ${url}`);
|
||||||
|
|
||||||
if (this.preLoad) {
|
{
|
||||||
if (url in PRELOADED_CONTEXTS) {
|
if (url in PRELOADED_CONTEXTS) {
|
||||||
this.logger.debug(`Preload HIT: ${url}`);
|
this.logger.debug(`Preload HIT: ${url}`);
|
||||||
return {
|
return {
|
||||||
|
@ -144,7 +178,6 @@ class JsonLd {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/ld+json, application/json',
|
Accept: 'application/ld+json, application/json',
|
||||||
},
|
},
|
||||||
timeout: this.loderTimeout,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
throwErrorWhenResponseNotOk: false,
|
throwErrorWhenResponseNotOk: false,
|
||||||
|
@ -168,19 +201,3 @@ class JsonLd {
|
||||||
return hash.digest('hex');
|
return hash.digest('hex');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class JsonLdService {
|
|
||||||
private readonly logger: Logger;
|
|
||||||
constructor(
|
|
||||||
private httpRequestService: HttpRequestService,
|
|
||||||
loggerService: LoggerService,
|
|
||||||
) {
|
|
||||||
this.logger = loggerService.getLogger('json-ld');
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public use(): JsonLd {
|
|
||||||
return new JsonLd(this.httpRequestService, this.logger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
||||||
import { StatusError } from '@/misc/status-error.js';
|
import { StatusError } from '@/misc/status-error.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
||||||
import { JsonLdService } from '@/core/activitypub/JsonLdService.js';
|
import { isSigned, JsonLdService } from '@/core/activitypub/JsonLdService.js';
|
||||||
import { ApInboxService } from '@/core/activitypub/ApInboxService.js';
|
import { ApInboxService } from '@/core/activitypub/ApInboxService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
|
@ -179,8 +179,8 @@ export class InboxProcessorService implements OnApplicationShutdown {
|
||||||
// また、signatureのsignerは、activity.actorと一致する必要がある
|
// また、signatureのsignerは、activity.actorと一致する必要がある
|
||||||
if (!httpSignatureValidated || authUser.user.uri !== actorId) {
|
if (!httpSignatureValidated || authUser.user.uri !== actorId) {
|
||||||
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
|
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
|
||||||
|
if (isSigned(activity)) {
|
||||||
const ldSignature = activity.signature;
|
const ldSignature = activity.signature;
|
||||||
if (ldSignature) {
|
|
||||||
if (ldSignature.type !== 'RsaSignature2017') {
|
if (ldSignature.type !== 'RsaSignature2017') {
|
||||||
throw new Bull.UnrecoverableError(`skip: unsupported LD-signature type ${ldSignature.type}`);
|
throw new Bull.UnrecoverableError(`skip: unsupported LD-signature type ${ldSignature.type}`);
|
||||||
}
|
}
|
||||||
|
@ -202,24 +202,21 @@ export class InboxProcessorService implements OnApplicationShutdown {
|
||||||
throw new Bull.UnrecoverableError('skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした');
|
throw new Bull.UnrecoverableError('skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした');
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonLd = this.jsonLdService.use();
|
|
||||||
|
|
||||||
// LD-Signature検証
|
// LD-Signature検証
|
||||||
const verified = await jsonLd.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
|
const verified = await this.jsonLdService.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
throw new Bull.UnrecoverableError('skip: LD-Signatureの検証に失敗しました');
|
throw new Bull.UnrecoverableError('skip: LD-Signatureの検証に失敗しました');
|
||||||
}
|
}
|
||||||
|
|
||||||
// アクティビティを正規化
|
// アクティビティを正規化
|
||||||
delete activity.signature;
|
const copy = { ...activity, signature: undefined };
|
||||||
try {
|
try {
|
||||||
activity = await jsonLd.compact(activity) as IActivity;
|
activity = await this.jsonLdService.compact(copy) as IActivity;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Bull.UnrecoverableError(`skip: failed to compact activity: ${e}`);
|
throw new Bull.UnrecoverableError(`skip: failed to compact activity: ${e}`);
|
||||||
}
|
}
|
||||||
// TODO: 元のアクティビティと非互換な形に正規化される場合は転送をスキップする
|
// TODO: 元のアクティビティと非互換な形に正規化される場合は転送をスキップする
|
||||||
// https://github.com/mastodon/mastodon/blob/664b0ca/app/services/activitypub/process_collection_service.rb#L24-L29
|
// https://github.com/mastodon/mastodon/blob/664b0ca/app/services/activitypub/process_collection_service.rb#L24-L29
|
||||||
activity.signature = ldSignature;
|
|
||||||
|
|
||||||
// もう一度actorチェック
|
// もう一度actorチェック
|
||||||
if (authUser.user.uri !== actorId) {
|
if (authUser.user.uri !== actorId) {
|
||||||
|
|
|
@ -473,8 +473,6 @@ describe('ActivityPub', () => {
|
||||||
|
|
||||||
describe('JSON-LD', () => {
|
describe('JSON-LD', () => {
|
||||||
test('Compaction', async () => {
|
test('Compaction', async () => {
|
||||||
const jsonLd = jsonLdService.use();
|
|
||||||
|
|
||||||
const object = {
|
const object = {
|
||||||
'@context': [
|
'@context': [
|
||||||
'https://www.w3.org/ns/activitystreams',
|
'https://www.w3.org/ns/activitystreams',
|
||||||
|
@ -493,7 +491,7 @@ describe('ActivityPub', () => {
|
||||||
unknown: 'test test bar',
|
unknown: 'test test bar',
|
||||||
undefined: 'test test baz',
|
undefined: 'test test baz',
|
||||||
};
|
};
|
||||||
const compacted = await jsonLd.compact(object);
|
const compacted = await jsonLdService.compact(object);
|
||||||
|
|
||||||
assert.deepStrictEqual(compacted, {
|
assert.deepStrictEqual(compacted, {
|
||||||
'@context': CONTEXT,
|
'@context': CONTEXT,
|
||||||
|
|
Loading…
Add table
Reference in a new issue