fix: signin の資格情報が足りないだけの場合はエラーにせず200を返すように
This commit is contained in:
parent
b36d13d90c
commit
cfde3248df
|
@ -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',
|
||||
finished: false,
|
||||
next: 'password',
|
||||
},
|
||||
} satisfies { error: SigninErrorResponse };
|
||||
} satisfies Misskey.entities.SigninResponse;
|
||||
} else {
|
||||
return {
|
||||
error: {
|
||||
id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf',
|
||||
finished: false,
|
||||
next: 'captcha',
|
||||
},
|
||||
} satisfies { error: SigninErrorResponse };
|
||||
} 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',
|
||||
finished: false,
|
||||
next: 'passkey',
|
||||
authRequest,
|
||||
},
|
||||
} satisfies { error: SigninErrorResponse };
|
||||
} 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',
|
||||
finished: false,
|
||||
next: 'totp',
|
||||
},
|
||||
} satisfies { error: SigninErrorResponse };
|
||||
} satisfies Misskey.entities.SigninResponse;
|
||||
}
|
||||
}
|
||||
// never get here
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -227,26 +227,11 @@ async function tryLogin(req: Partial<Misskey.entities.SigninRequest>): Promise<M
|
|||
}
|
||||
|
||||
return await misskeyApi('signin', _req).then(async (res) => {
|
||||
if (res.finished) {
|
||||
emit('login', res);
|
||||
await onLoginSucceeded(res);
|
||||
return res;
|
||||
}).catch((err) => {
|
||||
onSigninApiError(err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
async function onLoginSucceeded(res: Misskey.entities.SigninResponse) {
|
||||
if (props.autoSet) {
|
||||
await login(res.i);
|
||||
}
|
||||
}
|
||||
|
||||
function onSigninApiError(err?: any): void {
|
||||
const id = err?.id ?? null;
|
||||
|
||||
if (typeof err === 'object' && 'next' in err) {
|
||||
switch (err.next) {
|
||||
} else {
|
||||
switch (res.next) {
|
||||
case 'captcha': {
|
||||
needCaptcha.value = true;
|
||||
page.value = 'password';
|
||||
|
@ -262,9 +247,9 @@ function onSigninApiError(err?: any): void {
|
|||
break;
|
||||
}
|
||||
case 'passkey': {
|
||||
if (webAuthnSupported() && 'authRequest' in err) {
|
||||
if (webAuthnSupported()) {
|
||||
credentialRequest.value = parseRequestOptionsFromJSON({
|
||||
publicKey: err.authRequest,
|
||||
publicKey: res.authRequest,
|
||||
});
|
||||
page.value = 'passkey';
|
||||
} else {
|
||||
|
@ -273,7 +258,23 @@ function onSigninApiError(err?: any): void {
|
|||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
return res;
|
||||
}).catch((err) => {
|
||||
onSigninApiError(err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
async function onLoginSucceeded(res: Misskey.entities.SigninResponse & { finished: true; }) {
|
||||
if (props.autoSet) {
|
||||
await login(res.i);
|
||||
}
|
||||
}
|
||||
|
||||
function onSigninApiError(err?: any): void {
|
||||
const id = err?.id ?? null;
|
||||
|
||||
switch (id) {
|
||||
case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
|
||||
os.alert({
|
||||
|
@ -352,7 +353,6 @@ function onSigninApiError(err?: any): void {
|
|||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (doingPasskeyFromInputPage.value === true) {
|
||||
doingPasskeyFromInputPage.value = false;
|
||||
|
|
|
@ -275,8 +275,13 @@ async function onSubmit(): Promise<void> {
|
|||
});
|
||||
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 {
|
||||
|
|
|
@ -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 extends Record<PropertyKey, unknown>> = T[keyof T];
|
||||
|
|
Loading…
Reference in New Issue