diff --git a/src/app/app-routes.ts b/src/app/app-routes.ts index c298068281..a2667cf2ac 100644 --- a/src/app/app-routes.ts +++ b/src/app/app-routes.ts @@ -1,4 +1,8 @@ -import { InMemoryScrollingOptions, Route, RouterConfigOptions, } from '@angular/router'; +import { + InMemoryScrollingOptions, + Route, + RouterConfigOptions, +} from '@angular/router'; import { NOTIFICATIONS_MODULE_PATH } from './admin/admin-routing-paths'; import { @@ -21,12 +25,8 @@ import { COLLECTION_MODULE_PATH } from './collection-page/collection-page-routin import { COMMUNITY_MODULE_PATH } from './community-page/community-page-routing-paths'; import { authBlockingGuard } from './core/auth/auth-blocking.guard'; import { authenticatedGuard } from './core/auth/authenticated.guard'; -import { - groupAdministratorGuard -} from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; -import { - siteAdministratorGuard -} from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; +import { groupAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; +import { siteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; import { siteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard'; import { endUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard'; import { reloadGuard } from './core/reload/reload.guard'; @@ -37,9 +37,7 @@ import { ITEM_MODULE_PATH } from './item-page/item-page-routing-paths'; import { menuResolver } from './menuResolver'; import { provideSuggestionNotificationsState } from './notifications/provide-suggestion-notifications-state'; import { ThemedPageErrorComponent } from './page-error/themed-page-error.component'; -import { - ThemedPageInternalServerErrorComponent -} from './page-internal-server-error/themed-page-internal-server-error.component'; +import { ThemedPageInternalServerErrorComponent } from './page-internal-server-error/themed-page-internal-server-error.component'; import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component'; import { PROCESS_MODULE_PATH } from './process-page/process-page-routing.paths'; import { provideSubmissionState } from './submission/provide-submission-state'; @@ -261,17 +259,17 @@ export const APP_ROUTES: Route[] = [ }, { path: 'external-login/:token', - loadChildren: () => import('./external-login-page/external-login-routes').then((m) => m.ROUTES) + loadChildren: () => import('./external-login-page/external-login-routes').then((m) => m.ROUTES), }, { path: 'review-account/:token', loadChildren: () => import('./external-login-review-account-info-page/external-login-review-account-info-page-routes') - .then((m) => m.ROUTES) + .then((m) => m.ROUTES), }, { path: 'email-confirmation', loadChildren: () => import('./external-login-email-confirmation-page/external-login-email-confirmation-page-routes') - .then((m) => m.ROUTES) + .then((m) => m.ROUTES), }, { path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent }, ], diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index 5c4843a937..3fb0de9d50 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -1,22 +1,36 @@ import { Observable } from 'rxjs'; -import { distinctUntilChanged, filter, map, switchMap, take, tap, } from 'rxjs/operators'; +import { + distinctUntilChanged, + filter, + map, + switchMap, + take, + tap, +} from 'rxjs/operators'; -import { isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; +import { + isNotEmpty, + isNotEmptyOperator, +} from '../../shared/empty.util'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; -import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { RequestService } from '../data/request.service'; -import { DeleteRequest, GetRequest, PostRequest } from '../data/request.models'; -import { HttpOptions } from '../dspace-rest/dspace-rest.service'; -import { getFirstCompletedRemoteData } from '../shared/operators'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteData } from '../data/remote-data'; +import { + DeleteRequest, + GetRequest, + PostRequest, +} from '../data/request.models'; +import { RequestService } from '../data/request.service'; import { RestRequest } from '../data/rest-request.model'; +import { HttpOptions } from '../dspace-rest/dspace-rest.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { NoContent } from '../shared/NoContent.model'; +import { getFirstCompletedRemoteData } from '../shared/operators'; +import { sendRequest } from '../shared/request.operators'; import { URLCombiner } from '../url-combiner/url-combiner'; import { AuthStatus } from './models/auth-status.model'; -import { ShortLivedToken } from './models/short-lived-token.model'; import { MachineToken } from './models/machine-token.model'; -import { NoContent } from '../shared/NoContent.model'; -import { sendRequest } from '../shared/request.operators'; +import { ShortLivedToken } from './models/short-lived-token.model'; /** * Abstract service to send authentication requests @@ -144,7 +158,7 @@ export abstract class AuthRequestService { map((href: string) => new URLCombiner(href, this.machinetokenEndpoint).toString()), map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL)), tap((request: RestRequest) => this.requestService.send(request)), - switchMap((request: RestRequest) => this.rdbService.buildFromRequestUUID(request.uuid)) + switchMap((request: RestRequest) => this.rdbService.buildFromRequestUUID(request.uuid)), ); } diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 27ed870a6a..59aabf25ec 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -1,14 +1,33 @@ import { HttpHeaders } from '@angular/common/http'; -import { Inject, Injectable, Optional, } from '@angular/core'; +import { + Inject, + Injectable, + Optional, +} from '@angular/core'; import { Router } from '@angular/router'; -import { select, Store, } from '@ngrx/store'; +import { + select, + Store, +} from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { CookieAttributes } from 'js-cookie'; -import { Observable, of as observableOf, } from 'rxjs'; -import { filter, map, startWith, switchMap, take, } from 'rxjs/operators'; +import { + Observable, + of as observableOf, +} from 'rxjs'; +import { + filter, + map, + startWith, + switchMap, + take, +} from 'rxjs/operators'; import { environment } from '../../../environments/environment'; -import { REQUEST, RESPONSE, } from '../../../express.tokens'; +import { + REQUEST, + RESPONSE, +} from '../../../express.tokens'; import { AppState } from '../../app.reducer'; import { hasNoValue, @@ -22,7 +41,10 @@ import { import { NotificationsService } from '../../shared/notifications/notifications.service'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { followLink } from '../../shared/utils/follow-link-config.model'; -import { buildPaginatedList, PaginatedList, } from '../data/paginated-list.model'; +import { + buildPaginatedList, + PaginatedList, +} from '../data/paginated-list.model'; import { RemoteData } from '../data/remote-data'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { EPersonDataService } from '../eperson/eperson-data.service'; @@ -32,17 +54,16 @@ import { CookieService } from '../services/cookie.service'; import { HardRedirectService } from '../services/hard-redirect.service'; import { RouteService } from '../services/route.service'; import { - getAuthenticatedUserId, - getAuthenticationToken, - getExternalAuthCookieStatus, - getRedirectUrl, - isAuthenticated, - isAuthenticatedLoaded, - isIdle, - isTokenRefreshing -} from './selectors'; -import { getAllSucceededRemoteDataPayload, getFirstCompletedRemoteData, } from '../shared/operators'; + NativeWindowRef, + NativeWindowService, +} from '../services/window.service'; +import { NoContent } from '../shared/NoContent.model'; +import { + getAllSucceededRemoteDataPayload, + getFirstCompletedRemoteData, +} from '../shared/operators'; import { PageInfo } from '../shared/page-info.model'; +import { URLCombiner } from '../url-combiner/url-combiner'; import { CheckAuthenticationTokenAction, RefreshTokenAction, @@ -55,11 +76,21 @@ import { import { AuthRequestService } from './auth-request.service'; import { AuthMethod } from './models/auth.method'; import { AuthStatus } from './models/auth-status.model'; -import { AuthTokenInfo, TOKENITEM, } from './models/auth-token-info.model'; -import { NoContent } from '../shared/NoContent.model'; -import { URLCombiner } from '../url-combiner/url-combiner'; +import { + AuthTokenInfo, + TOKENITEM, +} from './models/auth-token-info.model'; import { MachineToken } from './models/machine-token.model'; -import { NativeWindowRef, NativeWindowService } from '../services/window.service'; +import { + getAuthenticatedUserId, + getAuthenticationToken, + getExternalAuthCookieStatus, + getRedirectUrl, + isAuthenticated, + isAuthenticatedLoaded, + isIdle, + isTokenRefreshing, +} from './selectors'; export const LOGIN_ROUTE = '/login'; export const LOGOUT_ROUTE = '/logout'; diff --git a/src/app/core/auth/models/machine-token.model.ts b/src/app/core/auth/models/machine-token.model.ts index 0b22efb8bf..1d146d743a 100644 --- a/src/app/core/auth/models/machine-token.model.ts +++ b/src/app/core/auth/models/machine-token.model.ts @@ -1,10 +1,14 @@ -import { autoserialize, autoserializeAs, deserialize } from 'cerialize'; +import { + autoserialize, + autoserializeAs, + deserialize, +} from 'cerialize'; import { typedObject } from '../../cache/builders/build-decorators'; import { CacheableObject } from '../../cache/cacheable-object.model'; -import { excludeFromEquals } from '../../utilities/equals.decorators'; -import { ResourceType } from '../../shared/resource-type'; import { HALLink } from '../../shared/hal-link.model'; +import { ResourceType } from '../../shared/resource-type'; +import { excludeFromEquals } from '../../utilities/equals.decorators'; import { MACHINE_TOKEN } from './machine-token.resource-type'; /** diff --git a/src/app/core/data/eperson-registration.service.ts b/src/app/core/data/eperson-registration.service.ts index 524a9bbda3..29c110d80e 100644 --- a/src/app/core/data/eperson-registration.service.ts +++ b/src/app/core/data/eperson-registration.service.ts @@ -1,22 +1,36 @@ -import { HttpHeaders, HttpParams, } from '@angular/common/http'; +import { + HttpHeaders, + HttpParams, +} from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { RequestService } from './request.service'; -import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { GetRequest, PatchRequest, PostRequest } from './request.models'; +import { Operation } from 'fast-json-patch'; import { Observable } from 'rxjs'; -import { filter, find, map, } from 'rxjs/operators'; +import { + filter, + find, + map, +} from 'rxjs/operators'; -import { hasValue, isNotEmpty, } from '../../shared/empty.util'; +import { + hasValue, + isNotEmpty, +} from '../../shared/empty.util'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { GenericConstructor } from '../shared/generic-constructor'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { NoContent } from '../shared/NoContent.model'; import { getFirstCompletedRemoteData } from '../shared/operators'; import { Registration } from '../shared/registration.model'; import { ResponseParsingService } from './parsing.service'; import { RegistrationResponseParsingService } from './registration-response-parsing.service'; import { RemoteData } from './remote-data'; -import { Operation } from 'fast-json-patch'; -import { NoContent } from '../shared/NoContent.model'; +import { + GetRequest, + PatchRequest, + PostRequest, +} from './request.models'; +import { RequestService } from './request.service'; @Injectable({ providedIn: 'root', @@ -116,10 +130,10 @@ export class EpersonRegistrationService { map((rd) => { if (rd.hasSucceeded && hasValue(rd.payload)) { return Object.assign(rd, { payload: Object.assign(new Registration(), { - email: rd.payload.email, - token: token, - user: rd.payload.user, - }) }); + email: rd.payload.email, + token: token, + user: rd.payload.user, + }) }); } else { return rd; } @@ -144,7 +158,7 @@ export class EpersonRegistrationService { Object.assign(request, { getResponseParser(): GenericConstructor { return RegistrationResponseParsingService; - } + }, }); this.requestService.send(request, true); }); @@ -186,7 +200,7 @@ export class EpersonRegistrationService { let operations = []; if (values.length > 0 && hasValue(field) ) { operations = [{ - op: operator, path: `/${field}`, value: values + op: operator, path: `/${field}`, value: values, }]; } diff --git a/src/app/core/eperson/eperson-data.service.spec.ts b/src/app/core/eperson/eperson-data.service.spec.ts index 84ab2e2d67..ac735d8e92 100644 --- a/src/app/core/eperson/eperson-data.service.spec.ts +++ b/src/app/core/eperson/eperson-data.service.spec.ts @@ -1,8 +1,19 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { fakeAsync, TestBed, tick, waitForAsync, } from '@angular/core/testing'; +import { + fakeAsync, + TestBed, + tick, + waitForAsync, +} from '@angular/core/testing'; import { Store } from '@ngrx/store'; -import { MockStore, provideMockStore, } from '@ngrx/store/testing'; -import { compare, Operation, } from 'fast-json-patch'; +import { + MockStore, + provideMockStore, +} from '@ngrx/store/testing'; +import { + compare, + Operation, +} from 'fast-json-patch'; import { cold } from 'jasmine-marbles'; import { of as observableOf } from 'rxjs'; @@ -13,12 +24,21 @@ import { import { getMockRemoteDataBuildServiceHrefMap } from '../../shared/mocks/remote-data-build.service.mock'; import { getMockRequestService } from '../../shared/mocks/request.service.mock'; import { NotificationsService } from '../../shared/notifications/notifications.service'; -import { createNoContentRemoteDataObject$, createSuccessfulRemoteDataObject$, } from '../../shared/remote-data.utils'; -import { EPersonMock, EPersonMock2, } from '../../shared/testing/eperson.mock'; +import { + createNoContentRemoteDataObject$, + createSuccessfulRemoteDataObject$, +} from '../../shared/remote-data.utils'; +import { + EPersonMock, + EPersonMock2, +} from '../../shared/testing/eperson.mock'; import { GroupMock } from '../../shared/testing/group-mock'; import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; -import { createPaginatedList, createRequestEntry$, } from '../../shared/testing/utils.test'; +import { + createPaginatedList, + createRequestEntry$, +} from '../../shared/testing/utils.test'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RequestParam } from '../cache/models/request-param.model'; import { ObjectCacheService } from '../cache/object-cache.service'; @@ -26,13 +46,19 @@ import { CoreState } from '../core-state.model'; import { ChangeAnalyzer } from '../data/change-analyzer'; import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; import { FindListOptions } from '../data/find-list-options.model'; -import { PatchRequest, PostRequest, } from '../data/request.models'; +import { RemoteData } from '../data/remote-data'; +import { + PatchRequest, + PostRequest, +} from '../data/request.models'; import { RequestService } from '../data/request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Item } from '../shared/item.model'; -import { editEPersonSelector, EPersonDataService, } from './eperson-data.service'; +import { + editEPersonSelector, + EPersonDataService, +} from './eperson-data.service'; import { EPerson } from './models/eperson.model'; -import { RemoteData } from '../data/remote-data'; describe('EPersonDataService', () => { let service: EPersonDataService; diff --git a/src/app/core/eperson/eperson-data.service.ts b/src/app/core/eperson/eperson-data.service.ts index 7ffaf0f3ee..33a02de4d7 100644 --- a/src/app/core/eperson/eperson-data.service.ts +++ b/src/app/core/eperson/eperson-data.service.ts @@ -1,8 +1,16 @@ import { Injectable } from '@angular/core'; -import { createSelector, select, Store, } from '@ngrx/store'; +import { + createSelector, + select, + Store, +} from '@ngrx/store'; import { Operation } from 'fast-json-patch'; import { Observable } from 'rxjs'; -import { find, map, take, } from 'rxjs/operators'; +import { + find, + map, + take, +} from 'rxjs/operators'; import { getEPersonEditRoute } from '../../access-control/access-control-routing-paths'; import { @@ -11,27 +19,51 @@ import { } from '../../access-control/epeople-registry/epeople-registry.actions'; import { EPeopleRegistryState } from '../../access-control/epeople-registry/epeople-registry.reducers'; import { AppState } from '../../app.reducer'; -import { hasNoValue, hasValue, } from '../../shared/empty.util'; +import { + hasNoValue, + hasValue, +} from '../../shared/empty.util'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RequestParam } from '../cache/models/request-param.model'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { CreateData, CreateDataImpl, } from '../data/base/create-data'; -import { DeleteData, DeleteDataImpl, } from '../data/base/delete-data'; +import { + CreateData, + CreateDataImpl, +} from '../data/base/create-data'; +import { + DeleteData, + DeleteDataImpl, +} from '../data/base/delete-data'; import { IdentifiableDataService } from '../data/base/identifiable-data.service'; -import { PatchData, PatchDataImpl, } from '../data/base/patch-data'; -import { SearchData, SearchDataImpl, } from '../data/base/search-data'; +import { + PatchData, + PatchDataImpl, +} from '../data/base/patch-data'; +import { + SearchData, + SearchDataImpl, +} from '../data/base/search-data'; import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; import { FindListOptions } from '../data/find-list-options.model'; -import { buildPaginatedList, PaginatedList, } from '../data/paginated-list.model'; +import { + buildPaginatedList, + PaginatedList, +} from '../data/paginated-list.model'; import { RemoteData } from '../data/remote-data'; -import { PatchRequest, PostRequest, } from '../data/request.models'; +import { + PatchRequest, + PostRequest, +} from '../data/request.models'; import { RequestService } from '../data/request.service'; import { RestRequestMethod } from '../data/rest-request-method'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { NoContent } from '../shared/NoContent.model'; -import { getFirstSucceededRemoteData, getRemoteDataPayload, } from '../shared/operators'; +import { + getFirstSucceededRemoteData, + getRemoteDataPayload, +} from '../shared/operators'; import { PageInfo } from '../shared/page-info.model'; import { EPerson } from './models/eperson.model'; @@ -375,8 +407,8 @@ export class EPersonDataService extends IdentifiableDataService impleme map((href: string) => hasValue(metadataKey) ? `${href}/${uuid}?token=${token}&override=${metadataKey}` - : `${href}/${uuid}?token=${token}` - ) + : `${href}/${uuid}?token=${token}`, + ), ); hrefObs.pipe( diff --git a/src/app/core/shared/registration.model.ts b/src/app/core/shared/registration.model.ts index 8bb02a1251..90663042fc 100644 --- a/src/app/core/shared/registration.model.ts +++ b/src/app/core/shared/registration.model.ts @@ -1,10 +1,10 @@ // eslint-disable-next-line max-classes-per-file +import { AuthRegistrationType } from '../auth/models/auth.registration-type'; import { typedObject } from '../cache/builders/build-decorators'; +import { MetadataValue } from './metadata.models'; import { REGISTRATION } from './registration.resource-type'; import { ResourceType } from './resource-type'; import { UnCacheableObject } from './uncacheable-object.model'; -import { MetadataValue } from './metadata.models'; -import { AuthRegistrationType } from '../auth/models/auth.registration-type'; export class RegistrationDataMetadataMap { [key: string]: RegistrationDataMetadataValue[]; diff --git a/src/app/external-log-in/decorators/external-log-in.methods-decorator.ts b/src/app/external-log-in/decorators/external-log-in.methods-decorator.ts index ce90aea0a3..0144924776 100644 --- a/src/app/external-log-in/decorators/external-log-in.methods-decorator.ts +++ b/src/app/external-log-in/decorators/external-log-in.methods-decorator.ts @@ -9,7 +9,7 @@ const authMethodsMap = new Map(); * @param authMethodType the type of the external login method */ export function renderExternalLoginConfirmationFor( - authMethodType: AuthRegistrationType + authMethodType: AuthRegistrationType, ) { return function decorator(objectElement: any) { if (!objectElement) { @@ -23,7 +23,7 @@ export function renderExternalLoginConfirmationFor( * @param authMethodType the type of the external login method */ export function getExternalLoginConfirmationType( - authMethodType: AuthRegistrationType + authMethodType: AuthRegistrationType, ) { return authMethodsMap.get(authMethodType); } diff --git a/src/app/external-log-in/decorators/external-login-method-entry.component.ts b/src/app/external-log-in/decorators/external-login-method-entry.component.ts index 47158274b8..d4854cd4bb 100644 --- a/src/app/external-log-in/decorators/external-login-method-entry.component.ts +++ b/src/app/external-log-in/decorators/external-login-method-entry.component.ts @@ -1,22 +1,20 @@ -import { Component, Inject } from '@angular/core'; +import { Inject } from '@angular/core'; + import { Registration } from '../../core/shared/registration.model'; /** * This component renders a form to complete the registration process */ -@Component({ - template: '' -}) export abstract class ExternalLoginMethodEntryComponent { /** * The registration data object */ - public registratioData: Registration; + public registrationData: Registration; - constructor( + protected constructor( @Inject('registrationDataProvider') protected injectedRegistrationDataObject: Registration, ) { - this.registratioData = injectedRegistrationDataObject; + this.registrationData = injectedRegistrationDataObject; } } diff --git a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.spec.ts b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.spec.ts index 9e514472f0..5aa2b91653 100644 --- a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.spec.ts +++ b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.spec.ts @@ -1,24 +1,40 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ConfirmEmailComponent } from './confirm-email.component'; -import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; -import { TranslateLoader, TranslateModule, TranslateService, } from '@ngx-translate/core'; -import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; -import { ExternalLoginService } from '../../services/external-login.service'; +import { + EventEmitter, + NO_ERRORS_SCHEMA, +} from '@angular/core'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { + FormBuilder, + ReactiveFormsModule, +} from '@angular/forms'; +import { By } from '@angular/platform-browser'; +import { + TranslateLoader, + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { of } from 'rxjs'; + import { AuthService } from '../../../core/auth/auth.service'; import { AuthMethodType } from '../../../core/auth/models/auth.method-type'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; import { EPerson } from '../../../core/eperson/models/eperson.model'; import { HardRedirectService } from '../../../core/services/hard-redirect.service'; +import { NativeWindowService } from '../../../core/services/window.service'; import { Registration } from '../../../core/shared/registration.model'; +import { + MockWindow, + NativeWindowMockFactory, +} from '../../../shared/mocks/mock-native-window-ref'; import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; -import { NativeWindowService } from '../../../core/services/window.service'; -import { MockWindow, NativeWindowMockFactory } from '../../../shared/mocks/mock-native-window-ref'; -import { By } from '@angular/platform-browser'; +import { ExternalLoginService } from '../../services/external-login.service'; +import { ConfirmEmailComponent } from './confirm-email.component'; describe('ConfirmEmailComponent', () => { let component: ConfirmEmailComponent; @@ -52,7 +68,6 @@ describe('ConfirmEmailComponent', () => { redirect: {}, }); await TestBed.configureTestingModule({ - declarations: [ConfirmEmailComponent], providers: [ FormBuilder, { provide: NativeWindowService, useFactory: NativeWindowMockFactory }, @@ -65,13 +80,14 @@ describe('ConfirmEmailComponent', () => { ], imports: [ CommonModule, + ConfirmEmailComponent, TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateLoaderMock, }, }), - ReactiveFormsModule + ReactiveFormsModule, ], schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); @@ -108,7 +124,7 @@ describe('ConfirmEmailComponent', () => { spyOn(component as any, 'postCreateAccountFromToken'); component.submitForm(); expect( - (component as any).postCreateAccountFromToken + (component as any).postCreateAccountFromToken, ).toHaveBeenCalledWith('test-token', component.registrationData); }); @@ -127,7 +143,7 @@ describe('ConfirmEmailComponent', () => { spyOn(component as any, 'patchUpdateRegistration'); component.submitForm(); expect( - (component as any).postCreateAccountFromToken + (component as any).postCreateAccountFromToken, ).not.toHaveBeenCalled(); expect((component as any).patchUpdateRegistration).not.toHaveBeenCalled(); }); diff --git a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts index acc59e8f55..5da1c93705 100644 --- a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts @@ -1,24 +1,60 @@ -import { ChangeDetectionStrategy, Component, Inject, Input, OnDestroy, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { ExternalLoginService } from '../../services/external-login.service'; -import { TranslateService } from '@ngx-translate/core'; +import { NgIf } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + Inject, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { + FormBuilder, + FormGroup, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import isEqual from 'lodash/isEqual'; -import { combineLatest, Subscription, take } from 'rxjs'; +import { + combineLatest, + Subscription, + take, +} from 'rxjs'; + import { AuthService } from '../../../core/auth/auth.service'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; import { EPerson } from '../../../core/eperson/models/eperson.model'; import { HardRedirectService } from '../../../core/services/hard-redirect.service'; -import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../core/shared/operators'; +import { + NativeWindowRef, + NativeWindowService, +} from '../../../core/services/window.service'; +import { + getFirstCompletedRemoteData, + getRemoteDataPayload, +} from '../../../core/shared/operators'; import { Registration } from '../../../core/shared/registration.model'; -import { hasNoValue, hasValue } from '../../../shared/empty.util'; +import { + hasNoValue, + hasValue, +} from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; -import { NativeWindowRef, NativeWindowService } from '../../../core/services/window.service'; +import { ExternalLoginService } from '../../services/external-login.service'; @Component({ selector: 'ds-confirm-email', templateUrl: './confirm-email.component.html', styleUrls: ['./confirm-email.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + TranslateModule, + NgIf, + ReactiveFormsModule, + ], }) export class ConfirmEmailComponent implements OnInit, OnDestroy { /** @@ -55,7 +91,7 @@ export class ConfirmEmailComponent implements OnInit, OnDestroy { ngOnInit() { this.emailForm = this.formBuilder.group({ - email: [this.registrationData.email, [Validators.required, Validators.email]] + email: [this.registrationData.email, [Validators.required, Validators.email]], }); } @@ -102,7 +138,7 @@ export class ConfirmEmailComponent implements OnInit, OnDestroy { */ private postCreateAccountFromToken( token: string, - registrationData: Registration + registrationData: Registration, ) { // check if the netId is present // in order to create an account, the netId is required (since the user is created without a password) @@ -131,13 +167,13 @@ export class ConfirmEmailComponent implements OnInit, OnDestroy { getFirstCompletedRemoteData(), ), this.externalLoginService.getExternalAuthLocation(this.registrationData.registrationType), - this.authService.getRedirectUrl().pipe(take(1)) + this.authService.getRedirectUrl().pipe(take(1)), ]) .subscribe(([rd, location, redirectRoute]) => { if (rd.hasFailed) { this.notificationService.error( this.translate.get('external-login-page.provide-email.create-account.notifications.error.header'), - this.translate.get('external-login-page.provide-email.create-account.notifications.error.content') + this.translate.get('external-login-page.provide-email.create-account.notifications.error.content'), ); } else if (rd.hasSucceeded) { // set Redirect URL to User profile, so the user is redirected to the profile page after logging in @@ -145,12 +181,12 @@ export class ConfirmEmailComponent implements OnInit, OnDestroy { const externalServerUrl = this.authService.getExternalServerRedirectUrl( this._window.nativeWindow.origin, redirectRoute, - location + location, ); // redirect to external registration type authentication url this.hardRedirectService.redirect(externalServerUrl); } - }) + }), ); } diff --git a/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts b/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts index da4e5416d5..3e960a1b79 100644 --- a/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts +++ b/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts @@ -1,11 +1,21 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ConfirmationSentComponent } from './confirmation-sent.component'; import { CommonModule } from '@angular/common'; -import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core'; -import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; +import { + CUSTOM_ELEMENTS_SCHEMA, + EventEmitter, +} from '@angular/core'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { + TranslateLoader, + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { of } from 'rxjs'; + import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; +import { ConfirmationSentComponent } from './confirmation-sent.component'; describe('ConfirmationSentComponent', () => { let component: ConfirmationSentComponent; @@ -17,27 +27,27 @@ describe('ConfirmationSentComponent', () => { instant: (key: any) => 'Mocked Translation Text', onLangChange: new EventEmitter(), onTranslationChange: new EventEmitter(), - onDefaultLangChange: new EventEmitter() + onDefaultLangChange: new EventEmitter(), }; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ConfirmationSentComponent ], providers: [ { provide: TranslateService, useValue: translateServiceStub }, - ], - imports: [ + ], + imports: [ CommonModule, + ConfirmationSentComponent, TranslateModule.forRoot({ loader: { provide: TranslateLoader, - useClass: TranslateLoaderMock - } + useClass: TranslateLoaderMock, + }, }), ], - schemas: [CUSTOM_ELEMENTS_SCHEMA] + schemas: [CUSTOM_ELEMENTS_SCHEMA], }) - .compileComponents(); + .compileComponents(); }); beforeEach(() => { diff --git a/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.ts b/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.ts index 2f82991c0d..5d6f2786cf 100644 --- a/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.ts +++ b/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.ts @@ -1,9 +1,16 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, +} from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; @Component({ selector: 'ds-confirmation-sent', templateUrl: './confirmation-sent.component.html', styleUrls: ['./confirmation-sent.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TranslateModule], + standalone: true, + }) export class ConfirmationSentComponent { } diff --git a/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.spec.ts b/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.spec.ts index a346bfa930..1e78b5a32a 100644 --- a/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.spec.ts +++ b/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.spec.ts @@ -1,12 +1,18 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProvideEmailComponent } from './provide-email.component'; -import { FormBuilder } from '@angular/forms'; import { CommonModule } from '@angular/common'; -import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { ExternalLoginService } from '../../services/external-login.service'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { FormBuilder } from '@angular/forms'; +import { + TranslateLoader, + TranslateModule, +} from '@ngx-translate/core'; + import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; +import { ExternalLoginService } from '../../services/external-login.service'; +import { ProvideEmailComponent } from './provide-email.component'; describe('ProvideEmailComponent', () => { let component: ProvideEmailComponent; @@ -17,23 +23,23 @@ describe('ProvideEmailComponent', () => { const externalLoginService = jasmine.createSpyObj('ExternalLoginService', ['patchUpdateRegistration']); await TestBed.configureTestingModule({ - declarations: [ ProvideEmailComponent ], providers: [ FormBuilder, { provide: ExternalLoginService, useValue: externalLoginService }, ], imports: [ CommonModule, + ProvideEmailComponent, TranslateModule.forRoot({ loader: { provide: TranslateLoader, - useClass: TranslateLoaderMock - } + useClass: TranslateLoaderMock, + }, }), ], - schemas: [CUSTOM_ELEMENTS_SCHEMA] + schemas: [CUSTOM_ELEMENTS_SCHEMA], }) - .compileComponents(); + .compileComponents(); }); beforeEach(() => { diff --git a/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.ts b/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.ts index 4e3e220ece..0b88a9c573 100644 --- a/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.ts +++ b/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.ts @@ -1,14 +1,33 @@ -import { ChangeDetectionStrategy, Component, Input, OnDestroy } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { ExternalLoginService } from '../../services/external-login.service'; +import { NgIf } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + Input, + OnDestroy, +} from '@angular/core'; +import { + FormBuilder, + FormGroup, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; import { Subscription } from 'rxjs'; + import { hasValue } from '../../../shared/empty.util'; +import { ExternalLoginService } from '../../services/external-login.service'; @Component({ selector: 'ds-provide-email', templateUrl: './provide-email.component.html', styleUrls: ['./provide-email.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + TranslateModule, + NgIf, + ReactiveFormsModule, + ], + standalone: true, }) export class ProvideEmailComponent implements OnDestroy { /** diff --git a/src/app/external-log-in/external-log-in/external-log-in.component.spec.ts b/src/app/external-log-in/external-log-in/external-log-in.component.spec.ts index 9f29a41dfc..fa4668b524 100644 --- a/src/app/external-log-in/external-log-in/external-log-in.component.spec.ts +++ b/src/app/external-log-in/external-log-in/external-log-in.component.spec.ts @@ -1,20 +1,28 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ExternalLogInComponent } from './external-log-in.component'; -import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { EventEmitter } from '@angular/core'; -import { By } from '@angular/platform-browser'; -import { of as observableOf } from 'rxjs'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; import { FormBuilder } from '@angular/forms'; +import { By } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; +import { of as observableOf } from 'rxjs'; + +import { AuthService } from '../../core/auth/auth.service'; import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; import { MetadataValue } from '../../core/shared/metadata.models'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthServiceMock } from '../../shared/mocks/auth.service.mock'; import { Registration } from '../../core/shared/registration.model'; -import { OrcidConfirmationComponent } from '../registration-types/orcid-confirmation/orcid-confirmation.component'; +import { AuthServiceMock } from '../../shared/mocks/auth.service.mock'; import { BrowserOnlyPipe } from '../../shared/utils/browser-only.pipe'; +import { ConfirmEmailComponent } from '../email-confirmation/confirm-email/confirm-email.component'; +import { OrcidConfirmationComponent } from '../registration-types/orcid-confirmation/orcid-confirmation.component'; +import { ExternalLogInComponent } from './external-log-in.component'; describe('ExternalLogInComponent', () => { let component: ExternalLogInComponent; @@ -37,27 +45,32 @@ describe('ExternalLogInComponent', () => { place: -1, }), ], - } + }, }; const translateServiceStub = { get: () => observableOf('Info Text'), instant: (key: any) => 'Info Text', onLangChange: new EventEmitter(), onTranslationChange: new EventEmitter(), - onDefaultLangChange: new EventEmitter() + onDefaultLangChange: new EventEmitter(), }; beforeEach(() => TestBed.configureTestingModule({ - imports: [CommonModule, TranslateModule.forRoot({})], - declarations: [BrowserOnlyPipe, ExternalLogInComponent, OrcidConfirmationComponent], + imports: [CommonModule, TranslateModule.forRoot({}), BrowserOnlyPipe, ExternalLogInComponent, OrcidConfirmationComponent, BrowserAnimationsModule], providers: [ { provide: TranslateService, useValue: translateServiceStub }, { provide: AuthService, useValue: new AuthServiceMock() }, { provide: NgbModal, useValue: modalService }, - FormBuilder - ] - }).compileComponents() + FormBuilder, + ], + }) + .overrideComponent(ExternalLogInComponent, { + remove: { + imports: [ConfirmEmailComponent], + }, + }) + .compileComponents(), ); beforeEach(() => { diff --git a/src/app/external-log-in/external-log-in/external-log-in.component.ts b/src/app/external-log-in/external-log-in/external-log-in.component.ts index cf0a96f54a..a89fbb7564 100644 --- a/src/app/external-log-in/external-log-in/external-log-in.component.ts +++ b/src/app/external-log-in/external-log-in/external-log-in.component.ts @@ -1,19 +1,54 @@ -import { ChangeDetectionStrategy, Component, Injector, Input, OnDestroy, OnInit, } from '@angular/core'; -import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateService } from '@ngx-translate/core'; +import { + NgComponentOutlet, + NgIf, +} from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + Injector, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { + NgbModal, + NgbModalRef, +} from '@ng-bootstrap/ng-bootstrap'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; + import { AuthService } from '../../core/auth/auth.service'; import { AuthMethodType } from '../../core/auth/models/auth.method-type'; import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; import { Registration } from '../../core/shared/registration.model'; -import { hasValue, isEmpty } from '../../shared/empty.util'; -import { getExternalLoginConfirmationType } from '../decorators/external-log-in.methods-decorator'; +import { AlertComponent } from '../../shared/alert/alert.component'; import { AlertType } from '../../shared/alert/alert-type'; +import { + hasValue, + isEmpty, +} from '../../shared/empty.util'; +import { ThemedLogInComponent } from '../../shared/log-in/themed-log-in.component'; +import { getExternalLoginConfirmationType } from '../decorators/external-log-in.methods-decorator'; +import { ConfirmEmailComponent } from '../email-confirmation/confirm-email/confirm-email.component'; +import { ProvideEmailComponent } from '../email-confirmation/provide-email/provide-email.component'; @Component({ selector: 'ds-external-log-in', templateUrl: './external-log-in.component.html', styleUrls: ['./external-log-in.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + ProvideEmailComponent, + AlertComponent, + TranslateModule, + ConfirmEmailComponent, + ThemedLogInComponent, + NgIf, + NgComponentOutlet, + ], + standalone: true, }) export class ExternalLogInComponent implements OnInit, OnDestroy { /** @@ -108,7 +143,7 @@ export class ExternalLogInComponent implements OnInit, OnDestroy { const authMethodUppercase = authMethod.toUpperCase(); return this.translate.instant( 'external-login.haveEmail.informationText', - { authMethod: authMethodUppercase } + { authMethod: authMethodUppercase }, ); } } diff --git a/src/app/external-log-in/external-login.module.ts b/src/app/external-log-in/external-login.module.ts deleted file mode 100644 index b0d75aa730..0000000000 --- a/src/app/external-log-in/external-login.module.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { ConfirmEmailComponent } from './email-confirmation/confirm-email/confirm-email.component'; -import { ConfirmationSentComponent } from './email-confirmation/confirmation-sent/confirmation-sent.component'; -import { ProvideEmailComponent } from './email-confirmation/provide-email/provide-email.component'; -import { ExternalLogInComponent } from './external-log-in/external-log-in.component'; -import { OrcidConfirmationComponent } from './registration-types/orcid-confirmation/orcid-confirmation.component'; -import { SharedModule } from '../shared/shared.module'; - -const COMPONENTS = [ - ExternalLogInComponent, - ProvideEmailComponent, - ConfirmEmailComponent, - ConfirmationSentComponent, -]; - -const ENTRY_COMPONENTS = [OrcidConfirmationComponent]; - -@NgModule({ - declarations: [...COMPONENTS, ...ENTRY_COMPONENTS], - imports: [CommonModule, SharedModule], - exports: [...COMPONENTS, ...ENTRY_COMPONENTS], -}) -export class ExternalLoginModule { - /** - * NOTE: this method allows to resolve issue with components that using a custom decorator - * which are not loaded during SSR otherwise - */ - static withEntryComponents() { - return { - ngModule: ExternalLoginModule, - providers: ENTRY_COMPONENTS.map((component) => ({ provide: component })), - }; - } -} diff --git a/src/app/external-log-in/guards/registration-token-guard.ts b/src/app/external-log-in/guards/registration-token-guard.ts new file mode 100644 index 0000000000..df49c712c6 --- /dev/null +++ b/src/app/external-log-in/guards/registration-token-guard.ts @@ -0,0 +1,53 @@ +import { inject } from '@angular/core'; +import { + ActivatedRouteSnapshot, + CanActivateFn, + Router, + RouterStateSnapshot, +} from '@angular/router'; +import { + map, + Observable, + of, +} from 'rxjs'; + +import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; +import { RemoteData } from '../../core/data/remote-data'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { Registration } from '../../core/shared/registration.model'; +import { hasValue } from '../../shared/empty.util'; + +/** + * Determines if a user can activate a route based on the registration token. + * @param route - The activated route snapshot. + * @param state - The router state snapshot. + * @param epersonRegistrationService - The eperson registration service. + * @param router - The router. + * @returns A value indicating if the user can activate the route. + */ +export const registrationTokenGuard: CanActivateFn = ( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot, +): Observable => { + const epersonRegistrationService = inject(EpersonRegistrationService); + const router = inject(Router); + if (route.params.token) { + return epersonRegistrationService + .searchByTokenAndHandleError(route.params.token) + .pipe( + getFirstCompletedRemoteData(), + map( + (data: RemoteData) => { + if (data.hasSucceeded && hasValue(data)) { + return true; + } else { + router.navigate(['/404']); + } + }, + ), + ); + } else { + router.navigate(['/404']); + return of(false); + } +}; diff --git a/src/app/external-log-in/guards/registration-token.guard.spec.ts b/src/app/external-log-in/guards/registration-token.guard.spec.ts index 5a0289697f..940d6a8d66 100644 --- a/src/app/external-log-in/guards/registration-token.guard.spec.ts +++ b/src/app/external-log-in/guards/registration-token.guard.spec.ts @@ -1,79 +1,101 @@ -import { TestBed } from '@angular/core/testing'; -import { RegistrationTokenGuard } from './registration-token.guard'; -import { ActivatedRoute, convertToParamMap, Params, Router } from '@angular/router'; -import { of as observableOf } from 'rxjs'; -import { RouterMock } from '../../shared/mocks/router.mock'; -import { Registration } from '../../core/shared/registration.model'; -import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; -import { EPerson } from '../../core/eperson/models/eperson.model'; -import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; +import { + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; +import { + ActivatedRoute, + Router, + RouterStateSnapshot, +} from '@angular/router'; +import { + Observable, + of as observableOf, +} from 'rxjs'; + import { AuthService } from '../../core/auth/auth.service'; +import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; +import { EPerson } from '../../core/eperson/models/eperson.model'; +import { Registration } from '../../core/shared/registration.model'; +import { RouterMock } from '../../shared/mocks/router.mock'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { registrationTokenGuard } from './registration-token-guard'; -describe('RegistrationTokenGuard', () => { - let guard: RegistrationTokenGuard; - const route = new RouterMock(); - const registrationWithGroups = Object.assign(new Registration(), - { - email: 'test@email.org', - token: 'test-token', +describe('RegistrationTokenGuard', + () => { + const route = new RouterMock(); + const registrationWithGroups = Object.assign(new Registration(), + { + email: 'test@email.org', + token: 'test-token', + }); + const epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', { + searchByTokenAndHandleError: createSuccessfulRemoteDataObject$(registrationWithGroups), }); - const epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', { - searchByTokenAndHandleError: createSuccessfulRemoteDataObject$(registrationWithGroups) - }); - const authService = { - getAuthenticatedUserFromStore: () => observableOf(ePerson), - setRedirectUrl: () => { - return true; - } - } as any; - const ePerson = Object.assign(new EPerson(), { - id: 'test-eperson', - uuid: 'test-eperson' - }); - beforeEach(() => { - const paramObject: Params = {}; - paramObject.token = '1234'; - TestBed.configureTestingModule({ - providers: [{provide: Router, useValue: route}, - { - provide: ActivatedRoute, - useValue: { - queryParamMap: observableOf(convertToParamMap(paramObject)) - }, + const authService = { + getAuthenticatedUserFromStore: () => observableOf(ePerson), + setRedirectUrl: () => { + return true; + }, + } as any; + const ePerson = Object.assign(new EPerson(), { + id: 'test-eperson', + uuid: 'test-eperson', + }); + + let arouteStub = { + snapshot: { + params: { + token: '123456789', }, - {provide: EpersonRegistrationService, useValue: epersonRegistrationService}, - {provide: AuthService, useValue: authService} - ] - }); - guard = TestBed.get(RegistrationTokenGuard); - }); + }, + }; - it('should be created', () => { - expect(guard).toBeTruthy(); - }); - describe('based on the response of "searchByToken have', () => { - it('can activate must return true when registration data includes groups', () => { - (guard.canActivate({ params: { token: '123456789' } } as any, {} as any) as any) - .subscribe( - (canActivate) => { - expect(canActivate).toEqual(true); - } - ); + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [{ provide: Router, useValue: route }, + { + provide: ActivatedRoute, + useValue: arouteStub, + }, + { provide: EpersonRegistrationService, useValue: epersonRegistrationService }, + { provide: AuthService, useValue: authService }, + ], + }); }); - it('can activate must return false when registration data includes groups', () => { - const registrationWithDifferentUsedFromLoggedInt = Object.assign(new Registration(), - { + + describe('when token provided', () => { + it('can activate must return true when registration data includes groups', fakeAsync(() => { + const activatedRoute = TestBed.inject(ActivatedRoute); + + const result$ = TestBed.runInInjectionContext(() => { + return registrationTokenGuard(activatedRoute.snapshot, {} as RouterStateSnapshot) as Observable; + }); + + let output = null; + result$.subscribe((result) => (output = result)); + tick(100); + expect(output).toBeTrue(); + })); + }); + + describe('when no token provided', () => { + it('can activate must return false when registration data includes groups', fakeAsync(() => { + const registrationWithDifferentUserFromLoggedIn = Object.assign(new Registration(), { email: 't1@email.org', token: 'test-token', }); - epersonRegistrationService.searchByTokenAndHandleError.and.returnValue(observableOf(registrationWithDifferentUsedFromLoggedInt)); - (guard.canActivate({ params: { token: '123456789' } } as any, {} as any) as any) - .subscribe( - (canActivate) => { - expect(canActivate).toEqual(false); - } - ); - }); + epersonRegistrationService.searchByTokenAndHandleError.and.returnValue(observableOf(registrationWithDifferentUserFromLoggedIn)); + let activatedRoute = TestBed.inject(ActivatedRoute); + activatedRoute.snapshot.params.token = null; + const result$ = TestBed.runInInjectionContext(() => { + return registrationTokenGuard(activatedRoute.snapshot, {} as RouterStateSnapshot) as Observable; + }); + + let output = null; + result$.subscribe((result) => (output = result)); + expect(output).toBeFalse(); + })); + }); }); -}); diff --git a/src/app/external-log-in/guards/registration-token.guard.ts b/src/app/external-log-in/guards/registration-token.guard.ts deleted file mode 100644 index 2135e3a1ae..0000000000 --- a/src/app/external-log-in/guards/registration-token.guard.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, } from '@angular/router'; -import { map, Observable, of } from 'rxjs'; -import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; -import { getFirstCompletedRemoteData } from '../../core/shared/operators'; -import { Registration } from '../../core/shared/registration.model'; -import { RemoteData } from '../../core/data/remote-data'; -import { hasValue } from '../../shared/empty.util'; - -@Injectable({ - providedIn: 'root', -}) -export class RegistrationTokenGuard implements CanActivate { - constructor( - private router: Router, - private epersonRegistrationService: EpersonRegistrationService - ) { } - - /** - * Determines if a user can activate a route based on the registration token. - * @param route - The activated route snapshot. - * @param state - The router state snapshot. - * @returns A value indicating if the user can activate the route. - */ - canActivate( - route: ActivatedRouteSnapshot, - state: RouterStateSnapshot - ): Observable { - if (route.params.token) { - return this.epersonRegistrationService - .searchByTokenAndHandleError(route.params.token) - .pipe( - getFirstCompletedRemoteData(), - map( - (data: RemoteData) => { - if (data.hasSucceeded && hasValue(data)) { - return true; - } else { - this.router.navigate(['/404']); - } - } - ) - ); - } else { - this.router.navigate(['/404']); - return of(false); - } - } -} diff --git a/src/app/external-log-in/models/registration-data.mock.model.ts b/src/app/external-log-in/models/registration-data.mock.model.ts index 51f5fc4434..43efe5a0f3 100644 --- a/src/app/external-log-in/models/registration-data.mock.model.ts +++ b/src/app/external-log-in/models/registration-data.mock.model.ts @@ -1,6 +1,6 @@ -import { Registration } from '../../core/shared/registration.model'; import { AuthMethodType } from '../../core/auth/models/auth.method-type'; import { MetadataValue } from '../../core/shared/metadata.models'; +import { Registration } from '../../core/shared/registration.model'; export const mockRegistrationDataModel: Registration = Object.assign( new Registration(), @@ -41,5 +41,5 @@ export const mockRegistrationDataModel: Registration = Object.assign( }, ], }, - } + }, ); diff --git a/src/app/external-log-in/registration-types/orcid-confirmation/orcid-confirmation.component.html b/src/app/external-log-in/registration-types/orcid-confirmation/orcid-confirmation.component.html index df5bb454dd..c731b9e545 100644 --- a/src/app/external-log-in/registration-types/orcid-confirmation/orcid-confirmation.component.html +++ b/src/app/external-log-in/registration-types/orcid-confirmation/orcid-confirmation.component.html @@ -1,6 +1,6 @@