feat: make captcha required when signin to improve security
This commit is contained in:
		
							parent
							
								
									6de40cf789
								
							
						
					
					
						commit
						b21b058005
					
				|  | @ -1,20 +1,37 @@ | |||
| import { randomBytes } from 'node:crypto'; | ||||
| import Koa from 'koa'; | ||||
| import bcrypt from 'bcryptjs'; | ||||
| import * as speakeasy from 'speakeasy'; | ||||
| import signin from '../common/signin.js'; | ||||
| import { IsNull } from 'typeorm'; | ||||
| import config from '@/config/index.js'; | ||||
| import { Users, Signins, UserProfiles, UserSecurityKeys, AttestationChallenges } from '@/models/index.js'; | ||||
| import { ILocalUser } from '@/models/entities/user.js'; | ||||
| import { genId } from '@/misc/gen-id.js'; | ||||
| import { fetchMeta } from '@/misc/fetch-meta.js'; | ||||
| import { verifyHcaptcha, verifyRecaptcha } from '@/misc/captcha.js'; | ||||
| import { verifyLogin, hash } from '../2fa.js'; | ||||
| import { randomBytes } from 'node:crypto'; | ||||
| import { IsNull } from 'typeorm'; | ||||
| import signin from '../common/signin.js'; | ||||
| 
 | ||||
| export default async (ctx: Koa.Context) => { | ||||
| 	ctx.set('Access-Control-Allow-Origin', config.url); | ||||
| 	ctx.set('Access-Control-Allow-Credentials', 'true'); | ||||
| 
 | ||||
| 	const body = ctx.request.body as any; | ||||
| 
 | ||||
| 	const instance = await fetchMeta(true); | ||||
| 
 | ||||
| 	if (instance.enableHcaptcha && instance.hcaptchaSecretKey) { | ||||
| 		await verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(e => { | ||||
| 			ctx.throw(400, e); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	if (instance.enableRecaptcha && instance.recaptchaSecretKey) { | ||||
| 		await verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(e => { | ||||
| 			ctx.throw(400, e); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	const username = body['username']; | ||||
| 	const password = body['password']; | ||||
| 	const token = body['token']; | ||||
|  | @ -155,7 +172,7 @@ export default async (ctx: Koa.Context) => { | |||
| 				body.credentialId | ||||
| 					.replace(/-/g, '+') | ||||
| 					.replace(/_/g, '/'), | ||||
| 					'base64' | ||||
| 				'base64', | ||||
| 			).toString('hex'), | ||||
| 		}); | ||||
| 
 | ||||
|  |  | |||
|  | @ -33,6 +33,8 @@ | |||
| 					<template #label>{{ $ts.token }}</template> | ||||
| 					<template #prefix><i class="fas fa-gavel"></i></template> | ||||
| 				</MkInput> | ||||
| 				<MkCaptcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/> | ||||
| 				<MkCaptcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/> | ||||
| 				<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton> | ||||
| 			</div> | ||||
| 		</div> | ||||
|  | @ -60,6 +62,7 @@ export default defineComponent({ | |||
| 	components: { | ||||
| 		MkButton, | ||||
| 		MkInput, | ||||
| 		MkCaptcha: defineAsyncComponent(() => import('./captcha.vue')), | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
|  | @ -90,6 +93,8 @@ export default defineComponent({ | |||
| 			credential: null, | ||||
| 			challengeData: null, | ||||
| 			queryingKey: false, | ||||
| 			hCaptchaResponse: null, | ||||
| 			reCaptchaResponse: null, | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
|  | @ -139,11 +144,13 @@ export default defineComponent({ | |||
| 				return os.api('signin', { | ||||
| 					username: this.username, | ||||
| 					password: this.password, | ||||
| 					'hcaptcha-response': this.hCaptchaResponse, | ||||
| 					'g-recaptcha-response': this.reCaptchaResponse, | ||||
| 					signature: hexify(credential.response.signature), | ||||
| 					authenticatorData: hexify(credential.response.authenticatorData), | ||||
| 					clientDataJSON: hexify(credential.response.clientDataJSON), | ||||
| 					credentialId: credential.id, | ||||
| 					challengeId: this.challengeData.challengeId | ||||
| 					challengeId: this.challengeData.challengeId, | ||||
| 				}); | ||||
| 			}).then(res => { | ||||
| 				this.$emit('login', res); | ||||
|  | @ -164,7 +171,9 @@ export default defineComponent({ | |||
| 				if (window.PublicKeyCredential && this.user.securityKeys) { | ||||
| 					os.api('signin', { | ||||
| 						username: this.username, | ||||
| 						password: this.password | ||||
| 						password: this.password, | ||||
| 						'hcaptcha-response': this.hCaptchaResponse, | ||||
| 						'g-recaptcha-response': this.reCaptchaResponse, | ||||
| 					}).then(res => { | ||||
| 						this.totpLogin = true; | ||||
| 						this.signing = false; | ||||
|  | @ -179,7 +188,9 @@ export default defineComponent({ | |||
| 				os.api('signin', { | ||||
| 					username: this.username, | ||||
| 					password: this.password, | ||||
| 					token: this.user && this.user.twoFactorEnabled ? this.token : undefined | ||||
| 					'hcaptcha-response': this.hCaptchaResponse, | ||||
| 					'g-recaptcha-response': this.reCaptchaResponse, | ||||
| 					token: this.user && this.user.twoFactorEnabled ? this.token : undefined, | ||||
| 				}).then(res => { | ||||
| 					this.$emit('login', res); | ||||
| 					this.onLogin(res); | ||||
|  |  | |||
|  | @ -58,8 +58,8 @@ | |||
| 				</template> | ||||
| 			</I18n> | ||||
| 		</MkSwitch> | ||||
| 		<captcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/> | ||||
| 		<captcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/> | ||||
| 		<MkCaptcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/> | ||||
| 		<MkCaptcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/> | ||||
| 		<MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ $ts.start }}</MkButton> | ||||
| 	</template> | ||||
| </form> | ||||
|  | @ -81,7 +81,7 @@ export default defineComponent({ | |||
| 		MkButton, | ||||
| 		MkInput, | ||||
| 		MkSwitch, | ||||
| 		captcha: defineAsyncComponent(() => import('./captcha.vue')), | ||||
| 		MkCaptcha: defineAsyncComponent(() => import('./captcha.vue')), | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue