From 0922d928ff6fa0929e3d220eb2bd4a4cd2bdba68 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 13 Feb 2025 15:00:17 +0100 Subject: [PATCH] Request-a-copy improv: Altcha recaptcha component and service --- .../proof-of-work-captcha-data.service.ts | 25 ++++++++++ .../altcha-captcha.component.html | 6 +++ .../altcha-captcha.component.spec.ts | 46 +++++++++++++++++ .../altcha-captcha.component.ts | 50 +++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 src/app/core/data/proof-of-work-captcha-data.service.ts create mode 100644 src/app/item-page/bitstreams/request-a-copy/altcha-captcha.component.html create mode 100644 src/app/item-page/bitstreams/request-a-copy/altcha-captcha.component.spec.ts create mode 100644 src/app/item-page/bitstreams/request-a-copy/altcha-captcha.component.ts diff --git a/src/app/core/data/proof-of-work-captcha-data.service.ts b/src/app/core/data/proof-of-work-captcha-data.service.ts new file mode 100644 index 00000000000..1e1f886d36a --- /dev/null +++ b/src/app/core/data/proof-of-work-captcha-data.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { HALEndpointService } from '../shared/hal-endpoint.service'; + +@Injectable({ providedIn: 'root' }) +export class ProofOfWorkCaptchaDataService { + + private linkPath = 'captcha'; + + constructor( + private halService: HALEndpointService) { + } + + public getChallengeHref(): Observable { + return this.getEndpoint().pipe( + map((endpoint) => endpoint + '/challenge'), + ); + } + + protected getEndpoint(): Observable { + return this.halService.getEndpoint(this.linkPath); + } +} diff --git a/src/app/item-page/bitstreams/request-a-copy/altcha-captcha.component.html b/src/app/item-page/bitstreams/request-a-copy/altcha-captcha.component.html new file mode 100644 index 00000000000..c164c99b360 --- /dev/null +++ b/src/app/item-page/bitstreams/request-a-copy/altcha-captcha.component.html @@ -0,0 +1,6 @@ + diff --git a/src/app/item-page/bitstreams/request-a-copy/altcha-captcha.component.spec.ts b/src/app/item-page/bitstreams/request-a-copy/altcha-captcha.component.spec.ts new file mode 100644 index 00000000000..98a2fe15864 --- /dev/null +++ b/src/app/item-page/bitstreams/request-a-copy/altcha-captcha.component.spec.ts @@ -0,0 +1,46 @@ +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; + +import { AltchaCaptchaComponent } from './altcha-captcha.component'; + +describe('AltchaCaptchaComponent', () => { + let component: AltchaCaptchaComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), + AltchaCaptchaComponent, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(AltchaCaptchaComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create component successfully', () => { + expect(component).toBeTruthy(); + }); + + it('should emit payload when verification is successful', () => { + const testPayload = 'test-payload'; + const payloadSpy = jasmine.createSpy('payloadSpy'); + component.payload.subscribe(payloadSpy); + + const event = new CustomEvent('statechange', { + detail: { + state: 'verified', + payload: testPayload, + }, + }); + + document.querySelector('#altcha-widget').dispatchEvent(event); + + expect(payloadSpy).toHaveBeenCalledWith(testPayload); + }); +}); diff --git a/src/app/item-page/bitstreams/request-a-copy/altcha-captcha.component.ts b/src/app/item-page/bitstreams/request-a-copy/altcha-captcha.component.ts new file mode 100644 index 00000000000..9740068d676 --- /dev/null +++ b/src/app/item-page/bitstreams/request-a-copy/altcha-captcha.component.ts @@ -0,0 +1,50 @@ +import { + AsyncPipe, + NgIf, +} from '@angular/common'; +import { + Component, + CUSTOM_ELEMENTS_SCHEMA, + EventEmitter, + Input, + OnInit, + Output, +} from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { RouterLink } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; + +import { VarDirective } from '../../../shared/utils/var.directive'; + +@Component({ + selector: 'ds-altcha-captcha', + templateUrl: './altcha-captcha.component.html', + imports: [ + TranslateModule, + RouterLink, + AsyncPipe, + ReactiveFormsModule, + NgIf, + VarDirective, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + standalone: true, +}) +export class AltchaCaptchaComponent implements OnInit { + + @Input() challengeUrl: string; + @Input() autoload: string; + @Input() debug: boolean; + @Output() payload = new EventEmitter; + + ngOnInit(): void { + document.querySelector('#altcha-widget').addEventListener('statechange', (ev: any) => { + // state can be: unverified, verifying, verified, error + if (ev.detail.state === 'verified') { + // payload contains base64 encoded data for the server + this.payload.emit(ev.detail.payload); + } + }); + } + +}