Merge branch 'develop' into feature/2024.10

This commit is contained in:
dakkar 2024-12-05 09:17:55 +00:00
commit 74b9351572
21 changed files with 190 additions and 51 deletions

View file

@ -229,3 +229,8 @@ checkActivityPubGetSignature: false
# Upload or download file size limits (bytes) # Upload or download file size limits (bytes)
#maxFileSize: 262144000 #maxFileSize: 262144000
# CHMod-style permission bits to apply to uploaded files.
# Permission bits are specified as a base-8 string representing User/Group/Other permissions.
# This setting is only useful for custom deployments, such as using a reverse proxy to serve media.
#filePermissionBits: '644'

View file

@ -235,3 +235,8 @@ allowedPrivateNetworks: [
# Upload or download file size limits (bytes) # Upload or download file size limits (bytes)
#maxFileSize: 262144000 #maxFileSize: 262144000
# CHMod-style permission bits to apply to uploaded files.
# Permission bits are specified as a base-8 string representing User/Group/Other permissions.
# This setting is only useful for custom deployments, such as using a reverse proxy to serve media.
#filePermissionBits: '644'

View file

@ -312,3 +312,8 @@ checkActivityPubGetSignature: false
# Upload or download file size limits (bytes) # Upload or download file size limits (bytes)
#maxFileSize: 262144000 #maxFileSize: 262144000
# CHMod-style permission bits to apply to uploaded files.
# Permission bits are specified as a base-8 string representing User/Group/Other permissions.
# This setting is only useful for custom deployments, such as using a reverse proxy to serve media.
#filePermissionBits: '644'

View file

@ -348,3 +348,8 @@ checkActivityPubGetSignature: false
# PID File of master process # PID File of master process
#pidFile: /tmp/misskey.pid #pidFile: /tmp/misskey.pid
# CHMod-style permission bits to apply to uploaded files.
# Permission bits are specified as a base-8 string representing User/Group/Other permissions.
# This setting is only useful for custom deployments, such as using a reverse proxy to serve media.
#filePermissionBits: '644'

View file

@ -3,27 +3,33 @@
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/SECURITY.md) 🔒 Found a security vulnerability? [Please disclose it responsibly.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/SECURITY.md)
🤝 By submitting this feature request, you agree to follow our [Contribution Guidelines.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md) --> 🤝 By submitting this feature request, you agree to follow our [Contribution Guidelines.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md) -->
**What happened?** _(Please give us a brief description of what happened.)_ # **What happened?**
<!-- Please give us a brief description of what happened. -->
**What did you expect to happen?** _(Please give us a brief description of what you expected to happen.)_ # **What did you expect to happen?**
<!-- Please give us a brief description of what you expected to happen. -->
**Version** _(What version of Sharkey is your instance running? You can find this by clicking your instance's logo at the top left and then clicking instance information.)_ # **Version**
<!-- What version of Sharkey is your instance running? You can find this by clicking your instance's logo at the top left and then clicking instance information. -->
**Instance** _(What instance of Sharkey are you using?)_ # **Instance**
<!-- What instance of Sharkey are you using? -->
**What type of issue is this?** _(If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side.)_ # **What type of issue is this?**
<!-- If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side. -->
**What browser are you using? (Client-side issues only)** # **What browser are you using? (Client-side issues only)**
**What operating system are you using? (Client-side issues only)** # **What operating system are you using? (Client-side issues only)**
**How do you deploy Sharkey on your server? (Server-side issues only)** # **How do you deploy Sharkey on your server? (Server-side issues only)**
**What operating system are you using? (Server-side issues only)** # **What operating system are you using? (Server-side issues only)**
**Relevant log output** _(Please copy and paste any relevant log output. You can find your log by inspecting the page, and going to the "console" tab. This will be automatically formatted into code, so no need for backticks.)_ # **Relevant log output**
<!-- Please copy and paste any relevant log output. You can find your log by inspecting the page, and going to the "console" tab. This will be automatically formatted into code, so no need for backticks. -->
**Contribution Guidelines** # **Contribution Guidelines**
By submitting this issue, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md) By submitting this issue, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md)
- [ ] I agree to follow this project's Contribution Guidelines - [ ] I agree to follow this project's Contribution Guidelines
- [ ] I have searched the issue tracker for similar issues, and this is not a duplicate. - [ ] I have searched the issue tracker for similar issues, and this is not a duplicate.

