From cfde3248df2c46d427e9b2a1b83df471b04b115b Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 4 Oct 2024 19:07:25 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20signin=20=E3=81=AE=E8=B3=87=E6=A0=BC?= =?UTF-8?q?=E6=83=85=E5=A0=B1=E3=81=8C=E8=B6=B3=E3=82=8A=E3=81=AA=E3=81=84?= =?UTF-8?q?=E3=81=A0=E3=81=91=E3=81=AE=E5=A0=B4=E5=90=88=E3=81=AF=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E3=81=AB=E3=81=9B=E3=81=9A200=E3=82=92?= =?UTF-8?q?=E8=BF=94=E3=81=99=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/server/api/SigninApiService.ts | 66 ++---- .../backend/src/server/api/SigninService.ts | 6 +- packages/frontend/src/components/MkSignin.vue | 216 +++++++++--------- .../src/components/MkSignupDialog.form.vue | 7 +- packages/misskey-js/src/entities.ts | 12 +- 5 files changed, 148 insertions(+), 159 deletions(-) diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 81684beb3c..327076cd9c 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -5,8 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; -import * as OTPAuth from 'otpauth'; import { IsNull } from 'typeorm'; +import * as Misskey from 'misskey-js'; import { DI } from '@/di-symbols.js'; import type { MiMeta, @@ -26,27 +26,9 @@ import { CaptchaService } from '@/core/CaptchaService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { RateLimiterService } from './RateLimiterService.js'; import { SigninService } from './SigninService.js'; -import type { AuthenticationResponseJSON, PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types'; +import type { AuthenticationResponseJSON } from '@simplewebauthn/types'; import type { FastifyReply, FastifyRequest } from 'fastify'; -/** - * next を指定すると、次にクライアント側で行うべき処理を指定できる。 - * - * - `captcha`: パスワードと、(有効になっている場合は)CAPTCHAを求める - * - `password`: パスワードを求める - * - `totp`: ワンタイムパスワードを求める - * - `passkey`: WebAuthn認証を求める(WebAuthnに対応していないブラウザの場合はワンタイムパスワード) - */ - -type SigninErrorResponse = { - id: string; - next?: 'captcha' | 'password' | 'totp'; -} | { - id: string; - next: 'passkey'; - authRequest: PublicKeyCredentialRequestOptionsJSON; -}; - @Injectable() export class SigninApiService { constructor( @@ -101,7 +83,7 @@ export class SigninApiService { const password = body['password']; const token = body['token']; - function error(status: number, error: SigninErrorResponse) { + function error(status: number, error: { id: string }) { reply.code(status); return { error }; } @@ -152,21 +134,17 @@ export class SigninApiService { const securityKeysAvailable = await this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1); if (password == null) { - reply.code(403); + reply.code(200); if (profile.twoFactorEnabled) { return { - error: { - id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf', - next: 'password', - }, - } satisfies { error: SigninErrorResponse }; + finished: false, + next: 'password', + } satisfies Misskey.entities.SigninResponse; } else { return { - error: { - id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf', - next: 'captcha', - }, - } satisfies { error: SigninErrorResponse }; + finished: false, + next: 'captcha', + } satisfies Misskey.entities.SigninResponse; } } @@ -178,7 +156,7 @@ export class SigninApiService { // Compare password const same = await bcrypt.compare(password, profile.password!); - const fail = async (status?: number, failure?: SigninErrorResponse) => { + const fail = async (status?: number, failure?: { id: string; }) => { // Append signin history await this.signinsRepository.insert({ id: this.idService.gen(), @@ -268,27 +246,23 @@ export class SigninApiService { const authRequest = await this.webAuthnService.initiateAuthentication(user.id); - reply.code(403); + reply.code(200); return { - error: { - id: '06e661b9-8146-4ae3-bde5-47138c0ae0c4', - next: 'passkey', - authRequest, - }, - } satisfies { error: SigninErrorResponse }; + finished: false, + next: 'passkey', + authRequest, + } satisfies Misskey.entities.SigninResponse; } else { if (!same || !profile.twoFactorEnabled) { return await fail(403, { id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', }); } else { - reply.code(403); + reply.code(200); return { - error: { - id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf', - next: 'totp', - }, - } satisfies { error: SigninErrorResponse }; + finished: false, + next: 'totp', + } satisfies Misskey.entities.SigninResponse; } } // never get here diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts index 4b041f373f..82d3ca1546 100644 --- a/packages/backend/src/server/api/SigninService.ts +++ b/packages/backend/src/server/api/SigninService.ts @@ -4,6 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import * as Misskey from 'misskey-js'; import { DI } from '@/di-symbols.js'; import type { SigninsRepository, UserProfilesRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; @@ -57,9 +58,10 @@ export class SigninService { reply.code(200); return { + finished: true, id: user.id, - i: user.token, - }; + i: user.token!, + } satisfies Misskey.entities.SigninResponse; } } diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index 03dd61f6c6..2e8169ec0b 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -227,8 +227,38 @@ async function tryLogin(req: Partial): Promise { - emit('login', res); - await onLoginSucceeded(res); + if (res.finished) { + emit('login', res); + await onLoginSucceeded(res); + } else { + switch (res.next) { + case 'captcha': { + needCaptcha.value = true; + page.value = 'password'; + break; + } + case 'password': { + needCaptcha.value = false; + page.value = 'password'; + break; + } + case 'totp': { + page.value = 'totp'; + break; + } + case 'passkey': { + if (webAuthnSupported()) { + credentialRequest.value = parseRequestOptionsFromJSON({ + publicKey: res.authRequest, + }); + page.value = 'passkey'; + } else { + page.value = 'totp'; + } + break; + } + } + } return res; }).catch((err) => { onSigninApiError(err); @@ -236,7 +266,7 @@ async function tryLogin(req: Partial): Promise { }); emit('signup', res); - if (props.autoSet) { + if (props.autoSet && res.finished) { return login(res.i); + } else { + os.alert({ + type: 'error', + text: i18n.ts.somethingHappened, + }); } } } catch { diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index 98ac50e5a1..e19ec5b32a 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -293,8 +293,16 @@ export type SigninWithPasskeyResponse = { }; export type SigninResponse = { - id: User['id'], - i: string, + finished: true; + id: User['id']; + i: string; +} | { + finished: false; + next: 'captcha' | 'password' | 'totp'; +} | { + finished: false; + next: 'passkey'; + authRequest: PublicKeyCredentialRequestOptionsJSON; }; type Values> = T[keyof T];