})
export class EgBaseComponent {
- title = 'EgBase';
}
import { NgModule } from '@angular/core';
import { Router } from '@angular/router'; // Debugging
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { CookieModule } from 'ngx-cookie'; // import CookieMonster
import { EgBaseComponent } from './base.component';
import { EgBaseRoutingModule } from './base-routing.module';
import { EgIdlService } from '@eg/core/idl.service';
import { EgNetService } from '@eg/core/net.service';
import { EgAuthService } from '@eg/core/auth.service';
+import { EgStoreService } from '@eg/core/store.service';
@NgModule({
declarations: [
imports: [
EgBaseRoutingModule,
BrowserModule,
- NgbModule.forRoot()
+ NgbModule.forRoot(),
+ CookieModule.forRoot()
],
providers: [
EgEventService,
EgIdlService,
EgNetService,
- EgAuthService
+ EgAuthService,
+ EgStoreService
],
bootstrap: [EgBaseComponent]
})
token: String;
authtime: Number;
- constructor(token: String, authtime: Number, workstation: String) {
+ constructor(token: String, authtime: Number, workstation?: String) {
this.token = token;
this.workstation = workstation;
this.authtime = authtime;
// again, when the op-change cycle has completed.
private opChangeUser: EgAuthUser;
+ redirectUrl: string;
+
constructor(
private egEvt: EgEventService,
private egNet: EgNetService,
// Workstation name.
workstation(): String {
- return this.activeUser.workstation
+ return this.activeUser.workstation;
};
token(): String {
- return this.activeUser.token
+ return this.activeUser ? this.activeUser.token : null;
};
authtime(): Number {
return this.activeUser.authtime
};
- login(args: EgAuthLoginArgs, ops?: Object): Promise<any> {
- if (!ops) ops = {};
+ // NOTE: EgNetService emits an event if the auth session has expired.
+ testAuthToken(): Promise<any> {
+
+ this.activeUser = new EgAuthUser(
+ this.egStore.getLoginSessionItem('eg.auth.token'),
+ this.egStore.getLoginSessionItem('eg.auth.time')
+ );
+
+ if (!this.token()) return Promise.reject('no authtoken');
+
+ 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();
+ });
+ });
+ }
+
+ checkWorkstation(): void {
+ // TODO:
+ // Emits event on invalid workstation.
+ }
+
+ login(args: EgAuthLoginArgs, isOpChange?: boolean): Promise<any> {
return new Promise<any>((resolve, reject) => {
this.egNet.request('open-ils.auth', 'open-ils.auth.login', args)
.subscribe(res => {
- let evt = this.egEvt.parse(res);
- if (evt) {
- if (evt.textcode == 'SUCCESS') {
- this.handleLoginOk(args, evt, false);
- resolve();
- }
- } else {
- // Should never get here.
- reject();
- }
+ this.handleLoginResponse(args, this.egEvt.parse(res), isOpChange);
+ resolve(); // TODO: depends on above...
});
});
}
- testAuthToken(): Promise<any> {
- return Promise.resolve();
+ handleLoginResponse(
+ args: EgAuthLoginArgs, evt: EgEvent, isOpChange: boolean): void {
+
+ switch (evt.textcode) {
+ case 'SUCCESS':
+ this.handleLoginOk(args, evt, isOpChange);
+ break;
+ case 'WORKSTATION_NOT_FOUND':
+ // TODO relogin without workstation and go to ws admin page
+ break;
+ default:
+ // TODO: reject...
+ console.error(`Login returned unexpected event: ${evt}`);
+ }
}
// Stash the login data
- handleLoginOk(args: EgAuthLoginArgs, evt: EgEvent, isOpChange: Boolean): void {
+ handleLoginOk(args: EgAuthLoginArgs, evt: EgEvent, isOpChange: boolean): void {
if (isOpChange) {
this.egStore.setLoginSessionItem('eg.auth.token.oc', this.token());
undoOpChange(): Promise<any> {
if (this.opChangeUser) {
+ this.deleteSession();
this.activeUser = this.opChangeUser;
this.opChangeUser = null;
this.egStore.removeLoginSessionItem('eg.auth.token.oc');
}
return this.testAuthToken();
}
+
+ sessionPoll(): void {
+ // TODO
+ }
+
+
+ deleteSession(): void {
+ if (this.token()) {
+ // fire and forget
+ this.egNet.request(
+ 'open-ils.auth',
+ 'open-ils.auth.session.delete', this.token());
+ }
+ }
+
+ logout(broadcast?: boolean) {
+
+ if (broadcast) {
+ // TODO
+ //this.authChannel.postMessage({action : 'logout'});
+ }
+
+ this.deleteSession();
+ this.egStore.clearLoginSessionItems();
+ this.activeUser = null;
+ this.opChangeUser = null;
+ }
}
* Creates the class constructor and getter/setter
* methods for each IDL class.
*/
-
let mkclass = (cls, fields) => {
this_.classes[cls].classname = cls;
*/
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
+import { CookieService } from 'ngx-cookie';
@Injectable()
export class EgStoreService {
+
+ // Base path for cookie-based storage.
+ loginSessionBasePath: string;
+
+ 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}`);
+ }
+ }
+
+ return val;
+ }
setItem(key: string, val: any, isJson?: Boolean): Promise<any> {
setLoginSessionItem(key: string, val: any, isJson?:Boolean): void {
if (!isJson) val = JSON.stringify(val);
+ console.log(`storing ses item ${key} : ${val}`);
+ this.cookieService.put(key, val, {path : this.loginSessionBasePath});
}
getItem(key: string): Promise<any> {
}
getLocalItem(key: string): any {
- let valJson: string = window.localStorage.getItem(key);
- if (valJson === null) return null;
- try {
- return JSON.parse(valJson);
- } catch (E) {
- console.error(`Deleting invalid JSON for localItem: `
- + `${key} => ${valJson} : ${E}`);
- this.removeLocalItem(key);
- }
- return null;
+ return this.parseJson(window.localStorage.getItem(key));
}
getServerItem(key: string): Promise<any> {
}
getLoginSessionItem(key: string): any {
+ return this.parseJson(this.cookieService.get(key));
}
removeItem(key: string): Promise<any> {
}
removeLocalItem(key: string): void {
- let valJson: string = window.localStorage.getItem(key);
- if (valJson === null) return null;
- try {
- return JSON.parse(valJson);
- } catch (E) {
- console.error(
- `Deleting invalid JSON for localItem: ${key} => ${valJson}`);
- this.removeLocalItem(key);
- return null;
- }
+ window.localStorage.removeItem(key);
}
removeServerItem(key: string): Promise<any> {
}
removeSessionItem(key: string): void {
+ this.cookieService.remove(key, {path : this.loginSessionBasePath});
}
removeLoginSessionItem(key: string): void {
}
+ clearLoginSessionItems(): void {
+ }
}
--- /dev/null
+import { Injectable } from '@angular/core';
+import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot,
+ CanActivateChild } from '@angular/router';
+import { EgAuthService } from '@eg/core/auth.service';
+
+/**
+ * Confirm we have a valid auth token before allowing access
+ * any staff pages (except for login).
+ */
+
+@Injectable()
+export class EgStaffAuthGuard implements CanActivate, CanActivateChild {
+
+ constructor(private egAuth: EgAuthService, private router: Router) {}
+
+ canActivate(
+ route: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot): Promise<boolean> {
+
+ console.debug('EgStaffAuthGuard:canActivate()');
+
+ // Note: avoid verifying the workstation here, since the
+ // workstation admin page requires access to this route.
+ return new Promise((resolve, error) => {
+ this.egAuth.testAuthToken().then(
+ ok => { resolve(true); },
+ err => {
+ this.egAuth.redirectUrl = state.url;
+ this.router.navigate(['/staff/login']);
+ resolve(false);
+ }
+ );
+ });
+ }
+
+ canActivateChild(
+ route: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot): Promise<boolean> {
+ return this.canActivate(route, state);
+ }
+}
+
--- /dev/null
+import { Injectable } from '@angular/core';
+import { Router, Resolve, RouterStateSnapshot,
+ ActivatedRouteSnapshot } from '@angular/router';
+import { EgAuthService } from '@eg/core/auth.service';
+
+/**
+ * Check auth token and load data required by all staff components.
+ */
+@Injectable()
+export class EgStaffCommonDataResolver implements Resolve<Promise<boolean>> {
+
+ constructor(
+ private router: Router,
+ private egAuth: EgAuthService
+ ) {}
+
+ resolve(
+ route: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot): Promise<boolean> {
+
+ console.debug('EgStaffCommonDataResolver:resolve()');
+
+ // TODO verify workstation
+
+ return Promise.resolve(true);
+ }
+}
+
import { Component, OnInit, Renderer } from '@angular/core';
+import { Router } from '@angular/router';
import { EgAuthService } from '@eg/core/auth.service';
@Component({
workstations = [];
constructor(
+ private router: Router,
private egAuth: EgAuthService,
private renderer: Renderer
) {}
ngOnInit() {
- // Focus username
- this.renderer.selectRootElement('#username').focus();
- // load workstations...
+ // clear out any stale auth data
+ this.egAuth.logout();
+
+ // Focus username
+ this.renderer.selectRootElement('#username').focus();
+
+ // load local workstation data
}
handleSubmit() {
- this.egAuth.login(this.args).then(
- // redirect to desire page.
- res => console.log('Authtoken: ' + this.egAuth.token())
- );
+ // where are we go after succesful login
+ 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]);
+ });
}
}
--- /dev/null
+import { Injectable } from '@angular/core';
+import { Router, Resolve, RouterStateSnapshot,
+ ActivatedRouteSnapshot } from '@angular/router';
+import { EgStoreService } from '@eg/core/store.service';
+import { EgAuthService } from '@eg/core/auth.service';
+
+/**
+ * Apply configuration, etc. required by all staff components.
+ * This resolver is called before authentication is confirmed.
+ * See EgStaffCommonDataResolver for staff-wide, post-auth activities.
+ */
+@Injectable()
+export class EgStaffResolver implements Resolve<Promise<boolean>> {
+
+ constructor(
+ private router: Router,
+ private egStore: EgStoreService,
+ private egAuth: EgAuthService
+ ) {}
+
+ resolve(
+ route: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot): Promise<boolean> {
+ // Staff-global configuration, etc.
+
+ console.debug('EgStaffResolver:resolve()');
+
+ // Staff cookies stay in /$base/staff/
+ this.egStore.loginSessionBasePath = '/webby/staff'; // TODO dynamic
+
+ return Promise.resolve(true);
+ }
+}
+
--- /dev/null
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { EgStaffResolver } from './resolver.service';
+import { EgStaffCommonDataResolver } from './common-data-resolver.service';
+import { EgStaffAuthGuard } from './auth-guard.service';
+import { EgStaffComponent } from './staff.component';
+import { EgStaffLoginComponent } from './login.component';
+import { EgStaffSplashComponent } from './splash.component';
+
+const routes: Routes = [
+ { path: '',
+ component: EgStaffComponent,
+ resolve : {staffResolver : EgStaffResolver},
+ children : [
+ { path: 'login',
+ component: EgStaffLoginComponent
+ }, {
+ path : 'splash',
+ canActivate : [EgStaffAuthGuard],
+ resolve : {commonDataResolver : EgStaffCommonDataResolver},
+ component: EgStaffSplashComponent
+ }, {
+ path : 'circ',
+ canActivate : [EgStaffAuthGuard],
+ resolve : {commonDataResolver : EgStaffCommonDataResolver},
+ loadChildren : '@eg/staff/circ/circ.module#EgCircModule'
+ }
+ ],
+ }
+];
+
+@NgModule({
+ imports: [ RouterModule.forChild(routes) ],
+ exports: [ RouterModule ],
+ providers: [
+ EgStaffResolver,
+ EgStaffCommonDataResolver,
+ EgStaffAuthGuard
+ ]
+})
+
+export class EgStaffRoutingModule {}
+
--- /dev/null
+
+<b>Staff Splash Page</b>
+
--- /dev/null
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+
+@Component({
+ styleUrls: ['splash.component.css'],
+ templateUrl: 'splash.component.html'
+})
+
+export class EgStaffSplashComponent implements OnInit {
+ constructor(private route: ActivatedRoute) {}
+
+ ngOnInit() {
+
+ }
+}
+
+
+++ /dev/null
-import { Injectable } from '@angular/core';
-import { Observable } from 'rxjs/Observable';
-import { Router, Resolve, RouterStateSnapshot,
- ActivatedRouteSnapshot } from '@angular/router';
-
-/**
- * TODO: import network, etc. and implement startup routines.
- */
-
-@Injectable()
-export class EgStaffResolver implements Resolve<any> {
-
- constructor(private router: Router) {}
-
- resolve(
- route: ActivatedRouteSnapshot,
- state: RouterStateSnapshot): Observable<any> {
-
- console.debug('EgStaffResolver:resolve()');
-
- // TODO: check auth session
-
- // async placeholder for staff startup routines
- return Observable.create(
- observer => {
- observer.next(123);
- observer.complete();
- }
- );
- }
-
-}
-
+++ /dev/null
-import { NgModule } from '@angular/core';
-import { RouterModule, Routes } from '@angular/router';
-import { EgStaffComponent } from './staff.component';
-import { EgStaffLoginComponent } from './login.component';
-import { EgStaffResolver } from './staff-resolver.service';
-
-const routes: Routes = [
- { path: '',
- component: EgStaffComponent,
- resolve : {startup : EgStaffResolver},
- children : [
- {
- path: 'login',
- component: EgStaffLoginComponent
- }, {
- path : 'circ',
- loadChildren : '@eg/staff/circ/circ.module#EgCircModule'
- }
- ]
- }
-];
-
-@NgModule({
- imports: [ RouterModule.forChild(routes) ],
- exports: [ RouterModule ],
- providers: [ EgStaffResolver ]
-})
-
-export class EgStaffRoutingModule {}
import { Component, OnInit } from '@angular/core';
-import { ActivatedRoute, Router } from '@angular/router';
+import { Router, ActivatedRoute } from '@angular/router';
@Component({
templateUrl: 'staff.component.html'
export class EgStaffComponent implements OnInit {
- constructor(private route: ActivatedRoute) {}
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute) {}
ngOnInit() {
- this.route.data.subscribe((data: { startup : any }) => {
+ console.debug(`EgStaffComponent loading route ${this.router.url}`);
+
+ /*
+ if (this.router.url == '/staff') {
+ this.router.navigate(['/staff/splash']);
+ }
+ */
+
+ this.route.data.subscribe((data: {staffResolver : any}) => {
console.debug('EgStaff ngOnInit complete');
});
}
import { CommonModule } from '@angular/common';
-import { NgModule } from '@angular/core';
-import { FormsModule } from '@angular/forms';
-import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
-import { EgStaffComponent } from './staff.component';
-import { EgStaffRoutingModule } from './staff-routing.module';
-import { EgStaffNavComponent } from './nav.component';
+import { EgStaffComponent } from './staff.component';
+import { EgStaffRoutingModule } from './routing.module';
+import { EgStaffNavComponent } from './nav.component';
import { EgStaffLoginComponent } from './login.component';
+import { EgStaffSplashComponent } from './splash.component';
@NgModule({
declarations: [
EgStaffComponent,
EgStaffNavComponent,
+ EgStaffSplashComponent,
EgStaffLoginComponent
],
imports: [