diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b8a20c8078..f6b3804f84 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -165,6 +165,11 @@ pnpm jest -- foo.ts ### e2e tests TODO +## Environment Variable + +- `MISSKEY_CONFIG_YML`: Specify the file path of config.yml instead of default.yml (e.g. `2nd.yml`). +- `MISSKEY_WEBFINGER_USE_HTTP`: If it's set true, WebFinger requests will be http instead of https, useful for testing federation between servers in localhost. NEVER USE IN PRODUCTION. + ## Continuous integration Misskey uses GitHub Actions for executing automated tests. Configuration files are located in [`/.github/workflows`](/.github/workflows). diff --git a/packages/backend/src/core/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts index 69df2d0c1b..3ee7990643 100644 --- a/packages/backend/src/core/WebfingerService.ts +++ b/packages/backend/src/core/WebfingerService.ts @@ -43,7 +43,8 @@ export class WebfingerService { const m = query.match(/^([^@]+)@(.*)/); if (m) { const hostname = m[2]; - return `https://${hostname}/.well-known/webfinger?` + urlQuery({ resource: `acct:${query}` }); + const useHttp = process.env.MISSKEY_WEBFINGER_USE_HTTP && process.env.MISSKEY_WEBFINGER_USE_HTTP.toLowerCase() === 'true'; + return `http${useHttp ? '' : 's'}://${hostname}/.well-known/webfinger?${urlQuery({ resource: `acct:${query}` })}`; } throw new Error(`Invalid query (${query})`); diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index 3b671af127..0043907c21 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -12,6 +12,7 @@ import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { ApResolverService } from '../ApResolverService.js'; import { ApLoggerService } from '../ApLoggerService.js'; +import { checkHttps } from '@/misc/check-https.js'; @Injectable() export class ApImageService { @@ -48,8 +49,8 @@ export class ApImageService { throw new Error('invalid image: url not privided'); } - if (!image.url.startsWith('https://')) { - throw new Error('invalid image: unexpected shcema of url: ' + image.url); + if (!checkHttps(image.url)) { + throw new Error('invalid image: unexpected schema of url: ' + image.url); } this.logger.info(`Creating the Image: ${image.url}`); diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 5bbb036e03..a9a1f926d2 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -32,6 +32,7 @@ import { ApQuestionService } from './ApQuestionService.js'; import { ApImageService } from './ApImageService.js'; import type { Resolver } from '../ApResolverService.js'; import type { IObject, IPost } from '../type.js'; +import { checkHttps } from '@/misc/check-https.js'; @Injectable() export class ApNoteService { @@ -130,13 +131,13 @@ export class ApNoteService { this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); - if (note.id && !note.id.startsWith('https://')) { + if (note.id && !checkHttps(note.id)) { throw new Error('unexpected shcema of note.id: ' + note.id); } const url = getOneApHrefNullable(note.url); - if (url && !url.startsWith('https://')) { + if (url && !checkHttps(url)) { throw new Error('unexpected shcema of note url: ' + url); } diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index 21797cfcb7..6f2b8e5c3d 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -42,6 +42,7 @@ import type { ApLoggerService } from '../ApLoggerService.js'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports import type { ApImageService } from './ApImageService.js'; import type { IActor, IObject } from '../type.js'; +import { checkHttps } from '@/misc/check-https.js'; const nameLength = 128; const summaryLength = 2048; @@ -134,6 +135,12 @@ export class ApPersonService implements OnModuleInit { this.logger = this.apLoggerService.logger; } + private punyHost(url: string): string { + const urlObj = new URL(url); + const host = `${this.utilityService.toPuny(urlObj.hostname)}${urlObj.port.length > 0 ? ':' + urlObj.port : ''}`; + return host; + } + /** * Validate and convert to actor object * @param x Fetched object @@ -141,7 +148,7 @@ export class ApPersonService implements OnModuleInit { */ @bindThis private validateActor(x: IObject, uri: string): IActor { - const expectHost = this.utilityService.toPuny(new URL(uri).hostname); + const expectHost = this.punyHost(uri); if (x == null) { throw new Error('invalid Actor: object is null'); @@ -182,7 +189,7 @@ export class ApPersonService implements OnModuleInit { x.summary = truncate(x.summary, summaryLength); } - const idHost = this.utilityService.toPuny(new URL(x.id!).hostname); + const idHost = this.punyHost(x.id); if (idHost !== expectHost) { throw new Error('invalid Actor: id has different host'); } @@ -192,7 +199,7 @@ export class ApPersonService implements OnModuleInit { throw new Error('invalid Actor: publicKey.id is not a string'); } - const publicKeyIdHost = this.utilityService.toPuny(new URL(x.publicKey.id).hostname); + const publicKeyIdHost = this.punyHost(x.publicKey.id); if (publicKeyIdHost !== expectHost) { throw new Error('invalid Actor: publicKey.id has different host'); } @@ -252,7 +259,7 @@ export class ApPersonService implements OnModuleInit { this.logger.info(`Creating the Person: ${person.id}`); - const host = this.utilityService.toPuny(new URL(object.id).hostname); + const host = this.punyHost(object.id); const { fields } = this.analyzeAttachments(person.attachment ?? []); @@ -264,8 +271,8 @@ export class ApPersonService implements OnModuleInit { const url = getOneApHrefNullable(person.url); - if (url && !url.startsWith('https://')) { - throw new Error('unexpected shcema of person url: ' + url); + if (url && !checkHttps(url)) { + throw new Error('unexpected schema of person url: ' + url); } // Create user @@ -459,8 +466,8 @@ export class ApPersonService implements OnModuleInit { const url = getOneApHrefNullable(person.url); - if (url && !url.startsWith('https://')) { - throw new Error('unexpected shcema of person url: ' + url); + if (url && !checkHttps(url)) { + throw new Error('unexpected schema of person url: ' + url); } const updates = { diff --git a/packages/backend/src/misc/check-https.ts b/packages/backend/src/misc/check-https.ts new file mode 100644 index 0000000000..b33f019973 --- /dev/null +++ b/packages/backend/src/misc/check-https.ts @@ -0,0 +1,4 @@ +export function checkHttps(url: string) { + return url.startsWith('https://') || + (url.startsWith('http://') && process.env.NODE_ENV !== 'production'); +}