1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 15:23:33 +00:00

Feature/use hcaptcha on register if bot (#434)

* Parse captcha required from error messages

CaptchaProtectedAttribute produces an error with captcha information.
We want to parse that data out to make it easily accessible to components

* Don't show error on catpcha

The component should hande this situation.

* Add captchaResponse to captcha protected api endpoints

* Extract captcha logic to abstract base class

* Add captcha to register

* linter fixes

* Make sure to log Captcha required responses

* Match file naming convention

* Separate import into logical groups by folder

* PR review
This commit is contained in:
Matt Gibson
2021-07-22 12:28:45 -05:00
committed by GitHub
parent ea0c8267d4
commit e9d9cd0182
8 changed files with 117 additions and 64 deletions

View File

@@ -0,0 +1,50 @@
import { Directive, Input } from '@angular/core';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CaptchaIFrame } from 'jslib-common/misc/captcha_iframe';
import { Utils } from 'jslib-common/misc/utils';
@Directive()
export abstract class CaptchaProtectedComponent {
@Input() captchaSiteKey: string = null;
captchaToken: string = null;
captcha: CaptchaIFrame;
constructor(protected environmentService: EnvironmentService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService) { }
async setupCaptcha() {
let webVaultUrl = this.environmentService.getWebVaultUrl();
if (webVaultUrl == null) {
webVaultUrl = 'https://vault.bitwarden.com';
}
this.captcha = new CaptchaIFrame(window, webVaultUrl,
this.i18nService, (token: string) => {
this.captchaToken = token;
}, (error: string) => {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), error);
}, (info: string) => {
this.platformUtilsService.showToast('info', this.i18nService.t('info'), info);
}
);
}
showCaptcha() {
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
}
protected handleCaptchaRequired(response: { captchaSiteKey: string; }): boolean {
if (Utils.isNullOrWhitespace(response.captchaSiteKey)) {
return false;
}
this.captchaSiteKey = response.captchaSiteKey;
this.captcha.init(response.captchaSiteKey);
return true;
}
}

View File

@@ -19,24 +19,22 @@ import { StorageService } from 'jslib-common/abstractions/storage.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { CaptchaIFrame } from 'jslib-common/misc/captcha_iframe';
import { Utils } from 'jslib-common/misc/utils';
import { CaptchaProtectedComponent } from './captchaProtected.component';
const Keys = {
rememberedEmail: 'rememberedEmail',
rememberEmail: 'rememberEmail',
};
@Directive()
export class LoginComponent implements OnInit {
export class LoginComponent extends CaptchaProtectedComponent implements OnInit {
@Input() email: string = '';
@Input() rememberEmail = true;
masterPassword: string = '';
showPassword: boolean = false;
captchaSiteKey: string = null;
captchaToken: string = null;
captcha: CaptchaIFrame;
formPromise: Promise<AuthResult>;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
@@ -46,10 +44,12 @@ export class LoginComponent implements OnInit {
protected successRoute = 'vault';
constructor(protected authService: AuthService, protected router: Router,
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
protected stateService: StateService, protected environmentService: EnvironmentService,
platformUtilsService: PlatformUtilsService, i18nService: I18nService,
protected stateService: StateService, environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService,
protected cryptoFunctionService: CryptoFunctionService, private storageService: StorageService) { }
protected cryptoFunctionService: CryptoFunctionService, private storageService: StorageService) {
super(environmentService, i18nService, platformUtilsService);
}
async ngOnInit() {
if (this.email == null || this.email === '') {
@@ -66,19 +66,7 @@ export class LoginComponent implements OnInit {
this.focusInput();
}
let webVaultUrl = this.environmentService.getWebVaultUrl();
if (webVaultUrl == null) {
webVaultUrl = 'https://vault.bitwarden.com';
}
this.captcha = new CaptchaIFrame(window, webVaultUrl,
this.i18nService, (token: string) => {
this.captchaToken = token;
}, (error: string) => {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), error);
}, (info: string) => {
this.platformUtilsService.showToast('info', this.i18nService.t('info'), info);
}
);
this.setupCaptcha();
}
async submit() {
@@ -107,9 +95,8 @@ export class LoginComponent implements OnInit {
} else {
await this.storageService.remove(Keys.rememberedEmail);
}
if (!Utils.isNullOrWhitespace(response.captchaSiteKey)) {
this.captchaSiteKey = response.captchaSiteKey;
this.captcha.init(response.captchaSiteKey);
if (this.handleCaptchaRequired(response)) {
return;
} else if (response.twoFactor) {
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
this.onSuccessfulLoginTwoFactorNavigate();
@@ -165,9 +152,6 @@ export class LoginComponent implements OnInit {
'&state=' + state + '&codeChallenge=' + codeChallenge);
}
showCaptcha() {
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
}
protected focusInput() {
document.getElementById(this.email == null || this.email === '' ? 'email' : 'masterPassword').focus();
}

View File

@@ -1,3 +1,4 @@
import { Directive, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { KeysRequest } from 'jslib-common/models/request/keysRequest';
@@ -7,6 +8,7 @@ import { RegisterRequest } from 'jslib-common/models/request/registerRequest';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
@@ -14,7 +16,10 @@ import { StateService } from 'jslib-common/abstractions/state.service';
import { KdfType } from 'jslib-common/enums/kdfType';
export class RegisterComponent {
import { CaptchaProtectedComponent } from './captchaProtected.component';
@Directive()
export class RegisterComponent extends CaptchaProtectedComponent implements OnInit {
name: string = '';
email: string = '';
masterPassword: string = '';
@@ -31,13 +36,18 @@ export class RegisterComponent {
private masterPasswordStrengthTimeout: any;
constructor(protected authService: AuthService, protected router: Router,
protected i18nService: I18nService, protected cryptoService: CryptoService,
i18nService: I18nService, protected cryptoService: CryptoService,
protected apiService: ApiService, protected stateService: StateService,
protected platformUtilsService: PlatformUtilsService,
protected passwordGenerationService: PasswordGenerationService) {
platformUtilsService: PlatformUtilsService,
protected passwordGenerationService: PasswordGenerationService, environmentService: EnvironmentService) {
super(environmentService, i18nService, platformUtilsService);
this.showTerms = !platformUtilsService.isSelfHost();
}
async ngOnInit() {
this.setupCaptcha();
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
@@ -127,7 +137,7 @@ export class RegisterComponent {
const hashedPassword = await this.cryptoService.hashPassword(this.masterPassword, key);
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const request = new RegisterRequest(this.email, this.name, hashedPassword,
this.hint, encKey[1].encryptedString, kdf, kdfIterations, this.referenceData);
this.hint, encKey[1].encryptedString, kdf, kdfIterations, this.referenceData, this.captchaToken);
request.keys = new KeysRequest(keys[0], keys[1].encryptedString);
const orgInvite = await this.stateService.get<any>('orgInvitation');
if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) {
@@ -137,7 +147,15 @@ export class RegisterComponent {
try {
this.formPromise = this.apiService.postRegister(request);
await this.formPromise;
try {
await this.formPromise;
} catch (e) {
if (this.handleCaptchaRequired(e)) {
return;
} else {
throw e;
}
}
this.platformUtilsService.showToast('success', null, this.i18nService.t('newAccountCreated'));
this.router.navigate([this.successRoute], { queryParams: { email: this.email } });
} catch { }