enhance(dev): non-production環境でhttpサーバー間でもユーザー、ノートの連合が可能なように (#10717)
* enhance(dev): non-production環境でhttpサーバー間でもユーザー、ノートの連合が可能なように * refactor (use checkHttps) * MISSKEY_WEBFINGER_USE_HTTP * Environment Variable readme * NEVER USE IN PRODUCTION * fix punyHost
This commit is contained in:
parent
2d3d986d13
commit
09764b909b
|
@ -165,6 +165,11 @@ pnpm jest -- foo.ts
|
||||||
### e2e tests
|
### e2e tests
|
||||||
TODO
|
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
|
## Continuous integration
|
||||||
Misskey uses GitHub Actions for executing automated tests.
|
Misskey uses GitHub Actions for executing automated tests.
|
||||||
Configuration files are located in [`/.github/workflows`](/.github/workflows).
|
Configuration files are located in [`/.github/workflows`](/.github/workflows).
|
||||||
|
|
|
@ -43,7 +43,8 @@ export class WebfingerService {
|
||||||
const m = query.match(/^([^@]+)@(.*)/);
|
const m = query.match(/^([^@]+)@(.*)/);
|
||||||
if (m) {
|
if (m) {
|
||||||
const hostname = m[2];
|
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})`);
|
throw new Error(`Invalid query (${query})`);
|
||||||
|
|
|
@ -12,6 +12,7 @@ import type Logger from '@/logger.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { ApResolverService } from '../ApResolverService.js';
|
import { ApResolverService } from '../ApResolverService.js';
|
||||||
import { ApLoggerService } from '../ApLoggerService.js';
|
import { ApLoggerService } from '../ApLoggerService.js';
|
||||||
|
import { checkHttps } from '@/misc/check-https.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApImageService {
|
export class ApImageService {
|
||||||
|
@ -48,8 +49,8 @@ export class ApImageService {
|
||||||
throw new Error('invalid image: url not privided');
|
throw new Error('invalid image: url not privided');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!image.url.startsWith('https://')) {
|
if (!checkHttps(image.url)) {
|
||||||
throw new Error('invalid image: unexpected shcema of url: ' + image.url);
|
throw new Error('invalid image: unexpected schema of url: ' + image.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info(`Creating the Image: ${image.url}`);
|
this.logger.info(`Creating the Image: ${image.url}`);
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { ApQuestionService } from './ApQuestionService.js';
|
||||||
import { ApImageService } from './ApImageService.js';
|
import { ApImageService } from './ApImageService.js';
|
||||||
import type { Resolver } from '../ApResolverService.js';
|
import type { Resolver } from '../ApResolverService.js';
|
||||||
import type { IObject, IPost } from '../type.js';
|
import type { IObject, IPost } from '../type.js';
|
||||||
|
import { checkHttps } from '@/misc/check-https.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApNoteService {
|
export class ApNoteService {
|
||||||
|
@ -130,13 +131,13 @@ export class ApNoteService {
|
||||||
|
|
||||||
this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
|
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);
|
throw new Error('unexpected shcema of note.id: ' + note.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = getOneApHrefNullable(note.url);
|
const url = getOneApHrefNullable(note.url);
|
||||||
|
|
||||||
if (url && !url.startsWith('https://')) {
|
if (url && !checkHttps(url)) {
|
||||||
throw new Error('unexpected shcema of note url: ' + url);
|
throw new Error('unexpected shcema of note url: ' + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ import type { ApLoggerService } from '../ApLoggerService.js';
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||||
import type { ApImageService } from './ApImageService.js';
|
import type { ApImageService } from './ApImageService.js';
|
||||||
import type { IActor, IObject } from '../type.js';
|
import type { IActor, IObject } from '../type.js';
|
||||||
|
import { checkHttps } from '@/misc/check-https.js';
|
||||||
|
|
||||||
const nameLength = 128;
|
const nameLength = 128;
|
||||||
const summaryLength = 2048;
|
const summaryLength = 2048;
|
||||||
|
@ -134,6 +135,12 @@ export class ApPersonService implements OnModuleInit {
|
||||||
this.logger = this.apLoggerService.logger;
|
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
|
* Validate and convert to actor object
|
||||||
* @param x Fetched object
|
* @param x Fetched object
|
||||||
|
@ -141,7 +148,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.toPuny(new URL(uri).hostname);
|
const expectHost = this.punyHost(uri);
|
||||||
|
|
||||||
if (x == null) {
|
if (x == null) {
|
||||||
throw new Error('invalid Actor: object is null');
|
throw new Error('invalid Actor: object is null');
|
||||||
|
@ -182,7 +189,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
x.summary = truncate(x.summary, summaryLength);
|
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) {
|
if (idHost !== expectHost) {
|
||||||
throw new Error('invalid Actor: id has different host');
|
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');
|
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) {
|
if (publicKeyIdHost !== expectHost) {
|
||||||
throw new Error('invalid Actor: publicKey.id has different host');
|
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}`);
|
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 ?? []);
|
const { fields } = this.analyzeAttachments(person.attachment ?? []);
|
||||||
|
|
||||||
|
@ -264,8 +271,8 @@ export class ApPersonService implements OnModuleInit {
|
||||||
|
|
||||||
const url = getOneApHrefNullable(person.url);
|
const url = getOneApHrefNullable(person.url);
|
||||||
|
|
||||||
if (url && !url.startsWith('https://')) {
|
if (url && !checkHttps(url)) {
|
||||||
throw new Error('unexpected shcema of person url: ' + url);
|
throw new Error('unexpected schema of person url: ' + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
|
@ -459,8 +466,8 @@ export class ApPersonService implements OnModuleInit {
|
||||||
|
|
||||||
const url = getOneApHrefNullable(person.url);
|
const url = getOneApHrefNullable(person.url);
|
||||||
|
|
||||||
if (url && !url.startsWith('https://')) {
|
if (url && !checkHttps(url)) {
|
||||||
throw new Error('unexpected shcema of person url: ' + url);
|
throw new Error('unexpected schema of person url: ' + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updates = {
|
const updates = {
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export function checkHttps(url: string) {
|
||||||
|
return url.startsWith('https://') ||
|
||||||
|
(url.startsWith('http://') && process.env.NODE_ENV !== 'production');
|
||||||
|
}
|
Loading…
Reference in New Issue