From 16fa3a0250188bb4cac0dc8273136f163183df37 Mon Sep 17 00:00:00 2001 From: doregg <dor.moshe@frontegg.com> Date: Sun, 10 Mar 2024 13:35:51 +0200 Subject: [PATCH] Example app makeover --- .../example/src/app/app-routing.module.ts | 26 ++++-- projects/example/src/app/app.component.html | 38 +++++++- projects/example/src/app/app.component.scss | 22 +++++ projects/example/src/app/app.component.ts | 47 +++++++++- projects/example/src/app/app.module.ts | 31 ++++++- .../authorized-content-page.component.html | 24 +++++ .../authorized-content-page.component.scss | 5 + .../authorized-content-page.component.ts | 25 +++++ .../entitlements-page.component.html | 24 +++++ .../entitlements-page.component.scss | 49 ++++++++++ .../entitlements-page.component.ts | 92 +++++++++++++++++++ .../home-page/home-page.component.html | 7 ++ .../home-page/home-page.component.ts | 24 +++++ .../src/app/components/home.component.html | 20 ---- .../src/app/components/home.component.ts | 44 --------- .../src/app/components/not-found.component.ts | 2 +- .../base-step-up/base-step-up.component.html | 14 +++ .../base-step-up/base-step-up.component.scss | 8 ++ .../base-step-up/base-step-up.component.ts | 47 ++++++++++ .../step-up-full/step-up-full.component.html | 15 +++ .../step-up-full/step-up-full.component.scss | 3 + .../step-up-full/step-up-full.component.ts | 46 ++++++++++ .../step-up-high-max-age-page.component.ts | 11 +++ .../step-up-no-max-age-page.component.ts | 9 ++ .../step-up-small-max-age-page.component.ts | 11 +++ projects/example/src/app/links.ts | 13 +++ projects/example/src/styles.scss | 22 +++++ 27 files changed, 598 insertions(+), 81 deletions(-) create mode 100644 projects/example/src/app/app.component.scss create mode 100644 projects/example/src/app/components/authorized-content/authorized-content-page.component.html create mode 100644 projects/example/src/app/components/authorized-content/authorized-content-page.component.scss create mode 100644 projects/example/src/app/components/authorized-content/authorized-content-page.component.ts create mode 100644 projects/example/src/app/components/entitlements-page/entitlements-page.component.html create mode 100644 projects/example/src/app/components/entitlements-page/entitlements-page.component.scss create mode 100644 projects/example/src/app/components/entitlements-page/entitlements-page.component.ts create mode 100644 projects/example/src/app/components/home-page/home-page.component.html create mode 100644 projects/example/src/app/components/home-page/home-page.component.ts delete mode 100644 projects/example/src/app/components/home.component.html delete mode 100644 projects/example/src/app/components/home.component.ts create mode 100644 projects/example/src/app/components/step-up/base-step-up/base-step-up.component.html create mode 100644 projects/example/src/app/components/step-up/base-step-up/base-step-up.component.scss create mode 100644 projects/example/src/app/components/step-up/base-step-up/base-step-up.component.ts create mode 100644 projects/example/src/app/components/step-up/step-up-full/step-up-full.component.html create mode 100644 projects/example/src/app/components/step-up/step-up-full/step-up-full.component.scss create mode 100644 projects/example/src/app/components/step-up/step-up-full/step-up-full.component.ts create mode 100644 projects/example/src/app/components/step-up/step-up-high-max-age-page.component.ts create mode 100644 projects/example/src/app/components/step-up/step-up-no-max-age-page.component.ts create mode 100644 projects/example/src/app/components/step-up/step-up-small-max-age-page.component.ts create mode 100644 projects/example/src/app/links.ts diff --git a/projects/example/src/app/app-routing.module.ts b/projects/example/src/app/app-routing.module.ts index bb3bd5b9..c3ddf216 100644 --- a/projects/example/src/app/app-routing.module.ts +++ b/projects/example/src/app/app-routing.module.ts @@ -1,16 +1,30 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; +import { FronteggAuthGuard } from '@frontegg/angular'; import { PrivateRouteComponent } from './components/private-route.component'; import { NotFoundComponent } from './components/not-found.component'; -import { AppHomeComponent } from './components/home.component'; -import { FronteggAuthGuard } from '@frontegg/angular'; +import { HomePage } from './components/home-page/home-page.component'; +import { StepUpHighMaxAgePage } from './components/step-up/step-up-high-max-age-page.component'; +import { EntitlementsPage } from './components/entitlements-page/entitlements-page.component'; + +import { StepUpSmallMaxAgePage } from './components/step-up/step-up-small-max-age-page.component'; +import { StepUpNoMaxAgePage } from './components/step-up/step-up-no-max-age-page.component'; +import { StepUpFull } from './components/step-up/step-up-full/step-up-full.component'; +import { AuthorizedContentPage } from './components/authorized-content/authorized-content-page.component'; +import { ROUTE_PATHS } from './links'; const protectSingleRoutes: Routes = [ - { path: '', component: AppHomeComponent }, - { path: 'test-private-route', component: PrivateRouteComponent, canActivate: [FronteggAuthGuard] }, - { path: 'test', pathMatch: 'full', redirectTo: 'nested-router' }, + { path: ROUTE_PATHS.HOME_PAGE, component: HomePage }, + { path: ROUTE_PATHS.ENTITLEMENTS, component: EntitlementsPage }, + { path: ROUTE_PATHS.STEP_UP_HIGH_MAX_AGE, component: StepUpHighMaxAgePage }, + { path: ROUTE_PATHS.STEP_UP_SMALL_MAX_AGE, component: StepUpSmallMaxAgePage }, + { path: ROUTE_PATHS.STEP_UP_NO_MAX_AGE, component: StepUpNoMaxAgePage }, + { path: ROUTE_PATHS.STEPPED_UP_FULL, component: StepUpFull }, + { path: ROUTE_PATHS.AUTHORIZED_CONTENT, component: AuthorizedContentPage }, + { path: ROUTE_PATHS.TEST_PRIVATE_ROUTE, component: PrivateRouteComponent, canActivate: [FronteggAuthGuard] }, + { path: ROUTE_PATHS.TEST, pathMatch: 'full', redirectTo: ROUTE_PATHS.NESTED_ROUTER }, { - path: 'nested-router', + path: ROUTE_PATHS.NESTED_ROUTER, loadChildren: () => import('./nested-module/nested-module.module').then(m => m.NestedModule), }, { path: '**', component: NotFoundComponent }, diff --git a/projects/example/src/app/app.component.html b/projects/example/src/app/app.component.html index dd16d673..a724c015 100644 --- a/projects/example/src/app/app.component.html +++ b/projects/example/src/app/app.component.html @@ -1,5 +1,37 @@ -<div *ngIf="!isLoading"> - <router-outlet></router-outlet> - <div *authorizedContent="['Admin']">Authorized content section for Admin's (example)</div> +<div *ngIf="!isLoading" class="centered-flex-container"> + <h1>Frontegg App</h1> + + <div class='buttons-list'> + <button *ngIf="!authenticated && fronteggAppService.fronteggApp.options.hostedLoginBox === true" data-test-id="open-hosted" (click)="loginWithRedirect()">Login</button> + <a *ngIf="!authenticated && fronteggAppService.fronteggApp.options.hostedLoginBox === false" data-test-id="open-embedded" routerLink="/account/login"><button>Login</button></a> + + <button *ngIf="authenticated" data-test-id="open-admin-portal-btn" (click)="showApp()">Open Admin Portal</button> + + <span *ngIf="authenticated" (click)="logout()"><button>Logout</button></span> + </div> + <div *ngIf="authenticated"> + <p>Authenticated as {{user?.name}}</p> + </div> + + <div> + {{ fronteggAppService.fronteggApp.options.hostedLoginBox ? 'Hosted' : 'Embedded' }} + </div> + + <nav class="buttons-list"> + Pages: + <a routerLink="{{ROUTE_PATHS.HOME_PAGE}}"><button>Home</button></a> + <a routerLink="/{{ROUTE_PATHS.ENTITLEMENTS}}"><button>Entitlements</button></a> + <a routerLink="/{{ROUTE_PATHS.STEP_UP_NO_MAX_AGE}}"><button>Step up - no max age</button></a> + <a routerLink="/{{ROUTE_PATHS.STEP_UP_HIGH_MAX_AGE}}"><button>Step up - max age 5000</button></a> + <a routerLink="/{{ROUTE_PATHS.STEP_UP_SMALL_MAX_AGE}}"><button>Step up - max age 35</button></a> + <a routerLink="/{{ROUTE_PATHS.STEPPED_UP_FULL}}"><button>Stepped up (full)</button></a> + <a routerLink="/{{ROUTE_PATHS.AUTHORIZED_CONTENT}}"><button>Authorized content</button></a> + <a routerLink="/{{ROUTE_PATHS.TEST_PRIVATE_ROUTE}}"><button>Private route</button></a> + <a routerLink="/{{ROUTE_PATHS.UNKNOWN_ROUTE}}"><button>Fallback route</button></a> + </nav> + + <section class='router-content'> + <router-outlet></router-outlet> + </section> </div> diff --git a/projects/example/src/app/app.component.scss b/projects/example/src/app/app.component.scss new file mode 100644 index 00000000..6d820fb2 --- /dev/null +++ b/projects/example/src/app/app.component.scss @@ -0,0 +1,22 @@ +.router-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-width: 600px; + min-height: 400px; + margin: 25px 0; + padding: 10px; + border: 2px dotted rgb(21, 125, 243); +} + +.buttons-list { + display: flex; + justify-content: center; + align-items: center; + margin-top: 10px; +} + +.buttons-list > * { + margin-left: 4px; +} \ No newline at end of file diff --git a/projects/example/src/app/app.component.ts b/projects/example/src/app/app.component.ts index d2504008..a6447db9 100644 --- a/projects/example/src/app/app.component.ts +++ b/projects/example/src/app/app.component.ts @@ -1,24 +1,63 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; -import { FronteggAppService } from '@frontegg/angular'; +import { Router } from '@angular/router'; +import { FronteggAppService, FronteggAuthService } from '@frontegg/angular'; import { Subscription } from 'rxjs'; +import { ROUTE_PATHS } from './links'; @Component({ selector: 'app-root', templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit, OnDestroy { isLoading = true; loadingSubscription: Subscription; + isAuthenticatedSubscription: Subscription; + userSubscription?: Subscription; + authenticated = false; + user?: any; + ROUTE_PATHS = ROUTE_PATHS; - constructor(private fronteggAppService: FronteggAppService) { - this.loadingSubscription = fronteggAppService.isLoading$.subscribe((isLoading) => this.isLoading = isLoading); + constructor( + public fronteggAppService: FronteggAppService, + private fronteggAuthService: FronteggAuthService, + private router: Router, + ) { + this.loadingSubscription = fronteggAppService.isLoading$.subscribe((isLoading) => { + this.isLoading = isLoading; + }); + + this.isAuthenticatedSubscription = this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => { + this.authenticated = isAuthenticated; + }); + } + + showApp(): void { + this.fronteggAppService.showAdminPortal(); + } + + logout(): void { + if (this.fronteggAppService.fronteggApp.options.hostedLoginBox) { + this.fronteggAuthService.logout(); + return; + } + + this.router.navigateByUrl(this.fronteggAppService.authRoutes.logoutUrl); + } + + loginWithRedirect(): void { + this.fronteggAuthService.loginWithRedirect(); } ngOnInit(): void { - console.log('AppComponent', 'ngOnInit'); + this.userSubscription = this.fronteggAuthService.user$.subscribe((user: any) => { + this.user = user; + }); } ngOnDestroy(): void { this.loadingSubscription.unsubscribe(); + this.isAuthenticatedSubscription.unsubscribe(); + this.userSubscription?.unsubscribe(); } } diff --git a/projects/example/src/app/app.module.ts b/projects/example/src/app/app.module.ts index 0595d781..a200f053 100644 --- a/projects/example/src/app/app.module.ts +++ b/projects/example/src/app/app.module.ts @@ -6,13 +6,34 @@ import { CommonModule } from '@angular/common'; import { NotFoundComponent } from './components/not-found.component'; import { EmptyAppComponent } from './components/empty.component'; import { PrivateRouteComponent } from './components/private-route.component'; -import { AppHomeComponent } from './components/home.component'; -import { FronteggComponent, FronteggAppModule } from '@frontegg/angular'; +import { HomePage } from './components/home-page/home-page.component'; +import { FronteggAppModule } from '@frontegg/angular'; import { CheckoutDialogModule } from './checkout-dialog/checkout-dialog.module'; +import { StepUpHighMaxAgePage } from './components/step-up/step-up-high-max-age-page.component'; +import { EntitlementsPage } from './components/entitlements-page/entitlements-page.component'; +import { BaseStepUp } from './components/step-up/base-step-up/base-step-up.component'; +import { StepUpSmallMaxAgePage } from './components/step-up/step-up-small-max-age-page.component'; +import { StepUpNoMaxAgePage } from './components/step-up/step-up-no-max-age-page.component'; +import { StepUpFull } from './components/step-up/step-up-full/step-up-full.component'; +import { AuthorizedContentPage } from './components/authorized-content/authorized-content-page.component'; +const IS_HOSTED_MODE = false; @NgModule({ - declarations: [ AppComponent, NotFoundComponent, AppHomeComponent, EmptyAppComponent, PrivateRouteComponent ], + declarations: [ + AppComponent, + NotFoundComponent, + HomePage, + BaseStepUp, + StepUpHighMaxAgePage, + StepUpSmallMaxAgePage, + StepUpNoMaxAgePage, + StepUpFull, + EntitlementsPage, + EmptyAppComponent, + AuthorizedContentPage, + PrivateRouteComponent, + ], imports: [ CommonModule, BrowserModule, @@ -25,9 +46,13 @@ import { CheckoutDialogModule } from './checkout-dialog/checkout-dialog.module'; baseUrl: 'https://demo.frontegg.com', clientId: 'b6adfe4c-d695-4c04-b95f-3ec9fd0c6cca', }, + hostedLoginBox: IS_HOSTED_MODE, // don't remove it. Change it via the const value above authOptions: { keepSessionAlive: true, }, + entitlementsOptions: { + enabled: true + } }, ), CheckoutDialogModule, diff --git a/projects/example/src/app/components/authorized-content/authorized-content-page.component.html b/projects/example/src/app/components/authorized-content/authorized-content-page.component.html new file mode 100644 index 00000000..3a81c5f9 --- /dev/null +++ b/projects/example/src/app/components/authorized-content/authorized-content-page.component.html @@ -0,0 +1,24 @@ +<section *ngIf="!authenticated"> + Not Authenticated +</section> + +<div><b>Admin</b> role section: (empty when not authorized)</div> +<section class="authorized-section-container"> + <div *authorizedContent="['Admin']"> + Authorized content section for <b>Admin</b> role + </div> +</section> + +<div><b>Dora & Admin</b> role section: (empty when not authorized for both)</div> +<section class="authorized-section-container"> + <div *authorizedContent="['Dora', 'Admin']"> + Authorized content section for <b>Dora & Admin</b> role + </div> +</section> + +<div><b>Dora</b> role section: (empty when not authorized)</div> +<section class="authorized-section-container"> + <div *authorizedContent="['Dora']"> + Authorized content section for <b>Dora</b> role + </div> +</section> diff --git a/projects/example/src/app/components/authorized-content/authorized-content-page.component.scss b/projects/example/src/app/components/authorized-content/authorized-content-page.component.scss new file mode 100644 index 00000000..1d17a6a3 --- /dev/null +++ b/projects/example/src/app/components/authorized-content/authorized-content-page.component.scss @@ -0,0 +1,5 @@ +.authorized-section-container { + padding: 10px; + margin: 8px 0 24px 0; + border: 2px dotted rgb(21, 125, 243); +} \ No newline at end of file diff --git a/projects/example/src/app/components/authorized-content/authorized-content-page.component.ts b/projects/example/src/app/components/authorized-content/authorized-content-page.component.ts new file mode 100644 index 00000000..57e232bf --- /dev/null +++ b/projects/example/src/app/components/authorized-content/authorized-content-page.component.ts @@ -0,0 +1,25 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { FronteggAppService } from '@frontegg/angular'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'authorized-content-page', + templateUrl: 'authorized-content-page.component.html', + styleUrls: ['authorized-content-page.component.scss'], +}) +export class AuthorizedContentPage implements OnInit, OnDestroy { + authenticated = false; + isAuthenticatedSubscription?: Subscription; + + constructor(private fronteggAppService: FronteggAppService) {} + + ngOnInit(): void { + this.isAuthenticatedSubscription = this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => { + this.authenticated = isAuthenticated; + }); + } + + ngOnDestroy(): void { + this.isAuthenticatedSubscription?.unsubscribe(); + } +} diff --git a/projects/example/src/app/components/entitlements-page/entitlements-page.component.html b/projects/example/src/app/components/entitlements-page/entitlements-page.component.html new file mode 100644 index 00000000..3148ce8d --- /dev/null +++ b/projects/example/src/app/components/entitlements-page/entitlements-page.component.html @@ -0,0 +1,24 @@ +<section *ngIf="!authenticated"> + Not Authenticated +</section> + +<section *ngIf="authenticated" class="outer-container"> + <div class="boxes-container"> + <span *ngFor="let ent of entitlementsResults | keyvalue"> + <div *ngIf="ent.value.isEntitled" class="truthy"> + {{ ent.value.name }} + </div> + + <div *ngIf="!ent.value.isEntitled" class="falsy"> + {{ ent.value.name }} + <br/><br/> + <i>{{ent.value.justification?.toString()}}</i> + </div> + </span> + </div> + + <div class="centered-flex-container" [ngStyle]="{ 'flex-direction': 'row' }"> + <button (click)="onLoadEntitlementsClicked()">Load entitlements</button> + <button (click)="onLoadEntitlementsWithCallbackClicked()">Load entitlements with callback</button> + </div> +</section> \ No newline at end of file diff --git a/projects/example/src/app/components/entitlements-page/entitlements-page.component.scss b/projects/example/src/app/components/entitlements-page/entitlements-page.component.scss new file mode 100644 index 00000000..fa822b40 --- /dev/null +++ b/projects/example/src/app/components/entitlements-page/entitlements-page.component.scss @@ -0,0 +1,49 @@ +.truthy { + background-color: lightgreen; +} + +.falsy { + background-color: pink; +} + +.truthy, .falsy { + display: inline-block; + height: 100px; + width: 420px; + word-wrap: break-word; + margin: 10px; + padding: 5px; + border: 1px dotted black; + + animation: fadeInAnimation ease 3s; + animation-iteration-count: 1; + animation-fill-mode: forwards; + + @keyframes fadeInAnimation { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } + } +} + +.outer-container { + display: flex; + align-items: center; + flex-direction: column; +} + +.boxes-container { + display: flex; + flex-wrap: wrap; + max-width: 900px; + justify-content: center; + align-items: center; + text-align: center; +} + +.centered-flex-container button { + margin: 10px 10px 0 0; +} \ No newline at end of file diff --git a/projects/example/src/app/components/entitlements-page/entitlements-page.component.ts b/projects/example/src/app/components/entitlements-page/entitlements-page.component.ts new file mode 100644 index 00000000..337b8d9f --- /dev/null +++ b/projects/example/src/app/components/entitlements-page/entitlements-page.component.ts @@ -0,0 +1,92 @@ +import { Component, NgZone, OnDestroy, OnInit } from '@angular/core'; +import { FronteggAppService, FronteggEntitlementsService } from '@frontegg/angular'; +import { Entitlement } from '@frontegg/types'; +import { Subscription } from 'rxjs'; + +enum EntitlementsQueryType { + FEATURE = 'featureEntitlements', + PERMISSION = 'permissionEntitlements', + ENTITLEMENTS = 'entitlements', +} + +@Component({ + selector: 'entitlements-page', + templateUrl: './entitlements-page.component.html', + styleUrls: ['./entitlements-page.component.scss'] +}) +export class EntitlementsPage implements OnDestroy { + authenticated = false; + + entitlementsResults: { + [key: string]: { + name: string, + isEntitled: Entitlement['isEntitled'], + justification?: Entitlement['justification'] + } + } = {}; + + user?: any; + subscriptions: Subscription[] = []; + isAuthenticatedSubscription: Subscription; + + constructor( + private fronteggAppService: FronteggAppService, + private fronteggEntitlementsService: FronteggEntitlementsService, + private ngZone: NgZone, + ) { + this.isAuthenticatedSubscription = this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => { + this.authenticated = isAuthenticated; + }); + + this.feedEntitlements(); + } + + feedEntitlements() { + const entitlementsRequests = [ + { queryType: EntitlementsQueryType.FEATURE, arg: 'sso', }, + { queryType: EntitlementsQueryType.FEATURE, arg: 'sso', customAttributes: { env: 'dev' } }, + { queryType: EntitlementsQueryType.ENTITLEMENTS, arg: { featureKey: 'proteins.*' }, customAttributes: { pro: '20gr' }}, + { queryType: EntitlementsQueryType.PERMISSION, arg: 'dora.protein.*', }, + { queryType: EntitlementsQueryType.ENTITLEMENTS, arg: { permissionKey: 'fe.secure.*' } }, + { queryType: EntitlementsQueryType.PERMISSION, arg: 'fe.secure.*', customAttributes: { env: 'dev' } }, + ]; + + const subscriptionGenerators: any = { + featureEntitlements: this.fronteggEntitlementsService.featureEntitlements$.bind(this.fronteggEntitlementsService), + permissionEntitlements: this.fronteggEntitlementsService.permissionEntitlements$.bind(this.fronteggEntitlementsService), + entitlements: this.fronteggEntitlementsService.entitlements$.bind(this.fronteggEntitlementsService), + } + + this.subscriptions = entitlementsRequests.map(({ queryType, arg, customAttributes }) => ( + subscriptionGenerators[queryType](arg, { + next: ( + result: Entitlement + ) => { + this.ngZone.run(() => { + const customAttributesNamePart = customAttributes ? `, ${JSON.stringify(customAttributes)}` : ''; + const name = `${queryType}$(${JSON.stringify(arg)}${customAttributesNamePart})`; + this.entitlementsResults[name] = { + name, + ...result + }; + }); + } + }) + )); + } + + onLoadEntitlementsClicked() { + this.fronteggEntitlementsService.loadEntitlements(); + } + + onLoadEntitlementsWithCallbackClicked() { + this.fronteggEntitlementsService.loadEntitlements( + (isSucceeded: boolean) => console.log(`Load entitlements on demand ${isSucceeded ? 'succeeded' : 'failed'}`) + ); + } + + ngOnDestroy(): void { + this.subscriptions.forEach(subscription => subscription.unsubscribe()); + this.isAuthenticatedSubscription.unsubscribe(); + } +} diff --git a/projects/example/src/app/components/home-page/home-page.component.html b/projects/example/src/app/components/home-page/home-page.component.html new file mode 100644 index 00000000..1bb61d0d --- /dev/null +++ b/projects/example/src/app/components/home-page/home-page.component.html @@ -0,0 +1,7 @@ +<section *ngIf="!authenticated"> + Not Authenticated +</section> + +<section *ngIf="authenticated"> + Home Page +</section> diff --git a/projects/example/src/app/components/home-page/home-page.component.ts b/projects/example/src/app/components/home-page/home-page.component.ts new file mode 100644 index 00000000..f98474c2 --- /dev/null +++ b/projects/example/src/app/components/home-page/home-page.component.ts @@ -0,0 +1,24 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { FronteggAppService } from '@frontegg/angular'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'home-page', + templateUrl: 'home-page.component.html', +}) +export class HomePage implements OnInit, OnDestroy { + authenticated = false; + isAuthenticatedSubscription?: Subscription; + + constructor(private fronteggAppService: FronteggAppService) {} + + ngOnInit(): void { + this.isAuthenticatedSubscription = this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => { + this.authenticated = isAuthenticated; + }); + } + + ngOnDestroy(): void { + this.isAuthenticatedSubscription?.unsubscribe(); + } +} diff --git a/projects/example/src/app/components/home.component.html b/projects/example/src/app/components/home.component.html deleted file mode 100644 index 665e19a5..00000000 --- a/projects/example/src/app/components/home.component.html +++ /dev/null @@ -1,20 +0,0 @@ -<div> - <br /> - <br /> - <br /> - <h1>Toolbar Frontegg App</h1> - - - <br /> - <button data-test-id="open-admin-portal-btn" (click)="showApp()"><h2>Open Frontegg app</h2></button> - <button data-test-id="open-hosted" (click)="loginWithRedirect()"><h2>Open Hosted Login</h2></button> - <br /> - <br /> - <p>Authenticated: {{authenticated}}</p> - <p>Authenticated as: {{user?.name}}</p> - <a routerLink="/account/login"><b>Login</b></a> --- - <span (click)="doLogout()"><b>Logout</b></span> --- - <a routerLink="/test-private-route"><b>private-route</b></a> - <br /> - <br /> -</div> diff --git a/projects/example/src/app/components/home.component.ts b/projects/example/src/app/components/home.component.ts deleted file mode 100644 index efb671ed..00000000 --- a/projects/example/src/app/components/home.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { FronteggAppService, FronteggAuthService } from '@frontegg/angular'; -import { Router } from '@angular/router'; - -@Component({ - selector: 'app-home', - templateUrl: 'home.component.html', -}) -export class AppHomeComponent implements OnInit { - authenticated?: boolean; - user?: any; - - constructor(private fronteggAppService: FronteggAppService, - private fronteggAuthService: FronteggAuthService, - private router: Router) { - } - - ngOnInit(): void { - this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => { - this.authenticated = isAuthenticated; - }); - - this.fronteggAuthService.user$.subscribe((user: any) => { - this.user = user; - }); - } - - showApp(): void { - this.fronteggAppService.showAdminPortal(); - } - - doLogout(): void { - if (this.fronteggAppService.fronteggApp.options.hostedLoginBox) { - this.fronteggAuthService.logout(); - } else { - this.router.navigateByUrl(this.fronteggAppService.authRoutes.logoutUrl); - } - } - - loginWithRedirect(): void { - this.fronteggAuthService.loginWithRedirect(); - } - -} diff --git a/projects/example/src/app/components/not-found.component.ts b/projects/example/src/app/components/not-found.component.ts index 078fe38a..5a99ba07 100644 --- a/projects/example/src/app/components/not-found.component.ts +++ b/projects/example/src/app/components/not-found.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; @Component({ - selector: 'app-not-fpund', + selector: 'app-not-found', template: ` <div> 404 not found diff --git a/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.html b/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.html new file mode 100644 index 00000000..24edcc47 --- /dev/null +++ b/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.html @@ -0,0 +1,14 @@ +<section *ngIf="!authenticated"> + Not Authenticated +</section> + +<section *ngIf="authenticated" class="centered-flex-container"> + <div *ngIf="maxAge" class="max-age-container">Max age is <span class="positive">{{ maxAge }}</span></div> + <div *ngIf="!maxAge" class="max-age-container">No max age</div> + + <button *ngIf="!isSteppedUp" (click)="stepUp({ maxAge: maxAge })" class="step-up-button">Step Up</button> + + <div class="stepped-up-container" *ngIf="isSteppedUp"> + <p>Honey, you are <b class="positive">STEPPED UP</b>!</p> + </div> +</section> \ No newline at end of file diff --git a/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.scss b/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.scss new file mode 100644 index 00000000..e1cf7f17 --- /dev/null +++ b/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.scss @@ -0,0 +1,8 @@ +.max-age-container { + margin-bottom: 4px; +} + +.step-up-button { + font-size: 1.2em; + padding: 0.7em; +} \ No newline at end of file diff --git a/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.ts b/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.ts new file mode 100644 index 00000000..4082d995 --- /dev/null +++ b/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.ts @@ -0,0 +1,47 @@ +import { Component, NgZone, OnDestroy, OnInit } from '@angular/core'; +import { FronteggAppService, FronteggAuthService } from '@frontegg/angular'; +import { StepUpOptions } from '@frontegg/types'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'base-step-up', + templateUrl: './base-step-up.component.html', + styleUrls: ['./base-step-up.component.scss'] +}) +export class BaseStepUp implements OnDestroy, OnInit { + isLoading = true; + steppedUpSubscription?: Subscription; + isAuthenticatedSubscription: Subscription; + isSteppedUp = false; + authenticated = false; + maxAge: number | undefined; + + constructor( + private fronteggAppService: FronteggAppService, + private fronteggAuthService: FronteggAuthService, + private ngZone: NgZone, + ) { + this.isAuthenticatedSubscription = this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => { + this.authenticated = isAuthenticated; + }); + } + + ngOnInit(): void { + this.steppedUpSubscription = this.fronteggAuthService.isSteppedUp$({ + next: (isSteppedUp: boolean) => { + this.ngZone.run(() => { + this.isSteppedUp = isSteppedUp; + }); + } + }, { maxAge: this.maxAge }); + } + + stepUp(options?: StepUpOptions) { + this.fronteggAuthService.stepUp(options); + } + + ngOnDestroy(): void { + this.isAuthenticatedSubscription.unsubscribe(); + this.steppedUpSubscription?.unsubscribe(); + } +} diff --git a/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.html b/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.html new file mode 100644 index 00000000..b671259a --- /dev/null +++ b/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.html @@ -0,0 +1,15 @@ +<section *ngIf="!authenticated"> + Not Authenticated +</section> + +<section *ngIf="authenticated" class="centered-flex-container"> + <div *ngIf="maxAge" class="max-age-container">Max age is <span class="positive">{{ maxAge }}</span></div> + + <div class="stepped-up-container" *ngIf="isSteppedUp"> + <p>Honey, you are <b class="positive">STEPPED UP</b>!</p> + </div> + + <div class="stepped-up-container" *ngIf="!isSteppedUp"> + <p>Honey, you are NOT stepped up! If you see it there is a BUG!</p> + </div> +</section> \ No newline at end of file diff --git a/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.scss b/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.scss new file mode 100644 index 00000000..10d4b726 --- /dev/null +++ b/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.scss @@ -0,0 +1,3 @@ +.max-age-container { + margin-bottom: 4px; +} \ No newline at end of file diff --git a/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.ts b/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.ts new file mode 100644 index 00000000..89db1341 --- /dev/null +++ b/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.ts @@ -0,0 +1,46 @@ +import { Component, NgZone, OnDestroy, OnInit } from '@angular/core'; +import { FronteggAppService, FronteggAuthService } from '@frontegg/angular'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'step-up-full', + templateUrl: './step-up-full.component.html', + styleUrls: ['./step-up-full.component.scss'] +}) +export class StepUpFull implements OnDestroy, OnInit { + isLoading = true; + steppedUpSubscription?: Subscription; + isAuthenticatedSubscription: Subscription; + isSteppedUp = false; + authenticated = false; + maxAge = 5000; + + constructor( + private fronteggAppService: FronteggAppService, + private fronteggAuthService: FronteggAuthService, + private ngZone: NgZone, + ) { + this.isAuthenticatedSubscription = this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => { + this.authenticated = isAuthenticated; + }); + } + + ngOnInit(): void { + this.steppedUpSubscription = this.fronteggAuthService.isSteppedUp$({ + next: (isSteppedUp: boolean) => { + this.ngZone.run(() => { + this.isSteppedUp = isSteppedUp; + + if (isSteppedUp) return; + + this.fronteggAuthService.stepUp({ maxAge: this.maxAge }); + }); + } + }, { maxAge: this.maxAge }); + } + + ngOnDestroy(): void { + this.isAuthenticatedSubscription.unsubscribe(); + this.steppedUpSubscription?.unsubscribe(); + } +} diff --git a/projects/example/src/app/components/step-up/step-up-high-max-age-page.component.ts b/projects/example/src/app/components/step-up/step-up-high-max-age-page.component.ts new file mode 100644 index 00000000..df0e4045 --- /dev/null +++ b/projects/example/src/app/components/step-up/step-up-high-max-age-page.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +import { BaseStepUp } from './base-step-up/base-step-up.component'; + +@Component({ + selector: 'step-up-high-max-age-page', + templateUrl: './base-step-up/base-step-up.component.html', + styleUrls: ['./base-step-up/base-step-up.component.scss'], +}) +export class StepUpHighMaxAgePage extends BaseStepUp { + maxAge = 5000; +} diff --git a/projects/example/src/app/components/step-up/step-up-no-max-age-page.component.ts b/projects/example/src/app/components/step-up/step-up-no-max-age-page.component.ts new file mode 100644 index 00000000..12df03ee --- /dev/null +++ b/projects/example/src/app/components/step-up/step-up-no-max-age-page.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; +import { BaseStepUp } from './base-step-up/base-step-up.component'; + +@Component({ + selector: 'step-up-no-max-age-page', + templateUrl: './base-step-up/base-step-up.component.html', + styleUrls: ['./base-step-up/base-step-up.component.scss'], +}) +export class StepUpNoMaxAgePage extends BaseStepUp {} diff --git a/projects/example/src/app/components/step-up/step-up-small-max-age-page.component.ts b/projects/example/src/app/components/step-up/step-up-small-max-age-page.component.ts new file mode 100644 index 00000000..2430797a --- /dev/null +++ b/projects/example/src/app/components/step-up/step-up-small-max-age-page.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +import { BaseStepUp } from './base-step-up/base-step-up.component'; + +@Component({ + selector: 'step-up-small-max-age-page', + templateUrl: './base-step-up/base-step-up.component.html', + styleUrls: ['./base-step-up/base-step-up.component.scss'], +}) +export class StepUpSmallMaxAgePage extends BaseStepUp { + maxAge = 35; +} diff --git a/projects/example/src/app/links.ts b/projects/example/src/app/links.ts new file mode 100644 index 00000000..9e441de7 --- /dev/null +++ b/projects/example/src/app/links.ts @@ -0,0 +1,13 @@ +export const ROUTE_PATHS = { + HOME_PAGE: '', + ENTITLEMENTS: 'entitlements', + STEP_UP_HIGH_MAX_AGE: 'step-up-high-max-age', + STEP_UP_SMALL_MAX_AGE: 'step-up-small-max-age', + STEP_UP_NO_MAX_AGE: 'step-up-no-max-age', + STEPPED_UP_FULL: 'stepped-up-full', + UNKNOWN_ROUTE: 'unknown-route', + AUTHORIZED_CONTENT: 'authorized-content', + TEST_PRIVATE_ROUTE: 'test-private-route', + NESTED_ROUTER: 'nested-router', + TEST: 'test', +}; diff --git a/projects/example/src/styles.scss b/projects/example/src/styles.scss index 732e93b1..cae790c9 100644 --- a/projects/example/src/styles.scss +++ b/projects/example/src/styles.scss @@ -1,3 +1,5 @@ +$positive-color: dodgerblue; + * { box-sizing: border-box; } @@ -24,3 +26,23 @@ html, body { text-decoration: none; } } + +button { + border-radius: 4px; + background: $positive-color; + color: white; + border: 0; + padding: 8px 16px; + margin-bottom: 4px; + cursor: pointer; +} + +.centered-flex-container { + display: flex; + flex-direction: column; + align-items: center; +} + +.positive { + color: $positive-color; +} \ No newline at end of file