LP#626157 Ang2 experiments
authorBill Erickson <berickxx@gmail.com>
Thu, 23 Nov 2017 18:12:38 +0000 (13:12 -0500)
committerBill Erickson <berickxx@gmail.com>
Mon, 11 Dec 2017 17:39:51 +0000 (12:39 -0500)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
27 files changed:
Open-ILS/webby-src/src/app/core/auth.service.ts
Open-ILS/webby-src/src/app/core/idl.service.ts
Open-ILS/webby-src/src/app/core/store.service.ts
Open-ILS/webby-src/src/app/resolver.service.ts
Open-ILS/webby-src/src/app/staff/admin/admin.module.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/admin/routing.module.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/admin/workstation/admin-ws.module.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/admin/workstation/routing.module.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/admin/workstation/workstations.component.html [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/admin/workstation/workstations.component.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/auth-guard.service.ts
Open-ILS/webby-src/src/app/staff/circ/circ-routing.module.ts [deleted file]
Open-ILS/webby-src/src/app/staff/circ/circ.component.ts [deleted file]
Open-ILS/webby-src/src/app/staff/circ/circ.module.ts
Open-ILS/webby-src/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts
Open-ILS/webby-src/src/app/staff/circ/patron/bcsearch/bcsearch.module.ts
Open-ILS/webby-src/src/app/staff/circ/routing.module.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/data-resolver.service.ts
Open-ILS/webby-src/src/app/staff/login.component.html
Open-ILS/webby-src/src/app/staff/login.component.ts
Open-ILS/webby-src/src/app/staff/logout.component.ts [deleted file]
Open-ILS/webby-src/src/app/staff/nav.component.html
Open-ILS/webby-src/src/app/staff/resolver.service.ts
Open-ILS/webby-src/src/app/staff/routing.module.ts
Open-ILS/webby-src/src/app/staff/splash.component.html
Open-ILS/webby-src/src/app/staff/staff.component.ts
Open-ILS/webby-src/src/app/staff/staff.module.ts

index d04e765..ec97f37 100644 (file)
@@ -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<any>( (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<any> {
+    login(args: EgAuthLoginArgs, isOpChange?: boolean): Promise<void> {
 
-        return new Promise<any>((resolve, reject) => {
+        return new Promise<void>((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<void> {
 
         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<void> {
+        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()) {
index 7d44e10..49a4317 100644 (file)
@@ -16,6 +16,8 @@ export interface EgIdlObject {
     a: any[];
     classname: String;
     _isfieldmapper: Boolean;
+    // Dynamically appended functions from the IDL.
+    [fields: string]: any;
 }
 
 @Injectable()
index 083def0..f508f55 100644 (file)
@@ -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<any> {
-
         // 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<any> {
-        // 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<any> {
         // 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<any> {
-        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 {
index e1e8b73..9f41183 100644 (file)
@@ -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<any> {
+export class EgBaseResolver implements Resolve<Promise<void>> {
 
     constructor(private router: Router, private egIdl: EgIdlService) {}
 
     resolve(
         route: ActivatedRouteSnapshot, 
-        state: RouterStateSnapshot): Observable<any> {
+        state: RouterStateSnapshot): Promise<void> {
 
         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 (file)
index 0000000..f66ab52
--- /dev/null
@@ -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 (file)
index 0000000..39de569
--- /dev/null
@@ -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 (file)
index 0000000..43eb5de
--- /dev/null
@@ -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 (file)
index 0000000..ebf7829
--- /dev/null
@@ -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 (file)
index 0000000..becc930
--- /dev/null
@@ -0,0 +1,6 @@
+
+<br/>
+<br/>
+<br/>
+<br/>
+<b> WORKSTATIONS ADMIN </b>
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 (file)
index 0000000..aebfa06
--- /dev/null
@@ -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()');
+
+    }
+}
+
+
index d54ec41..4d8d7c0 100644 (file)
@@ -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<boolean> {
 
-        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<boolean> {
+        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 (file)
index 85a1b53..0000000
+++ /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 (file)
index de96ea6..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Component } from '@angular/core';
-import { Router } from '@angular/router';
-
-@Component({
-    template: '<router-outlet></router-outlet>'
-})
-
-export class EgCircComponent { }
-
-
index 240b28a..b9ea323 100644 (file)
@@ -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
index 18765e4..9156170 100644 (file)
@@ -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 {
index 0f38729..be9f6b8 100644 (file)
@@ -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 (file)
index 0000000..98f2a4e
--- /dev/null
@@ -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 {}
index 1819802..474ff8e 100644 (file)
@@ -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<Promise<boolean>> {
+export class EgStaffDataResolver implements Resolve<Observable<any>> {
+
+    private WS_ADMIN_URL = '/staff/admin/workstation/workstations';
 
     constructor(
         private router: Router, 
+        private egNet: EgNetService,
         private egAuth: EgAuthService
     ) {}
 
     resolve(
         route: ActivatedRouteSnapshot, 
-        state: RouterStateSnapshot): Promise<boolean> {
+        state: RouterStateSnapshot): Observable<any> {
 
         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<void> {
+        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<any>): Promise<void> {
+        return Promise.resolve();
     }
 }
 
index 09726bf..705eede 100644 (file)
@@ -3,6 +3,7 @@
     <div class="col-md-6 offset-md-3">
       <fieldset>
         <legend i18n>Sign In</legend>
+        <hr/>
         <form (ngSubmit)="handleSubmit()" #loginForm="ngForm">
 
           <div class="form-group">
index 4ae20cf..ca2b2f8 100644 (file)
@@ -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 (file)
index 51587c6..0000000
+++ /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 : '<div></div>'
-})
-
-export class EgStaffLogoutComponent implements OnInit {
-
-    constructor(
-      private router: Router,
-      private egAuth: EgAuthService,
-    ) {}
-  
-    ngOnInit() {
-        this.egAuth.logout();
-        this.router.navigate(['/staff/login']);
-    }
-}
index 0907376..b7d566b 100644 (file)
@@ -25,7 +25,7 @@ routerLink="/suff"
       <li ngbDropdown class="nav-item dropdown">
         <a ngbDropdownToggle i18n class="nav-link dropdown-toggle">Hamburger</a>
         <div class="dropdown-menu" ngbDropdownMenu>
-          <a i18n class="dropdown-item" routerLink="/staff/logout">Logout</a>
+          <a i18n class="dropdown-item" routerLink="/staff/login">Logout</a>
         </div>
       </li>
     </ul>
index f697a8a..e8f975e 100644 (file)
@@ -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<Promise<boolean>> {
+export class EgStaffResolver implements Resolve<Observable<any>> {
+
+    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<boolean> {
-        // Staff-global configuration, etc.
+        state: RouterStateSnapshot): Observable<any> {
 
         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<any>): Promise<void> {
+        return Promise.resolve();
     }
 }
 
index 615c3e3..83d86f2 100644 (file)
@@ -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
   ]
 })
 
index 2e6b23a..0bb2f6c 100644 (file)
@@ -1,6 +1,10 @@
 <br/>
 <br/>
 <br/>
-<br/>
 <b>Staff Splash Page</b>
 
+<br/>
+Some links to test...
+
+<a routerLink="/staff/admin/workstation/workstations">Workstation Admin</a>
+
index 2e8460e..e04aa29 100644 (file)
@@ -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]);
+            }
+        }
+    }
 }
 
 
index a468dbb..529528d 100644 (file)
@@ -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,