View file

@ -3,15 +3,19 @@
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/SECURITY.md) 🔒 Found a security vulnerability? [Please disclose it responsibly.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/SECURITY.md)
🤝 By submitting this feature request, you agree to follow our [Contribution Guidelines.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md) --> 🤝 By submitting this feature request, you agree to follow our [Contribution Guidelines.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md) -->
**What feature would you like implemented?** _(Please give us a brief description of what you'd like.)_ # **What feature would you like implemented?**
<!-- Please give us a brief description of what you'd like. -->
**Why should we add this feature?** _(Please give us a brief description of why your feature is important.)_ # **Why should we add this feature?**
<!-- Please give us a brief description of why your feature is important. -->
**Version** _(What version of Sharkey is your instance running? You can find this by clicking your instance's logo at the top left and then clicking instance information.)_ # **Version**
<!-- What version of Sharkey is your instance running? You can find this by clicking your instance's logo at the top left and then clicking instance information. -->
**Instance** _(What instance of Sharkey are you using?)_ # **Instance**
<!-- What instance of Sharkey are you using? -->
**Contribution Guidelines** # **Contribution Guidelines**
By submitting this issue, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md) By submitting this issue, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md)
- [ ] I agree to follow this project's Contribution Guidelines - [ ] I agree to follow this project's Contribution Guidelines
- [ ] I have searched the issue tracker for similar requests, and this is not a duplicate. - [ ] I have searched the issue tracker for similar requests, and this is not a duplicate.

View file

@ -1,11 +1,12 @@
<!-- Thanks for taking the time to make Sharkey better! --> <!-- Thanks for taking the time to make Sharkey better! -->
**What does this PR do?** _(Please give us a brief description of what this PR does.)_ # **What does this MR do?**
<!-- Please give us a brief description of what this PR does. -->
**Contribution Guidelines** # **Contribution Guidelines**
By submitting this merge request, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md) By submitting this merge request, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md)
- [ ] I agree to follow this project's Contribution Guidelines - [ ] I agree to follow this project's Contribution Guidelines
- [ ] I have made sure to test this pull request - [ ] I have made sure to test this merge request
<!-- Uncomment if your merge request has multiple authors --> <!-- Uncomment if your merge request has multiple authors -->
<!-- Co-authored-by: Name <email@email.com> --> <!-- Co-authored-by: Name <email@email.com> -->

View file

@ -95,6 +95,7 @@
"@swc/core": "1.9.2", "@swc/core": "1.9.2",
"@transfem-org/sfm-js": "0.24.5", "@transfem-org/sfm-js": "0.24.5",
"@twemoji/parser": "15.1.1", "@twemoji/parser": "15.1.1",
"@types/psl": "^1.1.3",
"accepts": "1.3.8", "accepts": "1.3.8",
"ajv": "8.17.1", "ajv": "8.17.1",
"archiver": "7.0.1", "archiver": "7.0.1",
@ -137,6 +138,7 @@
"json5": "2.2.3", "json5": "2.2.3",
"jsonld": "8.3.2", "jsonld": "8.3.2",
"jsrsasign": "11.1.0", "jsrsasign": "11.1.0",
"juice": "11.0.0",
"megalodon": "workspace:*", "megalodon": "workspace:*",
"meilisearch": "0.45.0", "meilisearch": "0.45.0",
"juice": "11.0.0", "juice": "11.0.0",
@ -160,6 +162,7 @@
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"proxy-addr": "^2.0.7", "proxy-addr": "^2.0.7",
"psl": "^1.13.0",
"pug": "3.0.3", "pug": "3.0.3",
"punycode": "2.3.1", "punycode": "2.3.1",
"qrcode": "1.5.4", "qrcode": "1.5.4",

View file

