Skip to content

Commit

Permalink
fix: handle google recaptcha v3 expiration (#1347)
Browse files Browse the repository at this point in the history
* fix: handle google recaptcha v3 expiration
* fix: fetch captcha token after a REST error failed

reCAPTCHA tokens expire after two minutes. It is recommended in the documentation to make sure to
call execute when the user takes the action rather than on page load. An automatic refresh has been set up after the form becomes valid.

---------

Co-authored-by: Silke <s.grueber@intershop.de>
  • Loading branch information
tbouliere-datasolution and SGrueber authored Feb 1, 2023
1 parent 0e3c633 commit db071ec
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { ChangeDetectionStrategy, Component, Input, NgModule, OnDestroy, OnInit
import { FormGroup, Validators } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { RECAPTCHA_V3_SITE_KEY, ReCaptchaV3Service, RecaptchaV3Module } from 'ng-recaptcha';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Subject, timer } from 'rxjs';
import { filter, switchMap, take, takeUntil } from 'rxjs/operators';

import { DirectivesModule } from 'ish-core/directives.module';
import { whenTruthy } from 'ish-core/utils/operators';

import {
SitekeyProviderService,
Expand All @@ -32,13 +33,39 @@ export class CaptchaV3Component implements OnInit, OnDestroy {
ngOnInit() {
this.parentForm.get('captchaAction').setValidators([Validators.required]);

this.recaptchaV3Service
.execute(this.parentForm.get('captchaAction').value)
.pipe(takeUntil(this.destroy$))
.subscribe(token => {
this.parentForm.get('captcha').setValue(token);
this.parentForm.get('captcha').updateValueAndValidity();
});
// as soon as the form gets valid request a captcha token every 2 minutes
if (!SSR) {
this.parentForm.statusChanges
.pipe(
filter(status => status === 'VALID'),
take(1),
switchMap(() =>
timer(0, 2 * (60 - 3) * 1000).pipe(
switchMap(() => this.recaptchaV3Service.execute(this.parentForm.get('captchaAction').value))
)
),
whenTruthy(),
takeUntil(this.destroy$)
)
.subscribe(token => {
this.parentForm.get('captcha').setValue(token);
this.parentForm.get('captcha').updateValueAndValidity();
});

// if the captcha is set to undefined from outside request a captcha token
this.parentForm
.get('captcha')
.valueChanges.pipe(
filter(token => token === undefined),
switchMap(() => this.recaptchaV3Service.execute(this.parentForm.get('captchaAction').value)),
whenTruthy(),
takeUntil(this.destroy$)
)
.subscribe(token => {
this.parentForm.get('captcha').setValue(token);
this.parentForm.get('captcha').updateValueAndValidity();
});
}
}

ngOnDestroy() {
Expand Down
16 changes: 12 additions & 4 deletions src/app/pages/registration/registration-page.component.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/* eslint-disable ish-custom-rules/no-intelligence-in-artifacts */
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { UntypedFormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { FormlyFieldConfig, FormlyFormOptions } from '@ngx-formly/core';
import { Observable } from 'rxjs';
import { Observable, tap } from 'rxjs';

import { AccountFacade } from 'ish-core/facades/account.facade';
import { HttpError } from 'ish-core/models/http-error/http-error.model';
import { whenTruthy } from 'ish-core/utils/operators';
import { markAsDirtyRecursive } from 'ish-shared/forms/utils/form-utils';

import {
Expand Down Expand Up @@ -39,10 +40,13 @@ export class RegistrationPageComponent implements OnInit {
fields: FormlyFieldConfig[];
model: Record<string, unknown>;
options: FormlyFormOptions;
form = new FormGroup({});
form = new UntypedFormGroup({});

ngOnInit() {
this.error$ = this.registrationFormConfiguration.getErrorSources();
this.error$ = this.registrationFormConfiguration.getErrorSources().pipe(
whenTruthy(),
tap(() => this.clearCaptchaToken())
);

const snapshot = this.route.snapshot;
this.model = this.registrationFormConfiguration.extractModel(snapshot);
Expand Down Expand Up @@ -70,4 +74,8 @@ export class RegistrationPageComponent implements OnInit {
get submitDisabled(): boolean {
return this.form.invalid && this.submitted;
}

private clearCaptchaToken() {
this.form.get('captcha')?.setValue(undefined);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export class RegistrationFormConfigurationService {

getErrorSources() {
return merge(
this.accountFacade.userError$.pipe(filter(error => error.status !== 404)),
this.accountFacade.userError$.pipe(filter(error => error?.status !== 404)),
this.accountFacade.ssoRegistrationError$
);
}
Expand Down

0 comments on commit db071ec

Please sign in to comment.