add IObjectWithId type for APIs that work with objects required to have an ID.

This commit is contained in:
Hazelnoot 2025-03-02 21:08:05 -05:00
parent ad49faa956
commit f88430aebc
4 changed files with 23 additions and 17 deletions

View file

@ -16,7 +16,7 @@ import type { Config } from '@/config.js';
import { StatusError } from '@/misc/status-error.js';
import { bindThis } from '@/decorators.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
import { IObject } from '@/core/activitypub/type.js';
import type { IObject, IObjectWithId } from '@/core/activitypub/type.js';
import { ApUtilityService } from './activitypub/ApUtilityService.js';
import type { Response } from 'node-fetch';
import type { URL } from 'node:url';
@ -217,7 +217,7 @@ export class HttpRequestService {
}
@bindThis
public async getActivityJson(url: string, isLocalAddressAllowed = false): Promise<IObject> {
public async getActivityJson(url: string, isLocalAddressAllowed = false): Promise<IObjectWithId> {
const res = await this.send(url, {
method: 'GET',
headers: {
@ -237,7 +237,7 @@ export class HttpRequestService {
// The caller (ApResolverService) will verify the ID against the original / entry URL, which ensures that all three match.
this.apUtilityService.assertIdMatchesUrlAuthority(activity, res.url);
return activity;
return activity as IObjectWithId;
}
@bindThis

View file

@ -17,7 +17,7 @@ import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import type Logger from '@/logger.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
import type { IObject } from './type.js';
import type { IObject, IObjectWithId } from './type.js';
type Request = {
url: string;
@ -185,7 +185,7 @@ export class ApRequestService {
* @param followAlternate
*/
@bindThis
public async signedGet(url: string, user: { id: MiUser['id'] }, followAlternate?: boolean): Promise<IObject> {
public async signedGet(url: string, user: { id: MiUser['id'] }, followAlternate?: boolean): Promise<IObjectWithId> {
const _followAlternate = followAlternate ?? true;
const keypair = await this.userKeypairService.getUserKeypair(user.id);
@ -273,6 +273,6 @@ export class ApRequestService {
// The caller (ApResolverService) will verify the ID against the original / entry URL, which ensures that all three match.
this.apUtilityService.assertIdMatchesUrlAuthority(activity, res.url);
return activity;
return activity as IObjectWithId;
}
}

View file

@ -19,7 +19,7 @@ import { fromTuple } from '@/misc/from-tuple.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { ApLogService, calculateDurationSince, extractObjectContext } from '@/core/ApLogService.js';
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
import { getApId, getNullableApId, isCollectionOrOrderedCollection } from './type.js';
import { getApId, getNullableApId, IObjectWithId, isCollectionOrOrderedCollection } from './type.js';
import { ApDbResolverService } from './ApDbResolverService.js';
import { ApRendererService } from './ApRendererService.js';
import { ApRequestService } from './ApRequestService.js';
@ -82,7 +82,7 @@ export class Resolver {
* In all other cases, the object is re-fetched from remote by input string or object ID.
*/
@bindThis
public async secureResolve(input: ApObject, sentFromUri: string): Promise<IObject> {
public async secureResolve(input: ApObject, sentFromUri: string): Promise<IObjectWithId> {
// Unpack arrays to get the value element.
const value = fromTuple(input);
if (value == null) {
@ -96,13 +96,15 @@ export class Resolver {
// Our security requires that the object ID matches the host authority that sent it, otherwise it can't be trusted.
// A mismatch isn't necessarily malicious, it just means we can't use the object we were given.
if (typeof(value) === 'object' && this.apUtilityService.haveSameAuthority(id, sentFromUri)) {
return value;
return value as IObjectWithId;
}
// If the checks didn't pass, then we must fetch the object and use that.
return await this.resolve(id);
}
public async resolve(value: string | [string]): Promise<IObjectWithId>;
public async resolve(value: string | IObject | [string | IObject]): Promise<IObject>;
@bindThis
public async resolve(value: string | IObject | [string | IObject]): Promise<IObject> {
// eslint-disable-next-line no-param-reassign
@ -120,7 +122,7 @@ export class Resolver {
}
}
private async _resolveLogged(requestUri: string, host: string): Promise<IObject> {
private async _resolveLogged(requestUri: string, host: string): Promise<IObjectWithId> {
const startTime = process.hrtime.bigint();
const log = await this.apLogService.createFetchLog({
@ -149,7 +151,7 @@ export class Resolver {
}
}
private async _resolve(value: string, host: string, log?: SkApFetchLog): Promise<IObject> {
private async _resolve(value: string, host: string, log?: SkApFetchLog): Promise<IObjectWithId> {
if (value.includes('#')) {
// URLs with fragment parts cannot be resolved correctly because
// the fragment part does not get transmitted over HTTP(S).
@ -168,7 +170,7 @@ export class Resolver {
this.history.add(value);
if (this.utilityService.isSelfHost(host)) {
return await this.resolveLocal(value);
return await this.resolveLocal(value) as IObjectWithId;
}
if (!this.utilityService.isFederationAllowedHost(host)) {
@ -180,8 +182,8 @@ export class Resolver {
}
const object = (this.user
? await this.apRequestService.signedGet(value, this.user) as IObject
: await this.httpRequestService.getActivityJson(value)) as IObject;
? await this.apRequestService.signedGet(value, this.user)
: await this.httpRequestService.getActivityJson(value));
if (log) {
const { object: objectOnly, context, contextHash } = extractObjectContext(object);
@ -226,7 +228,7 @@ export class Resolver {
}
@bindThis
private resolveLocal(url: string): Promise<IObject> {
private resolveLocal(url: string): Promise<IObjectWithId> {
const parsed = this.apDbResolverService.parseUri(url);
if (!parsed.local) throw new IdentifiableError('02b40cd0-fa92-4b0c-acc9-fb2ada952ab8', `resolveLocal - not a local URL: ${url}`);
@ -241,7 +243,7 @@ export class Resolver {
} else {
return this.apRendererService.renderNote(note, author);
}
});
}) as Promise<IObjectWithId>;
case 'users':
return this.usersRepository.findOneByOrFail({ id: parsed.id })
.then(user => this.apRendererService.renderPerson(user as MiLocalUser));
@ -251,7 +253,7 @@ export class Resolver {
this.notesRepository.findOneByOrFail({ id: parsed.id }),
this.pollsRepository.findOneByOrFail({ noteId: parsed.id }),
])
.then(([note, poll]) => this.apRendererService.renderQuestion({ id: note.userId }, note, poll));
.then(([note, poll]) => this.apRendererService.renderQuestion({ id: note.userId }, note, poll)) as Promise<IObjectWithId>;
case 'likes':
return this.noteReactionsRepository.findOneByOrFail({ id: parsed.id }).then(async reaction =>
this.apRendererService.addContext(await this.apRendererService.renderLike(reaction, { uri: null })));

View file

@ -39,6 +39,10 @@ export interface IObject {
sensitive?: boolean;
}
export interface IObjectWithId extends IObject {
id: string;
}
/**
* Get array of ActivityStreams Objects id
*/