@ -117,6 +117,7 @@ type Source = {
}; };
pidFile: string; pidFile: string;
filePermissionBits?: string;
}; };
export type Config = { export type Config = {
@ -215,6 +216,7 @@ export type Config = {
} | undefined; } | undefined;
pidFile: string; pidFile: string;
filePermissionBits?: string;
}; };
const _filename = fileURLToPath(import.meta.url); const _filename = fileURLToPath(import.meta.url);
@ -351,6 +353,7 @@ export function loadConfig(): Config {
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7), deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
import: config.import, import: config.import,
pidFile: config.pidFile, pidFile: config.pidFile,
filePermissionBits: config.filePermissionBits,
}; };
} }
@ -456,7 +459,10 @@ function applyEnvOverrides(config: Source) {
} }
} }
const alwaysStrings = { 'chmodSocket': true } as { [key: string]: boolean }; const alwaysStrings: { [key in string]?: boolean } = {
'chmodSocket': true,
'filePermissionBits': true,
};
function _assign(path: (string | number)[], lastStep: string | number, value: string) { function _assign(path: (string | number)[], lastStep: string | number, value: string) {
let thisConfig = config as any; let thisConfig = config as any;
@ -494,7 +500,7 @@ function applyEnvOverrides(config: Source) {
_apply_top(['sentryForBackend', 'enableNodeProfiling']); _apply_top(['sentryForBackend', 'enableNodeProfiling']);
_apply_top([['clusterLimit', 'deliverJobConcurrency', 'inboxJobConcurrency', 'relashionshipJobConcurrency', 'deliverJobPerSec', 'inboxJobPerSec', 'relashionshipJobPerSec', 'deliverJobMaxAttempts', 'inboxJobMaxAttempts']]); _apply_top([['clusterLimit', 'deliverJobConcurrency', 'inboxJobConcurrency', 'relashionshipJobConcurrency', 'deliverJobPerSec', 'inboxJobPerSec', 'relashionshipJobPerSec', 'deliverJobMaxAttempts', 'inboxJobMaxAttempts']]);
_apply_top([['outgoingAddress', 'outgoingAddressFamily', 'proxy', 'proxySmtp', 'mediaProxy', 'proxyRemoteFiles', 'videoThumbnailGenerator']]); _apply_top([['outgoingAddress', 'outgoingAddressFamily', 'proxy', 'proxySmtp', 'mediaProxy', 'proxyRemoteFiles', 'videoThumbnailGenerator']]);
_apply_top([['maxFileSize', 'maxNoteLength', 'maxRemoteNoteLength', 'maxAltTextLength', 'maxRemoteAltTextLength', 'pidFile']]); _apply_top([['maxFileSize', 'maxNoteLength', 'maxRemoteNoteLength', 'maxAltTextLength', 'maxRemoteAltTextLength', 'pidFile', 'filePermissionBits']]);
_apply_top(['import', ['downloadTimeout', 'maxFileSize']]); _apply_top(['import', ['downloadTimeout', 'maxFileSize']]);
_apply_top([['signToActivityPubGet', 'checkActivityPubGetSignature', 'setupPassword']]); _apply_top([['signToActivityPubGet', 'checkActivityPubGetSignature', 'setupPassword']]);
} }

View file

@ -4,7 +4,7 @@
*/ */
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import { copyFile, mkdir, unlink, writeFile } from 'node:fs/promises'; import { copyFile, unlink, writeFile, chmod } from 'node:fs/promises';
import * as Path from 'node:path'; import * as Path from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path'; import { dirname } from 'node:path';
@ -41,12 +41,20 @@ export class InternalStorageService {
@bindThis @bindThis
public async saveFromPath(key: string, srcPath: string): Promise<string> { public async saveFromPath(key: string, srcPath: string): Promise<string> {
await copyFile(srcPath, this.resolvePath(key)); await copyFile(srcPath, this.resolvePath(key));
return `${this.config.url}/files/${key}`; return await this.finalizeSavedFile(key);
} }
@bindThis @bindThis
public async saveFromBuffer(key: string, data: Buffer): Promise<string> { public async saveFromBuffer(key: string, data: Buffer): Promise<string> {
await writeFile(this.resolvePath(key), data); await writeFile(this.resolvePath(key), data);
return await this.finalizeSavedFile(key);
}
private async finalizeSavedFile(key: string): Promise<string> {
if (this.config.filePermissionBits) {
const path = this.resolvePath(key);
await chmod(path, this.config.filePermissionBits);
}
return `${this.config.url}/files/${key}`; return `${this.config.url}/files/${key}`;
} }

View file

@ -4,9 +4,10 @@
*/ */
import { URL } from 'node:url'; import { URL } from 'node:url';
import { toASCII } from 'punycode'; import punycode from 'punycode/punycode.js';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import RE2 from 're2'; import RE2 from 're2';
import psl from 'psl';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -106,13 +107,13 @@ export class UtilityService {
@bindThis @bindThis
public toPuny(host: string): string { public toPuny(host: string): string {
return toASCII(host.toLowerCase()); return punycode.toASCII(host.toLowerCase());
} }
@bindThis @bindThis
public toPunyNullable(host: string | null | undefined): string | null { public toPunyNullable(host: string | null | undefined): string | null {
if (host == null) return null; if (host == null) return null;
return toASCII(host.toLowerCase()); return punycode.toASCII(host.toLowerCase());
} }
@bindThis @bindThis
@ -122,6 +123,27 @@ export class UtilityService {
return host; return host;
} }
@bindThis
private specialSuffix(hostname: string): string | null {
// masto.host provides domain names for its clients, we have to
// treat it as if it were a public suffix
const mastoHost = hostname.match(/\.?([a-zA-Z0-9-]+\.masto\.host)$/i);
if (mastoHost) {
return mastoHost[1];
}
return null;
}
@bindThis
public punyHostPSLDomain(url: string): string {
const urlObj = new URL(url);
const hostname = urlObj.hostname;
const domain = this.specialSuffix(hostname) ?? psl.get(hostname) ?? hostname;
const host = `${this.toPuny(domain)}${urlObj.port.length > 0 ? ':' + urlObj.port : ''}`;
return host;
}
@bindThis @bindThis
public isFederationAllowedHost(host: string): boolean { public isFederationAllowedHost(host: string): boolean {
if (this.meta.federation === 'none') return false; if (this.meta.federation === 'none') return false;

View file

@ -32,7 +32,7 @@ import { AbuseReportService } from '@/core/AbuseReportService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { fromTuple } from '@/misc/from-tuple.js'; import { fromTuple } from '@/misc/from-tuple.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { getApHrefNullable, getApId, getApIds, getApType, getNullableApId, isAccept, isActor, isAdd, isAnnounce, isApObject, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; import { getApHrefNullable, getApId, getApIds, getApType, getNullableApId, isAccept, isActor, isAdd, isAnnounce, isApObject, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isDislike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
import { ApNoteService } from './models/ApNoteService.js'; import { ApNoteService } from './models/ApNoteService.js';
import { ApLoggerService } from './ApLoggerService.js'; import { ApLoggerService } from './ApLoggerService.js';
import { ApDbResolverService } from './ApDbResolverService.js'; import { ApDbResolverService } from './ApDbResolverService.js';
@ -41,7 +41,7 @@ import { ApAudienceService } from './ApAudienceService.js';
import { ApPersonService } from './models/ApPersonService.js'; import { ApPersonService } from './models/ApPersonService.js';
import { ApQuestionService } from './models/ApQuestionService.js'; import { ApQuestionService } from './models/ApQuestionService.js';
import type { Resolver } from './ApResolverService.js'; import type { Resolver } from './ApResolverService.js';
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js'; import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IDislike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js';
@Injectable() @Injectable()
export class ApInboxService { export class ApInboxService {
@ -167,6 +167,8 @@ export class ApInboxService {
return await this.announce(actor, activity, resolver); return await this.announce(actor, activity, resolver);
} else if (isLike(activity)) { } else if (isLike(activity)) {
return await this.like(actor, activity, resolver); return await this.like(actor, activity, resolver);
} else if (isDislike(activity)) {
return await this.dislike(actor, activity);
} else if (isUndo(activity)) { } else if (isUndo(activity)) {
return await this.undo(actor, activity, resolver); return await this.undo(actor, activity, resolver);
} else if (isBlock(activity)) { } else if (isBlock(activity)) {
@ -221,6 +223,11 @@ export class ApInboxService {
} }
} }
@bindThis
private async dislike(actor: MiRemoteUser, dislike: IDislike): Promise<string> {
return await this.undoLike(actor, dislike);
}
@bindThis @bindThis
private async accept(actor: MiRemoteUser, activity: IAccept, resolver?: Resolver): Promise<string> { private async accept(actor: MiRemoteUser, activity: IAccept, resolver?: Resolver): Promise<string> {
const uri = activity.id ?? activity; const uri = activity.id ?? activity;
@ -783,7 +790,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async undoLike(actor: MiRemoteUser, activity: ILike): Promise<string> { private async undoLike(actor: MiRemoteUser, activity: ILike | IDislike): Promise<string> {
const targetUri = getApId(activity.object); const targetUri = getApId(activity.object);
const note = await this.apNoteService.fetchNote(targetUri); const note = await this.apNoteService.fetchNote(targetUri);

View file

@ -242,8 +242,10 @@ export class ApRequestService {
const alternate = document.querySelector('head > link[rel="alternate"][type="application/activity+json"]'); const alternate = document.querySelector('head > link[rel="alternate"][type="application/activity+json"]');
if (alternate) { if (alternate) {
const href = alternate.getAttribute('href'); const href = alternate.getAttribute('href');
if (href && this.utilityService.punyHost(url) === this.utilityService.punyHost(href)) { if (href) {
return await this.signedGet(href, user, false); if (this.utilityService.punyHostPSLDomain(url) === this.utilityService.punyHostPSLDomain(href)) {
return await this.signedGet(href, user, false);
}
} }
} }
} catch (e) { } catch (e) {

View file

@ -131,7 +131,7 @@ export class Resolver {
throw new UnrecoverableError(`invalid AP object ${value}: missing id`); throw new UnrecoverableError(`invalid AP object ${value}: missing id`);
} }
if (this.utilityService.punyHost(object.id) !== this.utilityService.punyHost(value)) { if (this.utilityService.punyHostPSLDomain(object.id) !== this.utilityService.punyHostPSLDomain(value)) {
throw new UnrecoverableError(`invalid AP object ${value}: id ${object.id} has different host`); throw new UnrecoverableError(`invalid AP object ${value}: id ${object.id} has different host`);
} }

View file

@ -192,8 +192,8 @@ export class ApNoteService {
throw new UnrecoverableError(`unexpected schema of note.url ${url} in ${entryUri}`); throw new UnrecoverableError(`unexpected schema of note.url ${url} in ${entryUri}`);
} }
if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(note.id)) { if (this.utilityService.punyHostPSLDomain(url) !== this.utilityService.punyHostPSLDomain(note.id)) {
throw new Error(`note url <> uri host mismatch: ${url} <> ${note.id} in ${entryUri}`); throw new UnrecoverableError(`note url <> uri host mismatch: ${url} <> ${note.id} in ${entryUri}`);
} }
} }
@ -444,7 +444,7 @@ export class ApNoteService {
throw new UnrecoverableError(`unexpected schema of note.url ${url} in ${noteUri}`); throw new UnrecoverableError(`unexpected schema of note.url ${url} in ${noteUri}`);
} }
if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(note.id)) { if (this.utilityService.punyHostPSLDomain(url) !== this.utilityService.punyHostPSLDomain(note.id)) {
throw new UnrecoverableError(`note url <> id host mismatch: ${url} <> ${note.id} in ${noteUri}`); throw new UnrecoverableError(`note url <> id host mismatch: ${url} <> ${note.id} in ${noteUri}`);
} }
} }

View file

@ -138,7 +138,7 @@ export class ApPersonService implements OnModuleInit {
*/ */
@bindThis @bindThis
private validateActor(x: IObject, uri: string): IActor { private validateActor(x: IObject, uri: string): IActor {
const expectHost = this.utilityService.punyHost(uri); const expectHost = this.utilityService.punyHostPSLDomain(uri);
if (!isActor(x)) { if (!isActor(x)) {
throw new UnrecoverableError(`invalid Actor type '${x.type}' in ${uri}`); throw new UnrecoverableError(`invalid Actor type '${x.type}' in ${uri}`);
@ -152,7 +152,7 @@ export class ApPersonService implements OnModuleInit {
throw new UnrecoverableError(`invalid Actor ${uri} - wrong inbox type`); throw new UnrecoverableError(`invalid Actor ${uri} - wrong inbox type`);
} }
const inboxHost = this.utilityService.punyHost(x.inbox); const inboxHost = this.utilityService.punyHostPSLDomain(x.inbox);
if (inboxHost !== expectHost) { if (inboxHost !== expectHost) {
throw new UnrecoverableError(`invalid Actor ${uri} - wrong inbox ${inboxHost}`); throw new UnrecoverableError(`invalid Actor ${uri} - wrong inbox ${inboxHost}`);
} }
@ -160,7 +160,7 @@ export class ApPersonService implements OnModuleInit {
const sharedInboxObject = x.sharedInbox ?? (x.endpoints ? x.endpoints.sharedInbox : undefined); const sharedInboxObject = x.sharedInbox ?? (x.endpoints ? x.endpoints.sharedInbox : undefined);
if (sharedInboxObject != null) { if (sharedInboxObject != null) {
const sharedInbox = getApId(sharedInboxObject); const sharedInbox = getApId(sharedInboxObject);
if (!(typeof sharedInbox === 'string' && sharedInbox.length > 0 && this.utilityService.punyHost(sharedInbox) === expectHost)) { if (!(typeof sharedInbox === 'string' && sharedInbox.length > 0 && this.utilityService.punyHostPSLDomain(sharedInbox) === expectHost)) {
throw new UnrecoverableError(`invalid Actor ${uri} - wrong shared inbox ${sharedInbox}`); throw new UnrecoverableError(`invalid Actor ${uri} - wrong shared inbox ${sharedInbox}`);
} }
} }
@ -170,7 +170,7 @@ export class ApPersonService implements OnModuleInit {
if (xCollection != null) { if (xCollection != null) {
const collectionUri = getApId(xCollection); const collectionUri = getApId(xCollection);
if (typeof collectionUri === 'string' && collectionUri.length > 0) { if (typeof collectionUri === 'string' && collectionUri.length > 0) {
if (this.utilityService.punyHost(collectionUri) !== expectHost) { if (this.utilityService.punyHostPSLDomain(collectionUri) !== expectHost) {
throw new UnrecoverableError(`invalid Actor ${uri} - wrong ${collection} ${collectionUri}`); throw new UnrecoverableError(`invalid Actor ${uri} - wrong ${collection} ${collectionUri}`);
} }
} else if (collectionUri != null) { } else if (collectionUri != null) {
@ -202,7 +202,7 @@ export class ApPersonService implements OnModuleInit {
x.summary = truncate(x.summary, summaryLength); x.summary = truncate(x.summary, summaryLength);
} }
const idHost = this.utilityService.punyHost(x.id); const idHost = this.utilityService.punyHostPSLDomain(x.id);
if (idHost !== expectHost) { if (idHost !== expectHost) {
throw new UnrecoverableError(`invalid Actor ${uri} - wrong id ${x.id}`); throw new UnrecoverableError(`invalid Actor ${uri} - wrong id ${x.id}`);
} }
@ -212,7 +212,7 @@ export class ApPersonService implements OnModuleInit {
throw new UnrecoverableError(`invalid Actor ${uri} - wrong publicKey.id type`); throw new UnrecoverableError(`invalid Actor ${uri} - wrong publicKey.id type`);
} }
const publicKeyIdHost = this.utilityService.punyHost(x.publicKey.id); const publicKeyIdHost = this.utilityService.punyHostPSLDomain(x.publicKey.id);
if (publicKeyIdHost !== expectHost) { if (publicKeyIdHost !== expectHost) {
throw new UnrecoverableError(`invalid Actor ${uri} - wrong publicKey.id ${x.publicKey.id}`); throw new UnrecoverableError(`invalid Actor ${uri} - wrong publicKey.id ${x.publicKey.id}`);
} }
@ -357,7 +357,7 @@ export class ApPersonService implements OnModuleInit {
throw new UnrecoverableError(`unexpected schema of person url ${url} in ${uri}`); throw new UnrecoverableError(`unexpected schema of person url ${url} in ${uri}`);
} }
if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(person.id)) { if (this.utilityService.punyHostPSLDomain(url) !== this.utilityService.punyHostPSLDomain(person.id)) {
throw new UnrecoverableError(`person url <> uri host mismatch: ${url} <> ${person.id} in ${uri}`); throw new UnrecoverableError(`person url <> uri host mismatch: ${url} <> ${person.id} in ${uri}`);
} }
} }
@ -574,7 +574,7 @@ export class ApPersonService implements OnModuleInit {
throw new UnrecoverableError(`unexpected schema of person url ${url} in ${uri}`); throw new UnrecoverableError(`unexpected schema of person url ${url} in ${uri}`);
} }
if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(person.id)) { if (this.utilityService.punyHostPSLDomain(url) !== this.utilityService.punyHostPSLDomain(person.id)) {
throw new UnrecoverableError(`person url <> uri host mismatch: ${url} <> ${person.id} in ${uri}`); throw new UnrecoverableError(`person url <> uri host mismatch: ${url} <> ${person.id} in ${uri}`);
} }
} }

View file

@ -339,6 +339,10 @@ export interface ILike extends IActivity {
_misskey_reaction?: string; _misskey_reaction?: string;
} }
export interface IDislike extends IActivity {
type: 'Dislike';
}
export interface IAnnounce extends IActivity { export interface IAnnounce extends IActivity {
type: 'Announce'; type: 'Announce';
} }
@ -371,6 +375,7 @@ export const isLike = (object: IObject): object is ILike => {
const type = getApType(object); const type = getApType(object);
return type != null && ['Like', 'EmojiReaction', 'EmojiReact'].includes(type); return type != null && ['Like', 'EmojiReaction', 'EmojiReact'].includes(type);
}; };
export const isDislike = (object: IObject): object is IDislike => getApType(object) === 'Dislike';
export const isAnnounce = (object: IObject): object is IAnnounce => getApType(object) === 'Announce'; export const isAnnounce = (object: IObject): object is IAnnounce => getApType(object) === 'Announce';
export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block'; export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block';
export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag'; export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag';

