Commit d1362047 authored by Chris Hines's avatar Chris Hines
Browse files

package upgrades and compat with electron

parent e5ce8c2e
This diff is collapsed.
{
"name": "sv2",
"version": "0.0.0",
"license": "MIT",
"name": "angular-electron",
"version": "9.0.4",
"description": "Angular 11 with Electron (Typescript + SASS + Hot Reload)",
"homepage": "https://github.com/maximegris/angular-electron",
"author": {
"name": "Maxime GRIS",
"email": "maxime.gris@gmail.com"
},
"keywords": [
"angular",
"angular 11",
"electron",
"nodejs",
"typescript",
"spectron",
"eslint",
"sass",
"windows",
"mac",
"linux"
],
"main": "main.js",
"private": true,
"scripts": {
"postinstall": "electron-builder install-app-deps",
"ng": "ng",
"start": "ng serve",
"build": "ng build --prod",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
"start": "npm-run-all -p electron:serve ng:serve",
"build": "npm run electron:serve-tsc && ng build --base-href ./",
"build:dev": "npm run build -- -c dev",
"build:prod": "npm run build -- -c production",
"ng:serve": "ng serve -c web -o",
"electron:serve-tsc": "tsc -p tsconfig.serve.json",
"electron:serve": "wait-on tcp:4200 && npm run electron:serve-tsc && npx electron . --serve",
"electron:local": "npm run build:prod && npx electron .",
"electron:build": "npm run build:prod && electron-builder build --publish=never",
"test": "ng test --watch=false",
"test:watch": "ng test",
"e2e": "npm run build:prod && cross-env TS_NODE_PROJECT='e2e/tsconfig.e2e.json' mocha --timeout 300000 --require ts-node/register e2e/**/*.e2e.ts",
"version": "conventional-changelog -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md",
"lint": "ng lint"
},
"private": true,
"dependencies": {
"@angular/animations": "9.1.12",
"@angular/cdk": "^9.2.4",
"@angular/common": "9.1.12",
"@angular/compiler": "9.1.12",
"@angular/core": "9.1.12",
"@angular/flex-layout": "^9.0.0-beta.31",
"@angular/forms": "9.1.12",
"@angular/material": "^9.2.4",
"@angular/platform-browser": "9.1.12",
"@angular/platform-browser-dynamic": "9.1.12",
"@angular/router": "9.1.12",
"buffer": "^5.5.0",
"core-js": "^3.6.4",
"jwk-to-ssh": "^1.2.0",
"keypair": "^1.0.1",
"node-forge": "^0.8.5",
"rxjs": "^6.6.3",
"rxjs-compat": "^6.5.4",
"tslib": "^1.10.0",
"web-ext": "^5.1.0",
"zone.js": "~0.10.2"
"@angular/animations": "11.2.4",
"@angular/cdk": "11.2.3",
"@angular/flex-layout": "11.0.0-beta.33",
"@angular/material": "11.2.3",
"buffer": "6.0.3",
"jwk-to-ssh": "1.2.0",
"keypair": "1.0.2",
"node-forge": "0.10.0",
"rxjs-compat": "6.6.6",
"web-ext": "5.5.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.901.12",
"@angular/cli": "9.1.12",
"@angular/compiler-cli": "9.1.12",
"@angular/language-service": "9.1.12",
"@types/jasmine": "^3.5.2",
"@types/jasminewd2": "^2.0.8",
"@types/node": "^12.11.1",
"codelyzer": "^5.1.2",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "^5.2.2",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "^2.1.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.5.2",
"protractor": "^7.0.0",
"ts-node": "~8.2.0",
"tslint": "~5.17.0",
"typescript": "3.8.3"
}
"@angular-builders/custom-webpack": "11.0.0",
"@angular-devkit/build-angular": "0.1102.0",
"@angular-eslint/builder": "1.2.0",
"@angular-eslint/eslint-plugin": "1.2.0",
"@angular-eslint/eslint-plugin-template": "1.2.0",
"@angular-eslint/schematics": "1.2.0",
"@angular-eslint/template-parser": "1.2.0",
"@angular/cli": "11.2.0",
"@angular/common": "11.2.0",
"@angular/compiler": "11.2.0",
"@angular/compiler-cli": "11.2.0",
"@angular/core": "11.2.0",
"@angular/forms": "11.2.0",
"@angular/language-service": "11.2.0",
"@angular/platform-browser": "11.2.0",
"@angular/platform-browser-dynamic": "11.2.0",
"@angular/router": "11.2.0",
"@ngx-translate/core": "13.0.0",
"@ngx-translate/http-loader": "6.0.0",
"@types/jasmine": "3.6.3",
"@types/jasminewd2": "2.0.8",
"@types/mocha": "8.2.0",
"@types/node": "12.12.6",
"@typescript-eslint/eslint-plugin": "4.15.0",
"@typescript-eslint/eslint-plugin-tslint": "4.15.0",
"@typescript-eslint/parser": "4.15.0",
"chai": "4.2.0",
"conventional-changelog-cli": "2.1.1",
"core-js": "3.6.5",
"cross-env": "7.0.3",
"electron": "11.2.0",
"electron-builder": "22.9.1",
"electron-reload": "1.5.0",
"eslint": "7.20.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsdoc": "31.6.1",
"eslint-plugin-prefer-arrow": "1.2.3",
"jasmine-core": "3.6.0",
"jasmine-spec-reporter": "6.0.0",
"karma": "6.1.1",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-electron": "6.3.3",
"karma-jasmine": "4.0.1",
"karma-jasmine-html-reporter": "1.5.4",
"mocha": "8.2.1",
"npm-run-all": "4.1.5",
"rxjs": "6.6.3",
"spectron": "13.0.0",
"ts-node": "9.1.1",
"tslib": "2.1.0",
"typescript": "4.0.5",
"wait-on": "5.0.1",
"webdriver-manager": "12.1.8",
"zone.js": "0.10.3"
},
"engines": {
"node": ">=10.13.0"
},
"browserslist": [
"chrome 83"
]
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { AboutUsComponent } from './aboutus.component';
......@@ -6,7 +6,7 @@ describe('AboutUsComponent', () => {
let component: AboutUsComponent;
let fixture: ComponentFixture<AboutUsComponent>;
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ AboutUsComponent ]
})
......
import { Component, OnInit } from '@angular/core';
import {BackendSelectionService } from '../backend-selection.service';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import {OverlayContainer} from '@angular/cdk/overlay';
import { AuthorisationService } from '../authorisation.service';
import { ComputesitesService } from '../computesites.service';
import { Location } from '@angular/common';
import { MatButtonToggleGroup } from '@angular/material/button-toggle';
......
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { AccountinfoComponent } from './accountinfo.component';
......@@ -6,7 +6,7 @@ describe('AccountinfoComponent', () => {
let component: AccountinfoComponent;
let fixture: ComponentFixture<AccountinfoComponent>;
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ AccountinfoComponent ]
})
......
......@@ -32,6 +32,7 @@ const routes: Routes = [
// { path: 'finishlaunch', component: LauncherComponent},
//{ path: 'cancellaunch', component: LauncherComponent},
{ path: 'sshauthz_callback', component: KeygenComponent},
{ path: 'sshauthz_callback*', component: KeygenComponent},
{ path: 'transfer', component: TransferComponent },
{ path: 'noaccount/:site', component: NoaccountComponent},
{ path: '**', component: LauncherComponent},
......@@ -45,7 +46,7 @@ const routes: Routes = [
@NgModule({
imports: [
RouterModule.forRoot(routes)
RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })
],
exports: [ RouterModule ],
})
......
:host {
}
\ No newline at end of file
import { TestBed, async } from '@angular/core/testing';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
it('should create the app', waitForAsync(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
it(`should have as title 'app'`, waitForAsync(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
it('should render title in a h1 tag', waitForAsync(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
......
......@@ -2,7 +2,8 @@ import { Component } from '@angular/core';
import { TesService } from './tes.service';
import { AuthorisationService} from './authorisation.service';
import { ComputesitesService} from './computesites.service';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import { IpcService } from './ipc.service';
import {BehaviorSubject} from 'rxjs';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Computesite, Health } from './computesite';
import {SettingsService } from './settings.service';
......@@ -35,7 +36,9 @@ export class AppComponent {
private overlayContainer: OverlayContainer,
private route: ActivatedRoute,
private router: Router,
private notifications: NotificationsService) {
private notifications: NotificationsService,
private ipcService: IpcService
) {
};
......@@ -49,8 +52,19 @@ export class AppComponent {
this.authService.loggedOutAuthZ.pipe(
isdefined
).subscribe((v) => { this.loggedout = (<[]>v).length } );
this.ipcService.once('oauth2-redirect',(event, ...args: any[]) => this.route_to_keygen(event, args));
}
route_to_keygen(event, ... args: any[]) {
console.log('calling back from oauth', args);
var url: string = args[0][0];
let fragment = "#" + url.split('#')[1];
console.log('app.component is updating fragment');
this.authService.updateFragment(fragment);
this.router.navigate(['/sshauthz_callback']);
}
toggleMenu() {
if (this.settingsService.menuToggle$.value == true) {
this.settingsService.menuToggle$.next(false);
......
......@@ -34,6 +34,7 @@ import { TesService} from './tes.service';
import { BrowserWindowService } from './browser-window.service';
import { BackendSelectionService} from './backend-selection.service';
import { SubmitAppService } from './submit-app.service';
import { IpcService } from './ipc.service';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { JobComponent } from './job/job.component';
......@@ -143,7 +144,7 @@ import { WarndialogComponent } from './warndialog/warndialog.component';
],
entryComponents: [ LogoutdialogComponent, ModaldialogComponent, WarndialogComponent],
//providers: [ StrudelappsService, ComputesitesService, TesService, SubmitAppService, MatDialog, AuthorisationService,BackendSelectionService,SettingsService],
providers: [NotificationsService, ComputesitesService, TesService, BrowserWindowService, SubmitAppService, MatDialog, AuthorisationService,BackendSelectionService,SettingsService, JobsService],
providers: [NotificationsService, ComputesitesService, TesService, BrowserWindowService, SubmitAppService, MatDialog, AuthorisationService,BackendSelectionService,SettingsService, JobsService, IpcService],
bootstrap: [AppComponent]
})
export class AppModule { }
import { Injectable } from '@angular/core';
import { HttpClientModule, HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, Subject, BehaviorSubject } from 'rxjs';
import { catchError, map, tap, take,filter,skip, switchMap } from 'rxjs/operators';
import {LocationStrategy, Location} from '@angular/common';
......@@ -9,8 +10,11 @@ import * as forge from "node-forge";
import { Identity, AuthToken, KeyCert, SshAuthzServer } from './identity';
import { APIServer } from './apiserver';
import {BackendSelectionService} from './backend-selection.service';
import { throwError, of, combineLatest } from 'rxjs';
import { throwError, of, combineLatest, from } from 'rxjs';
import {NotificationsService} from './notifications.service';
import { IpcService } from './ipc.service';
import * as jwktossh from "jwk-to-ssh";
export class SshauthzServer {}
......@@ -30,13 +34,16 @@ export class AuthorisationService {
// private keyCert: Subject<KeyCert>;
public backendURI: string;
public keys: KeyCert[];
private fragment$: BehaviorSubject<string> = new BehaviorSubject(null);
constructor(private http: HttpClient,
private locationStrategy: LocationStrategy,
private router: Router,
private backendSelectionService: BackendSelectionService,
private location: Location,
private notifications: NotificationsService) {
private notifications: NotificationsService,
private ipcService: IpcService) {
this.sshAuthzServers = new BehaviorSubject<SshAuthzServer[]>([]);
this.loggedInAuthZ = new BehaviorSubject<SshAuthzServer[]>(null);
this.loggedOutAuthZ = new BehaviorSubject<SshAuthzServer[]>([]);
......@@ -68,8 +75,14 @@ export class AuthorisationService {
authZ$.subscribe(([loggedin,loggedout]) => { this.loggedInAuthZ.next(loggedin); this.loggedOutAuthZ.next(loggedout); })
this.loggedInAuthZ$ = authZ$[0];
this.loggedOutAuthZ$ = authZ$[1];
this.initKeygenPipelines();
}
updateFragment(frag) {
this.fragment$.next(frag);
}
storeLocalAuthZ(authz: any) {
try {
......@@ -254,6 +267,15 @@ public getKeys(id?: Identity) {
public login(authservice: SshAuthzServer) {
let redirect_uri = window.location.origin+this.locationStrategy.getBaseHref()+"sshauthz_callback";
if (redirect_uri.includes("file:///")) {
console.log('altering redirect_uri');
console.log(redirect_uri);
console.log(window.location.origin);
console.log(this.locationStrategy.getBaseHref());
redirect_uri = "http://localhost:4200/sshauthz_callback";
}
console.log('redirect_uri will be',redirect_uri);
debugger;
let nonce=Math.random().toString(36).substring(2, 15)
sessionStorage.setItem('authservice', JSON.stringify([authservice,nonce]));
......@@ -279,4 +301,148 @@ public getKeys(id?: Identity) {
// return of(result as T);
};
}
initKeygenPipelines() {
const token$: Observable<AuthToken> = this.fragment$.pipe(
filter((v) => v !== null),
map((v) => this.extractToken(v)),
);
const key$ = from(window.crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-256",
},
true,
["sign","verify"]
)).pipe(
switchMap((v) => { return combineLatest([from(window.crypto.subtle.exportKey("jwk",v.privateKey)),from(window.crypto.subtle.exportKey("jwk",v.publicKey))]) }),
map(([key,pub]) => {
return {'private': jwktossh.pack({'jwk': key, 'comment': '', 'public': false})+"\n", 'public': jwktossh.pack({'jwk': pub, 'comment': '', 'public': true})+"\n"};
}),
)
const apiserver$: Observable<APIServer> = this.backendSelectionService.apiserver.pipe(
filter((v) => v !== undefined),filter((v) => v !== null),
);
let keycert$ = combineLatest([token$, key$, apiserver$]).pipe(
switchMap(([token,key,apiserver]) => this.getCert(token,key,apiserver),
([token,key,apiserver],cert) => [key,cert,token]),
tap(([key,cert,token]) => this.logout_sshauthz(token.sshauthzservice)),
);
let agent$ = combineLatest([keycert$.pipe(filter((v) => v !== null)),apiserver$]).pipe(
switchMap(([keycert,apiserver]) => this.addCert(keycert,apiserver)),
switchMap((_) => this.updateAgentContents()),
switchMap((_) => this.loggedInAuthZ),
switchMap((_) => of([null])),
);
agent$.subscribe( (res) => this.router.navigate([sessionStorage.getItem('path')]),
(err) => { console.log(err) ;
if (err.sshauthzservice !== undefined ) {
this.logout_sshauthz(err.sshauthzservice)
this.router.navigate(['/noaccount',err.sshauthzservice.name])
} else {
this.router.navigate(['/login'])}
} )
}
extractToken(frag: string) {
if (frag === undefined || frag == null) {
return;
}
let tokenmatch = null;
let statematch = null;
if (!(frag === undefined) && !(frag == null)) {
tokenmatch = frag.match(/access_token\=([^&]+)(&|$)/);
statematch = frag.match(/state\=([^&]+)(&|$)/);
}
if (tokenmatch == null || statematch == null) {
throw new Error('no token present');
}
let accesstoken = tokenmatch[1];
let state = statematch[1];
let tuple = JSON.parse(sessionStorage.getItem('authservice'));
if (tuple[1] != state) {
throw new Error('callback state parameter does not match');
}
return new AuthToken(tokenmatch[1],tuple[0]);
}
logout_sshauthz(sshauthzserver) {
if (sshauthzserver !== undefined && sshauthzserver.logout !== null) {
window.open(sshauthzserver.logout);
}
}
getCert(token: AuthToken, key: any, apiserver: APIServer): Observable<any> {
let headers = new HttpHeaders({'Authorization':'Bearer '+token.token});
let options = { headers: headers, withCredentials: false};
var now = new Date()
var end = new Date(now.getTime() + 28*24*60*60*1000); //request a certificate valid for 28 days
//its expected that the user will terminate the session by closing their browser/sleeping their laptop before this
//var end = new Date(now.getTime() + 30*1000); // Uncomment if you want to test certificates expiring
let data = {'public_key': key.public, 'end': end.toISOString()};
return this.http.post<any>(token.sshauthzservice.sign,data, options).pipe(
tap((v) => console.log('in getCert',v)),
map((v) => v.certificate),
catchError((e) => { console.error(e); return throwError(token) })
)
}
addCert(kclist: any, apiserver: APIServer): Observable<any> {
let keyCert = new KeyCert()
keyCert.key = kclist[0].private
keyCert.cert = kclist[1]
console.log('in authService.addCert, adding to',apiserver);
return this.sshAdd(keyCert,apiserver);
}
public sshAdd(keyCert: KeyCert, apiserver) {
let headers = new HttpHeaders();
let options = { headers: headers, withCredentials: true};
var anyvar: any;
let data = {'key': keyCert.key, 'cert': keyCert.cert};
console.log('in authService.sshAdd, adding to',apiserver);
return this.http.post<any>(apiserver.tes+'/sshagent',data,options)
}
storeKey(keyCert: KeyCert) {
var keys: KeyCert[] = [];
try{
keys = JSON.parse(sessionStorage.getItem('keys'));
} catch {
keys = [];
}
if (keys === null) {
keys = [];
}
keys.push(keyCert);
sessionStorage.setItem('keys',JSON.stringify(keys))
}
/* ngAfterViewInit() {
this.authService.getCert();
}
genKey() {
let newkeypair = keypair();
let publicKey = forge.pki.publicKeyFromPem(newkeypair.public);
let sshpub = forge.ssh.publicKeyToOpenSSH(publicKey, 'sv2@monash.edu');
return sshpub
}*/
/* navigate(readyToNavigate: [Boolean,string]) {
if (readyToNavigate[0]) {
this.router.navigate([readyToNavigate[1]]);
}
}*/
}
......@@ -140,7 +140,7 @@ export class BackendSelectionService {
localStorage.setItem('apiserver', JSON.stringify(s));
}
private setApiServer(server: APIServer) {
public setApiServer(server: APIServer) {
this.apiserver.next(server);
if (this.apiserver.value.tes.indexOf('localhost') != -1) {
this.localapi.next(true);
......
......@@ -37,7 +37,7 @@ export class BrowserWindowService {
private notifications: NotificationsService,
private http: HttpClient,
private settingsService: SettingsService) {
this.backendSelectionService.apiserver.subscribe( (value) => { if (value != null) {this.twsproxy = value.tws ; this.Base = value.tes }});
this.backendSelectionService.apiserver.subscribe( (value) => { if (value != null) {this.twsproxy = value.tws }});
this.authdone$ = new Subject<boolean>();
this.openapps = [];
this.cancelJob$ = new Subject<Job>();
......
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {BehaviorSubject} from 'rxjs';
export class Computesite {
url: string; // The URL runs a web service to help construct sbatch commands
......@@ -13,6 +13,7 @@ export class Computesite {
// to figure out which compute site a certificate is valid
// for
appCatalog: [];
appCatalogUri: string;
appCatalogCmd: string;
internalfirewall: boolean; // Does a firewall exist within the site necessitating an extra level of ssh tunnel
......
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';