mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
252 lines
9.6 KiB
TypeScript
252 lines
9.6 KiB
TypeScript
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, Optional } from '@angular/core';
|
|
import {EpersonRegistrationService} from '../core/data/eperson-registration.service';
|
|
import {NotificationsService} from '../shared/notifications/notifications.service';
|
|
import {TranslateService} from '@ngx-translate/core';
|
|
import {Router} from '@angular/router';
|
|
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators, ValidatorFn } from '@angular/forms';
|
|
import {Registration} from '../core/shared/registration.model';
|
|
import {RemoteData} from '../core/data/remote-data';
|
|
import {ConfigurationDataService} from '../core/data/configuration-data.service';
|
|
import { getAllSucceededRemoteDataPayload, getFirstSucceededRemoteDataPayload } from '../core/shared/operators';
|
|
import {ConfigurationProperty} from '../core/shared/configuration-property.model';
|
|
import {isNotEmpty} from '../shared/empty.util';
|
|
import {BehaviorSubject, combineLatest, Observable, of, switchMap} from 'rxjs';
|
|
import {map, startWith, take} from 'rxjs/operators';
|
|
import {CAPTCHA_NAME, GoogleRecaptchaService} from '../core/google-recaptcha/google-recaptcha.service';
|
|
import {AlertType} from '../shared/alert/alert-type';
|
|
import {KlaroService} from '../shared/cookies/klaro.service';
|
|
import {CookieService} from '../core/services/cookie.service';
|
|
import { Subscription } from 'rxjs';
|
|
|
|
export const TYPE_REQUEST_FORGOT = 'forgot';
|
|
export const TYPE_REQUEST_REGISTER = 'register';
|
|
|
|
@Component({
|
|
selector: 'ds-register-email-form',
|
|
templateUrl: './register-email-form.component.html'
|
|
})
|
|
/**
|
|
* Component responsible to render an email registration form.
|
|
*/
|
|
export class RegisterEmailFormComponent implements OnDestroy, OnInit {
|
|
|
|
/**
|
|
* The form containing the mail address
|
|
*/
|
|
form: UntypedFormGroup;
|
|
|
|
/**
|
|
* The message prefix
|
|
*/
|
|
@Input()
|
|
MESSAGE_PREFIX: string;
|
|
|
|
/**
|
|
* Type of register request to be done, register new email or forgot password (same endpoint)
|
|
*/
|
|
@Input()
|
|
typeRequest: string = null;
|
|
|
|
public AlertTypeEnum = AlertType;
|
|
|
|
/**
|
|
* registration verification configuration
|
|
*/
|
|
registrationVerification = false;
|
|
|
|
/**
|
|
* Return true if the user completed the reCaptcha verification (checkbox mode)
|
|
*/
|
|
checkboxCheckedSubject$ = new BehaviorSubject<boolean>(false);
|
|
|
|
disableUntilChecked = true;
|
|
|
|
validMailDomains: string[];
|
|
TYPE_REQUEST_REGISTER = TYPE_REQUEST_REGISTER;
|
|
|
|
subscriptions: Subscription[] = [];
|
|
|
|
captchaVersion(): Observable<string> {
|
|
return this.googleRecaptchaService.captchaVersion();
|
|
}
|
|
|
|
captchaMode(): Observable<string> {
|
|
return this.googleRecaptchaService.captchaMode();
|
|
}
|
|
|
|
constructor(
|
|
private epersonRegistrationService: EpersonRegistrationService,
|
|
private notificationService: NotificationsService,
|
|
private translateService: TranslateService,
|
|
private router: Router,
|
|
private formBuilder: UntypedFormBuilder,
|
|
private configService: ConfigurationDataService,
|
|
public googleRecaptchaService: GoogleRecaptchaService,
|
|
public cookieService: CookieService,
|
|
@Optional() public klaroService: KlaroService,
|
|
private changeDetectorRef: ChangeDetectorRef,
|
|
private notificationsService: NotificationsService,
|
|
) {
|
|
}
|
|
|
|
ngOnDestroy(): void {
|
|
this.subscriptions.forEach((sub: Subscription) => sub.unsubscribe());
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
const validators: ValidatorFn[] = [
|
|
Validators.required,
|
|
Validators.email,
|
|
// Regex pattern borrowed from HTML5 specs for a valid email address:
|
|
// https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
|
|
Validators.pattern('^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$')
|
|
];
|
|
this.form = this.formBuilder.group({
|
|
email: new UntypedFormControl('', {
|
|
validators: validators,
|
|
})
|
|
});
|
|
this.validMailDomains = [];
|
|
if (this.typeRequest === TYPE_REQUEST_REGISTER) {
|
|
this.subscriptions.push(this.configService.findByPropertyName('authentication-password.domain.valid')
|
|
.pipe(getAllSucceededRemoteDataPayload())
|
|
.subscribe((remoteData: ConfigurationProperty) => {
|
|
this.validMailDomains = remoteData.values;
|
|
for (const remoteValue of remoteData.values) {
|
|
if (this.validMailDomains.length !== 0) {
|
|
this.form.get('email').setValidators([
|
|
...validators,
|
|
Validators.pattern(this.validMailDomains.map((domain: string) => '(^.*' + domain.replace(new RegExp('\\.', 'g'), '\\.') + '$)').join('|')),
|
|
]);
|
|
this.form.updateValueAndValidity();
|
|
}
|
|
}
|
|
this.changeDetectorRef.detectChanges();
|
|
}));
|
|
}
|
|
this.subscriptions.push(this.configService.findByPropertyName('registration.verification.enabled').pipe(
|
|
getFirstSucceededRemoteDataPayload(),
|
|
map((res: ConfigurationProperty) => res?.values[0].toLowerCase() === 'true')
|
|
).subscribe((res: boolean) => {
|
|
this.registrationVerification = res;
|
|
}));
|
|
|
|
this.subscriptions.push(this.disableUntilCheckedFcn().subscribe((res) => {
|
|
this.disableUntilChecked = res;
|
|
this.changeDetectorRef.detectChanges();
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* execute the captcha function for v2 invisible
|
|
*/
|
|
executeRecaptcha() {
|
|
this.googleRecaptchaService.executeRecaptcha();
|
|
}
|
|
|
|
/**
|
|
* Register an email address
|
|
*/
|
|
register(tokenV2?) {
|
|
if (!this.form.invalid) {
|
|
if (this.registrationVerification) {
|
|
this.subscriptions.push(combineLatest([this.captchaVersion(), this.captchaMode()]).pipe(
|
|
switchMap(([captchaVersion, captchaMode]) => {
|
|
if (captchaVersion === 'v3') {
|
|
return this.googleRecaptchaService.getRecaptchaToken('register_email');
|
|
} else if (captchaVersion === 'v2' && captchaMode === 'checkbox') {
|
|
return of(this.googleRecaptchaService.getRecaptchaTokenResponse());
|
|
} else if (captchaVersion === 'v2' && captchaMode === 'invisible') {
|
|
return of(tokenV2);
|
|
} else {
|
|
console.error(`Invalid reCaptcha configuration: version = ${captchaVersion}, mode = ${captchaMode}`);
|
|
this.showNotification('error');
|
|
}
|
|
}),
|
|
take(1),
|
|
).subscribe((token) => {
|
|
if (isNotEmpty(token)) {
|
|
this.registration(token);
|
|
} else {
|
|
console.error('reCaptcha error');
|
|
this.showNotification('error');
|
|
}
|
|
}
|
|
));
|
|
} else {
|
|
this.registration();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registration of an email address
|
|
*/
|
|
registration(captchaToken = null) {
|
|
let registerEmail$ = captchaToken ?
|
|
this.epersonRegistrationService.registerEmail(this.email.value, captchaToken, this.typeRequest) :
|
|
this.epersonRegistrationService.registerEmail(this.email.value, null, this.typeRequest);
|
|
this.subscriptions.push(registerEmail$.subscribe((response: RemoteData<Registration>) => {
|
|
if (response.hasSucceeded) {
|
|
this.notificationService.success(this.translateService.get(`${this.MESSAGE_PREFIX}.success.head`),
|
|
this.translateService.get(`${this.MESSAGE_PREFIX}.success.content`, {email: this.email.value}));
|
|
this.router.navigate(['/home']);
|
|
} else if (response.statusCode === 422) {
|
|
this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), this.translateService.get(`${this.MESSAGE_PREFIX}.error.maildomain`, {domains: this.validMailDomains.join(', ')}));
|
|
} else {
|
|
this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`),
|
|
this.translateService.get(`${this.MESSAGE_PREFIX}.error.content`, {email: this.email.value}));
|
|
}
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Return true if the user has accepted the required cookies for reCaptcha
|
|
*/
|
|
isRecaptchaCookieAccepted(): boolean {
|
|
const klaroAnonymousCookie = this.cookieService.get('klaro-anonymous');
|
|
return isNotEmpty(klaroAnonymousCookie) ? klaroAnonymousCookie[CAPTCHA_NAME] : false;
|
|
}
|
|
|
|
/**
|
|
* Return true if the user has not completed the reCaptcha verification (checkbox mode)
|
|
*/
|
|
disableUntilCheckedFcn(): Observable<boolean> {
|
|
const checked$ = this.checkboxCheckedSubject$.asObservable();
|
|
return combineLatest([this.captchaVersion(), this.captchaMode(), checked$]).pipe(
|
|
// disable if checkbox is not checked or if reCaptcha is not in v2 checkbox mode
|
|
switchMap(([captchaVersion, captchaMode, checked]) => captchaVersion === 'v2' && captchaMode === 'checkbox' ? of(!checked) : of(false)),
|
|
startWith(true),
|
|
);
|
|
}
|
|
|
|
get email() {
|
|
return this.form.get('email');
|
|
}
|
|
|
|
onCheckboxChecked(checked: boolean) {
|
|
this.checkboxCheckedSubject$.next(checked);
|
|
}
|
|
|
|
/**
|
|
* Show a notification to the user
|
|
* @param key
|
|
*/
|
|
showNotification(key) {
|
|
const notificationTitle = this.translateService.get(this.MESSAGE_PREFIX + '.google-recaptcha.notification.title');
|
|
const notificationErrorMsg = this.translateService.get(this.MESSAGE_PREFIX + '.google-recaptcha.notification.message.error');
|
|
const notificationExpiredMsg = this.translateService.get(this.MESSAGE_PREFIX + '.google-recaptcha.notification.message.expired');
|
|
switch (key) {
|
|
case 'expired':
|
|
this.notificationsService.warning(notificationTitle, notificationExpiredMsg);
|
|
break;
|
|
case 'error':
|
|
this.notificationsService.error(notificationTitle, notificationErrorMsg);
|
|
break;
|
|
default:
|
|
console.warn(`Unimplemented notification '${key}' from reCaptcha service`);
|
|
}
|
|
}
|
|
|
|
}
|