View file

@ -0,0 +1,43 @@
import * as assert from 'assert';
import { Test } from '@nestjs/testing';
import { CoreModule } from '@/core/CoreModule.js';
import { UtilityService } from '@/core/UtilityService.js';
import { GlobalModule } from '@/GlobalModule.js';
describe('UtilityService', () => {
let utilityService: UtilityService;
beforeAll(async () => {
const app = await Test.createTestingModule({
imports: [GlobalModule, CoreModule],
}).compile();
utilityService = app.get<UtilityService>(UtilityService);
});
describe('punyHost', () => {
test('simple', () => {
assert.equal(utilityService.punyHost('http://www.foo.com'), 'www.foo.com');
});
test('japanese', () => {
assert.equal(utilityService.punyHost('http://www.新聞.com'), 'www.xn--efvv70d.com');
});
});
describe('punyHostPSLDomain', () => {
test('simple', () => {
assert.equal(utilityService.punyHostPSLDomain('http://www.foo.com'), 'foo.com');
});
test('japanese', () => {
assert.equal(utilityService.punyHostPSLDomain('http://www.新聞.com'), 'xn--efvv70d.com');
});
test('lower', () => {
assert.equal(utilityService.punyHostPSLDomain('http://foo.github.io'), 'foo.github.io');
assert.equal(utilityService.punyHostPSLDomain('http://foo.bar.github.io'), 'bar.github.io');
});
test('special', () => {
assert.equal(utilityService.punyHostPSLDomain('http://foo.masto.host'), 'foo.masto.host');
assert.equal(utilityService.punyHostPSLDomain('http://foo.bar.masto.host'), 'bar.masto.host');
});
});
});

