From 62dede02eaf93a6ca08983bbf84a8a71e67fa6eb Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Sun, 18 Jul 2021 00:53:16 +0900 Subject: [PATCH 01/23] =?UTF-8?q?API=20Authenticate=E3=81=A7DB=E6=8E=A5?= =?UTF-8?q?=E7=B6=9A=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=AA=E3=81=A9=E3=81=8C?= =?UTF-8?q?=E7=99=BA=E7=94=9F=E3=81=99=E3=82=8B=E3=81=A8=E3=83=AD=E3=82=B0?= =?UTF-8?q?=E3=82=A2=E3=82=A6=E3=83=88=E3=81=95=E3=81=9B=E3=82=89=E3=82=8C?= =?UTF-8?q?=E3=81=A6=E3=81=97=E3=81=BE=E3=81=86=E3=81=AE=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=20Fix=20#7603=20(#7604)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/account.ts | 2 +- src/server/api/api-handler.ts | 18 +++++++++++------- src/server/api/authenticate.ts | 13 ++++++++++--- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/client/account.ts b/src/client/account.ts index 102269a0dc..2b860b3ddf 100644 --- a/src/client/account.ts +++ b/src/client/account.ts @@ -47,7 +47,7 @@ function fetchAccount(token): Promise { }) .then(res => { // When failed to authenticate user - if (res.status !== 200 && res.status < 500) { + if (res.status >= 400 && res.status < 500) { return signout(); } diff --git a/src/server/api/api-handler.ts b/src/server/api/api-handler.ts index 80a4fd97c8..cbace8917e 100644 --- a/src/server/api/api-handler.ts +++ b/src/server/api/api-handler.ts @@ -1,7 +1,7 @@ import * as Koa from 'koa'; import { IEndpoint } from './endpoints'; -import authenticate from './authenticate'; +import authenticate, { AuthenticationError } from './authenticate'; import call from './call'; import { ApiError } from './error'; @@ -37,11 +37,15 @@ export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise((res) => { }).catch((e: ApiError) => { reply(e.httpStatusCode ? e.httpStatusCode : e.kind === 'client' ? 400 : 500, e); }); - }).catch(() => { - reply(403, new ApiError({ - message: 'Authentication failed. Please ensure your token is correct.', - code: 'AUTHENTICATION_FAILED', - id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14' - })); + }).catch(e => { + if (e instanceof AuthenticationError) { + reply(403, new ApiError({ + message: 'Authentication failed. Please ensure your token is correct.', + code: 'AUTHENTICATION_FAILED', + id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14' + })); + } else { + reply(500, new ApiError()); + } }); }); diff --git a/src/server/api/authenticate.ts b/src/server/api/authenticate.ts index 6ea5a111bc..bba4db4ace 100644 --- a/src/server/api/authenticate.ts +++ b/src/server/api/authenticate.ts @@ -8,7 +8,14 @@ import { Cache } from '@/misc/cache'; // ref. https://github.com/typeorm/typeorm/blob/master/docs/caching.md const cache = new Cache(1000 * 60 * 60); -export default async (token: string): Promise<[User | null | undefined, AccessToken | null | undefined]> => { +export class AuthenticationError extends Error { + constructor(message: string) { + super(message); + this.name = 'AuthenticationError'; + } +} + +export default async (token: string): Promise<[User | null | undefined, App | null | undefined]> => { if (token == null) { return [null, null]; } @@ -24,7 +31,7 @@ export default async (token: string): Promise<[User | null | undefined, AccessTo .findOne({ token }); if (user == null) { - throw new Error('user not found'); + throw new AuthenticationError('user not found'); } cache.set(token, user); @@ -41,7 +48,7 @@ export default async (token: string): Promise<[User | null | undefined, AccessTo }); if (accessToken == null) { - throw new Error('invalid signature'); + throw new AuthenticationError('invalid signature'); } AccessTokens.update(accessToken.id, { From 04e27e160e92d006db59d3285aeaf5c535e82861 Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Sun, 18 Jul 2021 19:57:53 +0900 Subject: [PATCH 02/23] =?UTF-8?q?=E8=AA=8D=E8=A8=BC=E3=81=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=20(#7597)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * authenticateのキャッシュを廃止 * 凍結ユーザーがサインイン出来てしまうのを修正 * 凍結ユーザーはストリーミング接続出来ないように * 他人のアクセストークンはrevoke出来ないように, 正常削除を待機するように * ユーザー/アクセストークンを無効化したらストリーミングを切断するように * Revert TODO * ストリーミングterminateは、ユーザー削除後に行うように * signinでsuspendは別のエラーにする * トークン再生成後のストリーミング切断は少し待つように * サスペンド後のストリーミング切断はローカルユーザーのみに --- src/server/api/authenticate.ts | 13 ------------- src/server/api/endpoints/admin/suspend-user.ts | 6 ++++++ src/server/api/endpoints/i/delete-account.ts | 4 ++++ src/server/api/endpoints/i/regenerate-token.ts | 7 ++++++- src/server/api/endpoints/i/revoke-token.ts | 9 ++++++++- src/server/api/private/signin.ts | 7 +++++++ src/server/api/stream/index.ts | 5 +++++ src/server/api/streaming.ts | 5 +++++ 8 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/server/api/authenticate.ts b/src/server/api/authenticate.ts index bba4db4ace..6148ad33c5 100644 --- a/src/server/api/authenticate.ts +++ b/src/server/api/authenticate.ts @@ -2,11 +2,6 @@ import isNativeToken from './common/is-native-token'; import { User } from '../../models/entities/user'; import { Users, AccessTokens, Apps } from '../../models'; import { AccessToken } from '../../models/entities/access-token'; -import { Cache } from '@/misc/cache'; - -// TODO: TypeORMのカスタムキャッシュプロバイダを使っても良いかも -// ref. https://github.com/typeorm/typeorm/blob/master/docs/caching.md -const cache = new Cache(1000 * 60 * 60); export class AuthenticationError extends Error { constructor(message: string) { @@ -21,11 +16,6 @@ export default async (token: string): Promise<[User | null | undefined, App | nu } if (isNativeToken(token)) { - const cached = cache.get(token); - if (cached) { - return [cached, null]; - } - // Fetch user const user = await Users .findOne({ token }); @@ -34,11 +24,8 @@ export default async (token: string): Promise<[User | null | undefined, App | nu throw new AuthenticationError('user not found'); } - cache.set(token, user); - return [user, null]; } else { - // TODO: cache const accessToken = await AccessTokens.findOne({ where: [{ hash: token.toLowerCase() // app diff --git a/src/server/api/endpoints/admin/suspend-user.ts b/src/server/api/endpoints/admin/suspend-user.ts index 9f3c8eb6f8..912d6a5162 100644 --- a/src/server/api/endpoints/admin/suspend-user.ts +++ b/src/server/api/endpoints/admin/suspend-user.ts @@ -6,6 +6,7 @@ import { Users, Followings, Notifications } from '../../../../models'; import { User } from '../../../../models/entities/user'; import { insertModerationLog } from '../../../../services/insert-moderation-log'; import { doPostSuspend } from '../../../../services/suspend-user'; +import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['admin'], @@ -43,6 +44,11 @@ export default define(meta, async (ps, me) => { targetId: user.id, }); + // Terminate streaming + if (Users.isLocalUser(user)) { + publishUserEvent(user.id, 'terminate', {}); + } + (async () => { await doPostSuspend(user).catch(e => {}); await unFollowAll(user).catch(e => {}); diff --git a/src/server/api/endpoints/i/delete-account.ts b/src/server/api/endpoints/i/delete-account.ts index 0f04c4c92d..f5f0f32a4a 100644 --- a/src/server/api/endpoints/i/delete-account.ts +++ b/src/server/api/endpoints/i/delete-account.ts @@ -3,6 +3,7 @@ import * as bcrypt from 'bcryptjs'; import define from '../../define'; import { Users, UserProfiles } from '../../../../models'; import { doPostSuspend } from '../../../../services/suspend-user'; +import { publishUserEvent } from '@/services/stream'; export const meta = { requireCredential: true as const, @@ -30,4 +31,7 @@ export default define(meta, async (ps, user) => { await doPostSuspend(user).catch(e => {}); await Users.delete(user.id); + + // Terminate streaming + publishUserEvent(user.id, 'terminate', {}); }); diff --git a/src/server/api/endpoints/i/regenerate-token.ts b/src/server/api/endpoints/i/regenerate-token.ts index 3596e20197..3665ed0532 100644 --- a/src/server/api/endpoints/i/regenerate-token.ts +++ b/src/server/api/endpoints/i/regenerate-token.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; -import { publishMainStream } from '../../../../services/stream'; +import { publishMainStream, publishUserEvent } from '../../../../services/stream'; import generateUserToken from '../../common/generate-native-user-token'; import define from '../../define'; import { Users, UserProfiles } from '../../../../models'; @@ -36,4 +36,9 @@ export default define(meta, async (ps, user) => { // Publish event publishMainStream(user.id, 'myTokenRegenerated'); + + // Terminate streaming + setTimeout(() => { + publishUserEvent(user.id, 'terminate', {}); + }, 5000); }); diff --git a/src/server/api/endpoints/i/revoke-token.ts b/src/server/api/endpoints/i/revoke-token.ts index d71a1bd135..d22d9ca693 100644 --- a/src/server/api/endpoints/i/revoke-token.ts +++ b/src/server/api/endpoints/i/revoke-token.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import define from '../../define'; import { AccessTokens } from '../../../../models'; import { ID } from '@/misc/cafy-id'; +import { publishUserEvent } from '@/services/stream'; export const meta = { requireCredential: true as const, @@ -19,6 +20,12 @@ export default define(meta, async (ps, user) => { const token = await AccessTokens.findOne(ps.tokenId); if (token) { - AccessTokens.delete(token.id); + await AccessTokens.delete({ + id: ps.tokenId, + userId: user.id, + }); + + // Terminate streaming + publishUserEvent(user.id, 'terminate'); } }); diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts index 0a17b0bd02..c01c1f265a 100644 --- a/src/server/api/private/signin.ts +++ b/src/server/api/private/signin.ts @@ -46,6 +46,13 @@ export default async (ctx: Koa.Context) => { return; } + if (user.isSuspended) { + ctx.throw(403, { + error: 'user is suspended' + }); + return; + } + const profile = await UserProfiles.findOneOrFail(user.id); // Compare password diff --git a/src/server/api/stream/index.ts b/src/server/api/stream/index.ts index 647b890ff8..75d82cfe66 100644 --- a/src/server/api/stream/index.ts +++ b/src/server/api/stream/index.ts @@ -92,6 +92,11 @@ export default class Connection { this.userProfile = body; break; + case 'terminate': + this.wsConnection.close(); + this.dispose(); + break; + default: break; } diff --git a/src/server/api/streaming.ts b/src/server/api/streaming.ts index 57e8c90860..b431bc5ad3 100644 --- a/src/server/api/streaming.ts +++ b/src/server/api/streaming.ts @@ -22,6 +22,11 @@ module.exports = (server: http.Server) => { // (現状はエラーがキャッチされておらずサーバーのログに流れて邪魔なので) const [user, app] = await authenticate(q.i as string); + if (user?.isSuspended) { + request.reject(400); + return; + } + const connection = request.accept(); const ev = new EventEmitter(); From 42d293ee60c671ba424671d49072fc6c880d44b0 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 19 Jul 2021 11:36:35 +0900 Subject: [PATCH 03/23] Classic UI --- locales/ja-JP.yml | 9 +- src/client/components/launch-pad.vue | 4 +- src/client/components/ui/container.vue | 2 + src/client/components/widgets.vue | 1 + src/client/{sidebar.ts => menu.ts} | 2 +- src/client/pages/settings/index.vue | 4 +- .../pages/settings/{sidebar.vue => menu.vue} | 36 ++- src/client/scripts/sticky-sidebar.ts | 16 +- src/client/store.ts | 5 +- src/client/style.scss | 2 +- src/client/ui/_common_/sidebar.vue | 8 +- src/client/ui/chat/index.vue | 4 +- src/client/ui/deck.vue | 4 +- src/client/ui/default.header.vue | 274 ++++++++++++++++++ src/client/ui/default.sidebar.vue | 8 +- src/client/ui/default.vue | 55 +++- src/client/ui/default.widgets.vue | 16 +- src/client/ui/desktop.vue | 4 +- src/client/ui/universal.vue | 4 +- 19 files changed, 391 insertions(+), 67 deletions(-) rename src/client/{sidebar.ts => menu.ts} (99%) rename src/client/pages/settings/{sidebar.vue => menu.vue} (73%) create mode 100644 src/client/ui/default.header.vue diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index a92d838388..36d1f62ac0 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -528,7 +528,7 @@ removeAllFollowing: "フォローを全解除" removeAllFollowingDescription: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。" userSuspended: "このユーザーは凍結されています。" userSilenced: "このユーザーはサイレンスされています。" -sidebar: "サイドバー" +menu: "メニュー" divider: "分割線" addItem: "項目を追加" rooms: "ルーム" @@ -927,9 +927,10 @@ _channel: usersCount: "{n}人が参加中" notesCount: "{n}投稿があります" -_sidebar: - full: "フル" - icon: "アイコン" +_menuDisplay: + sideFull: "横" + sideIcon: "横(アイコン)" + top: "上部" hide: "隠す" _wordMute: diff --git a/src/client/components/launch-pad.vue b/src/client/components/launch-pad.vue index 58b74bdaee..6f97d4d3aa 100644 --- a/src/client/components/launch-pad.vue +++ b/src/client/components/launch-pad.vue @@ -36,7 +36,7 @@ + + diff --git a/src/client/ui/default.sidebar.vue b/src/client/ui/default.sidebar.vue index c7e2d30c7a..2e0336878d 100644 --- a/src/client/ui/default.sidebar.vue +++ b/src/client/ui/default.sidebar.vue @@ -45,7 +45,7 @@ import { defineComponent } from 'vue'; import { host } from '@client/config'; import { search } from '@client/scripts/search'; import * as os from '@client/os'; -import { sidebarDef } from '@client/sidebar'; +import { menuDef } from '@client/menu'; import { getAccounts, addAccount, login } from '@client/account'; import MkButton from '@client/components/ui/button.vue'; import { StickySidebar } from '@client/scripts/sticky-sidebar'; @@ -62,7 +62,7 @@ export default defineComponent({ host: host, accounts: [], connection: null, - menuDef: sidebarDef, + menuDef: menuDef, iconOnly: false, settingsWindowed: false, }; @@ -83,7 +83,7 @@ export default defineComponent({ }, watch: { - '$store.reactiveState.sidebarDisplay.value'() { + '$store.reactiveState.menuDisplay.value'() { this.calcViewState(); }, @@ -108,7 +108,7 @@ export default defineComponent({ methods: { calcViewState() { - this.iconOnly = (window.innerWidth <= 1400) || (this.$store.state.sidebarDisplay === 'icon'); + this.iconOnly = (window.innerWidth <= 1400) || (this.$store.state.menuDisplay === 'sideIcon'); this.settingsWindowed = (window.innerWidth > 1400); }, diff --git a/src/client/ui/default.vue b/src/client/ui/default.vue index 3c87bf7ab4..f18685d78c 100644 --- a/src/client/ui/default.vue +++ b/src/client/ui/default.vue @@ -1,9 +1,16 @@