Verified Commit 28de03d6 authored by Raphael Ochsenbein's avatar Raphael Ochsenbein
Browse files

Make openid module config depending on environment, optimize toast

parent 3fb03cff
Pipeline #636 failed with stages
in 58 seconds
......@@ -18,21 +18,32 @@ const checkJwt = jwt({
}),
// Validate the audience and the issuer
audience: 'https://dev-l-3l8u04.eu.auth0.com/api/v2/', // replace with your API's audience, available at Dashboard > APIs
audience: 'https://openid.akehir.com:55001/', // replace with your API's audience, available at Dashboard > APIs
issuer: 'https://dev-l-3l8u04.eu.auth0.com/',
algorithms: [ 'RS256' ] // we are using RS256 to sign our tokens
});
app.all('/*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
next();
});
// create retrieve balance endpoint
app.get('/balance', checkJwt, jwtAuthz(['view:balance']), function (req, res) {
// code that retrieves the user's balance and sends it back to the calling app
res.status(201).send({message: "This is the GET /balance endpoint"});
res.status(201).send({message: "{\"message\": \"This is the GET /balance endpoint\""});
});
// create transfer funds endpoint
app.post('/transfer', checkJwt, jwtAuthz(['transfer:funds']), function (req, res) {
// code that transfers funds from one account to another
checkJwt.
res.status(201).send({message: "This is the POST /transfer endpoint"});
});
......
......@@ -3,13 +3,13 @@
Welcome to {{ title }}!
</h1>
</div>
<h2>Log In / Log Out</h2>
<!--<button [ibmButton]="'primary'" [size]="'normal'" (click)="showToast()">Toast</button><span> </span>-->
<hr/>
<button [ibmButton]="'primary'" [size]="'normal'" (click)="loginPopup()">Log In</button>
<button [ibmButton]="'danger--primary'" [size]="'normal'" (click)="logoutPopup()">Log Out</button>
<hr/>
<button [ibmButton]="'primary'" [size]="'normal'" (click)="loginPopup()">Call 1FA API</button>
<button [ibmButton]="'danger--primary'" [size]="'normal'" (click)="loginPopup()">Call 2FA API</button>
<button [ibmButton]="'primary'" [size]="'normal'" (click)="call1FAAPI()">Call 1FA API</button>
<button [ibmButton]="'danger--primary'" [size]="'normal'" (click)="call2FAAPI()">Call 2FA API</button>
<hr/>
<button [ibmButton]="'primary'" [size]="'normal'" (click)="check2FA()">Check 2FA</button>
<button [ibmButton]="'danger--primary'" [size]="'normal'" (click)="loginPopup2fA()">Login Request 2FA</button>
<hr/>
<button [ibmButton]="'primary'" [size]="'normal'" (click)="loginPopup()">Check 2FA</button>
<button [ibmButton]="'danger--primary'" [size]="'normal'" (click)="loginPopup()">Login Request 2FA</button>
import { Component } from '@angular/core';
import {Component, OnDestroy} from '@angular/core';
import { OidcFacade} from 'ng-oidc-client';
import {ToastService} from './toast';
import {merge, Observable, of, Subscription} from 'rxjs';
import {catchError, mergeMap, take} from 'rxjs/operators';
import { environment } from '.././environments/environment';
import {HttpClient} from '@angular/common/http';
import {isArray} from 'util';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
export class AppComponent implements OnDestroy {
private audienceApi = 'https://openid.akehir.com:55001/';
private audience = { audience: this.audienceApi };
title = 'openid-connect-playground';
loadingSub: Subscription;
expiringSub: Subscription;
expiredSub: Subscription;
errorSub: Subscription;
loggedInSub: Subscription;
identitySub: Subscription;
constructor(
private oidcFacade: OidcFacade,
private toast: ToastService,
) {}
private http: HttpClient,
) {
this.loadingSub = this.oidcFacade.loading$.subscribe((data) => {
console.log('Loading', data);
});
this.expiringSub = this.oidcFacade.expiring$.subscribe((data) => {
console.log('Expiring', data);
});
this.expiredSub = this.oidcFacade.expired$.subscribe((data) => {
console.log('Expired', data);
});
this.loggedInSub = this.oidcFacade.loggedIn$.subscribe((data) => {
console.log('Logged In', data);
});
this.errorSub = this.oidcFacade.errors$.subscribe((data) => {
console.log('Error', data);
this.toast.show({
type: 'error',
title: 'Error',
caption: `${JSON.stringify(data)}`,
});
});
this.identitySub = this.oidcFacade.identity$.subscribe((data) => {
console.log('Identity', data);
});
}
loginPopup() {
this.toast.show({
......@@ -21,7 +67,10 @@ export class AppComponent {
title: 'Log In',
caption: 'Logging in.',
});
this.oidcFacade.signinPopup();
this.oidcFacade.signinPopup({
extraQueryParams: this.audience,
});
}
logoutPopup() {
......@@ -33,12 +82,124 @@ export class AppComponent {
this.oidcFacade.signoutPopup();
}
showToast() {
check2FA() {
const userManager = this.oidcFacade.getUserManager();
userManager.getUser().then((user) => {
if (user) {
if (user.profile) {
if (user.profile.amr) {
if (user.profile.amr.indexOf('mfa') >= 0) {
this.toast.show({
type: 'success',
title: '2FA',
caption: 'You are logged in with 2FA!',
});
} else {
this.toast.show({
type: 'error',
title: '2FA',
caption: 'You are logged in with 1FA!',
});
}
} else {
this.toast.show({
type: 'error',
title: '2FA',
caption: 'You are logged in with 1FA!',
});
}
} else {
this.toast.show({
type: 'error',
title: '2FA',
caption: 'Your profile could not be decoded',
});
}
} else {
this.toast.show({
type: 'error',
title: '2FA',
caption: 'You are not logged in!',
});
}
});
}
loginPopup2fA() {
this.toast.show({
type: 'info',
title: 'Test',
caption: 'Testing',
type: 'warn',
title: 'Log In',
caption: 'Logging in, requesting 2FA.',
});
this.oidcFacade.signinPopup( {
scope: 'openid profile offline_access view:balance transfer:funds',
extraQueryParams: this.audience,
acr_values: 'http://schemas.openid.net/pape/policies/2007/06/multi-factor',
});
}
call1FAAPI() {
this.apiCall('/balance');
}
call2FAAPI() {
this.apiCall('/transfer');
}
apiCall(api: string) {
this.urlCall(this.getApi(this.getUrl(api), api));
}
getUrl(api: string) {
if (environment.production) {
return 'https://openid.akehir.com:55001';
}
return 'http://localhost:55001';
}
urlCall(api: Observable<{}>) {
api.pipe(
catchError((error) => {
console.log('api error', error);
this.toast.show({
type: 'error',
title: 'API Call Error',
caption: `${error.message}`,
});
return of(false);
})
).subscribe((data) => {
if (data) {
console.log('api data', data);
this.toast.show({
type: 'success',
title: 'API Call',
caption: `Received: ${JSON.stringify(data)}`,
});
}
});
}
getApi(url: string, api: string) {
if (api === '/transfer') {
return this.http.post(url + api, {});
} else if (api === '/balance') {
return this.http.get(url + api);
}
return of({});
}
ngOnDestroy(): void {
this.loadingSub.unsubscribe();
this.expiringSub.unsubscribe();
this.expiredSub.unsubscribe();
this.errorSub.unsubscribe();
this.loggedInSub.unsubscribe();
this.identitySub.unsubscribe();
}
}
......@@ -5,10 +5,10 @@ import { AppComponent } from './app.component';
import {ActionReducerMap, StoreModule} from '@ngrx/store';
import {Log, WebStorageStateStore} from 'oidc-client';
import {EffectsModule} from '@ngrx/effects';
import {NgOidcClientModule} from 'ng-oidc-client';
import {Config as OidcConfig, NgOidcClientModule} from 'ng-oidc-client';
import {routerReducer, RouterReducerState} from '@ngrx/router-store';
import {OidcGuardService} from './oidc-guard.service';
import {HTTP_INTERCEPTORS} from '@angular/common/http';
import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import {OidcInterceptorService} from './oidc-interceptor.service';
import {OidcEffectsService} from './oidc-effects.service';
import {metaReducers} from './logout.metareducer';
......@@ -16,7 +16,8 @@ import {ButtonModule, NotificationModule, NotificationService} from 'carbon-comp
import {ToastModule} from './toast';
import {FormsModule} from '@angular/forms';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {environment} from '../environments/environment';
import {ConfiguredOidcModuleModule} from './configured-oidc.module';
// Setup done according to https://www.npmjs.com/package/ng-oidc-client
......@@ -28,7 +29,6 @@ export const rootStore: ActionReducerMap<State> = {
router: routerReducer
};
// export const storage = new WebStorageStateStore({store: window.localStorage});
@NgModule({
declarations: [
......@@ -38,28 +38,12 @@ export const rootStore: ActionReducerMap<State> = {
BrowserModule,
FormsModule,
BrowserAnimationsModule,
HttpClientModule,
ButtonModule,
ToastModule.forRoot(),
StoreModule.forRoot(rootStore, { metaReducers}),
EffectsModule.forRoot([OidcEffectsService]),
NgOidcClientModule.forRoot({
oidc_config: {
authority: 'https://dev-l-3l8u04.eu.auth0.com',
client_id: 'FYg99lRIvbUwY4c0h5PkBArUOPnCcpH3',
redirect_uri: 'http://localhost:4200/callback.html',
response_type: 'id_token token',
scope: 'openid profile offline_access api1',
post_logout_redirect_uri: 'http://localhost:4200/signout-callback.html',
silent_redirect_uri: 'http://localhost:4200/renew-callback.html',
accessTokenExpiringNotificationTime: 10,
automaticSilentRenew: true,
// userStore: storage,
},
// log: {
// logger: window.console,
// level: 0,
// },
}),
ConfiguredOidcModuleModule.forRoot(environment),
],
providers: [
OidcGuardService,
......
import {ModuleWithProviders, NgModule} from '@angular/core';
import {NgOidcClientModule} from 'ng-oidc-client';
@NgModule ( {
imports: [ NgOidcClientModule.forRoot({
oidc_config: {
authority: 'https://dev-l-3l8u04.eu.auth0.com',
client_id: 'FYg99lRIvbUwY4c0h5PkBArUOPnCcpH3',
redirect_uri: 'https://openid.akehir.com/callback.html',
response_type: 'id_token token',
scope: 'openid profile offline_access view:balance',
post_logout_redirect_uri: 'https://openid.akehir.com/signout-callback.html',
silent_redirect_uri: 'https://openid.akehir.com/renew-callback.html',
accessTokenExpiringNotificationTime: 10,
automaticSilentRenew: true,
}, })
],
providers: []
} )
export class ConfiguredOidcModuleForProd {}
@NgModule ( {
imports: [ NgOidcClientModule.forRoot({
oidc_config: {
authority: 'https://dev-l-3l8u04.eu.auth0.com',
client_id: 'FYg99lRIvbUwY4c0h5PkBArUOPnCcpH3',
redirect_uri: 'http://localhost:4200/callback.html',
response_type: 'id_token token',
scope: 'openid profile offline_access view:balance',
post_logout_redirect_uri: 'http://localhost:4200/signout-callback.html',
silent_redirect_uri: 'http://localhost:4200/renew-callback.html',
accessTokenExpiringNotificationTime: 10,
automaticSilentRenew: true,
},
}), ],
providers: [ ]
} )
export class ConfiguredOidcModuleForDev {}
@NgModule({
imports: [ConfiguredOidcModuleForProd, ConfiguredOidcModuleForDev],
declarations: [],
entryComponents: []
})
export class ConfiguredOidcModuleModule {
public static forRoot( environment ) {
return { ngModule: ( environment.production ? ConfiguredOidcModuleForProd : ConfiguredOidcModuleForDev ) };
}
}
import {switchMap} from 'rxjs/operators';
import {switchMap, tap} from 'rxjs/operators';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Observable} from 'rxjs';
import {OidcFacade} from 'ng-oidc-client';
......
......@@ -8,7 +8,7 @@ import { toastAnimations, ToastAnimationState } from './toast.animation';
@Component({
selector: 'app-toast',
template: `
<ibm-toast [notificationObj]="data"
<ibm-toast [notificationObj]="data" (close)="close()"
[@fadeAnimation]="{value: animationState, params:
{ fadeIn: toastConfig.animation.fadeIn, fadeOut: toastConfig.animation.fadeOut }}"
(@fadeAnimation.done)="onFadeFinished($event)">
......@@ -25,14 +25,12 @@ export class ToastComponent implements OnInit, OnDestroy {
readonly ref: ToastRef,
@Inject(_TOAST_CONFIG_TOKEN) public toastConfig: MyToastConfig
) {
// this.iconType = data.type === 'success' ? 'done' : data.type;
// somehow empty injection of the config... maybe issue with carbon?
this.toastConfig = {...defaultToastConfig, ...this.toastConfig };
}
ngOnInit() {
this.intervalId = setTimeout(() => this.animationState = 'closing', 5000);
this.intervalId = setTimeout(() => this.animationState = 'closing', this.toastConfig.stayTime);
}
ngOnDestroy() {
......
......@@ -20,6 +20,7 @@ export class ToastData {
export type ToastType = 'warning' | 'info' | 'success';
export interface MyToastConfig {
stayTime?: number;
position?: {
top: number;
right: number;
......@@ -31,13 +32,14 @@ export interface MyToastConfig {
}
export const defaultToastConfig: MyToastConfig = {
stayTime: 1500,
position: {
top: 20,
right: 20,
},
animation: {
fadeOut: 2500,
fadeIn: 300,
fadeOut: 1000,
fadeIn: 200,
},
};
......
......@@ -5,11 +5,16 @@ import { ComponentPortal, PortalInjector } from '@angular/cdk/portal';
import { ToastComponent } from './toast.component';
import {ToastData, _TOAST_CONFIG_TOKEN, MyToastConfig, defaultToastConfig} from './toast.config';
import { ToastRef } from './toast.ref';
import {interval, Observable, ReplaySubject, Subject} from 'rxjs';
import {auditTime, debounceTime, delay, delayWhen, throttleTime} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class ToastService {
private lastToast: ToastRef;
private toastsSubject: Subject<ToastData> = new ReplaySubject(1);
private trefSubject: Subject<ToastRef> = new ReplaySubject(1);
private lastToastTime: number;
constructor(
private overlay: Overlay,
......@@ -19,9 +24,27 @@ export class ToastService {
// console.log(parentInjector, this.toastConfig);
// somehow empty injection of the config... maybe issue with carbon?
this.toastConfig = {...defaultToastConfig, ...this.toastConfig };
this.lastToastTime = performance.now();
this.toastsSubject.pipe(
delayWhen((data) => {
const current = performance.now();
const diff = (current - this.lastToastTime) > 100;
if (diff) {
this.lastToastTime = current;
return interval(0);
}
this.lastToastTime = current + 100;
return interval(100);
}),
).subscribe((data) => {
const ref = this.display(data);
this.trefSubject.next(ref);
});
}
show(data: ToastData) {
display(data: ToastData): ToastRef {
const positionStrategy = this.getPositionStrategy();
const overlayRef = this.overlay.create({ positionStrategy });
......@@ -35,6 +58,13 @@ export class ToastService {
return toastRef;
}
show(data: ToastData): Observable<ToastRef> {
this.toastsSubject.next(data);
return this.trefSubject.asObservable();
}
getPositionStrategy() {
return this.overlay.position()
.global()
......
......@@ -17,7 +17,7 @@
var config = {
userStore: new Oidc.WebStorageStateStore({ store: window.localStorage })
}
};
if ((Oidc && Oidc.Log && Oidc.Log.logger)) {
Oidc.Log.logger = console;
......
......@@ -14,7 +14,7 @@
<script>
var config = {
userStore: new Oidc.WebStorageStateStore({ store: window.localStorage })
}
};
new Oidc.UserManager(config).signinSilentCallback().catch(function (e) {
console.error(e);
});
......
......@@ -17,7 +17,7 @@
var config = {
userStore: new Oidc.WebStorageStateStore({ store: window.localStorage })
}
};
if ((Oidc && Oidc.Log && Oidc.Log.logger)) {
Oidc.Log.logger = console;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment