From ef354e94f20ace67b94faa2859c458a436cdd3f7 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Sun, 25 Jun 2023 04:04:33 +0200 Subject: refactor(backend): replace rndstr with secureRndstr (#11044) * refactor(backend): replace rndstr with secureRndstr * Update pnpm-lock.yaml * .js --- .../backend/src/server/api/SignupApiService.ts | 28 +++++++++++----------- .../src/server/api/endpoints/admin/emoji/add.ts | 1 - .../server/api/endpoints/admin/reset-password.ts | 4 ++-- .../backend/src/server/api/endpoints/app/create.ts | 2 +- .../src/server/api/endpoints/auth/accept.ts | 2 +- .../src/server/api/endpoints/i/update-email.ts | 4 ++-- .../backend/src/server/api/endpoints/invite.ts | 7 +++--- .../src/server/api/endpoints/miauth/gen-token.ts | 2 +- .../server/api/endpoints/request-reset-password.ts | 6 ++--- 9 files changed, 27 insertions(+), 29 deletions(-) (limited to 'packages/backend/src/server/api') diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index b2bd7d82e7..fc5f3811eb 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -1,5 +1,4 @@ import { Inject, Injectable } from '@nestjs/common'; -import rndstr from 'rndstr'; import bcrypt from 'bcryptjs'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; @@ -16,6 +15,7 @@ import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { bindThis } from '@/decorators.js'; import { SigninService } from './SigninService.js'; import type { FastifyRequest, FastifyReply } from 'fastify'; +import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js'; @Injectable() export class SignupApiService { @@ -67,7 +67,7 @@ export class SignupApiService { const body = request.body; const instance = await this.metaService.fetch(true); - + // Verify *Captcha // ただしテスト時はこの機構は障害となるため無効にする if (process.env.NODE_ENV !== 'test') { @@ -76,7 +76,7 @@ export class SignupApiService { throw new FastifyReplyError(400, err); }); } - + if (instance.enableRecaptcha && instance.recaptchaSecretKey) { await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => { throw new FastifyReplyError(400, err); @@ -89,44 +89,44 @@ export class SignupApiService { }); } } - + const username = body['username']; const password = body['password']; const host: string | null = process.env.NODE_ENV === 'test' ? (body['host'] ?? null) : null; const invitationCode = body['invitationCode']; const emailAddress = body['emailAddress']; - + if (instance.emailRequiredForSignup) { if (emailAddress == null || typeof emailAddress !== 'string') { reply.code(400); return; } - + const res = await this.emailService.validateEmailForAccount(emailAddress); if (!res.available) { reply.code(400); return; } } - + if (instance.disableRegistration) { if (invitationCode == null || typeof invitationCode !== 'string') { reply.code(400); return; } - + const ticket = await this.registrationTicketsRepository.findOneBy({ code: invitationCode, }); - + if (ticket == null) { reply.code(400); return; } - + this.registrationTicketsRepository.delete(ticket.id); } - + if (instance.emailRequiredForSignup) { if (await this.usersRepository.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) { throw new FastifyReplyError(400, 'DUPLICATED_USERNAME'); @@ -142,7 +142,7 @@ export class SignupApiService { throw new FastifyReplyError(400, 'DENIED_USERNAME'); } - const code = rndstr('a-z0-9', 16); + const code = secureRndstr(16, { chars: L_CHARS }); // Generate hash of password const salt = await bcrypt.genSalt(8); @@ -170,12 +170,12 @@ export class SignupApiService { const { account, secret } = await this.signupService.signup({ username, password, host, }); - + const res = await this.userEntityService.pack(account, account, { detail: true, includeSecrets: true, }); - + return { ...res, token: secret, diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index 509224e9c3..2fcf0da3f0 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -1,5 +1,4 @@ import { Inject, Injectable } from '@nestjs/common'; -import rndstr from 'rndstr'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index d263f99f6e..e9c3b0e69f 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -1,9 +1,9 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; -import rndstr from 'rndstr'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; +import { secureRndstr } from '@/misc/secure-rndstr.js'; export const meta = { tags: ['admin'], @@ -54,7 +54,7 @@ export default class extends Endpoint { throw new Error('cannot reset password of root'); } - const passwd = rndstr('a-zA-Z0-9', 8); + const passwd = secureRndstr(8); // Generate hash of password const hash = bcrypt.hashSync(passwd); diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index c1d0a9dd74..aaef02d03f 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -44,7 +44,7 @@ export default class extends Endpoint { ) { super(meta, paramDef, async (ps, me) => { // Generate secret - const secret = secureRndstr(32, true); + const secret = secureRndstr(32); // for backward compatibility const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1'))); diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index 05842460cf..e69f9c12e2 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -55,7 +55,7 @@ export default class extends Endpoint { throw new ApiError(meta.errors.noSuchSession); } - const accessToken = secureRndstr(32, true); + const accessToken = secureRndstr(32); // Fetch exist access token const exist = await this.accessTokensRepository.findOneBy({ diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 4f543a6472..58e056bd37 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -1,5 +1,4 @@ import { Inject, Injectable } from '@nestjs/common'; -import rndstr from 'rndstr'; import ms from 'ms'; import bcrypt from 'bcryptjs'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -9,6 +8,7 @@ import { EmailService } from '@/core/EmailService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -94,7 +94,7 @@ export default class extends Endpoint { this.globalEventService.publishMainStream(me.id, 'meUpdated', iObj); if (ps.email != null) { - const code = rndstr('a-z0-9', 16); + const code = secureRndstr(16, { chars: L_CHARS }); await this.userProfilesRepository.update(me.id, { emailVerifyCode: code, diff --git a/packages/backend/src/server/api/endpoints/invite.ts b/packages/backend/src/server/api/endpoints/invite.ts index 5d2c479e79..276adcb07f 100644 --- a/packages/backend/src/server/api/endpoints/invite.ts +++ b/packages/backend/src/server/api/endpoints/invite.ts @@ -1,9 +1,9 @@ -import rndstr from 'rndstr'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { RegistrationTicketsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; +import { secureRndstr } from '@/misc/secure-rndstr.js'; export const meta = { tags: ['meta'], @@ -42,9 +42,8 @@ export default class extends Endpoint { private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { - const code = rndstr({ - length: 8, - chars: '2-9A-HJ-NP-Z', // [0-9A-Z] w/o [01IO] (32 patterns) + const code = secureRndstr(8, { + chars: '23456789ABCDEFGHJKLMNPQRSTUVWXYZ', // [0-9A-Z] w/o [01IO] (32 patterns) }); await this.registrationTicketsRepository.insert({ diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts index 97def86262..0ea29f04dc 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -49,7 +49,7 @@ export default class extends Endpoint { ) { super(meta, paramDef, async (ps, me) => { // Generate access token - const accessToken = secureRndstr(32, true); + const accessToken = secureRndstr(32); const now = new Date(); diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 3b6ebfe281..284ed8410d 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -1,4 +1,3 @@ -import rndstr from 'rndstr'; import ms from 'ms'; import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; @@ -8,6 +7,7 @@ import { IdService } from '@/core/IdService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { EmailService } from '@/core/EmailService.js'; +import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js'; export const meta = { tags: ['reset password'], @@ -41,7 +41,7 @@ export default class extends Endpoint { constructor( @Inject(DI.config) private config: Config, - + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -77,7 +77,7 @@ export default class extends Endpoint { return; } - const token = rndstr('a-z0-9', 64); + const token = secureRndstr(64, { chars: L_CHARS }); await this.passwordResetRequestsRepository.insert({ id: this.idService.genId(), -- cgit v1.2.3-freya From 8099bc24e181790b6bfd267d013aac0713c68f31 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 25 Jun 2023 21:13:15 +0900 Subject: refactor(backend): use node16 for moduleResolution (#10938) * refactor(backend): use node16 for moduleResolution * update deps * Update tsconfig.json * :v: * revive KEYWORD * restore strict-event-emitter-types dependency * restore ms dependency * cancel redundant import reorder * fix * Delete ms.ts * remove rndstr --------- Co-authored-by: Kagami Sascha Rosylight --- packages/backend/.swcrc | 2 +- packages/backend/package.json | 6 +- packages/backend/src/core/GlobalEventService.ts | 2 +- packages/backend/src/core/LoggerService.ts | 2 +- .../backend/src/core/PushNotificationService.ts | 2 +- packages/backend/src/core/RoleService.ts | 2 +- .../backend/src/core/entities/UserEntityService.ts | 3 +- packages/backend/src/logger.ts | 2 +- .../processors/ImportAntennasProcessorService.ts | 4 +- .../src/server/api/StreamingApiServerService.ts | 2 +- packages/backend/src/server/api/endpoint-base.ts | 4 +- .../src/server/api/endpoints/i/import-antennas.ts | 4 +- .../src/server/api/endpoints/i/import-blocking.ts | 2 +- .../src/server/api/endpoints/i/import-following.ts | 2 +- .../src/server/api/endpoints/i/import-muting.ts | 2 +- .../server/api/endpoints/i/import-user-lists.ts | 2 +- .../src/server/api/endpoints/notes/unrenote.ts | 2 +- .../server/api/endpoints/users/recommendation.ts | 2 +- .../src/server/api/endpoints/users/report-abuse.ts | 2 +- packages/backend/src/server/api/stream/channel.ts | 2 +- packages/backend/src/server/api/stream/types.ts | 4 +- packages/backend/test/tsconfig.json | 6 +- packages/backend/tsconfig.json | 6 +- packages/frontend/package.json | 3 - packages/frontend/test/tsconfig.json | 8 +-- packages/frontend/tsconfig.json | 4 +- packages/misskey-js/tsconfig.json | 4 +- packages/sw/tsconfig.json | 8 +-- pnpm-lock.yaml | 64 ++++++++-------------- 29 files changed, 72 insertions(+), 86 deletions(-) (limited to 'packages/backend/src/server/api') diff --git a/packages/backend/.swcrc b/packages/backend/.swcrc index 08d4222d01..0504a2d389 100644 --- a/packages/backend/.swcrc +++ b/packages/backend/.swcrc @@ -17,7 +17,7 @@ "paths": { "@/*": ["*"] }, - "target": "es2021" + "target": "es2022" }, "minify": false } diff --git a/packages/backend/package.json b/packages/backend/package.json index c13f292c76..6de4e634fd 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -79,7 +79,7 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "bullmq": "4.1.0", - "cacheable-lookup": "6.1.0", + "cacheable-lookup": "7.0.0", "cbor": "9.0.0", "chalk": "5.2.0", "chalk-template": "0.4.0", @@ -95,7 +95,7 @@ "file-type": "18.5.0", "fluent-ffmpeg": "2.1.2", "form-data": "4.0.0", - "got": "12.6.0", + "got": "13.0.0", "happy-dom": "9.20.3", "hpagent": "1.2.0", "ioredis": "5.3.2", @@ -137,7 +137,6 @@ "rxjs": "7.8.1", "s-age": "1.1.2", "sanitize-html": "2.11.0", - "seedrandom": "3.0.5", "semver": "7.5.3", "sharp": "0.32.1", "sharp-read-bmp": "github:misskey-dev/sharp-read-bmp", @@ -178,6 +177,7 @@ "@types/jsonld": "1.5.9", "@types/jsrsasign": "10.5.8", "@types/mime-types": "2.1.1", + "@types/ms": "^0.7.31", "@types/node": "20.3.1", "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.8", diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 0ed5241148..19d9370083 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -20,7 +20,7 @@ import type { Packed } from '@/misc/json-schema.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { bindThis } from '@/decorators.js'; -import { Role } from '@/models'; +import { Role } from '@/models/index.js'; @Injectable() export class GlobalEventService { diff --git a/packages/backend/src/core/LoggerService.ts b/packages/backend/src/core/LoggerService.ts index 441c254f48..14df9aa40c 100644 --- a/packages/backend/src/core/LoggerService.ts +++ b/packages/backend/src/core/LoggerService.ts @@ -3,7 +3,7 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; -import type { KEYWORD } from 'color-convert/conversions'; +import type { KEYWORD } from 'color-convert/conversions.js'; @Injectable() export class LoggerService { diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index 15a1d74878..9ee83df644 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -3,7 +3,7 @@ import push from 'web-push'; import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; -import type { Packed } from '@/misc/json-schema'; +import type { Packed } from '@/misc/json-schema.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; import type { SwSubscription, SwSubscriptionsRepository } from '@/models/index.js'; import { MetaService } from '@/core/MetaService.js'; diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 79922d0a87..23ecf0157d 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -13,7 +13,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { StreamMessages } from '@/server/api/stream/types.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; -import type { Packed } from '@/misc/json-schema'; +import type { Packed } from '@/misc/json-schema.js'; import type { OnApplicationShutdown } from '@nestjs/common'; export type RolePolicies = { diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index bfd506ea86..f1a4e56c02 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, Not } from 'typeorm'; import * as Redis from 'ioredis'; -import Ajv from 'ajv'; +import _Ajv from 'ajv'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; @@ -31,6 +31,7 @@ type IsMeAndIsUserDetailed : Packed<'UserLite'>; +const Ajv = _Ajv.default; const ajv = new Ajv(); function isLocalUser(user: User): user is LocalUser; diff --git a/packages/backend/src/logger.ts b/packages/backend/src/logger.ts index 91039098f1..465b557ce4 100644 --- a/packages/backend/src/logger.ts +++ b/packages/backend/src/logger.ts @@ -4,7 +4,7 @@ import { default as convertColor } from 'color-convert'; import { format as dateFormat } from 'date-fns'; import { bindThis } from '@/decorators.js'; import { envOption } from './env.js'; -import type { KEYWORD } from 'color-convert/conversions'; +import type { KEYWORD } from 'color-convert/conversions.js'; type Context = { name: string; diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts index 575cad69d5..0c09f2796f 100644 --- a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts @@ -1,5 +1,5 @@ import { Injectable, Inject } from '@nestjs/common'; -import Ajv from 'ajv'; +import _Ajv from 'ajv'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import Logger from '@/logger.js'; @@ -10,6 +10,8 @@ import { QueueLoggerService } from '../QueueLoggerService.js'; import { DBAntennaImportJobData } from '../types.js'; import type * as Bull from 'bullmq'; +const Ajv = _Ajv.default; + const validate = new Ajv().compile({ type: 'object', properties: { diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index d1394d6d76..8f2e51d584 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -10,7 +10,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { CacheService } from '@/core/CacheService.js'; -import { LocalUser } from '@/models/entities/User'; +import { LocalUser } from '@/models/entities/User.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; import MainStreamConnection from './stream/index.js'; import { ChannelsService } from './stream/ChannelsService.js'; diff --git a/packages/backend/src/server/api/endpoint-base.ts b/packages/backend/src/server/api/endpoint-base.ts index 1555a3ca46..05141854c7 100644 --- a/packages/backend/src/server/api/endpoint-base.ts +++ b/packages/backend/src/server/api/endpoint-base.ts @@ -1,11 +1,13 @@ import * as fs from 'node:fs'; -import Ajv from 'ajv'; +import _Ajv from 'ajv'; import type { Schema, SchemaType } from '@/misc/json-schema.js'; import type { LocalUser } from '@/models/entities/User.js'; import type { AccessToken } from '@/models/entities/AccessToken.js'; import { ApiError } from './error.js'; import type { IEndpointMeta } from './endpoints.js'; +const Ajv = _Ajv.default; + const ajv = new Ajv({ useDefaults: true, }); diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts index efb5ce4223..12ec5855d3 100644 --- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts +++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts @@ -54,7 +54,7 @@ export default class extends Endpoint { constructor ( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, - + @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, @@ -79,6 +79,6 @@ export default class extends Endpoint { this.queueService.createImportAntennasJob(me, antennas); }); } -} +} export type Antenna = (_Antenna & { userListAccts: string[] | null })[]; diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index 811971591a..32c16300fb 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -72,7 +72,7 @@ export default class extends Endpoint { const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), - true + true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index 8af278c883..1926a1f503 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -71,7 +71,7 @@ export default class extends Endpoint { const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), - true + true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index eb0f9ba474..34f2627563 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -72,7 +72,7 @@ export default class extends Endpoint { const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), - true + true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index 4568e93901..1b3cb5359d 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -71,7 +71,7 @@ export default class extends Endpoint { const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), - true + true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 74e459b426..e9581beedc 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -4,8 +4,8 @@ import type { UsersRepository, NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteDeleteService } from '@/core/NoteDeleteService.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index 5498b8c854..6fcc04e2c5 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -44,7 +44,7 @@ export default class extends Endpoint { @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, - + private userEntityService: UserEntityService, private queryService: QueryService, ) { diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index d19d4007d6..be361e02c4 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -1,4 +1,4 @@ -import * as sanitizeHtml from 'sanitize-html'; +import sanitizeHtml from 'sanitize-html'; import { Inject, Injectable } from '@nestjs/common'; import type { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index e67aec9ecd..94b92e02ef 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -1,5 +1,5 @@ import { bindThis } from '@/decorators.js'; -import type Connection from '.'; +import type Connection from './index.js'; /** * Stream channel diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index d9dba682cd..f239b06637 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -12,7 +12,7 @@ import type { Page } from '@/models/entities/Page.js'; import type { Packed } from '@/misc/json-schema.js'; import type { Webhook } from '@/models/entities/Webhook.js'; import type { Meta } from '@/models/entities/Meta.js'; -import { Role, RoleAssignment } from '@/models'; +import { Role, RoleAssignment } from '@/models/index.js'; import type Emitter from 'strict-event-emitter-types'; import type { EventEmitter } from 'events'; @@ -233,7 +233,7 @@ export type StreamMessages = { // API event definitions // ストリームごとのEmitterの辞書を用意 -type EventEmitterDictionary = { [x in keyof StreamMessages]: Emitter void }> }; +type EventEmitterDictionary = { [x in keyof StreamMessages]: Emitter.default void }> }; // 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; // Emitter辞書から共用体型を作り、UnionToIntersectionで交差型にする diff --git a/packages/backend/test/tsconfig.json b/packages/backend/test/tsconfig.json index 8a024a678b..21afe1aaf3 100644 --- a/packages/backend/test/tsconfig.json +++ b/packages/backend/test/tsconfig.json @@ -9,9 +9,9 @@ "noFallthroughCasesInSwitch": true, "declaration": false, "sourceMap": true, - "target": "es2021", + "target": "ES2022", "module": "es2020", - "moduleResolution": "node", + "moduleResolution": "node16", "allowSyntheticDefaultImports": true, "removeComments": false, "noLib": false, @@ -39,6 +39,6 @@ "include": [ "./**/*.ts", "../src/**/*.test.ts", - "../src/@types/**/*.ts", + "../src/@types/**/*.ts" ] } diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index faadbcdfc6..93944a68d5 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -9,9 +9,9 @@ "noFallthroughCasesInSwitch": true, "declaration": false, "sourceMap": false, - "target": "es2021", - "module": "esnext", - "moduleResolution": "node", + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "node16", "allowSyntheticDefaultImports": true, "removeComments": false, "noLib": false, diff --git a/packages/frontend/package.json b/packages/frontend/package.json index b9208a566e..898002082c 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -54,12 +54,10 @@ "prismjs": "1.29.0", "punycode": "2.3.0", "querystring": "0.2.1", - "rndstr": "1.0.0", "rollup": "3.25.1", "s-age": "1.1.2", "sanitize-html": "2.11.0", "sass": "1.63.6", - "seedrandom": "3.0.5", "strict-event-emitter-types": "2.0.0", "syuilo-password-strength": "0.0.1", "textarea-caret": "3.1.0", @@ -107,7 +105,6 @@ "@types/node": "20.3.1", "@types/punycode": "2.1.0", "@types/sanitize-html": "2.9.0", - "@types/seedrandom": "3.0.5", "@types/testing-library__jest-dom": "^5.14.6", "@types/throttle-debounce": "5.0.0", "@types/tinycolor2": "1.4.3", diff --git a/packages/frontend/test/tsconfig.json b/packages/frontend/test/tsconfig.json index 1424fdbdfb..42372eae7d 100644 --- a/packages/frontend/test/tsconfig.json +++ b/packages/frontend/test/tsconfig.json @@ -9,9 +9,9 @@ "noFallthroughCasesInSwitch": true, "declaration": false, "sourceMap": true, - "target": "es2021", + "target": "ES2022", "module": "es2020", - "moduleResolution": "node", + "moduleResolution": "node16", "allowSyntheticDefaultImports": true, "removeComments": false, "noLib": false, @@ -27,7 +27,7 @@ "@/*": ["../src/*"] }, "typeRoots": [ - "../node_modules/@types", + "../node_modules/@types" ], "lib": [ "esnext", @@ -38,6 +38,6 @@ "compileOnSave": false, "include": [ "./**/*.ts", - "../src/**/*.vue", + "../src/**/*.vue" ] } diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json index e02acb6c5a..1dc5beb1a2 100644 --- a/packages/frontend/tsconfig.json +++ b/packages/frontend/tsconfig.json @@ -9,9 +9,9 @@ "noFallthroughCasesInSwitch": true, "declaration": false, "sourceMap": false, - "target": "es2021", + "target": "ES2022", "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "node16", "removeComments": false, "noLib": false, "strict": true, diff --git a/packages/misskey-js/tsconfig.json b/packages/misskey-js/tsconfig.json index c679347fea..24e5af4c40 100644 --- a/packages/misskey-js/tsconfig.json +++ b/packages/misskey-js/tsconfig.json @@ -1,9 +1,9 @@ { "$schema": "http://json.schemastore.org/tsconfig", "compilerOptions": { - "target": "es2022", + "target": "ES2022", "module": "ES2020", - "moduleResolution": "node", + "moduleResolution": "node16", "declaration": true, "declarationMap": true, "sourceMap": true, diff --git a/packages/sw/tsconfig.json b/packages/sw/tsconfig.json index 750aeff969..96e47db961 100644 --- a/packages/sw/tsconfig.json +++ b/packages/sw/tsconfig.json @@ -9,9 +9,9 @@ "noFallthroughCasesInSwitch": true, "declaration": false, "sourceMap": false, - "target": "es2021", + "target": "ES2022", "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "node16", "removeComments": false, "noLib": false, "strict": true, @@ -21,11 +21,11 @@ "isolatedModules": true, "baseUrl": ".", "paths": { - "@/*": ["./src/*"], + "@/*": ["./src/*"] }, "typeRoots": [ "node_modules/@types", - "@types", + "@types" ], "lib": [ "esnext", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 893e5409b1..be9204ad76 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -153,8 +153,8 @@ importers: specifier: 4.1.0 version: 4.1.0 cacheable-lookup: - specifier: 6.1.0 - version: 6.1.0 + specifier: 7.0.0 + version: 7.0.0 cbor: specifier: 9.0.0 version: 9.0.0 @@ -201,8 +201,8 @@ importers: specifier: 4.0.0 version: 4.0.0 got: - specifier: 12.6.0 - version: 12.6.0 + specifier: 13.0.0 + version: 13.0.0 happy-dom: specifier: 9.20.3 version: 9.20.3 @@ -326,9 +326,6 @@ importers: sanitize-html: specifier: 2.11.0 version: 2.11.0 - seedrandom: - specifier: 3.0.5 - version: 3.0.5 semver: specifier: 7.5.3 version: 7.5.3 @@ -526,6 +523,9 @@ importers: '@types/mime-types': specifier: 2.1.1 version: 2.1.1 + '@types/ms': + specifier: ^0.7.31 + version: 0.7.31 '@types/node': specifier: 20.3.1 version: 20.3.1 @@ -748,9 +748,6 @@ importers: querystring: specifier: 0.2.1 version: 0.2.1 - rndstr: - specifier: 1.0.0 - version: 1.0.0 rollup: specifier: 3.25.1 version: 3.25.1 @@ -763,9 +760,6 @@ importers: sass: specifier: 1.63.6 version: 1.63.6 - seedrandom: - specifier: 3.0.5 - version: 3.0.5 strict-event-emitter-types: specifier: 2.0.0 version: 2.0.0 @@ -902,9 +896,6 @@ importers: '@types/sanitize-html': specifier: 2.9.0 version: 2.9.0 - '@types/seedrandom': - specifier: 3.0.5 - version: 3.0.5 '@types/testing-library__jest-dom': specifier: ^5.14.6 version: 5.14.6 @@ -7879,10 +7870,6 @@ packages: resolution: {integrity: sha512-AnxLHewubLVzoF/A4qdxBGHCKifw8cY32iro3DQX9TPcetE95zBeVt3jnsvtvAUf1vwzMfwzp4t/L2yqPlnjkQ==} dev: false - /@types/seedrandom@3.0.5: - resolution: {integrity: sha512-kopEpYpFQvQdYsZkZVwht/0THHmTFFYXDaqV/lM45eweJ8kcGVDgZHs0RVTolSq55UPZNmjhKc9r7UvLu/mQQg==} - dev: true - /@types/semver@7.5.0: resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} dev: true @@ -9693,11 +9680,6 @@ packages: engines: {node: '>=10.6.0'} dev: false - /cacheable-lookup@6.1.0: - resolution: {integrity: sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==} - engines: {node: '>=10.6.0'} - dev: false - /cacheable-lookup@7.0.0: resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} engines: {node: '>=14.16'} @@ -12914,6 +12896,23 @@ packages: p-cancelable: 3.0.0 responselike: 3.0.0 + /got@13.0.0: + resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==} + engines: {node: '>=16'} + dependencies: + '@sindresorhus/is': 5.3.0 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 10.2.8 + decompress-response: 6.0.0 + form-data-encoder: 2.1.4 + get-stream: 6.0.1 + http2-wrapper: 2.2.0 + lowercase-keys: 3.0.0 + p-cancelable: 3.0.0 + responselike: 3.0.0 + dev: false + /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -17634,10 +17633,6 @@ packages: engines: {node: '>= 0.6'} dev: true - /rangestr@0.0.1: - resolution: {integrity: sha512-9CRCUX/w4+fNMzlYgA8GeJz7BZwBPwaGm3FhAm9Hi50k8wNy2CyiJQa8awygWJay87uVVCV0/FwbLcD6+/A9KQ==} - dev: false - /ratelimiter@3.4.1: resolution: {integrity: sha512-5FJbRW/Jkkdk29ksedAfWFkQkhbUrMx3QJGwMKAypeIiQf4yrLW+gtPKZiaWt4zPrtw1uGufOjGO7UGM6VllsQ==} dev: false @@ -18299,13 +18294,6 @@ packages: dependencies: glob: 7.2.3 - /rndstr@1.0.0: - resolution: {integrity: sha512-3KN+BHTiHcsyW1qjRw3Xhms8TQfTIN4fUVgqqJpj6FnmuCnto5/lLyppSmGfdTmOiKDWeuXU4XPp58I9fsoWFQ==} - dependencies: - rangestr: 0.0.1 - seedrandom: 2.4.2 - dev: false - /rollup@3.25.1: resolution: {integrity: sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} @@ -18425,10 +18413,6 @@ packages: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} dev: false - /seedrandom@2.4.2: - resolution: {integrity: sha512-uQ72txMoObtuJooiBLSVs5Yu2e9d/lHQz0boaqHjW8runXB9vR8nFtaZV54wYii613N0C8ZqTBLsfwDhAdpvqQ==} - dev: false - /seedrandom@3.0.5: resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} dev: false -- cgit v1.2.3-freya From d23ad8b5117a46067464d1b693ae8898d127d5fc Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Mon, 26 Jun 2023 03:09:12 +0200 Subject: fix(backend): APIエラーのHTTP status code変更 (#11047) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/server/api/ApiCallService.ts | 8 +++-- packages/backend/test/e2e/api.ts | 44 ++++++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) (limited to 'packages/backend/src/server/api') diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index dad1a4132a..45fb473763 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -83,7 +83,7 @@ export class ApiCallService implements OnApplicationShutdown { } }).catch(err => { if (err instanceof AuthenticationError) { - this.send(reply, 403, new ApiError({ + this.send(reply, 401, new ApiError({ message: 'Authentication failed. Please ensure your token is correct.', code: 'AUTHENTICATION_FAILED', id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', @@ -137,7 +137,7 @@ export class ApiCallService implements OnApplicationShutdown { } }).catch(err => { if (err instanceof AuthenticationError) { - this.send(reply, 403, new ApiError({ + this.send(reply, 401, new ApiError({ message: 'Authentication failed. Please ensure your token is correct.', code: 'AUTHENTICATION_FAILED', id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', @@ -278,6 +278,7 @@ export class ApiCallService implements OnApplicationShutdown { throw new ApiError({ message: 'You are not assigned to a moderator role.', code: 'ROLE_PERMISSION_DENIED', + kind: 'permission', id: 'd33d5333-db36-423d-a8f9-1a2b9549da41', }); } @@ -285,6 +286,7 @@ export class ApiCallService implements OnApplicationShutdown { throw new ApiError({ message: 'You are not assigned to an administrator role.', code: 'ROLE_PERMISSION_DENIED', + kind: 'permission', id: 'c3d38592-54c0-429d-be96-5636b0431a61', }); } @@ -296,6 +298,7 @@ export class ApiCallService implements OnApplicationShutdown { throw new ApiError({ message: 'You are not assigned to a required role.', code: 'ROLE_PERMISSION_DENIED', + kind: 'permission', id: '7f86f06f-7e15-4057-8561-f4b6d4ac755a', }); } @@ -305,6 +308,7 @@ export class ApiCallService implements OnApplicationShutdown { throw new ApiError({ message: 'Your app does not have the necessary permissions to use this endpoint.', code: 'PERMISSION_DENIED', + kind: 'permission', id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838', }); } diff --git a/packages/backend/test/e2e/api.ts b/packages/backend/test/e2e/api.ts index 194ded7e8b..4b9167b6b1 100644 --- a/packages/backend/test/e2e/api.ts +++ b/packages/backend/test/e2e/api.ts @@ -1,7 +1,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { signup, api, startServer } from '../utils.js'; +import { signup, api, startServer, successfulApiCall, failedApiCall } from '../utils.js'; import type { INestApplicationContext } from '@nestjs/common'; import type * as misskey from 'misskey-js'; @@ -81,4 +81,46 @@ describe('API', () => { assert.strictEqual(res.body.nullableDefault, 'hello'); }); }); + + test('管理者専用のAPIのアクセス制限', async () => { + // aliceは管理者、APIを使える + await successfulApiCall({ + endpoint: '/admin/get-index-stats', + parameters: {}, + user: alice, + }); + + // bobは一般ユーザーだからダメ + await failedApiCall({ + endpoint: '/admin/get-index-stats', + parameters: {}, + user: bob, + }, { + status: 403, + code: 'ROLE_PERMISSION_DENIED', + id: 'c3d38592-54c0-429d-be96-5636b0431a61', + }); + + // publicアクセスももちろんダメ + await failedApiCall({ + endpoint: '/admin/get-index-stats', + parameters: {}, + user: undefined, + }, { + status: 401, + code: 'CREDENTIAL_REQUIRED', + id: '1384574d-a912-4b81-8601-c7b1c4085df1', + }); + + // ごまがしもダメ + await failedApiCall({ + endpoint: '/admin/get-index-stats', + parameters: {}, + user: { token: 'tsukawasete' }, + }, { + status: 401, + code: 'AUTHENTICATION_FAILED', + id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', + }); + }); }); -- cgit v1.2.3-freya From 1b1f82a2e26ddabd8bdf400760a817acbf290157 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Wed, 28 Jun 2023 06:37:13 +0200 Subject: feat(backend): accept OAuth bearer token (#11052) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(backend): accept OAuth bearer token * refactor * Update packages/backend/src/server/api/ApiCallService.ts Co-authored-by: Acid Chicken (硫酸鶏) * Update packages/backend/src/server/api/ApiCallService.ts Co-authored-by: Acid Chicken (硫酸鶏) * fix * kind: permission for account moved error * also for suspended error * Update packages/backend/src/server/api/StreamingApiServerService.ts Co-authored-by: Acid Chicken (硫酸鶏) --------- Co-authored-by: Acid Chicken (硫酸鶏) Co-authored-by: syuilo --- packages/backend/src/server/api/ApiCallService.ts | 81 +++++++++++------- .../src/server/api/StreamingApiServerService.ts | 14 ++- packages/backend/test/e2e/api.ts | 99 +++++++++++++++++++++- packages/backend/test/utils.ts | 57 +++++++++---- packages/misskey-js/etc/misskey-js.api.md | 12 ++- packages/misskey-js/src/api.types.ts | 11 ++- 6 files changed, 222 insertions(+), 52 deletions(-) (limited to 'packages/backend/src/server/api') diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index 45fb473763..09e3724394 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -53,44 +53,72 @@ export class ApiCallService implements OnApplicationShutdown { }, 1000 * 60 * 60); } + #sendApiError(reply: FastifyReply, err: ApiError): void { + let statusCode = err.httpStatusCode; + if (err.httpStatusCode === 401) { + reply.header('WWW-Authenticate', 'Bearer realm="Misskey"'); + } else if (err.kind === 'client') { + reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="invalid_request", error_description="${err.message}"`); + statusCode = statusCode ?? 400; + } else if (err.kind === 'permission') { + // (ROLE_PERMISSION_DENIEDは関係ない) + if (err.code === 'PERMISSION_DENIED') { + reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="insufficient_scope", error_description="${err.message}"`); + } + statusCode = statusCode ?? 403; + } else if (!statusCode) { + statusCode = 500; + } + this.send(reply, statusCode, err); + } + + #sendAuthenticationError(reply: FastifyReply, err: unknown): void { + if (err instanceof AuthenticationError) { + const message = 'Authentication failed. Please ensure your token is correct.'; + reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="invalid_token", error_description="${message}"`); + this.send(reply, 401, new ApiError({ + message: 'Authentication failed. Please ensure your token is correct.', + code: 'AUTHENTICATION_FAILED', + id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', + })); + } else { + this.send(reply, 500, new ApiError()); + } + } + @bindThis public handleRequest( endpoint: IEndpoint & { exec: any }, request: FastifyRequest<{ Body: Record | undefined, Querystring: Record }>, reply: FastifyReply, - ) { + ): void { const body = request.method === 'GET' ? request.query : request.body; - const token = body?.['i']; + // https://datatracker.ietf.org/doc/html/rfc6750.html#section-2.1 (case sensitive) + const token = request.headers.authorization?.startsWith('Bearer ') + ? request.headers.authorization.slice(7) + : body?.['i']; if (token != null && typeof token !== 'string') { reply.code(400); return; } this.authenticateService.authenticate(token).then(([user, app]) => { this.call(endpoint, user, app, body, null, request).then((res) => { - if (request.method === 'GET' && endpoint.meta.cacheSec && !body?.['i'] && !user) { + if (request.method === 'GET' && endpoint.meta.cacheSec && !token && !user) { reply.header('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`); } this.send(reply, res); }).catch((err: ApiError) => { - this.send(reply, err.httpStatusCode ? err.httpStatusCode : err.kind === 'client' ? 400 : err.kind === 'permission' ? 403 : 500, err); + this.#sendApiError(reply, err); }); if (user) { this.logIp(request, user); } }).catch(err => { - if (err instanceof AuthenticationError) { - this.send(reply, 401, new ApiError({ - message: 'Authentication failed. Please ensure your token is correct.', - code: 'AUTHENTICATION_FAILED', - id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', - })); - } else { - this.send(reply, 500, new ApiError()); - } + this.#sendAuthenticationError(reply, err); }); } @@ -99,7 +127,7 @@ export class ApiCallService implements OnApplicationShutdown { endpoint: IEndpoint & { exec: any }, request: FastifyRequest<{ Body: Record, Querystring: Record }>, reply: FastifyReply, - ) { + ): Promise { const multipartData = await request.file().catch(() => { /* Fastify throws if the remote didn't send multipart data. Return 400 below. */ }); @@ -117,7 +145,10 @@ export class ApiCallService implements OnApplicationShutdown { fields[k] = typeof v === 'object' && 'value' in v ? v.value : undefined; } - const token = fields['i']; + // https://datatracker.ietf.org/doc/html/rfc6750.html#section-2.1 (case sensitive) + const token = request.headers.authorization?.startsWith('Bearer ') + ? request.headers.authorization.slice(7) + : fields['i']; if (token != null && typeof token !== 'string') { reply.code(400); return; @@ -129,22 +160,14 @@ export class ApiCallService implements OnApplicationShutdown { }, request).then((res) => { this.send(reply, res); }).catch((err: ApiError) => { - this.send(reply, err.httpStatusCode ? err.httpStatusCode : err.kind === 'client' ? 400 : err.kind === 'permission' ? 403 : 500, err); + this.#sendApiError(reply, err); }); if (user) { this.logIp(request, user); } }).catch(err => { - if (err instanceof AuthenticationError) { - this.send(reply, 401, new ApiError({ - message: 'Authentication failed. Please ensure your token is correct.', - code: 'AUTHENTICATION_FAILED', - id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', - })); - } else { - this.send(reply, 500, new ApiError()); - } + this.#sendAuthenticationError(reply, err); }); } @@ -213,7 +236,7 @@ export class ApiCallService implements OnApplicationShutdown { } if (ep.meta.limit) { - // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. + // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. let limitActor: string; if (user) { limitActor = user.id; @@ -255,8 +278,8 @@ export class ApiCallService implements OnApplicationShutdown { throw new ApiError({ message: 'Your account has been suspended.', code: 'YOUR_ACCOUNT_SUSPENDED', + kind: 'permission', id: 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370', - httpStatusCode: 403, }); } } @@ -266,8 +289,8 @@ export class ApiCallService implements OnApplicationShutdown { throw new ApiError({ message: 'You have moved your account.', code: 'YOUR_ACCOUNT_MOVED', + kind: 'permission', id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31', - httpStatusCode: 403, }); } } @@ -321,7 +344,7 @@ export class ApiCallService implements OnApplicationShutdown { try { data[k] = JSON.parse(data[k]); } catch (e) { - throw new ApiError({ + throw new ApiError({ message: 'Invalid param.', code: 'INVALID_PARAM', id: '0b5f1631-7c1a-41a6-b399-cce335f34d85', diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index 8f2e51d584..4a0342d2b4 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -58,11 +58,21 @@ export class StreamingApiServerService { let user: LocalUser | null = null; let app: AccessToken | null = null; + // https://datatracker.ietf.org/doc/html/rfc6750.html#section-2.1 + // Note that the standard WHATWG WebSocket API does not support setting any headers, + // but non-browser apps may still be able to set it. + const token = request.headers.authorization?.startsWith('Bearer ') + ? request.headers.authorization.slice(7) + : q.get('i'); + try { - [user, app] = await this.authenticateService.authenticate(q.get('i')); + [user, app] = await this.authenticateService.authenticate(token); } catch (e) { if (e instanceof AuthenticationError) { - socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); + socket.write([ + 'HTTP/1.1 401 Unauthorized', + 'WWW-Authenticate: Bearer realm="Misskey", error="invalid_token", error_description="Failed to authenticate"', + ].join('\r\n') + '\r\n\r\n'); } else { socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n'); } diff --git a/packages/backend/test/e2e/api.ts b/packages/backend/test/e2e/api.ts index 4b9167b6b1..c6beec4f88 100644 --- a/packages/backend/test/e2e/api.ts +++ b/packages/backend/test/e2e/api.ts @@ -1,9 +1,10 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { signup, api, startServer, successfulApiCall, failedApiCall } from '../utils.js'; +import { signup, api, startServer, successfulApiCall, failedApiCall, uploadFile, waitFire, connectStream } from '../utils.js'; import type { INestApplicationContext } from '@nestjs/common'; import type * as misskey from 'misskey-js'; +import { IncomingMessage } from 'http'; describe('API', () => { let app: INestApplicationContext; @@ -123,4 +124,100 @@ describe('API', () => { id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', }); }); + + describe('Authentication header', () => { + test('一般リクエスト', async () => { + await successfulApiCall({ + endpoint: '/admin/get-index-stats', + parameters: {}, + user: { + token: alice.token, + bearer: true, + }, + }); + }); + + test('multipartリクエスト', async () => { + const result = await uploadFile({ + token: alice.token, + bearer: true, + }); + assert.strictEqual(result.status, 200); + }); + + test('streaming', async () => { + const fired = await waitFire( + { + token: alice.token, + bearer: true, + }, + 'homeTimeline', + () => api('notes/create', { text: 'foo' }, alice), + msg => msg.type === 'note' && msg.body.text === 'foo', + ); + assert.strictEqual(fired, true); + }); + }); + + describe('tokenエラー応答でWWW-Authenticate headerを送る', () => { + describe('invalid_token', () => { + test('一般リクエスト', async () => { + const result = await api('/admin/get-index-stats', {}, { + token: 'syuilo', + bearer: true, + }); + assert.strictEqual(result.status, 401); + assert.ok(result.headers.get('WWW-Authenticate')?.startsWith('Bearer realm="Misskey", error="invalid_token", error_description')); + }); + + test('multipartリクエスト', async () => { + const result = await uploadFile({ + token: 'syuilo', + bearer: true, + }); + assert.strictEqual(result.status, 401); + assert.ok(result.headers.get('WWW-Authenticate')?.startsWith('Bearer realm="Misskey", error="invalid_token", error_description')); + }); + + test('streaming', async () => { + await assert.rejects(connectStream( + { + token: 'syuilo', + bearer: true, + }, + 'homeTimeline', + () => { }, + ), (err: IncomingMessage) => { + assert.strictEqual(err.statusCode, 401); + assert.ok(err.headers['www-authenticate']?.startsWith('Bearer realm="Misskey", error="invalid_token", error_description')); + return true; + }); + }); + }); + + describe('tokenがないとrealmだけおくる', () => { + test('一般リクエスト', async () => { + const result = await api('/admin/get-index-stats', {}); + assert.strictEqual(result.status, 401); + assert.strictEqual(result.headers.get('WWW-Authenticate'), 'Bearer realm="Misskey"'); + }); + + test('multipartリクエスト', async () => { + const result = await uploadFile(); + assert.strictEqual(result.status, 401); + assert.strictEqual(result.headers.get('WWW-Authenticate'), 'Bearer realm="Misskey"'); + }); + }); + + test('invalid_request', async () => { + const result = await api('/notes/create', { text: true }, { + token: alice.token, + bearer: true, + }); + assert.strictEqual(result.status, 400); + assert.ok(result.headers.get('WWW-Authenticate')?.startsWith('Bearer realm="Misskey", error="invalid_request", error_description')); + }); + + // TODO: insufficient_scope test (authテストが全然なくて書けない) + }); }); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 8583f024cb..48947072e3 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -2,7 +2,7 @@ import * as assert from 'node:assert'; import { readFile } from 'node:fs/promises'; import { isAbsolute, basename } from 'node:path'; import { inspect } from 'node:util'; -import WebSocket from 'ws'; +import WebSocket, { ClientOptions } from 'ws'; import fetch, { Blob, File, RequestInit } from 'node-fetch'; import { DataSource } from 'typeorm'; import { JSDOM } from 'jsdom'; @@ -13,7 +13,10 @@ import type * as misskey from 'misskey-js'; export { server as startServer } from '@/boot/common.js'; -interface UserToken { token: string } +interface UserToken { + token: string; + bearer?: boolean; +} const config = loadConfig(); export const port = config.port; @@ -57,27 +60,33 @@ export const failedApiCall = async (request: ApiRequest, assertion: { return res.body; }; -const request = async (path: string, params: any, me?: UserToken): Promise<{ body: any, status: number }> => { - const auth = me ? { - i: me.token, - } : {}; +const request = async (path: string, params: any, me?: UserToken): Promise<{ status: number, headers: Headers, body: any }> => { + const bodyAuth: Record = {}; + const headers: Record = { + 'Content-Type': 'application/json', + }; + + if (me?.bearer) { + headers.Authorization = `Bearer ${me.token}`; + } else if (me) { + bodyAuth.i = me.token; + } const res = await relativeFetch(path, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(Object.assign(auth, params)), + headers, + body: JSON.stringify(Object.assign(bodyAuth, params)), redirect: 'manual', }); - const status = res.status; const body = res.headers.get('content-type') === 'application/json; charset=utf-8' ? await res.json() : null; return { - body, status, + status: res.status, + headers: res.headers, + body, }; }; @@ -241,7 +250,7 @@ interface UploadOptions { * Upload file * @param user User */ -export const uploadFile = async (user: UserToken, { path, name, blob }: UploadOptions = {}): Promise => { +export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadOptions = {}): Promise<{ status: number, headers: Headers, body: misskey.Endpoints['drive/files/create']['res'] | null }> => { const absPath = path == null ? new URL('resources/Lenna.jpg', import.meta.url) : isAbsolute(path.toString()) @@ -249,7 +258,6 @@ export const uploadFile = async (user: UserToken, { path, name, blob }: UploadOp : new URL(path, new URL('resources/', import.meta.url)); const formData = new FormData(); - formData.append('i', user.token); formData.append('file', blob ?? new File([await readFile(absPath)], basename(absPath.toString()))); formData.append('force', 'true'); @@ -257,15 +265,24 @@ export const uploadFile = async (user: UserToken, { path, name, blob }: UploadOp formData.append('name', name); } + const headers: Record = {}; + if (user?.bearer) { + headers.Authorization = `Bearer ${user.token}`; + } else if (user) { + formData.append('i', user.token); + } + const res = await relativeFetch('api/drive/files/create', { method: 'POST', body: formData, + headers, }); - const body = res.status !== 204 ? await res.json() : null; + const body = res.status !== 204 ? await res.json() as misskey.Endpoints['drive/files/create']['res'] : null; return { status: res.status, + headers: res.headers, body, }; }; @@ -294,8 +311,16 @@ export const uploadUrl = async (user: UserToken, url: string) => { export function connectStream(user: UserToken, channel: string, listener: (message: Record) => any, params?: any): Promise { return new Promise((res, rej) => { - const ws = new WebSocket(`ws://127.0.0.1:${port}/streaming?i=${user.token}`); + const url = new URL(`ws://127.0.0.1:${port}/streaming`); + const options: ClientOptions = {}; + if (user.bearer) { + options.headers = { Authorization: `Bearer ${user.token}` }; + } else { + url.searchParams.set('i', user.token); + } + const ws = new WebSocket(url, options); + ws.on('unexpected-response', (req, res) => rej(res)); ws.on('open', () => { ws.on('message', data => { const msg = JSON.parse(data.toString()); diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 5f292148ae..c9b3fd6056 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -960,8 +960,14 @@ export type Endpoints = { res: TODO; }; 'drive/files/create': { - req: TODO; - res: TODO; + req: { + folderId?: string; + name?: string; + comment?: string; + isSentisive?: boolean; + force?: boolean; + }; + res: DriveFile; }; 'drive/files/delete': { req: { @@ -2750,7 +2756,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u // // src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts // src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts -// src/api.types.ts:611:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts +// src/api.types.ts:620:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts index 293e0043b7..93f327e67e 100644 --- a/packages/misskey-js/src/api.types.ts +++ b/packages/misskey-js/src/api.types.ts @@ -262,7 +262,16 @@ export type Endpoints = { 'drive/files': { req: { folderId?: DriveFolder['id'] | null; type?: DriveFile['type'] | null; limit?: number; sinceId?: DriveFile['id']; untilId?: DriveFile['id']; }; res: DriveFile[]; }; 'drive/files/attached-notes': { req: TODO; res: TODO; }; 'drive/files/check-existence': { req: TODO; res: TODO; }; - 'drive/files/create': { req: TODO; res: TODO; }; + 'drive/files/create': { + req: { + folderId?: string, + name?: string, + comment?: string, + isSentisive?: boolean, + force?: boolean, + }; + res: DriveFile; + }; 'drive/files/delete': { req: { fileId: DriveFile['id']; }; res: null; }; 'drive/files/find-by-hash': { req: TODO; res: TODO; }; 'drive/files/find': { req: { name: string; folderId?: DriveFolder['id'] | null; }; res: DriveFile[]; }; -- cgit v1.2.3-freya From a1327fa9e1329f2fb00d70b1e2332cea015bfdee Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 2 Jul 2023 08:28:26 +0900 Subject: perf(backend): allow get for some endpoints --- packages/backend/src/server/api/endpoints/get-online-users-count.ts | 2 ++ packages/backend/src/server/api/endpoints/hashtags/trend.ts | 2 ++ packages/frontend/src/pages/admin/overview.stats.vue | 2 +- packages/frontend/src/widgets/WidgetOnlineUsers.vue | 2 +- packages/frontend/src/widgets/WidgetTrends.vue | 2 +- 5 files changed, 7 insertions(+), 3 deletions(-) (limited to 'packages/backend/src/server/api') diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index dea0f4799c..810bde03e8 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -9,6 +9,8 @@ export const meta = { tags: ['meta'], requireCredential: false, + allowGet: true, + cacheSec: 60 * 1, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts index cf45cc6c24..ce1cd9f01f 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts @@ -26,6 +26,8 @@ export const meta = { tags: ['hashtags'], requireCredential: false, + allowGet: true, + cacheSec: 60 * 1, res: { type: 'array', diff --git a/packages/frontend/src/pages/admin/overview.stats.vue b/packages/frontend/src/pages/admin/overview.stats.vue index 142e70c698..f746ad14b9 100644 --- a/packages/frontend/src/pages/admin/overview.stats.vue +++ b/packages/frontend/src/pages/admin/overview.stats.vue @@ -73,7 +73,7 @@ let fetching = $ref(true); onMounted(async () => { const [_stats, _onlineUsersCount] = await Promise.all([ os.api('stats', {}), - os.api('get-online-users-count').then(res => res.count), + os.apiGet('get-online-users-count').then(res => res.count), ]); stats = _stats; onlineUsersCount = _onlineUsersCount; diff --git a/packages/frontend/src/widgets/WidgetOnlineUsers.vue b/packages/frontend/src/widgets/WidgetOnlineUsers.vue index c920c3ca53..7e01eba487 100644 --- a/packages/frontend/src/widgets/WidgetOnlineUsers.vue +++ b/packages/frontend/src/widgets/WidgetOnlineUsers.vue @@ -40,7 +40,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name, const onlineUsersCount = ref(0); const tick = () => { - os.api('get-online-users-count').then(res => { + os.apiGet('get-online-users-count').then(res => { onlineUsersCount.value = res.count; }); }; diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue index 36f908d5ea..5fce4aedca 100644 --- a/packages/frontend/src/widgets/WidgetTrends.vue +++ b/packages/frontend/src/widgets/WidgetTrends.vue @@ -53,7 +53,7 @@ const stats = ref([]); const fetching = ref(true); const fetch = () => { - os.api('hashtags/trend').then(res => { + os.apiGet('hashtags/trend').then(res => { stats.value = res; fetching.value = false; }); -- cgit v1.2.3-freya From af3258dc79488b73435819e7799eb1515f15a6aa Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 2 Jul 2023 16:02:32 +0900 Subject: perf(backend): make some features optionable Resolve #11064 Resolve #11065 --- CHANGELOG.md | 4 ++++ locales/index.d.ts | 2 ++ locales/ja-JP.yml | 2 ++ packages/backend/assets/avatar.png | Bin 0 -> 13477 bytes .../migration/1688280713783-add-meta-options.js | 13 +++++++++++++ packages/backend/src/daemons/ServerStatsService.ts | 12 +++++++++--- packages/backend/src/models/entities/Meta.ts | 10 ++++++++++ packages/backend/src/server/ServerService.ts | 15 +++++++++++---- .../backend/src/server/api/endpoints/admin/meta.ts | 10 ++++++++++ .../src/server/api/endpoints/admin/update-meta.ts | 10 ++++++++++ .../backend/src/server/api/endpoints/server-info.ts | 19 +++++++++++++++++++ packages/frontend/src/pages/admin/other-settings.vue | 14 ++++++++++++++ .../frontend/src/widgets/server-metric/index.vue | 2 +- 13 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 packages/backend/assets/avatar.png create mode 100644 packages/backend/migration/1688280713783-add-meta-options.js (limited to 'packages/backend/src/server/api') diff --git a/CHANGELOG.md b/CHANGELOG.md index 681105fb7e..72c4f53d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ ## 13.x.x (unreleased) +### General +- identicon生成を無効にしてパフォーマンスを向上させることができるようになりました +- サーバーのマシン情報の公開を無効にしてパフォーマンスを向上させることができるようになりました + ### Client - Fix: サーバーメトリクスが90度傾いている diff --git a/locales/index.d.ts b/locales/index.d.ts index eed29f408c..af6b803278 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1066,6 +1066,8 @@ export interface Locale { "additionalEmojiDictionary": string; "installed": string; "branding": string; + "enableServerMachineStats": string; + "enableIdenticonGeneration": string; "_initialAccountSetting": { "accountCreated": string; "letsStartAccountSetup": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8004e53575..e7202bfbb5 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1063,6 +1063,8 @@ goToMisskey: "Misskeyへ" additionalEmojiDictionary: "絵文字の追加辞書" installed: "インストール済み" branding: "ブランディング" +enableServerMachineStats: "サーバーのマシン情報を公開する" +enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする" _initialAccountSetting: accountCreated: "アカウントの作成が完了しました!" diff --git a/packages/backend/assets/avatar.png b/packages/backend/assets/avatar.png new file mode 100644 index 0000000000..1b95a0c560 Binary files /dev/null and b/packages/backend/assets/avatar.png differ diff --git a/packages/backend/migration/1688280713783-add-meta-options.js b/packages/backend/migration/1688280713783-add-meta-options.js new file mode 100644 index 0000000000..12406fe085 --- /dev/null +++ b/packages/backend/migration/1688280713783-add-meta-options.js @@ -0,0 +1,13 @@ +export class AddMetaOptions1688280713783 { + name = 'AddMetaOptions1688280713783' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "enableServerMachineStats" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "meta" ADD "enableIdenticonGeneration" boolean NOT NULL DEFAULT true`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableIdenticonGeneration"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableServerMachineStats"`); + } +} diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index 6cd71c0e2a..375fd5e516 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -3,6 +3,7 @@ import si from 'systeminformation'; import Xev from 'xev'; import * as osUtils from 'os-utils'; import { bindThis } from '@/decorators.js'; +import { MetaService } from '@/core/MetaService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; const ev = new Xev(); @@ -14,9 +15,10 @@ const round = (num: number) => Math.round(num * 10) / 10; @Injectable() export class ServerStatsService implements OnApplicationShutdown { - private intervalId: NodeJS.Timer; + private intervalId: NodeJS.Timer | null = null; constructor( + private metaService: MetaService, ) { } @@ -24,7 +26,9 @@ export class ServerStatsService implements OnApplicationShutdown { * Report server stats regularly */ @bindThis - public start(): void { + public async start(): Promise { + if (!(await this.metaService.fetch(true)).enableServerMachineStats) return; + const log = [] as any[]; ev.on('requestServerStatsLog', x => { @@ -64,7 +68,9 @@ export class ServerStatsService implements OnApplicationShutdown { @bindThis public dispose(): void { - clearInterval(this.intervalId); + if (this.intervalId) { + clearInterval(this.intervalId); + } } @bindThis diff --git a/packages/backend/src/models/entities/Meta.ts b/packages/backend/src/models/entities/Meta.ts index f799551f30..a251c0b31c 100644 --- a/packages/backend/src/models/entities/Meta.ts +++ b/packages/backend/src/models/entities/Meta.ts @@ -413,6 +413,16 @@ export class Meta { }) public enableChartsForFederatedInstances: boolean; + @Column('boolean', { + default: false, + }) + public enableServerMachineStats: boolean; + + @Column('boolean', { + default: true, + }) + public enableIdenticonGeneration: boolean; + @Column('jsonb', { default: { }, }) diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index c3d45e4ad6..1bae71617b 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -16,6 +16,7 @@ import { createTemp } from '@/misc/create-temp.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; +import { MetaService } from '@/core/MetaService.js'; import { ActivityPubServerService } from './ActivityPubServerService.js'; import { NodeinfoServerService } from './NodeinfoServerService.js'; import { ApiServerService } from './api/ApiServerService.js'; @@ -45,6 +46,7 @@ export class ServerService implements OnApplicationShutdown { @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, + private metaService: MetaService, private userEntityService: UserEntityService, private apiServerService: ApiServerService, private openApiServerService: OpenApiServerService, @@ -161,11 +163,16 @@ export class ServerService implements OnApplicationShutdown { }); fastify.get<{ Params: { x: string } }>('/identicon/:x', async (request, reply) => { - const [temp, cleanup] = await createTemp(); - await genIdenticon(request.params.x, fs.createWriteStream(temp)); reply.header('Content-Type', 'image/png'); reply.header('Cache-Control', 'public, max-age=86400'); - return fs.createReadStream(temp).on('close', () => cleanup()); + + if ((await this.metaService.fetch()).enableIdenticonGeneration) { + const [temp, cleanup] = await createTemp(); + await genIdenticon(request.params.x, fs.createWriteStream(temp)); + return fs.createReadStream(temp).on('close', () => cleanup()); + } else { + return reply.redirect('/static-assets/avatar.png'); + } }); fastify.get<{ Params: { code: string } }>('/verify-email/:code', async (request, reply) => { @@ -224,7 +231,7 @@ export class ServerService implements OnApplicationShutdown { @bindThis public async dispose(): Promise { - await this.streamingApiServerService.detach(); + await this.streamingApiServerService.detach(); await this.#fastify.close(); } diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 4cc1b6011f..28aec7a090 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -262,6 +262,14 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + enableServerMachineStats: { + type: 'boolean', + optional: false, nullable: false, + }, + enableIdenticonGeneration: { + type: 'boolean', + optional: false, nullable: false, + }, policies: { type: 'object', optional: false, nullable: false, @@ -364,6 +372,8 @@ export default class extends Endpoint { enableActiveEmailValidation: instance.enableActiveEmailValidation, enableChartsForRemoteUser: instance.enableChartsForRemoteUser, enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances, + enableServerMachineStats: instance.enableServerMachineStats, + enableIdenticonGeneration: instance.enableIdenticonGeneration, policies: { ...DEFAULT_POLICIES, ...instance.policies }, }; }); diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 1de5e9efd3..5c9d8e3fac 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -96,6 +96,8 @@ export const paramDef = { enableActiveEmailValidation: { type: 'boolean' }, enableChartsForRemoteUser: { type: 'boolean' }, enableChartsForFederatedInstances: { type: 'boolean' }, + enableServerMachineStats: { type: 'boolean' }, + enableIdenticonGeneration: { type: 'boolean' }, serverRules: { type: 'array', items: { type: 'string' } }, preservedUsernames: { type: 'array', items: { type: 'string' } }, }, @@ -399,6 +401,14 @@ export default class extends Endpoint { set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances; } + if (ps.enableServerMachineStats !== undefined) { + set.enableServerMachineStats = ps.enableServerMachineStats; + } + + if (ps.enableIdenticonGeneration !== undefined) { + set.enableIdenticonGeneration = ps.enableIdenticonGeneration; + } + if (ps.serverRules !== undefined) { set.serverRules = ps.serverRules; } diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index 1620e8ae52..552441e430 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -2,9 +2,12 @@ import * as os from 'node:os'; import si from 'systeminformation'; import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MetaService } from '@/core/MetaService.js'; export const meta = { requireCredential: false, + allowGet: true, + cacheSec: 60 * 1, tags: ['meta'], } as const; @@ -19,8 +22,24 @@ export const paramDef = { @Injectable() export default class extends Endpoint { constructor( + private metaService: MetaService, ) { super(meta, paramDef, async () => { + if (!(await this.metaService.fetch()).enableServerMachineStats) return { + machine: '?', + cpu: { + model: '?', + cores: 0, + }, + mem: { + total: 0, + }, + fs: { + total: 0, + used: 0, + }, + }; + const memStats = await si.mem(); const fsStats = await si.fsSize(); diff --git a/packages/frontend/src/pages/admin/other-settings.vue b/packages/frontend/src/pages/admin/other-settings.vue index 15d720a070..d3e1d9efc1 100644 --- a/packages/frontend/src/pages/admin/other-settings.vue +++ b/packages/frontend/src/pages/admin/other-settings.vue @@ -4,6 +4,14 @@
+ + + + + + + + @@ -27,17 +35,23 @@ import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import MkSwitch from '@/components/MkSwitch.vue'; +let enableServerMachineStats: boolean = $ref(false); +let enableIdenticonGeneration: boolean = $ref(false); let enableChartsForRemoteUser: boolean = $ref(false); let enableChartsForFederatedInstances: boolean = $ref(false); async function init() { const meta = await os.api('admin/meta'); + enableServerMachineStats = meta.enableServerMachineStats; + enableIdenticonGeneration = meta.enableIdenticonGeneration; enableChartsForRemoteUser = meta.enableChartsForRemoteUser; enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances; } function save() { os.apiWithDialog('admin/update-meta', { + enableServerMachineStats, + enableIdenticonGeneration, enableChartsForRemoteUser, enableChartsForFederatedInstances, }).then(() => { diff --git a/packages/frontend/src/widgets/server-metric/index.vue b/packages/frontend/src/widgets/server-metric/index.vue index e019ff540b..1a78f4bcd6 100644 --- a/packages/frontend/src/widgets/server-metric/index.vue +++ b/packages/frontend/src/widgets/server-metric/index.vue @@ -62,7 +62,7 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name, const meta = ref(null); -os.api('server-info', {}).then(res => { +os.apiGet('server-info', {}).then(res => { meta.value = res; }); -- cgit v1.2.3-freya From 61e7eb8ff1d7ef222e60b687090cca53a182efc3 Mon Sep 17 00:00:00 2001 From: riku6460 <17585784+riku6460@users.noreply.github.com> Date: Tue, 4 Jul 2023 07:49:13 +0900 Subject: perf(backend): JSON.parse の呼び出しを削減する (#11091) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf(backend): JSON.parse の呼び出しを削減する Co-authored-by: Hidekazu Kobayashi * Update CHANGELOG.md --------- Co-authored-by: Hidekazu Kobayashi --- CHANGELOG.md | 3 +++ .../backend/src/server/api/StreamingApiServerService.ts | 16 +++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) (limited to 'packages/backend/src/server/api') diff --git a/CHANGELOG.md b/CHANGELOG.md index e7f900509b..c8fd80063d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ - Fix: サーバーメトリクスが90度傾いている - Fix: sparkle内にリンクを入れるとクリック不能になる問題の修正 +### Server +- JSON.parse の回数を削減することで、ストリーミングのパフォーマンスを向上しました + ## 13.13.2 ### General diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index 4a0342d2b4..e4291becf0 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -103,6 +103,13 @@ export class StreamingApiServerService { }); }); + const globalEv = new EventEmitter(); + + this.redisForSub.on('message', (_: string, data: string) => { + const parsed = JSON.parse(data); + globalEv.emit('message', parsed); + }); + this.#wss.on('connection', async (connection: WebSocket.WebSocket, request: http.IncomingMessage, ctx: { stream: MainStreamConnection, user: LocalUser | null; @@ -112,12 +119,11 @@ export class StreamingApiServerService { const ev = new EventEmitter(); - async function onRedisMessage(_: string, data: string): Promise { - const parsed = JSON.parse(data); - ev.emit(parsed.channel, parsed.message); + function onRedisMessage(data: any): void { + ev.emit(data.channel, data.message); } - this.redisForSub.on('message', onRedisMessage); + globalEv.on('message', onRedisMessage); await stream.listen(ev, connection); @@ -137,7 +143,7 @@ export class StreamingApiServerService { connection.once('close', () => { ev.removeAllListeners(); stream.dispose(); - this.redisForSub.off('message', onRedisMessage); + globalEv.off('message', onRedisMessage); this.#connections.delete(connection); if (userUpdateIntervalId) clearInterval(userUpdateIntervalId); }); -- cgit v1.2.3-freya From d84796588c1472334ddaf696a817f015c245ce44 Mon Sep 17 00:00:00 2001 From: okayurisotto Date: Sat, 8 Jul 2023 07:08:16 +0900 Subject: cleanup: trim trailing whitespace (#11136) * cleanup: trim trailing whitespace * update(`.editorconfig`) --------- Co-authored-by: syuilo --- .devcontainer/docker-compose.yml | 2 +- .editorconfig | 4 + CONTRIBUTING.md | 4 +- README.md | 6 +- assets/title_float.svg | 4 +- cypress/e2e/basic.cy.js | 2 +- packages/backend/src/core/AccountMoveService.ts | 2 +- packages/backend/src/core/AiService.ts | 8 +- packages/backend/src/core/AntennaService.ts | 30 +++--- packages/backend/src/core/CaptchaService.ts | 12 +-- .../backend/src/core/CreateSystemUserService.ts | 22 ++--- packages/backend/src/core/CustomEmojiService.ts | 6 +- packages/backend/src/core/DeleteAccountService.ts | 4 +- packages/backend/src/core/EmailService.ts | 18 ++-- .../backend/src/core/FederatedInstanceService.ts | 10 +- .../src/core/FetchInstanceMetadataService.ts | 92 +++++++++--------- packages/backend/src/core/FileInfoService.ts | 16 +-- packages/backend/src/core/HttpRequestService.ts | 8 +- packages/backend/src/core/IdService.ts | 2 +- packages/backend/src/core/InstanceActorService.ts | 4 +- packages/backend/src/core/MetaService.ts | 8 +- packages/backend/src/core/MfmService.ts | 108 ++++++++++----------- packages/backend/src/core/NoteCreateService.ts | 4 +- packages/backend/src/core/NoteDeleteService.ts | 2 +- packages/backend/src/core/NoteReadService.ts | 4 +- packages/backend/src/core/NotificationService.ts | 2 +- packages/backend/src/core/PollService.ts | 20 ++-- .../backend/src/core/PushNotificationService.ts | 12 +-- packages/backend/src/core/QueryService.ts | 40 ++++---- packages/backend/src/core/RelayService.ts | 28 +++--- .../backend/src/core/RemoteUserResolveService.ts | 30 +++--- packages/backend/src/core/RoleService.ts | 2 +- packages/backend/src/core/SignupService.ts | 30 +++--- .../src/core/TwoFactorAuthenticationService.ts | 86 ++++++++-------- packages/backend/src/core/UserSuspendService.ts | 24 ++--- .../backend/src/core/VideoProcessingService.ts | 2 +- packages/backend/src/core/WebhookService.ts | 2 +- .../src/core/activitypub/ApAudienceService.ts | 24 ++--- .../src/core/activitypub/ApDbResolverService.ts | 4 +- .../core/activitypub/ApDeliverManagerService.ts | 2 +- .../backend/src/core/activitypub/ApMfmService.ts | 4 +- .../src/core/activitypub/models/ApImageService.ts | 2 +- .../core/activitypub/models/ApMentionService.ts | 4 +- .../src/core/activitypub/models/ApNoteService.ts | 102 +++++++++---------- .../src/core/activitypub/models/ApPersonService.ts | 4 +- .../src/core/entities/DriveFileEntityService.ts | 2 +- .../backend/src/core/entities/NoteEntityService.ts | 10 +- .../src/core/entities/NoteReactionEntityService.ts | 2 +- .../src/core/entities/NotificationEntityService.ts | 2 +- .../backend/src/core/entities/UserEntityService.ts | 2 +- packages/backend/src/daemons/QueueStatsService.ts | 2 +- packages/backend/src/misc/json-schema.ts | 2 +- packages/backend/src/misc/prelude/url.ts | 2 +- .../backend/src/models/entities/UserProfile.ts | 2 +- .../backend/src/queue/QueueProcessorService.ts | 2 +- .../processors/ExportAntennasProcessorService.ts | 2 +- .../processors/ImportAntennasProcessorService.ts | 6 +- .../ImportCustomEmojisProcessorService.ts | 2 +- .../processors/WebhookDeliverProcessorService.ts | 10 +- .../backend/src/server/api/AuthenticateService.ts | 16 +-- .../backend/src/server/api/RateLimiterService.ts | 20 ++-- packages/backend/src/server/api/SigninService.ts | 2 +- packages/backend/src/server/api/endpoint-base.ts | 10 +- .../api/endpoints/admin/announcements/update.ts | 2 +- .../src/server/api/endpoints/admin/emoji/list.ts | 2 +- .../src/server/api/endpoints/admin/emoji/update.ts | 2 +- .../server/api/endpoints/admin/queue/promote.ts | 2 +- .../src/server/api/endpoints/admin/update-meta.ts | 2 +- .../src/server/api/endpoints/channels/timeline.ts | 2 +- .../src/server/api/endpoints/drive/files/update.ts | 2 +- packages/backend/src/server/api/endpoints/emoji.ts | 2 +- .../backend/src/server/api/endpoints/emojis.ts | 2 +- .../src/server/api/endpoints/hashtags/users.ts | 2 +- packages/backend/src/server/api/endpoints/i.ts | 2 +- .../src/server/api/endpoints/i/2fa/update-key.ts | 2 +- packages/backend/src/server/api/endpoints/meta.ts | 2 +- packages/backend/src/server/api/endpoints/notes.ts | 16 +-- .../src/server/api/endpoints/notes/search.ts | 4 +- .../src/server/api/endpoints/notes/translate.ts | 2 +- .../src/server/api/endpoints/roles/notes.ts | 2 +- .../endpoints/users/lists/create-from-public.ts | 4 +- .../server/api/endpoints/users/lists/favorite.ts | 2 +- .../src/server/api/endpoints/users/search.ts | 2 +- .../src/server/api/stream/ChannelsService.ts | 2 +- .../src/server/api/stream/channels/hashtag.ts | 2 +- .../server/api/stream/channels/home-timeline.ts | 2 +- .../server/api/stream/channels/role-timeline.ts | 2 +- .../src/server/api/stream/channels/user-list.ts | 2 +- packages/backend/src/server/web/FeedService.ts | 12 +-- packages/backend/src/server/web/bios.js | 6 +- packages/backend/src/server/web/cli.js | 6 +- packages/backend/src/server/web/views/base.pug | 4 +- packages/backend/src/server/web/views/error.pug | 4 +- packages/backend/src/server/web/views/note.pug | 2 +- packages/backend/test/prelude/get-api-validator.ts | 2 +- packages/backend/test/unit/DriveService.ts | 2 +- packages/backend/test/unit/FileInfoService.ts | 22 ++--- packages/backend/test/unit/RelayService.ts | 8 +- packages/backend/test/unit/chart.ts | 12 +-- packages/frontend/src/boot/main-boot.ts | 2 +- .../frontend/src/components/MkDrive.folder.vue | 6 +- .../frontend/src/components/MkDrive.navFolder.vue | 6 +- packages/frontend/src/components/MkDrive.vue | 6 +- .../frontend/src/components/MkFileListForAdmin.vue | 2 +- .../frontend/src/components/MkFlashPreview.vue | 2 +- packages/frontend/src/components/MkMediaVideo.vue | 4 +- .../frontend/src/components/MkNoteDetailed.vue | 2 +- packages/frontend/src/components/MkSuperMenu.vue | 2 +- packages/frontend/src/components/MkUserPopup.vue | 2 +- .../MkUserSetupDialog.Follow.stories.impl.ts | 2 +- .../MkUserSetupDialog.Privacy.stories.impl.ts | 2 +- .../MkUserSetupDialog.Profile.stories.impl.ts | 2 +- .../src/components/MkUserSetupDialog.User.vue | 2 +- .../components/MkUserSetupDialog.stories.impl.ts | 2 +- .../components/global/MkMisskeyFlavoredMarkdown.ts | 2 +- packages/frontend/src/directives/adaptive-bg.ts | 2 +- .../frontend/src/directives/adaptive-border.ts | 2 +- packages/frontend/src/directives/panel.ts | 2 +- packages/frontend/src/filters/date.ts | 2 +- packages/frontend/src/local-storage.ts | 2 +- packages/frontend/src/nirax.ts | 6 +- packages/frontend/src/pages/about.emojis.vue | 4 +- packages/frontend/src/pages/admin/index.vue | 2 +- packages/frontend/src/pages/admin/moderation.vue | 2 +- .../src/pages/admin/overview.ap-requests.vue | 2 +- .../src/pages/admin/overview.federation.vue | 2 +- .../frontend/src/pages/admin/overview.queue.vue | 2 +- packages/frontend/src/pages/admin/overview.vue | 2 +- packages/frontend/src/pages/admin/roles.editor.vue | 2 +- packages/frontend/src/pages/channel-editor.vue | 6 +- packages/frontend/src/pages/clip.vue | 2 +- .../frontend/src/pages/custom-emojis-manager.vue | 4 +- packages/frontend/src/pages/follow.vue | 2 +- packages/frontend/src/pages/instance-info.vue | 2 +- packages/frontend/src/pages/list.vue | 2 +- packages/frontend/src/pages/registry.keys.vue | 2 +- packages/frontend/src/pages/registry.value.vue | 2 +- packages/frontend/src/pages/reset-password.vue | 2 +- .../frontend/src/pages/settings/drive-cleaner.vue | 2 +- packages/frontend/src/pages/settings/navbar.vue | 2 +- .../src/pages/settings/preferences-backups.vue | 4 +- packages/frontend/src/pages/settings/privacy.vue | 4 +- packages/frontend/src/pages/settings/roles.vue | 2 +- packages/frontend/src/pages/settings/security.vue | 2 +- packages/frontend/src/pizzax.ts | 10 +- packages/frontend/src/scripts/aiscript/ui.ts | 2 +- packages/frontend/src/scripts/get-user-menu.ts | 2 +- packages/frontend/src/scripts/lookup.ts | 2 +- packages/frontend/src/scripts/url.ts | 2 +- packages/frontend/src/scripts/use-note-capture.ts | 4 +- packages/frontend/src/style.scss | 4 +- packages/frontend/src/themes/l-botanical.json5 | 2 +- packages/frontend/src/ui/classic.header.vue | 4 +- packages/frontend/src/ui/classic.vue | 2 +- .../frontend/src/widgets/WidgetAiscriptApp.vue | 2 +- packages/frontend/src/widgets/WidgetClicker.vue | 2 +- .../frontend/src/widgets/WidgetNotifications.vue | 2 +- packages/misskey-js/src/streaming.ts | 2 +- packages/misskey-js/test-d/streaming.ts | 2 +- packages/misskey-js/test/api.ts | 8 +- packages/misskey-js/test/streaming.ts | 2 +- 161 files changed, 615 insertions(+), 609 deletions(-) (limited to 'packages/backend/src/server/api') diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 8f8c5a13ab..2809cd2ca4 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.8' services: app: - build: + build: context: . dockerfile: Dockerfile diff --git a/.editorconfig b/.editorconfig index a6f988f8d7..def7baa1a8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,6 +6,10 @@ indent_size = 2 charset = utf-8 insert_final_newline = true end_of_line = lf +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false [*.{yml,yaml}] indent_style = space diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6b3804f84..896fb6b089 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -106,7 +106,7 @@ If your language is not listed in Crowdin, please open an issue. ![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg) ## Development -During development, it is useful to use the +During development, it is useful to use the ``` pnpm dev @@ -150,7 +150,7 @@ Prepare DB/Redis for testing. ``` docker compose -f packages/backend/test/docker-compose.yml up ``` -Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`. +Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`. Run all test. ``` diff --git a/README.md b/README.md index 2aae4bb865..ab4388c2eb 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Misskey logo - + **🌎 **[Misskey](https://misskey-hub.net/)** is an open source, decentralized social media platform that's free forever! 🚀** - + --- @@ -21,7 +21,7 @@ become a patron - + --- [![codecov](https://codecov.io/gh/misskey-dev/misskey/branch/develop/graph/badge.svg?token=R6IQZ3QJOL)](https://codecov.io/gh/misskey-dev/misskey) diff --git a/assets/title_float.svg b/assets/title_float.svg index 43205ac1c4..ed1749e321 100644 --- a/assets/title_float.svg +++ b/assets/title_float.svg @@ -23,13 +23,13 @@ + diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue index e8295c81b5..838c197f05 100644 --- a/packages/frontend/src/pages/admin/overview.vue +++ b/packages/frontend/src/pages/admin/overview.vue @@ -30,7 +30,7 @@ - + diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index a1fa9d2932..02a2d4366a 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -210,7 +210,7 @@
- +