From e1790b29667e8aeb5c94ce10730f2bc20bf4a4b3 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Thu, 23 Nov 2017 13:12:38 -0500 Subject: [PATCH] LP#626157 Ang2 experiments Signed-off-by: Bill Erickson --- Open-ILS/webby-src/src/app/core/auth.service.ts | 99 +++++++++++++++++----- Open-ILS/webby-src/src/app/core/idl.service.ts | 2 + Open-ILS/webby-src/src/app/core/store.service.ts | 33 +++++--- Open-ILS/webby-src/src/app/resolver.service.ts | 10 +-- .../webby-src/src/app/staff/admin/admin.module.ts | 10 +++ .../src/app/staff/admin/routing.module.ts | 17 ++++ .../app/staff/admin/workstation/admin-ws.module.ts | 19 +++++ .../app/staff/admin/workstation/routing.module.ts | 16 ++++ .../admin/workstation/workstations.component.html | 6 ++ .../admin/workstation/workstations.component.ts | 25 ++++++ .../webby-src/src/app/staff/auth-guard.service.ts | 46 +++++++--- .../src/app/staff/circ/circ-routing.module.ts | 21 ----- .../webby-src/src/app/staff/circ/circ.component.ts | 10 --- .../webby-src/src/app/staff/circ/circ.module.ts | 5 +- .../circ/patron/bcsearch/bcsearch.component.ts | 10 --- .../staff/circ/patron/bcsearch/bcsearch.module.ts | 5 +- .../webby-src/src/app/staff/circ/routing.module.ts | 20 +++++ .../src/app/staff/data-resolver.service.ts | 85 ++++++++++++++++--- .../webby-src/src/app/staff/login.component.html | 1 + .../webby-src/src/app/staff/login.component.ts | 34 +++++--- .../webby-src/src/app/staff/logout.component.ts | 20 ----- .../webby-src/src/app/staff/nav.component.html | 2 +- .../webby-src/src/app/staff/resolver.service.ts | 60 +++++++++++-- Open-ILS/webby-src/src/app/staff/routing.module.ts | 44 ++++------ .../webby-src/src/app/staff/splash.component.html | 6 +- .../webby-src/src/app/staff/staff.component.ts | 51 +++++++++-- Open-ILS/webby-src/src/app/staff/staff.module.ts | 4 +- 27 files changed, 468 insertions(+), 193 deletions(-) create mode 100644 Open-ILS/webby-src/src/app/staff/admin/admin.module.ts create mode 100644 Open-ILS/webby-src/src/app/staff/admin/routing.module.ts create mode 100644 Open-ILS/webby-src/src/app/staff/admin/workstation/admin-ws.module.ts create mode 100644 Open-ILS/webby-src/src/app/staff/admin/workstation/routing.module.ts create mode 100644 Open-ILS/webby-src/src/app/staff/admin/workstation/workstations.component.html create mode 100644 Open-ILS/webby-src/src/app/staff/admin/workstation/workstations.component.ts delete mode 100644 Open-ILS/webby-src/src/app/staff/circ/circ-routing.module.ts delete mode 100644 Open-ILS/webby-src/src/app/staff/circ/circ.component.ts create mode 100644 Open-ILS/webby-src/src/app/staff/circ/routing.module.ts delete mode 100644 Open-ILS/webby-src/src/app/staff/logout.component.ts diff --git a/Open-ILS/webby-src/src/app/core/auth.service.ts b/Open-ILS/webby-src/src/app/core/auth.service.ts index d04e76542f..ec97f377bd 100644 --- a/Open-ILS/webby-src/src/app/core/auth.service.ts +++ b/Open-ILS/webby-src/src/app/core/auth.service.ts @@ -24,12 +24,20 @@ class EgAuthUser { // Params required for calling the login() method. interface EgAuthLoginArgs { - username: String, - password: String, - workstation: String, - type: String // staff, persist, etc. + username: String, + password: String, + type: String, + workstation?: String } +export enum EgAuthWsState { + PENDING, + NOT_USED, + NOT_FOUND_SERVER, + NOT_FOUND_LOCAL, + VALID +}; + @Injectable() export class EgAuthService { @@ -40,6 +48,8 @@ export class EgAuthService { // again, when the op-change cycle has completed. private opChangeUser: EgAuthUser; + workstationState: EgAuthWsState = EgAuthWsState.PENDING; + redirectUrl: string; constructor( @@ -80,14 +90,17 @@ export class EgAuthService { return new Promise( (resolve, reject) => { this.egNet.request( 'open-ils.auth', - 'open-ils.auth.session.retrieve', this.token()) - .subscribe(user => { - // EgNetService interceps NO_SESSION events. - // We can only get here if the session is valid. - this.activeUser.user = user; - this.sessionPoll(); - resolve(); - }); + 'open-ils.auth.session.retrieve', this.token() + ).subscribe( + user => { + // EgNetService interceps NO_SESSION events. + // We can only get here if the session is valid. + this.activeUser.user = user; + this.sessionPoll(); + resolve(); + }, + err => { reject(); } + ); }); } @@ -96,30 +109,37 @@ export class EgAuthService { // Emits event on invalid workstation. } - login(args: EgAuthLoginArgs, isOpChange?: boolean): Promise { + login(args: EgAuthLoginArgs, isOpChange?: boolean): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { this.egNet.request('open-ils.auth', 'open-ils.auth.login', args) .subscribe(res => { - this.handleLoginResponse(args, this.egEvt.parse(res), isOpChange); - resolve(); // TODO: depends on above... + this.handleLoginResponse(args, this.egEvt.parse(res), isOpChange) + .then( + ok => resolve(ok), + notOk => reject(notOk) + ); }); }); } handleLoginResponse( - args: EgAuthLoginArgs, evt: EgEvent, isOpChange: boolean): void { + args: EgAuthLoginArgs, evt: EgEvent, isOpChange: boolean): Promise { switch (evt.textcode) { case 'SUCCESS': this.handleLoginOk(args, evt, isOpChange); - break; + return Promise.resolve(); + case 'WORKSTATION_NOT_FOUND': - // TODO relogin without workstation and go to ws admin page - break; + console.error(`No such workstation "${args.workstation}"`); + this.workstationState = EgAuthWsState.NOT_FOUND_SERVER; + delete args.workstation; + return this.login(args, isOpChange); + default: - // TODO: reject... console.error(`Login returned unexpected event: ${evt}`); + return Promise.reject('login failed'); } } @@ -159,6 +179,43 @@ export class EgAuthService { // TODO } + // Resolves if login workstation matches a workstation known to this + // browser instance. + verifyWorkstation(): Promise { + return new Promise((resolve, reject) => { + + if (!this.user()) { + this.workstationState = EgAuthWsState.PENDING; + reject(); + return; + } + + if (!this.user().wsid()) { + this.workstationState = EgAuthWsState.NOT_USED; + reject(); + return; + } + + console.debug(`Verifying workstation ${this.user().wsid()}`); + + this.egStore.getItem('eg.workstation.all') + .then(workstations => { + if (!workstations) workstations = []; + + var ws = workstations.filter( + w => {return w.id == this.user().wsid()})[0]; + + if (ws) { + this.activeUser.workstation = ws.name; + this.workstationState = EgAuthWsState.VALID; + resolve(); + } else { + this.workstationState = EgAuthWsState.NOT_FOUND_LOCAL; + reject(); + } + }); + }); + } deleteSession(): void { if (this.token()) { diff --git a/Open-ILS/webby-src/src/app/core/idl.service.ts b/Open-ILS/webby-src/src/app/core/idl.service.ts index 7d44e1047e..49a4317672 100644 --- a/Open-ILS/webby-src/src/app/core/idl.service.ts +++ b/Open-ILS/webby-src/src/app/core/idl.service.ts @@ -16,6 +16,8 @@ export interface EgIdlObject { a: any[]; classname: String; _isfieldmapper: Boolean; + // Dynamically appended functions from the IDL. + [fields: string]: any; } @Injectable() diff --git a/Open-ILS/webby-src/src/app/core/store.service.ts b/Open-ILS/webby-src/src/app/core/store.service.ts index 083def0c59..f508f551e5 100644 --- a/Open-ILS/webby-src/src/app/core/store.service.ts +++ b/Open-ILS/webby-src/src/app/core/store.service.ts @@ -9,8 +9,10 @@ import { CookieService } from 'ngx-cookie'; export class EgStoreService { // Base path for cookie-based storage. + // Useful for limiting cookies to subsections of the application. loginSessionBasePath: string; + // Set of keys whose values should disappear at logout. loginSessionKeys: string[] = [ 'eg.auth.token', 'eg.auth.time', @@ -21,23 +23,24 @@ export class EgStoreService { constructor(private cookieService: CookieService) {} private parseJson(valJson: string): any { - let val: any = null; - - if (valJson != null) { - try { - val = JSON.parse(valJson); - } catch(E) { - console.error(`Failure to parse JSON: ${E} => ${valJson}`); - } + if (valJson == null || valJson == '') return null; + try { + return JSON.parse(valJson); + } catch(E) { + console.error(`Failure to parse JSON: ${E} => ${valJson}`); + return null; } + } - return val; + /** + * Add a an app-local login session key + */ + addLoginSessionKey(key: string): void { + this.loginSessionKeys.push(key); } setItem(key: string, val: any, isJson?: Boolean): Promise { - // TODO: route keys appropriately - this.setLocalItem(key, val, false); return Promise.resolve(); } @@ -48,12 +51,12 @@ export class EgStoreService { } setServerItem(key: string, val: any): Promise { - // JSON-ify on the server? return Promise.resolve(); } setSessionItem(key: string, val: any, isJson?: Boolean): void { if (!isJson) val = JSON.stringify(val); + window.sessionStorage.setItem(key, val); } setLoginSessionItem(key: string, val: any, isJson?:Boolean): void { @@ -64,7 +67,6 @@ export class EgStoreService { getItem(key: string): Promise { // TODO: route keys appropriately - return Promise.resolve(this.getLocalItem(key)); } @@ -77,6 +79,7 @@ export class EgStoreService { } getSessionItem(key: string): any { + return this.parseJson(window.sessionStorage.getItem(key)); } getLoginSessionItem(key: string): any { @@ -84,7 +87,8 @@ export class EgStoreService { } removeItem(key: string): Promise { - return Promise.resolve(); + // TODO: route keys appropriately + return Promise.resolve(this.removeLocalItem(key)); } removeLocalItem(key: string): void { @@ -96,6 +100,7 @@ export class EgStoreService { } removeSessionItem(key: string): void { + window.sessionStorage.removeItem(key); } removeLoginSessionItem(key: string): void { diff --git a/Open-ILS/webby-src/src/app/resolver.service.ts b/Open-ILS/webby-src/src/app/resolver.service.ts index e1e8b731e4..9f41183490 100644 --- a/Open-ILS/webby-src/src/app/resolver.service.ts +++ b/Open-ILS/webby-src/src/app/resolver.service.ts @@ -1,23 +1,21 @@ -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs/Observable'; +import { Injectable } from '@angular/core'; import { Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router'; - import { EgIdlService } from '@eg/core/idl.service'; @Injectable() -export class EgBaseResolver implements Resolve { +export class EgBaseResolver implements Resolve> { constructor(private router: Router, private egIdl: EgIdlService) {} resolve( route: ActivatedRouteSnapshot, - state: RouterStateSnapshot): Observable { + state: RouterStateSnapshot): Promise { console.debug('EgBaseResolver:resolve()'); this.egIdl.parseIdl(); - return Observable.empty(); // Nothing to report. + return Promise.resolve(); } } diff --git a/Open-ILS/webby-src/src/app/staff/admin/admin.module.ts b/Open-ILS/webby-src/src/app/staff/admin/admin.module.ts new file mode 100644 index 0000000000..f66ab52a81 --- /dev/null +++ b/Open-ILS/webby-src/src/app/staff/admin/admin.module.ts @@ -0,0 +1,10 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { EgAdminRoutingModule } from './routing.module'; + +@NgModule({ + declarations: [], + imports: [ EgAdminRoutingModule ] +}) + +export class EgAdminModule {} diff --git a/Open-ILS/webby-src/src/app/staff/admin/routing.module.ts b/Open-ILS/webby-src/src/app/staff/admin/routing.module.ts new file mode 100644 index 0000000000..39de569dcb --- /dev/null +++ b/Open-ILS/webby-src/src/app/staff/admin/routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [{ + path: '', + children : [{ + path: 'workstation', + loadChildren: '@eg/staff/admin/workstation/admin-ws.module#EgAdminWsModule' + }] +}]; + +@NgModule({ + imports: [ RouterModule.forChild(routes) ], + exports: [ RouterModule ] +}) + +export class EgAdminRoutingModule {} diff --git a/Open-ILS/webby-src/src/app/staff/admin/workstation/admin-ws.module.ts b/Open-ILS/webby-src/src/app/staff/admin/workstation/admin-ws.module.ts new file mode 100644 index 0000000000..43eb5de3b9 --- /dev/null +++ b/Open-ILS/webby-src/src/app/staff/admin/workstation/admin-ws.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { EgAdminWsRoutingModule } from './routing.module'; +import { EgWorkstationsComponent } from './workstations.component'; + +@NgModule({ + declarations: [ + EgWorkstationsComponent + ], + imports: [ + EgAdminWsRoutingModule, + CommonModule, + FormsModule + ] +}) + +export class EgAdminWsModule {} + diff --git a/Open-ILS/webby-src/src/app/staff/admin/workstation/routing.module.ts b/Open-ILS/webby-src/src/app/staff/admin/workstation/routing.module.ts new file mode 100644 index 0000000000..ebf7829a76 --- /dev/null +++ b/Open-ILS/webby-src/src/app/staff/admin/workstation/routing.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { EgWorkstationsComponent } from './workstations.component'; + +const routes: Routes = [ + { path: 'workstations', + component: EgWorkstationsComponent + } +]; + +@NgModule({ + imports: [ RouterModule.forChild(routes) ], + exports: [ RouterModule ] +}) + +export class EgAdminWsRoutingModule {} diff --git a/Open-ILS/webby-src/src/app/staff/admin/workstation/workstations.component.html b/Open-ILS/webby-src/src/app/staff/admin/workstation/workstations.component.html new file mode 100644 index 0000000000..becc93002f --- /dev/null +++ b/Open-ILS/webby-src/src/app/staff/admin/workstation/workstations.component.html @@ -0,0 +1,6 @@ + +
+
+
+
+ WORKSTATIONS ADMIN diff --git a/Open-ILS/webby-src/src/app/staff/admin/workstation/workstations.component.ts b/Open-ILS/webby-src/src/app/staff/admin/workstation/workstations.component.ts new file mode 100644 index 0000000000..aebfa067ad --- /dev/null +++ b/Open-ILS/webby-src/src/app/staff/admin/workstation/workstations.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { EgNetService } from '@eg/core/net.service'; +import { EgAuthService } from '@eg/core/auth.service'; + +@Component({ + templateUrl: 'workstations.component.html' +}) + +export class EgWorkstationsComponent implements OnInit { + + constructor( + private route: ActivatedRoute, + private egNet: EgNetService, + private egAuth: EgAuthService + ) {} + + ngOnInit() { + + console.log('EgWorkstationsComponent:ngOnInit()'); + + } +} + + diff --git a/Open-ILS/webby-src/src/app/staff/auth-guard.service.ts b/Open-ILS/webby-src/src/app/staff/auth-guard.service.ts index d54ec410bc..4d8d7c0e0b 100644 --- a/Open-ILS/webby-src/src/app/staff/auth-guard.service.ts +++ b/Open-ILS/webby-src/src/app/staff/auth-guard.service.ts @@ -1,7 +1,8 @@ -import { Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot, - CanActivateChild } from '@angular/router'; -import { EgAuthService } from '@eg/core/auth.service'; + CanActivateChild } from '@angular/router'; +import { EgStoreService } from '@eg/core/store.service'; +import { EgAuthService } from '@eg/core/auth.service'; /** * Confirm we have a valid auth token before allowing access @@ -11,23 +12,47 @@ import { EgAuthService } from '@eg/core/auth.service'; @Injectable() export class EgStaffAuthGuard implements CanActivate, CanActivateChild { - constructor(private egAuth: EgAuthService, private router: Router) {} + readonly WS_ADMIN_PATH = '/staff/admin/workstation/workstations'; + + constructor( + private router: Router, + private egStore: EgStoreService, + private egAuth: EgAuthService + ) {} canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - console.debug('EgStaffAuthGuard:canActivate()'); + console.debug(`EgStaffAuthGuard:canActivate() path ${state.url}`); + + // Staff cookies stay in /$base/staff/ + // TODO dynamic + this.egStore.loginSessionBasePath = '/webby/staff'; + + // Any and all are allowed access to the login page. + if (state.url == '/staff/login') return Promise.resolve(true); - // Note: avoid verifying the workstation here, since the - // workstation admin page requires access to this route. + // canActivate() always resolves to true, but may redirect + // the caller to the appropriate auth pages along the way. return new Promise((resolve, error) => { this.egAuth.testAuthToken().then( - ok => { resolve(true); }, - err => { + ok => { + if (ok.invalidWorkstation + && state.url != this.WS_ADMIN_PATH + ) { + // Login is valid, but workstation is unknown + // to this browser. Send the caller to the + // workstation admin page. + this.router.navigate([this.WS_ADMIN_PATH]); + } + resolve(true); + }, + notOk => { + console.debug('No valid auth token, sending to login.'); this.egAuth.redirectUrl = state.url; this.router.navigate(['/staff/login']); - resolve(false); // cannot activate + resolve(true); } ); }); @@ -36,6 +61,7 @@ export class EgStaffAuthGuard implements CanActivate, CanActivateChild { canActivateChild( route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + console.log('canActivateChild()'); return this.canActivate(route, state); } } diff --git a/Open-ILS/webby-src/src/app/staff/circ/circ-routing.module.ts b/Open-ILS/webby-src/src/app/staff/circ/circ-routing.module.ts deleted file mode 100644 index 85a1b5350a..0000000000 --- a/Open-ILS/webby-src/src/app/staff/circ/circ-routing.module.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { EgCircComponent } from './circ.component'; - -const routes: Routes = [ - { path: '', - component: EgCircComponent, - children : [ - { path: 'patron/bcsearch', - loadChildren: '@eg/staff/circ/patron/bcsearch/bcsearch.module#EgBcSearchModule' - } - ] - }, -]; - -@NgModule({ - imports: [ RouterModule.forChild(routes) ], - exports: [ RouterModule ] -}) - -export class EgCircRoutingModule {} diff --git a/Open-ILS/webby-src/src/app/staff/circ/circ.component.ts b/Open-ILS/webby-src/src/app/staff/circ/circ.component.ts deleted file mode 100644 index de96ea6b66..0000000000 --- a/Open-ILS/webby-src/src/app/staff/circ/circ.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from '@angular/core'; -import { Router } from '@angular/router'; - -@Component({ - template: '' -}) - -export class EgCircComponent { } - - diff --git a/Open-ILS/webby-src/src/app/staff/circ/circ.module.ts b/Open-ILS/webby-src/src/app/staff/circ/circ.module.ts index 240b28a40c..b9ea323f2f 100644 --- a/Open-ILS/webby-src/src/app/staff/circ/circ.module.ts +++ b/Open-ILS/webby-src/src/app/staff/circ/circ.module.ts @@ -1,12 +1,9 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; - -import { EgCircComponent } from './circ.component'; -import { EgCircRoutingModule } from './circ-routing.module'; +import { EgCircRoutingModule } from './routing.module'; @NgModule({ declarations: [ - EgCircComponent ], imports: [ EgCircRoutingModule diff --git a/Open-ILS/webby-src/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts b/Open-ILS/webby-src/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts index 18765e4830..91561706d7 100644 --- a/Open-ILS/webby-src/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts +++ b/Open-ILS/webby-src/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts @@ -37,16 +37,6 @@ export class EgBcSearchComponent implements OnInit { 'opensrf.system.echo', 'hello', 'goodbye', 'in the middle' ).subscribe(res => this.strList.push(res)); - - this.egNet.request( - 'open-ils.actor', - 'opensrf.system.echo', - {textcode : 'NO_SESSION', code: 123, desc: 'test desc'} - ).subscribe( - x => console.log(x), - e => console.log('echo event: ' + e), - () => console.log('done') - ); } findUser(): void { diff --git a/Open-ILS/webby-src/src/app/staff/circ/patron/bcsearch/bcsearch.module.ts b/Open-ILS/webby-src/src/app/staff/circ/patron/bcsearch/bcsearch.module.ts index 0f38729c51..be9f6b8136 100644 --- a/Open-ILS/webby-src/src/app/staff/circ/patron/bcsearch/bcsearch.module.ts +++ b/Open-ILS/webby-src/src/app/staff/circ/patron/bcsearch/bcsearch.module.ts @@ -1,7 +1,6 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { EgNetService } from '@eg/core/net.service'; import { EgBcSearchComponent } from './bcsearch.component'; import { EgBcSearchRoutingModule } from './bcsearch-routing.module'; @@ -14,9 +13,7 @@ import { EgBcSearchRoutingModule } from './bcsearch-routing.module'; CommonModule, FormsModule ], - providers: [EgNetService] }) -export class EgBcSearchModule { +export class EgBcSearchModule {} -} diff --git a/Open-ILS/webby-src/src/app/staff/circ/routing.module.ts b/Open-ILS/webby-src/src/app/staff/circ/routing.module.ts new file mode 100644 index 0000000000..98f2a4ec99 --- /dev/null +++ b/Open-ILS/webby-src/src/app/staff/circ/routing.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [{ + path: '', + children : [{ + path: 'patron', + children: [{ + path: 'bcsearch', + loadChildren: '@eg/staff/circ/patron/bcsearch/bcsearch.module#EgBcSearchModule' + }] + }] +}]; + +@NgModule({ + imports: [ RouterModule.forChild(routes) ], + exports: [ RouterModule ] +}) + +export class EgCircRoutingModule {} diff --git a/Open-ILS/webby-src/src/app/staff/data-resolver.service.ts b/Open-ILS/webby-src/src/app/staff/data-resolver.service.ts index 1819802f9b..474ff8e9aa 100644 --- a/Open-ILS/webby-src/src/app/staff/data-resolver.service.ts +++ b/Open-ILS/webby-src/src/app/staff/data-resolver.service.ts @@ -1,37 +1,94 @@ -import { Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router'; -import { EgAuthService } from '@eg/core/auth.service'; +import { Observable, Observer } from 'rxjs/Rx'; +import { EgAuthService } from '@eg/core/auth.service'; +import { EgNetService } from '@eg/core/net.service'; /** - * Load post-authentication data required by all staff UI's + * Verify authentication info and load common startup data. */ @Injectable() -export class EgStaffDataResolver implements Resolve> { +export class EgStaffDataResolver implements Resolve> { + + private WS_ADMIN_URL = '/staff/admin/workstation/workstations'; constructor( private router: Router, + private egNet: EgNetService, private egAuth: EgAuthService ) {} resolve( route: ActivatedRouteSnapshot, - state: RouterStateSnapshot): Promise { + state: RouterStateSnapshot): Observable { console.debug('EgStaffDataResolver:resolve()'); - // TODO verify workstation + // Listen for auth timeout failures + this.egNet.authExpired$.subscribe( + uhOh => { + console.log('Auth session has expired. Send to login'); + this.egAuth.redirectUrl = this.router.url; + this.router.navigate(['/staff/login']); + } + ); - let wsPath = '/staff/admin/workstation'; + return Observable.create(observer => { + + console.debug('auth token => ' + this.egAuth.token()); - if (false) { // WS is invalid - if (this.router.url != wsPath) { - console.debug('re-routing to workstation admin page'); - this.router.navigate([wsPath]); - } - } + this.checkAuth().then( + ok => { + this.loadStartupData(observer); + // route resolvers require at least one observable event. + // until we emit other data, just return true. + observer.next(true); + observer.complete(); + }, + notOk => { + // Auth-related re-routing occurs within checkAuth. + observer.next(true); + observer.complete(); + } + ); + }); + } + + // Resolves if the caller should continue, rejects otherwise. + checkAuth(): Promise { + console.log('checkAuth()'); + return new Promise((resolve, reject) => { + console.log('checkAuth(2'); + this.egAuth.testAuthToken().then( + ok => {return this.egAuth.verifyWorkstation()}, + notOk => { + // No valid authtoken, redirect to login page + this.egAuth.redirectUrl = this.router.url; + this.router.navigate(['/staff/login']); + reject(); + } + ).then( + ok => { + // workstation verified. + resolve(); + }, + notOk => { + console.log('checkAuth(3)'); + // Login was valid, but we don't know about the + // workstation used. + if (this.router.url != this.WS_ADMIN_URL) { + console.log('checkAuth(4) ' + this.router.url); + this.router.navigate([this.WS_ADMIN_URL]); + } + reject(); + } + ); + }); + } - return Promise.resolve(true); + loadStartupData(observer: Observer): Promise { + return Promise.resolve(); } } diff --git a/Open-ILS/webby-src/src/app/staff/login.component.html b/Open-ILS/webby-src/src/app/staff/login.component.html index 09726bff8f..705eede90c 100644 --- a/Open-ILS/webby-src/src/app/staff/login.component.html +++ b/Open-ILS/webby-src/src/app/staff/login.component.html @@ -3,6 +3,7 @@
Sign In +
diff --git a/Open-ILS/webby-src/src/app/staff/login.component.ts b/Open-ILS/webby-src/src/app/staff/login.component.ts index 4ae20cfa6a..ca2b2f8fe5 100644 --- a/Open-ILS/webby-src/src/app/staff/login.component.ts +++ b/Open-ILS/webby-src/src/app/staff/login.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, Renderer } from '@angular/core'; import { Router } from '@angular/router'; -import { EgAuthService } from '@eg/core/auth.service'; +import { EgAuthService, EgAuthWsState } from '@eg/core/auth.service'; @Component({ templateUrl : './login.component.html' @@ -12,15 +12,16 @@ export class EgStaffLoginComponent implements OnInit { username : '', password : '', type : 'staff', - workstation : 'BR1-skiddoo' + workstation : '' + //workstation : 'BR1-skiddoo' // testing }; workstations = []; constructor( private router: Router, - private egAuth: EgAuthService, - private renderer: Renderer + private renderer: Renderer, + private egAuth: EgAuthService ) {} ngOnInit() { @@ -31,19 +32,28 @@ export class EgStaffLoginComponent implements OnInit { // Focus username this.renderer.selectRootElement('#username').focus(); - // load local workstation data + // load browser-local workstation data } handleSubmit() { - // where are we go after succesful login + + // post-login URL let url: string = this.egAuth.redirectUrl || '/staff/splash'; - this.egAuth.login(this.args).then(res => { - console.debug('Login succeeded'); - this.egAuth.redirectUrl = null; - console.log(`Routing to URL: ${url}`); - this.router.navigate([url]); - }); + this.egAuth.login(this.args).then( + ok => { + this.egAuth.redirectUrl = null; + if (this.egAuth.workstationState == EgAuthWsState.NOT_FOUND_SERVER) { + // User is logged in without a workstation. + // Redirect them to the WS admin page. + url = '/staff/admin/workstation/workstations'; + } + this.router.navigate([url]); + }, + notOk => { + // indicate failure in the UI. + } + ); } } diff --git a/Open-ILS/webby-src/src/app/staff/logout.component.ts b/Open-ILS/webby-src/src/app/staff/logout.component.ts deleted file mode 100644 index 51587c68c2..0000000000 --- a/Open-ILS/webby-src/src/app/staff/logout.component.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; -import { EgAuthService } from '@eg/core/auth.service'; - -@Component({ - template : '
' -}) - -export class EgStaffLogoutComponent implements OnInit { - - constructor( - private router: Router, - private egAuth: EgAuthService, - ) {} - - ngOnInit() { - this.egAuth.logout(); - this.router.navigate(['/staff/login']); - } -} diff --git a/Open-ILS/webby-src/src/app/staff/nav.component.html b/Open-ILS/webby-src/src/app/staff/nav.component.html index 0907376a27..b7d566b2bb 100644 --- a/Open-ILS/webby-src/src/app/staff/nav.component.html +++ b/Open-ILS/webby-src/src/app/staff/nav.component.html @@ -25,7 +25,7 @@ routerLink="/suff" diff --git a/Open-ILS/webby-src/src/app/staff/resolver.service.ts b/Open-ILS/webby-src/src/app/staff/resolver.service.ts index f697a8acb5..e8f975eaa2 100644 --- a/Open-ILS/webby-src/src/app/staff/resolver.service.ts +++ b/Open-ILS/webby-src/src/app/staff/resolver.service.ts @@ -1,8 +1,10 @@ -import { Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; +import { Observable, Observer } from 'rxjs/Rx'; import { Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router'; -import { EgStoreService } from '@eg/core/store.service'; -import { EgAuthService } from '@eg/core/auth.service'; +import { EgStoreService } from '@eg/core/store.service'; +import { EgNetService } from '@eg/core/net.service'; +import { EgAuthService } from '@eg/core/auth.service'; /** * Apply configuration, etc. required by all staff components. @@ -10,25 +12,69 @@ import { EgAuthService } from '@eg/core/auth.service'; * See EgStaffCommonDataResolver for staff-wide, post-auth activities. */ @Injectable() -export class EgStaffResolver implements Resolve> { +export class EgStaffResolver implements Resolve> { + + readonly loginPath = '/staff/login'; + readonly wsAdminPath = '/staff/admin/workstation/workstations'; constructor( private router: Router, private egStore: EgStoreService, + private egNet: EgNetService, private egAuth: EgAuthService ) {} resolve( route: ActivatedRouteSnapshot, - state: RouterStateSnapshot): Promise { - // Staff-global configuration, etc. + state: RouterStateSnapshot): Observable { console.debug('EgStaffResolver:resolve()'); // Staff cookies stay in /$base/staff/ this.egStore.loginSessionBasePath = '/webby/staff'; // TODO dynamic - return Promise.resolve(true); + // Login resets everything. No need to load any data. + if (state.url == '/staff/login') return Observable.of(true); + +console.log('1'); + + return Observable.create(observer => { +console.log('2'); + this.egAuth.testAuthToken().then( + ok => { +console.log('3'); + // Authtoken is OK. + this.egAuth.verifyWorkstation().then( + ok => { +console.log('4'); + // Workstation is OK. + this.loadStartupData(observer).then( + ok => observer.complete() + ); + }, + notOk => { +console.log('5'); + // Workstation is not OK. + if (state.url != this.wsAdminPath] { + this.router.navigate([this.wsAdminPath]); + } + observer.complete(); + } + ); + }, + notOk => { +console.log('6'); + // Authtoken is not OK. + this.egAuth.redirectUrl = this.router.url; + this.router.navigate([this.loginPath]); + observer.complete(); + } + ); + }); + } + + loadStartupData(observer: Observer): Promise { + return Promise.resolve(); } } diff --git a/Open-ILS/webby-src/src/app/staff/routing.module.ts b/Open-ILS/webby-src/src/app/staff/routing.module.ts index 615c3e3c96..83d86f2cb3 100644 --- a/Open-ILS/webby-src/src/app/staff/routing.module.ts +++ b/Open-ILS/webby-src/src/app/staff/routing.module.ts @@ -1,43 +1,34 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { EgStaffResolver } from './resolver.service'; -import { EgStaffDataResolver } from './data-resolver.service'; -import { EgStaffAuthGuard } from './auth-guard.service'; +//import { EgStaffDataResolver } from './data-resolver.service'; import { EgStaffComponent } from './staff.component'; import { EgStaffLoginComponent } from './login.component'; -import { EgStaffLogoutComponent } from './logout.component'; import { EgStaffSplashComponent } from './splash.component'; +// Not using 'canActivate' because it's called before all resolvers, +// but the resolvers parse the IDL, etc. + const routes: Routes = [{ path: '', + component: EgStaffComponent, resolve: {staffResolver : EgStaffResolver}, children: [{ + path: '', + redirectTo: 'splash', // must be to sibling path + pathMatch: 'full', + }, { path: 'login', component: EgStaffLoginComponent }, { - path: 'logout', - component: EgStaffLogoutComponent + path: 'splash', + component: EgStaffSplashComponent }, { - path: '', - // Require a valid authtoken to access child paths - canActivate : [EgStaffAuthGuard], - // Load data common to all staff UI's before loading child paths. - resolve: {dataResolver: EgStaffDataResolver}, - // EgStaffComponent houses the navbar and the page content shell. - component: EgStaffComponent, - children: [{ - path: '', - redirectTo: 'splash', // must be to sibling path - pathMatch: 'full', - }, { - path: 'splash', - component: EgStaffSplashComponent - }, { - // Lazy-load collections of sub-modules (circ, cat, etc.) - // with loadChildren - path: 'circ', - loadChildren : '@eg/staff/circ/circ.module#EgCircModule' - }] + path: 'circ', + loadChildren : '@eg/staff/circ/circ.module#EgCircModule' + }, { + path: 'admin', + loadChildren : '@eg/staff/admin/admin.module#EgAdminModule' }] }]; @@ -46,8 +37,7 @@ const routes: Routes = [{ exports: [ RouterModule ], providers: [ EgStaffResolver, - EgStaffDataResolver, - EgStaffAuthGuard +// EgStaffDataResolver ] }) diff --git a/Open-ILS/webby-src/src/app/staff/splash.component.html b/Open-ILS/webby-src/src/app/staff/splash.component.html index 2e6b23ad8b..0bb2f6cb81 100644 --- a/Open-ILS/webby-src/src/app/staff/splash.component.html +++ b/Open-ILS/webby-src/src/app/staff/splash.component.html @@ -1,6 +1,10 @@


-
Staff Splash Page +
+Some links to test... + +Workstation Admin + diff --git a/Open-ILS/webby-src/src/app/staff/staff.component.ts b/Open-ILS/webby-src/src/app/staff/staff.component.ts index 2e8460ea38..e04aa296c5 100644 --- a/Open-ILS/webby-src/src/app/staff/staff.component.ts +++ b/Open-ILS/webby-src/src/app/staff/staff.component.ts @@ -1,5 +1,7 @@ import { Component, OnInit } from '@angular/core'; -import { Router, ActivatedRoute } from '@angular/router'; +import { Router, ActivatedRoute, NavigationEnd } from '@angular/router'; +import { EgAuthService, EgAuthWsState } from '@eg/core/auth.service'; +import { EgNetService } from '@eg/core/net.service'; @Component({ templateUrl: 'staff.component.html' @@ -7,24 +9,57 @@ import { Router, ActivatedRoute } from '@angular/router'; export class EgStaffComponent implements OnInit { + readonly loginPath = '/staff/login'; + readonly wsAdminPath = '/staff/admin/workstation/workstations'; + constructor( private router: Router, - private route: ActivatedRoute) {} + private route: ActivatedRoute, + private egNet: EgNetService, + private egAuth: EgAuthService + ) {} ngOnInit() { - console.debug(`EgStaffComponent loading route ${this.router.url}`); + // Fires on all in-app router navigation, but not initial page load. + this.router.events.subscribe(routeEvent => { + if (routeEvent instanceof NavigationEnd) { + console.debug(`EgStaffComponent routing to ${routeEvent.url}`); + this.basicAuthChecks(routeEvent); + } + }); - /* - if (this.router.url == '/staff') { - this.router.navigate(['/staff/splash']); - } - */ + // Redirect to the login page on any auth timeout events. + this.egNet.authExpired$.subscribe(uhOh => { + console.log('Auth session has expired. Send to login'); + this.egAuth.redirectUrl = this.router.url; + this.router.navigate(['/staff/login']); + }); this.route.data.subscribe((data: {staffResolver : any}) => { console.debug('EgStaff ngOnInit complete'); }); } + + /** + * Verifying auth token on every route is overkill, since an expired + * token will make itself known with the first API call, but we do + * want to prevent navigation from the login or workstation admin + * page, since these can be accessed without a valid authtoken or + * workstation, respectively, once the initial route resolvers + * have done their jobs. + */ + basicAuthChecks(routeEvent: NavigationEnd): void { + if (!this.egAuth.token()) { + if (routeEvent.url != this.loginPath) { + this.router.navigate([this.loginPath]); + } + } else if (this.egAuth.workstationState != EgAuthWsState.VALID) { + if (routeEvent.url != this.wsAdminPath) { + this.router.navigate([this.wsAdminPath]); + } + } + } } diff --git a/Open-ILS/webby-src/src/app/staff/staff.module.ts b/Open-ILS/webby-src/src/app/staff/staff.module.ts index a468dbb130..529528ded7 100644 --- a/Open-ILS/webby-src/src/app/staff/staff.module.ts +++ b/Open-ILS/webby-src/src/app/staff/staff.module.ts @@ -7,7 +7,6 @@ import { EgStaffComponent } from './staff.component'; import { EgStaffRoutingModule } from './routing.module'; import { EgStaffNavComponent } from './nav.component'; import { EgStaffLoginComponent } from './login.component'; -import { EgStaffLogoutComponent } from './logout.component'; import { EgStaffSplashComponent } from './splash.component'; @NgModule({ @@ -15,8 +14,7 @@ import { EgStaffSplashComponent } from './splash.component'; EgStaffComponent, EgStaffNavComponent, EgStaffSplashComponent, - EgStaffLoginComponent, - EgStaffLogoutComponent + EgStaffLoginComponent ], imports: [ EgStaffRoutingModule, -- 2.11.0