Skip to content

Commit

Permalink
refactor: abstract ICM as identity provider (#447)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: login/logout handling is abstracted as identity provider service
  • Loading branch information
dhhyi committed Nov 3, 2020
1 parent 9a80b19 commit f97b114
Show file tree
Hide file tree
Showing 44 changed files with 693 additions and 1,021 deletions.
2 changes: 1 addition & 1 deletion e2e/cypress/integration/pages/account/my-account.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class MyAccountPage {
}

navigateToAddresses() {
cy.get('a[data-testing-id="addresses-link"]').click();
cy.get('a[data-testing-id="addresses-link"]').click({ force: true });
}

navigateToWishlists() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ describe('Anonymous Sleeping User', () => {
});

waitLoadingEnd(5000);
cy.window().screenshot();
});
at(ProductDetailPage, page => page.sku.should('have.text', _.product));
});
Expand Down Expand Up @@ -65,7 +64,6 @@ describe('Anonymous Sleeping User', () => {
});

waitLoadingEnd(5000);
cy.window().screenshot();
});
at(HomePage, page => page.header.miniCart.text.should('contain', '0 items'));
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ describe('Logged in Sleeping User', () => {
});

cy.wait(5000);
cy.window().screenshot();
});
at(LoginPage, page => {
page.header.myAccountLink.should('not.have.text', `${_.user.firstName} ${_.user.lastName}`);
Expand Down Expand Up @@ -77,7 +76,6 @@ describe('Logged in Sleeping User', () => {
url: `**`,
});
cy.wait(5000);
cy.window().screenshot();
});
at(LoginPage, page => {
page.header.myAccountLink.should('not.have.text', `${_.user.firstName} ${_.user.lastName}`);
Expand Down
4 changes: 2 additions & 2 deletions projects/organization-management/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { RouterModule } from '@angular/router';

import { CoreModule } from 'ish-core/core.module';
import { AuthGuard } from 'ish-core/guards/auth.guard';
import { LogoutGuard } from 'ish-core/guards/logout.guard';
import { IdentityProviderLogoutGuard } from 'ish-core/guards/identity-provider-logout.guard';
import { FormsSharedModule } from 'ish-shared/forms/forms.module';

import { AppComponent } from './app.component';
Expand All @@ -25,7 +25,7 @@ import { LoginComponent } from './login.component';
},
{
path: 'logout',
canActivate: [LogoutGuard],
canActivate: [IdentityProviderLogoutGuard],
component: LoginComponent,
},
{
Expand Down
10 changes: 8 additions & 2 deletions src/app/core/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import { AppearanceModule } from './appearance.module';
import { ConfigurationModule } from './configuration.module';
import { ExtrasModule } from './extras.module';
import { FeatureToggleModule } from './feature-toggle.module';
import { AuthInterceptor } from './interceptors/auth.interceptor';
import { IdentityProviderModule } from './identity-provider.module';
import { ICMErrorMapperInterceptor } from './interceptors/icm-error-mapper.interceptor';
import { IdentityProviderInterceptor } from './interceptors/identity-provider.interceptor';
import { MockInterceptor } from './interceptors/mock.interceptor';
import { InternationalizationModule } from './internationalization.module';
import { StateManagementModule } from './state-management.module';
Expand All @@ -30,6 +31,7 @@ import { ModuleLoaderService } from './utils/module-loader/module-loader.service
FeatureToggleModule,
FormlyModule.forRoot(),
HttpClientModule,
IdentityProviderModule,
InternationalizationModule,
NgxCookieBannerModule.forRoot({
cookieName: 'cookieLawSeen',
Expand All @@ -39,7 +41,11 @@ import { ModuleLoaderService } from './utils/module-loader/module-loader.service
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: ICMErrorMapperInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{
provide: HTTP_INTERCEPTORS,
useClass: IdentityProviderInterceptor,
multi: true,
},
{ provide: HTTP_INTERCEPTORS, useClass: MockInterceptor, multi: true },
{ provide: ErrorHandler, useClass: DefaultErrorhandler },
{
Expand Down
15 changes: 13 additions & 2 deletions src/app/core/directives.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import { NgModule } from '@angular/core';

import { ClickOutsideDirective } from './directives/click-outside.directive';
import { IdentityProviderCapabilityDirective } from './directives/identity-provider-capability.directive';
import { IntersectionObserverDirective } from './directives/intersection-observer.directive';
import { ServerHtmlDirective } from './directives/server-html.directive';

@NgModule({
declarations: [ClickOutsideDirective, IntersectionObserverDirective, ServerHtmlDirective],
exports: [ClickOutsideDirective, IntersectionObserverDirective, ServerHtmlDirective],
declarations: [
ClickOutsideDirective,
IdentityProviderCapabilityDirective,
IntersectionObserverDirective,
ServerHtmlDirective,
],
exports: [
ClickOutsideDirective,
IdentityProviderCapabilityDirective,
IntersectionObserverDirective,
ServerHtmlDirective,
],
})
export class DirectivesModule {}
25 changes: 25 additions & 0 deletions src/app/core/directives/identity-provider-capability.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

import { IdentityProviderFactory } from 'ish-core/identity-provider/identity-provider.factory';
import { IdentityProviderCapabilities } from 'ish-core/identity-provider/identity-provider.interface';

@Directive({
selector: '[ishIdentityProviderCapability]',
})
export class IdentityProviderCapabilityDirective {
constructor(
private templateRef: TemplateRef<unknown>,
private viewContainer: ViewContainerRef,
private identityProviderFactory: IdentityProviderFactory
) {}

@Input() set ishIdentityProviderCapability(val: keyof IdentityProviderCapabilities) {
const enabled = this.identityProviderFactory.getInstance().getCapabilities()[val];

if (enabled) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}
13 changes: 13 additions & 0 deletions src/app/core/guards/identity-provider-login.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';

import { IdentityProviderFactory } from 'ish-core/identity-provider/identity-provider.factory';

@Injectable({ providedIn: 'root' })
export class IdentityProviderLoginGuard implements CanActivate {
constructor(private identityProviderFactory: IdentityProviderFactory) {}

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.identityProviderFactory.getInstance().triggerLogin(route, state);
}
}
13 changes: 13 additions & 0 deletions src/app/core/guards/identity-provider-logout.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';

import { IdentityProviderFactory } from 'ish-core/identity-provider/identity-provider.factory';

@Injectable({ providedIn: 'root' })
export class IdentityProviderLogoutGuard implements CanActivate {
constructor(private identityProviderFactory: IdentityProviderFactory) {}

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.identityProviderFactory.getInstance().triggerLogout(route, state);
}
}
16 changes: 16 additions & 0 deletions src/app/core/guards/identity-provider-register.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';

import { IdentityProviderFactory } from 'ish-core/identity-provider/identity-provider.factory';

@Injectable({ providedIn: 'root' })
export class IdentityProviderRegisterGuard implements CanActivate {
constructor(private identityProviderFactory: IdentityProviderFactory) {}

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const identityProvider = this.identityProviderFactory.getInstance();
return identityProvider.triggerRegister
? identityProvider.triggerRegister(route, state)
: identityProvider.triggerLogin(route, state);
}
}
60 changes: 0 additions & 60 deletions src/app/core/guards/logout.guard.spec.ts

This file was deleted.

26 changes: 0 additions & 26 deletions src/app/core/guards/logout.guard.ts

This file was deleted.

40 changes: 40 additions & 0 deletions src/app/core/identity-provider.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { first, tap } from 'rxjs/operators';

import { ICMIdentityProvider } from './identity-provider/icm.identity-provider';
import { IDENTITY_PROVIDER_IMPLEMENTOR, IdentityProviderFactory } from './identity-provider/identity-provider.factory';
import { getIdentityProvider } from './store/core/configuration';
import { whenTruthy } from './utils/operators';

export function identityProviderFactoryInitializer(store: Store, identityProviderFactory: IdentityProviderFactory) {
return () =>
store
.pipe(
select(getIdentityProvider),
whenTruthy(),
first(),
tap(config => identityProviderFactory.init(config))
)
.toPromise();
}

@NgModule({
providers: [
{
provide: APP_INITIALIZER,
useFactory: identityProviderFactoryInitializer,
deps: [Store, IdentityProviderFactory],
multi: true,
},
{
provide: IDENTITY_PROVIDER_IMPLEMENTOR,
multi: true,
useValue: {
type: 'ICM',
implementor: ICMIdentityProvider,
},
},
],
})
export class IdentityProviderModule {}
59 changes: 59 additions & 0 deletions src/app/core/identity-provider/icm.identity-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { HttpEvent, HttpHandler, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Store, select } from '@ngrx/store';
import { Observable, noop } from 'rxjs';
import { map } from 'rxjs/operators';

import { selectQueryParam } from 'ish-core/store/core/router';
import { logoutUser } from 'ish-core/store/customer/user';
import { ApiTokenService } from 'ish-core/utils/api-token/api-token.service';

import { IdentityProvider, TriggerReturnType } from './identity-provider.interface';

@Injectable({ providedIn: 'root' })
export class ICMIdentityProvider implements IdentityProvider {
constructor(protected router: Router, protected store: Store, protected apiTokenService: ApiTokenService) {}

getCapabilities() {
return {
editPassword: true,
editEmail: true,
};
}

init() {
this.apiTokenService.restore$().subscribe(noop);

this.apiTokenService.cookieVanishes$.subscribe(type => {
this.store.dispatch(logoutUser());
if (type === 'user') {
this.router.navigate(['/login'], {
queryParams: { returnUrl: this.router.url, messageKey: 'session_timeout' },
});
}
});
}

triggerLogin(): TriggerReturnType {
return true;
}

triggerLogout(): TriggerReturnType {
this.store.dispatch(logoutUser());
this.apiTokenService.removeApiToken();
return this.store.pipe(
select(selectQueryParam('returnUrl')),
map(returnUrl => returnUrl || '/home'),
map(returnUrl => this.router.parseUrl(returnUrl))
);
}

triggerRegister(): TriggerReturnType {
return true;
}

intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
return this.apiTokenService.intercept(req, next);
}
}
Loading

0 comments on commit f97b114

Please sign in to comment.