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

package upgrades and compat with electron

parent e5ce8c2e
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"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';
import { Contac