View file

@ -612,8 +612,8 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) {
username: config.db.user, username: config.db.user,
password: config.db.pass, password: config.db.pass,
database: config.db.db, database: config.db.db,
synchronize: true && !justBorrow, synchronize: !justBorrow,
dropSchema: true && !justBorrow, dropSchema: !justBorrow,
entities: initEntities ?? entities, entities: initEntities ?? entities,
}); });

View file

@ -103,8 +103,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
async function getConfirmed(text: string): Promise<boolean> { async function getConfirmed(text: string): Promise<boolean> {
const confirm = await os.confirm({ const confirm = await os.confirm({
type: 'warning', type: 'question',
title: 'confirm',
text, text,
}); });

View file

@ -170,6 +170,9 @@ importers:
'@twemoji/parser': '@twemoji/parser':
specifier: 15.1.1 specifier: 15.1.1
version: 15.1.1 version: 15.1.1
'@types/psl':
specifier: ^1.1.3
version: 1.1.3
accepts: accepts:
specifier: 1.3.8 specifier: 1.3.8
version: 1.3.8 version: 1.3.8
@ -365,6 +368,9 @@ importers:
proxy-addr: proxy-addr:
specifier: ^2.0.7 specifier: ^2.0.7
version: 2.0.7 version: 2.0.7
psl:
specifier: ^1.13.0
version: 1.13.0
pug: pug:
specifier: 3.0.3 specifier: 3.0.3
version: 3.0.3 version: 3.0.3
@ -4563,6 +4569,9 @@ packages:
'@types/proxy-addr@2.0.3': '@types/proxy-addr@2.0.3':
resolution: {integrity: sha512-TgAHHO4tNG3HgLTUhB+hM4iwW6JUNeQHCLnF1DjaDA9c69PN+IasoFu2MYDhubFc+ZIw5c5t9DMtjvrD6R3Egg==} resolution: {integrity: sha512-TgAHHO4tNG3HgLTUhB+hM4iwW6JUNeQHCLnF1DjaDA9c69PN+IasoFu2MYDhubFc+ZIw5c5t9DMtjvrD6R3Egg==}
'@types/psl@1.1.3':
resolution: {integrity: sha512-Iu174JHfLd7i/XkXY6VDrqSlPvTDQOtQI7wNAXKKOAADJ9TduRLkNdMgjGiMxSttUIZnomv81JAbAbC0DhggxA==}
'@types/pug@2.0.10': '@types/pug@2.0.10':
resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
@ -9344,8 +9353,8 @@ packages:
pseudomap@1.0.2: pseudomap@1.0.2:
resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
psl@1.9.0: psl@1.13.0:
resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} resolution: {integrity: sha512-BFwmFXiJoFqlUpZ5Qssolv15DMyc84gTBds1BjsV1BfXEo1UyyD7GsmN67n7J77uRhoSNW1AXtXKPLcBFQn9Aw==}
pstree.remy@1.1.8: pstree.remy@1.1.8:
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
@ -15067,6 +15076,8 @@ snapshots:
dependencies: dependencies:
'@types/node': 22.9.0 '@types/node': 22.9.0
'@types/psl@1.1.3': {}
'@types/pug@2.0.10': {} '@types/pug@2.0.10': {}
'@types/punycode@2.1.4': {} '@types/punycode@2.1.4': {}
@ -21108,7 +21119,9 @@ snapshots:
pseudomap@1.0.2: {} pseudomap@1.0.2: {}
psl@1.9.0: {} psl@1.13.0:
dependencies:
punycode: 2.3.1
pstree.remy@1.1.8: {} pstree.remy@1.1.8: {}
@ -22305,7 +22318,7 @@ snapshots:
tough-cookie@4.1.4: tough-cookie@4.1.4:
dependencies: dependencies:
psl: 1.9.0 psl: 1.13.0
punycode: 2.3.1 punycode: 2.3.1
universalify: 0.2.0 universalify: 0.2.0
url-parse: 1.5.10 url-parse: 1.5.10