summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/SigninApiService.ts
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2024-10-04 15:23:33 +0900
committerGitHub <noreply@github.com>2024-10-04 15:23:33 +0900
commit975c2e7bc567618c3f8b0082afcba6530d679dae (patch)
tree2268801e42af4285f851b08077bbe9d569755156 /packages/backend/src/server/api/SigninApiService.ts
parentUpdate generate.tsx (diff)
downloadsharkey-975c2e7bc567618c3f8b0082afcba6530d679dae.tar.gz
sharkey-975c2e7bc567618c3f8b0082afcba6530d679dae.tar.bz2
sharkey-975c2e7bc567618c3f8b0082afcba6530d679dae.zip
enhance(frontend): サインイン画面の改善 (#14658)
* wip * Update MkSignin.vue * Update MkSignin.vue * wip * Update CHANGELOG.md * enhance(frontend): サインイン画面の改善 * Update Changelog * 14655の変更取り込み * spdx * fix * fix * fix * :art: * :art: * :art: * :art: * Captchaがリセットされない問題を修正 * 次の処理をsignin apiから読み取るように * Add Comments * fix * fix test * attempt to fix test * fix test * fix test * fix test * fix * fix test * fix: 一部のエラーがちゃんと出るように * Update Changelog * :art: * :art: * remove border --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
Diffstat (limited to 'packages/backend/src/server/api/SigninApiService.ts')
-rw-r--r--packages/backend/src/server/api/SigninApiService.ts86
1 files changed, 74 insertions, 12 deletions
diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts
index 2ccc75da00..81684beb3c 100644
--- a/packages/backend/src/server/api/SigninApiService.ts
+++ b/packages/backend/src/server/api/SigninApiService.ts
@@ -12,6 +12,7 @@ import type {
MiMeta,
SigninsRepository,
UserProfilesRepository,
+ UserSecurityKeysRepository,
UsersRepository,
} from '@/models/_.js';
import type { Config } from '@/config.js';
@@ -25,9 +26,27 @@ 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 } from '@simplewebauthn/types';
+import type { AuthenticationResponseJSON, PublicKeyCredentialRequestOptionsJSON } 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(
@@ -43,6 +62,9 @@ export class SigninApiService {
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
+ @Inject(DI.userSecurityKeysRepository)
+ private userSecurityKeysRepository: UserSecurityKeysRepository,
+
@Inject(DI.signinsRepository)
private signinsRepository: SigninsRepository,
@@ -60,7 +82,7 @@ export class SigninApiService {
request: FastifyRequest<{
Body: {
username: string;
- password: string;
+ password?: string;
token?: string;
credential?: AuthenticationResponseJSON;
'hcaptcha-response'?: string;
@@ -79,7 +101,7 @@ export class SigninApiService {
const password = body['password'];
const token = body['token'];
- function error(status: number, error: { id: string }) {
+ function error(status: number, error: SigninErrorResponse) {
reply.code(status);
return { error };
}
@@ -103,11 +125,6 @@ export class SigninApiService {
return;
}
- if (typeof password !== 'string') {
- reply.code(400);
- return;
- }
-
if (token != null && typeof token !== 'string') {
reply.code(400);
return;
@@ -132,11 +149,36 @@ export class SigninApiService {
}
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
+ const securityKeysAvailable = await this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1);
+
+ if (password == null) {
+ reply.code(403);
+ if (profile.twoFactorEnabled) {
+ return {
+ error: {
+ id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf',
+ next: 'password',
+ },
+ } satisfies { error: SigninErrorResponse };
+ } else {
+ return {
+ error: {
+ id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf',
+ next: 'captcha',
+ },
+ } satisfies { error: SigninErrorResponse };
+ }
+ }
+
+ if (typeof password !== 'string') {
+ reply.code(400);
+ return;
+ }
// Compare password
const same = await bcrypt.compare(password, profile.password!);
- const fail = async (status?: number, failure?: { id: string }) => {
+ const fail = async (status?: number, failure?: SigninErrorResponse) => {
// Append signin history
await this.signinsRepository.insert({
id: this.idService.gen(),
@@ -217,7 +259,7 @@ export class SigninApiService {
id: '93b86c4b-72f9-40eb-9815-798928603d1e',
});
}
- } else {
+ } else if (securityKeysAvailable) {
if (!same && !profile.usePasswordLessLogin) {
return await fail(403, {
id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
@@ -226,8 +268,28 @@ export class SigninApiService {
const authRequest = await this.webAuthnService.initiateAuthentication(user.id);
- reply.code(200);
- return authRequest;
+ reply.code(403);
+ return {
+ error: {
+ id: '06e661b9-8146-4ae3-bde5-47138c0ae0c4',
+ next: 'passkey',
+ authRequest,
+ },
+ } satisfies { error: SigninErrorResponse };
+ } else {
+ if (!same || !profile.twoFactorEnabled) {
+ return await fail(403, {
+ id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
+ });
+ } else {
+ reply.code(403);
+ return {
+ error: {
+ id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf',
+ next: 'totp',
+ },
+ } satisfies { error: SigninErrorResponse };
+ }
}
// never get here
}