diff --git a/locales/index.d.ts b/locales/index.d.ts index 00a8c60b3..8b0735424 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5516,6 +5516,14 @@ export interface Locale extends ILocale { * サーバー情報ページに表示するユーザーのIDを一行に一つずつ書きます。 */ "endingCreditMembersDescription": string; + /** + * メールアドレスでログイン + */ + "emailAddressLogin": string; + /** + * ユーザー名でログイン + */ + "usernameLogin": string; "_bubbleGame": { /** * 遊び方 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index ee1d4e017..a88c96048 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1372,6 +1372,8 @@ muteNotification: "通知をミュートする" unmuteNotification: "通知をミュート解除する" endingCreditMembers: "スタッフロールに表示するユーザー" endingCreditMembersDescription: "サーバー情報ページに表示するユーザーのIDを一行に一つずつ書きます。" +emailAddressLogin: "メールアドレスでログイン" +usernameLogin: "ユーザー名でログイン" _bubbleGame: howToPlay: "遊び方" diff --git a/package.json b/package.json index 21e625631..34410e2d9 100644 --- a/package.json +++ b/package.json @@ -48,11 +48,11 @@ "resolutions": { "@tensorflow/tfjs-core": "4.22.0", "axios": "1.7.9", - "chokidar": "4.0.2", + "chokidar": "4.0.3", "cookie": "1.0.2", "cookie-signature": "1.2.2", "debug": "4.4.0", - "esbuild": "0.24.0", + "esbuild": "0.24.2", "jpeg-js": "0.4.4", "lodash": "4.17.21", "punycode": "npm:punycode.js@2.3.1", diff --git a/packages/backend/package.json b/packages/backend/package.json index f4667fa4a..176a6b7f1 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -63,8 +63,8 @@ }, "dependencies": { "@authenio/samlify-node-xmllint": "2.0.0", - "@aws-sdk/client-s3": "3.714.0", - "@aws-sdk/lib-storage": "3.714.0", + "@aws-sdk/client-s3": "3.717.0", + "@aws-sdk/lib-storage": "3.717.0", "@bull-board/api": "6.5.3", "@bull-board/fastify": "6.5.3", "@bull-board/ui": "6.5.3", @@ -80,7 +80,7 @@ "@fastify/static": "8.0.3", "@fastify/view": "10.0.1", "@misskey-dev/sharp-read-bmp": "1.2.0", - "@misskey-dev/summaly": "MisskeyIO/summaly#5.1.2", + "@misskey-dev/summaly": "github:MisskeyIO/summaly#5.1.3", "@napi-rs/canvas": "0.1.65", "@nestjs/common": "10.4.15", "@nestjs/core": "10.4.15", @@ -99,12 +99,12 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.3", - "bullmq": "5.34.2", + "bullmq": "5.34.4", "cacheable-lookup": "7.0.0", "cbor": "10.0.3", - "chalk": "5.3.0", + "chalk": "5.4.0", "chalk-template": "1.1.0", - "chokidar": "4.0.2", + "chokidar": "4.0.3", "cli-highlight": "2.1.11", "color-convert": "2.0.1", "content-disposition": "0.5.4", @@ -122,7 +122,7 @@ "hpagent": "1.2.0", "htmlescape": "1.1.1", "http-link-header": "1.1.3", - "ioredis": "5.4.1", + "ioredis": "5.4.2", "ip-cidr": "4.0.2", "ipaddr.js": "2.2.0", "is-svg": "5.1.0", @@ -169,13 +169,13 @@ "rss-parser": "3.13.0", "rxjs": "7.8.1", "samlify": "2.8.11", - "sanitize-html": "2.13.1", + "sanitize-html": "2.14.0", "secure-json-parse": "3.0.1", "sharp": "0.33.5", "slacc": "0.0.10", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", - "systeminformation": "5.23.13", + "systeminformation": "5.23.14", "tinycolor2": "1.6.0", "tmp": "0.2.3", "tsc-alias": "1.8.10", diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 470da52a1..76f5d7447 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -389,6 +389,7 @@ import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by import * as ep___users_search from './endpoints/users/search.js'; import * as ep___users_show from './endpoints/users/show.js'; import * as ep___users_stats from './endpoints/users/stats.js'; +import * as ep___users_get_security_info from './endpoints/users/get-security-info.js'; import * as ep___users_achievements from './endpoints/users/achievements.js'; import * as ep___users_updateMemo from './endpoints/users/update-memo.js'; import * as ep___fetchRss from './endpoints/fetch-rss.js'; @@ -765,6 +766,7 @@ const $users_following: Provider = { provide: 'ep:users/following', useClass: ep const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default }; const $users_getFollowingBirthdayUsers: Provider = { provide: 'ep:users/get-following-birthday-users', useClass: ep___users_getFollowingBirthdayUsers.default }; const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default }; +const $users_getSecurityInfo: Provider = { provide: 'ep:users/get-security-info', useClass: ep___users_get_security_info.default }; const $users_getSkebStatus: Provider = { provide: 'ep:users/get-skeb-status', useClass: ep___users_getSkebStatus.default }; const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', useClass: ep___users_featuredNotes.default }; const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default }; @@ -1170,6 +1172,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $users_gallery_posts, $users_getFollowingBirthdayUsers, $users_getFrequentlyRepliedUsers, + $users_getSecurityInfo, $users_getSkebStatus, $users_featuredNotes, $users_lists_create, @@ -1567,6 +1570,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $users_gallery_posts, $users_getFollowingBirthdayUsers, $users_getFrequentlyRepliedUsers, + $users_getSecurityInfo, $users_getSkebStatus, $users_featuredNotes, $users_lists_create, diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index c8f76550d..ec1ec567d 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -3,11 +3,13 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { randomUUID } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { + MiUserProfile, SigninsRepository, UserProfilesRepository, UsersRepository, @@ -27,7 +29,6 @@ import { RateLimiterService } from './RateLimiterService.js'; import { SigninService } from './SigninService.js'; import type { AuthenticationResponseJSON } from '@simplewebauthn/server'; import type { FastifyReply, FastifyRequest } from 'fastify'; -import { randomUUID } from 'node:crypto'; @Injectable() export class SigninApiService { @@ -122,22 +123,34 @@ export class SigninApiService { } // Fetch user - const user = await this.usersRepository.findOneBy({ - usernameLower: username.toLowerCase(), - host: IsNull(), - }) as MiLocalUser; + const profile = await this.userProfilesRepository.findOne({ + relations: ['user'], + where: username.includes('@') ? { + email: username, + emailVerified: true, + user: { + host: IsNull(), + } + } : { + user: { + usernameLower: username.toLowerCase(), + host: IsNull(), + } + } + }); + const user = (profile?.user as MiLocalUser) ?? null; - if (user == null) { + if (!user || !profile) { logger.error('No such user.'); - return error(404, { - id: '6cc579cc-885d-43d8-95c2-b8c7fc963280', + return error(403, { + id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', }); } if (user.isDeleted && user.isSuspended) { logger.error('No such user. (logical deletion)'); - return error(404, { - id: '6cc579cc-885d-43d8-95c2-b8c7fc963280', + return error(403, { + id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', }); } @@ -148,8 +161,6 @@ export class SigninApiService { }); } - const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); - // Compare password const same = await bcrypt.compare(password, profile.password!); diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 1a6c4a15e..9769c0133 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -388,6 +388,7 @@ import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js'; import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js'; import * as ep___users_search from './endpoints/users/search.js'; import * as ep___users_show from './endpoints/users/show.js'; +import * as ep___users_get_security_info from './endpoints/users/get-security-info.js'; import * as ep___users_stats from './endpoints/users/stats.js'; import * as ep___users_achievements from './endpoints/users/achievements.js'; import * as ep___users_updateMemo from './endpoints/users/update-memo.js'; @@ -787,6 +788,7 @@ const eps = [ ['users/search-by-username-and-host', ep___users_searchByUsernameAndHost], ['users/search', ep___users_search], ['users/show', ep___users_show], + ['users/get-security-info', ep___users_get_security_info], ['users/stats', ep___users_stats], ['users/achievements', ep___users_achievements], ['users/update-memo', ep___users_updateMemo], diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 5460635e1..43f9db2aa 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -11,6 +11,8 @@ export const meta = { tags: ['meta'], requireCredential: false, + allowGet: true, + cacheSec: 60, res: { type: 'object', diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index 1e6983177..286a82cc5 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -12,6 +12,8 @@ import UsersChart from '@/core/chart/charts/users.js'; export const meta = { requireCredential: false, + allowGet: true, + cacheSec: 60, tags: ['meta'], diff --git a/packages/backend/src/server/api/endpoints/users/get-security-info.ts b/packages/backend/src/server/api/endpoints/users/get-security-info.ts new file mode 100644 index 000000000..e3fe2b6ff --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/get-security-info.ts @@ -0,0 +1,72 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import bcrypt from 'bcryptjs'; +import ms from 'ms'; + +export const meta = { + tags: ['users'], + + requireCredential: false, + + limit: { + duration: ms('1hour'), + max: 30, + }, + + res: { + type: 'object', + properties: { + twoFactorEnabled: { type: 'boolean' }, + usePasswordLessLogin: { type: 'boolean' }, + securityKeys: { type: 'boolean' }, + }, + }, + errors: { + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + email: { type: 'string' }, + password: { type: 'string' }, + }, + required: ['email', 'password'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.userSecurityKeysRepository) + private userSecurityKeysRepository: UserSecurityKeysRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneBy({ + email: ps.email, + emailVerified: true, + }); + + const passwordMatched = await bcrypt.compare(ps.password, profile?.password ?? ''); + if (!profile || !passwordMatched) { + return { + twoFactorEnabled: false, + usePasswordLessLogin: false, + securityKeys: false, + }; + } + + return { + twoFactorEnabled: profile.twoFactorEnabled, + usePasswordLessLogin: profile.usePasswordLessLogin, + securityKeys: profile.twoFactorEnabled + ? await this.userSecurityKeysRepository.countBy({ userId: profile.userId }).then(result => result >= 1) + : false, + }; + }); + } +} diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts index a996d896d..c64d87e31 100644 --- a/packages/backend/src/server/api/stream/channels/antenna.ts +++ b/packages/backend/src/server/api/stream/channels/antenna.ts @@ -15,6 +15,7 @@ class AntennaChannel extends Channel { public static readonly requireCredential = true as const; public static readonly kind = 'read:account'; private antennaId: string; + private idOnly: boolean; constructor( private noteEntityService: NoteEntityService, @@ -29,6 +30,7 @@ class AntennaChannel extends Channel { @bindThis public async init(params: any) { this.antennaId = params.antennaId as string; + this.idOnly = params.idOnly ?? false; // Subscribe stream this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent); @@ -57,9 +59,13 @@ class AntennaChannel extends Channel { if (this.isNoteMutedOrBlocked(note.reply)) return; } - this.connection.cacheNote(note); - - this.send('note', note); + if (this.idOnly && ['public', 'home'].includes(note.visibility)) { + const idOnlyNote = { id: note.id }; + this.send('note', idOnlyNote); + } else { + this.connection.cacheNote(note); + this.send('note', note); + } } else { this.send(data.type, data.body); } diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index a393b9161..667f01354 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -15,6 +15,7 @@ class ChannelChannel extends Channel { public static readonly shouldShare = false; public static readonly requireCredential = false as const; private channelId: string; + private idOnly: boolean; constructor( private noteEntityService: NoteEntityService, @@ -29,6 +30,7 @@ class ChannelChannel extends Channel { @bindThis public async init(params: any) { this.channelId = params.channelId as string; + this.idOnly = params.idOnly ?? false; // Subscribe stream this.subscriber.on('notesStream', this.onNote); @@ -63,9 +65,13 @@ class ChannelChannel extends Channel { } } - this.connection.cacheNote(note); - - this.send('note', note); + if (this.idOnly && ['public', 'home'].includes(note.visibility)) { + const idOnlyNote = { id: note.id }; + this.send('note', idOnlyNote); + } else { + this.connection.cacheNote(note); + this.send('note', note); + } } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 16d4635fc..71dbfa15c 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -18,6 +18,7 @@ class GlobalTimelineChannel extends Channel { public static readonly requireCredential = false as const; private withRenotes: boolean; private withFiles: boolean; + private idOnly: boolean; constructor( private metaService: MetaService, @@ -38,6 +39,7 @@ class GlobalTimelineChannel extends Channel { this.withRenotes = params.withRenotes ?? true; this.withFiles = params.withFiles ?? false; + this.idOnly = params.idOnly ?? false; // Subscribe events this.subscriber.on('notesStream', this.onNote); @@ -93,9 +95,13 @@ class GlobalTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - - this.send('note', note); + if (this.idOnly && ['public', 'home'].includes(note.visibility)) { + const idOnlyNote = { id: note.id }; + this.send('note', idOnlyNote); + } else { + this.connection.cacheNote(note); + this.send('note', note); + } } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 8e75d9fcc..4d74932e2 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -17,6 +17,7 @@ class HomeTimelineChannel extends Channel { public static readonly kind = 'read:account'; private withRenotes: boolean; private withFiles: boolean; + private idOnly: boolean; constructor( private noteEntityService: NoteEntityService, @@ -32,6 +33,7 @@ class HomeTimelineChannel extends Channel { public async init(params: any) { this.withRenotes = params.withRenotes ?? true; this.withFiles = params.withFiles ?? false; + this.idOnly = params.idOnly ?? false; this.subscriber.on('notesStream', this.onNote); } @@ -94,9 +96,13 @@ class HomeTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - - this.send('note', note); + if (this.idOnly && ['public', 'home'].includes(note.visibility)) { + const idOnlyNote = { id: note.id }; + this.send('note', idOnlyNote); + } else { + this.connection.cacheNote(note); + this.send('note', note); + } } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index cf0307358..eecb57a6a 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -20,6 +20,7 @@ class HybridTimelineChannel extends Channel { private withRenotes: boolean; private withReplies: boolean; private withFiles: boolean; + private idOnly: boolean; constructor( private metaService: MetaService, @@ -41,6 +42,7 @@ class HybridTimelineChannel extends Channel { this.withRenotes = params.withRenotes ?? true; this.withReplies = params.withReplies ?? false; this.withFiles = params.withFiles ?? false; + this.idOnly = params.idOnly ?? false; // Subscribe events this.subscriber.on('notesStream', this.onNote); @@ -110,9 +112,13 @@ class HybridTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - - this.send('note', note); + if (this.idOnly && ['public', 'home'].includes(note.visibility)) { + const idOnlyNote = { id: note.id }; + this.send('note', idOnlyNote); + } else { + this.connection.cacheNote(note); + this.send('note', note); + } } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index f6b91235d..dd588ebf9 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -19,6 +19,7 @@ class LocalTimelineChannel extends Channel { private withRenotes: boolean; private withReplies: boolean; private withFiles: boolean; + private idOnly: boolean; constructor( private metaService: MetaService, @@ -40,6 +41,7 @@ class LocalTimelineChannel extends Channel { this.withRenotes = params.withRenotes ?? true; this.withReplies = params.withReplies ?? false; this.withFiles = params.withFiles ?? false; + this.idOnly = params.idOnly ?? false; // Subscribe events this.subscriber.on('notesStream', this.onNote); @@ -93,9 +95,13 @@ class LocalTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - - this.send('note', note); + if (this.idOnly && ['public', 'home'].includes(note.visibility)) { + const idOnlyNote = { id: note.id }; + this.send('note', idOnlyNote); + } else { + this.connection.cacheNote(note); + this.send('note', note); + } } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/role-timeline.ts b/packages/backend/src/server/api/stream/channels/role-timeline.ts index c55c72bc4..8bfb65401 100644 --- a/packages/backend/src/server/api/stream/channels/role-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/role-timeline.ts @@ -16,6 +16,7 @@ class RoleTimelineChannel extends Channel { public static readonly shouldShare = false; public static readonly requireCredential = false as const; private roleId: string; + private idOnly: boolean; constructor( private noteEntityService: NoteEntityService, @@ -31,6 +32,7 @@ class RoleTimelineChannel extends Channel { @bindThis public async init(params: any) { this.roleId = params.roleId as string; + this.idOnly = params.idOnly ?? false; this.subscriber.on(`roleTimelineStream:${this.roleId}`, this.onEvent); } @@ -79,9 +81,13 @@ class RoleTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - - this.send('note', note); + if (this.idOnly && ['public', 'home'].includes(note.visibility)) { + const idOnlyNote = { id: note.id }; + this.send('note', idOnlyNote); + } else { + this.connection.cacheNote(note); + this.send('note', note); + } } else { this.send(data.type, data.body); } diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 297c253c4..651ab05f3 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -21,6 +21,7 @@ class UserListChannel extends Channel { private listUsersClock: NodeJS.Timeout; private withFiles: boolean; private withRenotes: boolean; + private idOnly: boolean; constructor( private userListsRepository: UserListsRepository, @@ -40,6 +41,7 @@ class UserListChannel extends Channel { this.listId = params.listId as string; this.withFiles = params.withFiles ?? false; this.withRenotes = params.withRenotes ?? true; + this.idOnly = params.idOnly ?? false; // Check existence and owner const listExist = await this.userListsRepository.exists({ @@ -128,9 +130,13 @@ class UserListChannel extends Channel { } } - this.connection.cacheNote(note); - - this.send('note', note); + if (this.idOnly && ['public', 'home'].includes(note.visibility)) { + const idOnlyNote = { id: note.id }; + this.send('note', idOnlyNote); + } else { + this.connection.cacheNote(note); + this.send('note', note); + } } @bindThis diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index d5c3ef3b0..9c8ee94d6 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -616,6 +616,81 @@ export class ClientServerService { } }); + fastify.get<{ Params: { note: string; } }>('/notes/:note.json', async (request, reply) => { + const note = await this.notesRepository.findOneBy({ + id: request.params.note, + visibility: In(['public', 'home']), + }); + + if (note) { + try { + const _note = await this.noteEntityService.pack(note, null); + reply.header('Content-Type', 'application/json; charset=utf-8'); + reply.header('Cache-Control', 'public, max-age=600'); + return reply.send(_note); + } catch (err) { + reply.header('Cache-Control', 'max-age=10, must-revalidate'); + if (err instanceof IdentifiableError) { + this.clientLoggerService.logger.error(`Internal error occurred in ${request.routeOptions.url}: ${err.message}`, { + path: request.routeOptions.url, + params: request.params, + query: request.query, + id: err.id, + error: { + message: err.message, + code: 'INTERNAL_ERROR', + stack: err.stack, + }, + }); + const httpStatusCode = err.id === '85ab9bd7-3a41-4530-959d-f07073900109' ? 403 : 500; + reply.code(httpStatusCode); + return reply.send({ + message: err.message, + code: 'INTERNAL_ERROR', + id: err.id, + kind: 'server', + httpStatusCode, + info: { + message: err.message, + code: err.name, + id: err.id, + }, + }); + } else { + const error = err as Error; + const errId = randomUUID(); + this.clientLoggerService.logger.error(`Internal error occurred in ${request.routeOptions.url}: ${error.message}`, { + path: request.routeOptions.url, + params: request.params, + query: request.query, + id: errId, + error: { + message: error.message, + code: error.name, + stack: error.stack, + }, + }); + reply.code(500); + return reply.send({ + message: 'Internal error occurred. Please contact us if the error persists.', + code: 'INTERNAL_ERROR', + id: 'b9f2a7f9-fe64-434b-9484-cb1f804d1a80', + kind: 'server', + httpStatusCode: 500, + info: { + message: error.message, + code: error.name, + id: errId, + }, + }); + } + } + } else { + reply.code(404); + return; + } + }); + // Page fastify.get<{ Params: { user: string; page: string; } }>('/@:user/pages/:page', async (request, reply) => { const { username, host } = Acct.parse(request.params.user); diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 9266fae47..ecce9d04d 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -32,25 +32,10 @@ renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.') } - const metaRes = await window.fetch('/api/meta', { - method: 'POST', - body: JSON.stringify({}), - credentials: 'omit', - cache: 'no-cache', - headers: { - 'Content-Type': 'application/json', - }, - }); - if (metaRes.status !== 200) { - renderError('META_FETCH'); - return; - } - const meta = await metaRes.json(); - const v = meta.version; - if (v == null) { - renderError('META_FETCH_V'); - return; - } + if (localStorage.getItem('id') === null) { + localStorage.setItem('id', crypto.randomUUID().replaceAll('-', '')); + } + let id = localStorage.getItem('id'); //#region Detect language & fetch translations if (!localStorage.hasOwn('locale')) { @@ -73,6 +58,25 @@ lang = 'ko-KR'; } + const metaRes = await window.fetch('/api/meta', { + method: 'GET', + credentials: 'omit', + headers: { + 'Content-Type': 'application/json', + 'X-Client-Transaction-Id': `${id}-misskey-${crypto.randomUUID().replaceAll('-', '')}` + }, + }); + if (metaRes.status !== 200) { + renderError('META_FETCH'); + return; + } + const meta = await metaRes.json(); + const v = meta.version; + if (v == null) { + renderError('META_FETCH_V'); + return; + } + const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`); if (localRes.status === 200) { localStorage.setItem('lang', lang); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 8c0a0354a..6ef69f4d3 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -400,7 +400,7 @@ export const waitFire = async (user: UserToken if (timer) clearTimeout(timer); res(true); } - }, params); + }, { ...params, idOnly: false }); } catch (e) { rej(e); } diff --git a/packages/frontend/package.json b/packages/frontend/package.json index fbe53118c..a26469fb3 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -31,7 +31,7 @@ "@twemoji/parser": "15.1.1", "@vitejs/plugin-vue": "5.2.1", "@vue/compiler-sfc": "3.5.13", - "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.9", + "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.13", "astring": "1.9.0", "broadcast-channel": "7.0.0", "buraha": "0.0.1", @@ -59,10 +59,10 @@ "misskey-reversi": "workspace:*", "photoswipe": "5.4.4", "punycode.js": "2.3.1", - "rollup": "4.28.1", - "sanitize-html": "2.13.1", + "rollup": "4.29.1", + "sanitize-html": "2.14.0", "sass": "1.83.0", - "shiki": "1.24.2", + "shiki": "1.24.3", "strict-event-emitter-types": "2.0.0", "textarea-caret": "3.1.0", "three": "0.171.0", @@ -73,7 +73,7 @@ "typescript": "5.7.2", "uuid": "11.0.3", "v-code-diff": "1.13.1", - "vite": "6.0.3", + "vite": "6.0.5", "vue": "3.5.13", "vue-gtag": "2.0.1", "vuedraggable": "next", @@ -81,7 +81,7 @@ }, "devDependencies": { "@misskey-dev/eslint-plugin": "1.0.0", - "@misskey-dev/summaly": "MisskeyIO/summaly#5.1.2", + "@misskey-dev/summaly": "github:MisskeyIO/summaly#5.1.3", "@storybook/addon-actions": "8.4.7", "@storybook/addon-essentials": "8.4.7", "@storybook/addon-interactions": "8.4.7", diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts index 935f0db33..372898aa6 100644 --- a/packages/frontend/src/account.ts +++ b/packages/frontend/src/account.ts @@ -5,16 +5,16 @@ import { defineAsyncComponent, reactive, ref } from 'vue'; import * as Misskey from 'misskey-js'; -import type { MenuButton } from '@/types/menu.js'; +import { set as gtagSet, time as gtagTime } from 'vue-gtag'; import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js'; import { i18n } from '@/i18n.js'; import { miLocalStorage } from '@/local-storage.js'; +import { MenuButton } from '@/types/menu.js'; import { del, get, set } from '@/scripts/idb-proxy.js'; import { apiUrl } from '@/config.js'; import { waiting, popup, popupMenu, success, alert } from '@/os.js'; import { generateClientTransactionId, misskeyApi } from '@/scripts/misskey-api.js'; import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js'; -import { set as gtagSet, time as gtagTime } from 'vue-gtag'; import { instance } from '@/instance.js'; // TODO: 他のタブと永続化されたstateを同期 diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index 0ecb60e02..df1dda5ab 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -63,10 +63,6 @@ export async function common(createVue: () => App) { }); } - if (miLocalStorage.getItem('id') === null) { - miLocalStorage.setItem('id', crypto.randomUUID()); - } - let isClientUpdated = false; //#region クライアントが更新されたかチェック diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index 60d7333df..feb1f8acf 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -6,24 +6,32 @@ SPDX-License-Identifier: AGPL-3.0-only