enhance: require captcha for signin (MisskeyIO#742)
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
parent
c5f5602301
commit
32aa76485f
@ -20,6 +20,9 @@ import { bindThis } from '@/decorators.js';
|
||||
import { WebAuthnService } from '@/core/WebAuthnService.js';
|
||||
import { UserAuthService } from '@/core/UserAuthService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { CaptchaService } from '@/core/CaptchaService.js';
|
||||
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { RateLimiterService } from './RateLimiterService.js';
|
||||
import { SigninService } from './SigninService.js';
|
||||
import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
|
||||
@ -46,6 +49,8 @@ export class SigninApiService {
|
||||
private signinService: SigninService,
|
||||
private userAuthService: UserAuthService,
|
||||
private webAuthnService: WebAuthnService,
|
||||
private metaService: MetaService,
|
||||
private captchaService: CaptchaService,
|
||||
) {
|
||||
}
|
||||
|
||||
@ -57,6 +62,10 @@ export class SigninApiService {
|
||||
password: string;
|
||||
token?: string;
|
||||
credential?: AuthenticationResponseJSON;
|
||||
'hcaptcha-response'?: string;
|
||||
'g-recaptcha-response'?: string;
|
||||
'turnstile-response'?: string;
|
||||
'm-captcha-response'?: string;
|
||||
};
|
||||
}>,
|
||||
reply: FastifyReply,
|
||||
@ -157,6 +166,33 @@ export class SigninApiService {
|
||||
};
|
||||
|
||||
if (!profile.twoFactorEnabled) {
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
const meta = await this.metaService.fetch();
|
||||
if (meta.enableHcaptcha && meta.hcaptchaSecretKey) {
|
||||
await this.captchaService.verifyHcaptcha(meta.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => {
|
||||
throw new FastifyReplyError(400, err);
|
||||
});
|
||||
}
|
||||
|
||||
if (meta.enableMcaptcha && meta.mcaptchaSecretKey && meta.mcaptchaSitekey && meta.mcaptchaInstanceUrl) {
|
||||
await this.captchaService.verifyMcaptcha(meta.mcaptchaSecretKey, meta.mcaptchaSitekey, meta.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
|
||||
throw new FastifyReplyError(400, err);
|
||||
});
|
||||
}
|
||||
|
||||
if (meta.enableRecaptcha && meta.recaptchaSecretKey) {
|
||||
await this.captchaService.verifyRecaptcha(meta.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
|
||||
throw new FastifyReplyError(400, err);
|
||||
});
|
||||
}
|
||||
|
||||
if (meta.enableTurnstile && meta.turnstileSecretKey) {
|
||||
await this.captchaService.verifyTurnstile(meta.turnstileSecretKey, body['turnstile-response']).catch(err => {
|
||||
throw new FastifyReplyError(400, err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (same) {
|
||||
logger.info('Successfully signed in with password.');
|
||||
return this.signinService.signin(request, reply, user);
|
||||
|
@ -91,6 +91,7 @@ if (loaded || props.provider === 'mcaptcha') {
|
||||
|
||||
function reset() {
|
||||
if (captcha.value.reset) captcha.value.reset();
|
||||
emit('update:modelValue', null);
|
||||
}
|
||||
|
||||
async function requestRender() {
|
||||
|
@ -19,7 +19,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #prefix><i class="ti ti-lock"></i></template>
|
||||
<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
|
||||
</MkInput>
|
||||
<MkButton type="submit" large primary rounded :disabled="!user || signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
|
||||
<MkCaptcha v-if="!user?.twoFactorEnabled && instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :class="$style.captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
|
||||
<MkCaptcha v-if="!user?.twoFactorEnabled && instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
|
||||
<MkCaptcha v-if="!user?.twoFactorEnabled && instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
|
||||
<MkCaptcha v-if="!user?.twoFactorEnabled && instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
|
||||
<MkButton type="submit" large primary rounded :disabled="!user || captchaFailed || signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
|
||||
</div>
|
||||
<div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
|
||||
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
|
||||
@ -49,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, ref } from 'vue';
|
||||
import { computed, defineAsyncComponent, ref } from 'vue';
|
||||
import { toUnicode } from 'punycode/';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
|
||||
@ -62,6 +66,8 @@ import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { login } from '@/account.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
|
||||
|
||||
const signing = ref(false);
|
||||
const userAbortController = ref<AbortController>();
|
||||
@ -74,6 +80,22 @@ const totpLogin = ref(false);
|
||||
const isBackupCode = ref(false);
|
||||
const queryingKey = ref(false);
|
||||
const credentialRequest = ref<CredentialRequestOptions | null>(null);
|
||||
const hcaptcha = ref<Captcha | undefined>();
|
||||
const mcaptcha = ref<Captcha | undefined>();
|
||||
const recaptcha = ref<Captcha | undefined>();
|
||||
const turnstile = ref<Captcha | undefined>();
|
||||
const hCaptchaResponse = ref<string | null>(null);
|
||||
const mCaptchaResponse = ref<string | null>(null);
|
||||
const reCaptchaResponse = ref<string | null>(null);
|
||||
const turnstileResponse = ref<string | null>(null);
|
||||
|
||||
const captchaFailed = computed((): boolean => {
|
||||
return !user.value?.twoFactorEnabled && (
|
||||
instance.enableHcaptcha && !hCaptchaResponse.value ||
|
||||
instance.enableMcaptcha && !mCaptchaResponse.value ||
|
||||
instance.enableRecaptcha && !reCaptchaResponse.value ||
|
||||
instance.enableTurnstile && !turnstileResponse.value);
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'login', v: any): void;
|
||||
@ -170,6 +192,10 @@ function onSubmit(): void {
|
||||
misskeyApi('signin', {
|
||||
username: username.value,
|
||||
password: password.value,
|
||||
'hcaptcha-response': hCaptchaResponse.value,
|
||||
'm-captcha-response': mCaptchaResponse.value,
|
||||
'g-recaptcha-response': reCaptchaResponse.value,
|
||||
'turnstile-response': turnstileResponse.value,
|
||||
token: user.value?.twoFactorEnabled ? token.value : undefined,
|
||||
}).then(res => {
|
||||
emit('login', res);
|
||||
@ -179,6 +205,11 @@ function onSubmit(): void {
|
||||
}
|
||||
|
||||
function loginFailed(err: any): void {
|
||||
hcaptcha.value?.reset?.();
|
||||
mcaptcha.value?.reset?.();
|
||||
recaptcha.value?.reset?.();
|
||||
turnstile.value?.reset?.();
|
||||
|
||||
switch (err.id) {
|
||||
case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
|
||||
os.alert({
|
||||
|
@ -90,6 +90,7 @@ const emit = defineEmits<{
|
||||
const host = toUnicode(config.host);
|
||||
|
||||
const hcaptcha = ref<Captcha | undefined>();
|
||||
const mcaptcha = ref<Captcha | undefined>();
|
||||
const recaptcha = ref<Captcha | undefined>();
|
||||
const turnstile = ref<Captcha | undefined>();
|
||||
|
||||
@ -217,6 +218,7 @@ async function onSubmit(): Promise<void> {
|
||||
} catch {
|
||||
submitting.value = false;
|
||||
hcaptcha.value?.reset?.();
|
||||
mcaptcha.value?.reset?.();
|
||||
recaptcha.value?.reset?.();
|
||||
turnstile.value?.reset?.();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user