diff --git a/packages/uhk-agent/src/electron-main.ts b/packages/uhk-agent/src/electron-main.ts index 60a7053842a..b1752b3a79d 100644 --- a/packages/uhk-agent/src/electron-main.ts +++ b/packages/uhk-agent/src/electron-main.ts @@ -118,7 +118,7 @@ async function createWindow() { setMenu(win, options.devtools); deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, options, packagesDir); - appUpdateService = new AppUpdateService(logger, win, app); + appUpdateService = new AppUpdateService(logger, win, options); appService = new AppService(logger, win, deviceService, options, packagesDir); sudoService = new SudoService(logger, options, deviceService, packagesDir); // and load the index.html of the app. diff --git a/packages/uhk-agent/src/services/app-update.service.ts b/packages/uhk-agent/src/services/app-update.service.ts index 6664930c085..17633eaeae0 100644 --- a/packages/uhk-agent/src/services/app-update.service.ts +++ b/packages/uhk-agent/src/services/app-update.service.ts @@ -3,8 +3,15 @@ import { autoUpdater } from 'electron-updater'; import { UpdateInfo, ProgressInfo } from 'builder-util-runtime'; import isDev from 'electron-is-dev'; import storage from 'electron-settings'; - -import { ApplicationSettings, IpcEvents, LogService } from 'uhk-common'; +import { inspect } from 'node:util'; + +import { + ApplicationSettings, + CommandLineArgs, + ERR_UPDATER_INVALID_SIGNATURE, + IpcEvents, + LogService, +} from 'uhk-common'; import { MainServiceBase } from './main-service-base'; import { getUpdaterLoggerService } from '../util'; @@ -16,13 +23,20 @@ export class AppUpdateService extends MainServiceBase { constructor(protected logService: LogService, protected win: Electron.BrowserWindow, - private app: Electron.App) { + private options: CommandLineArgs) { super(logService, win); autoUpdater.logger = getUpdaterLoggerService(logService); this.initListeners(); logService.misc('[AppUpdateService] init success'); + + if (options['simulate-invalid-codesign-signature']) { + logService.misc('[AppUpdateService] init simulate invalid codesign timer'); + setTimeout(() => { + this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateError, ERR_UPDATER_INVALID_SIGNATURE); + }, 10000) + } } private initListeners() { @@ -44,11 +58,17 @@ export class AppUpdateService extends MainServiceBase { } }); - autoUpdater.on('error', (ev: any, err: string) => { - this.logService.error('[AppUpdateService] error', err); + autoUpdater.on('error', (error: Error, message: string) => { + this.logService.error('[AppUpdateService] error', inspect(error)); + this.logService.error('[AppUpdateService] error message', message); + if ((error as NodeJS.ErrnoException)?.code === ERR_UPDATER_INVALID_SIGNATURE) { + this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateError, ERR_UPDATER_INVALID_SIGNATURE); + return + } + let msg = 'Electron updater error'; - if (err) { - msg = err.substr(0, 100); + if (message) { + msg = message.substring(0, 100); } this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateError, msg); diff --git a/packages/uhk-agent/src/util/command-line.ts b/packages/uhk-agent/src/util/command-line.ts index 632cfe54572..6ac838b95f9 100644 --- a/packages/uhk-agent/src/util/command-line.ts +++ b/packages/uhk-agent/src/util/command-line.ts @@ -21,6 +21,7 @@ const optionDefinitions: commandLineArgs.OptionDefinition[] = [ { name: 'reenumerate-and-exit', type: String }, { name: 'report-id', type: Number }, { name: 'serial-number', type: String }, + { name: 'simulate-invalid-codesign-signature', type: Boolean }, { name: 'spe', type: Boolean }, // simulate privilege escalation error { name: 'usb-interface', type: Number }, { name: 'usb-non-blocking', type: Boolean }, @@ -120,6 +121,11 @@ const sections: commandLineUsage.Section[] = [ description: 'Use the specified USB device that serial-number is matching.', type: String }, + { + name: 'simulate-invalid-codesign-signature', + description: 'Agent shows the invalid code sign signature error 10 seconds after start', + type: Boolean + }, { name: 'spe', description: 'Simulate privilege escalation error', diff --git a/packages/uhk-common/src/models/command-line-args.ts b/packages/uhk-common/src/models/command-line-args.ts index c7f364d22dc..9251c05c890 100644 --- a/packages/uhk-common/src/models/command-line-args.ts +++ b/packages/uhk-common/src/models/command-line-args.ts @@ -83,6 +83,11 @@ export interface CommandLineArgs extends DeviceIdentifier { * Report Id that used for USB communication */ 'report-id'?: number; + + /** + * Agent shows the invalid code sign signature error 10 seconds after start + */ + 'simulate-invalid-codesign-signature'?: boolean; /** * simulate privilege escalation error */ diff --git a/packages/uhk-common/src/util/constants.ts b/packages/uhk-common/src/util/constants.ts index 2f0586ce904..7ccfc0dfa20 100644 --- a/packages/uhk-common/src/util/constants.ts +++ b/packages/uhk-common/src/util/constants.ts @@ -7,3 +7,4 @@ export namespace Constants { } export const UHK_EEPROM_SIZE = 32768; +export const ERR_UPDATER_INVALID_SIGNATURE = 'ERR_UPDATER_INVALID_SIGNATURE' diff --git a/packages/uhk-web/src/app/app.component.html b/packages/uhk-web/src/app/app.component.html index b49b487f513..be00dccf3c0 100644 --- a/packages/uhk-web/src/app/app.component.html +++ b/packages/uhk-web/src/app/app.component.html @@ -65,3 +65,18 @@ + +

+ Agent's digital signature has changed. Please update it manually this time. Agent will auto-update afterward. +

+ +
diff --git a/packages/uhk-web/src/app/app.component.ts b/packages/uhk-web/src/app/app.component.ts index d7f508f37f6..1f2700389b3 100644 --- a/packages/uhk-web/src/app/app.component.ts +++ b/packages/uhk-web/src/app/app.component.ts @@ -1,11 +1,15 @@ import { Component, HostListener, OnDestroy, ChangeDetectorRef, ViewChild } from '@angular/core'; import { ActivatedRoute, Event, NavigationEnd, Router } from '@angular/router'; import { animate, style, transition, trigger } from '@angular/animations'; +import { NotifierService } from '@ert78gb/angular-notifier'; import { faPuzzlePiece, faArrowUp } from '@fortawesome/free-solid-svg-icons'; +import { Actions, ofType } from '@ngrx/effects'; import { SplitGutterInteractionEvent } from 'angular-split'; import { Observable, Subscription } from 'rxjs'; import { Action, Store } from '@ngrx/store'; +import { ERR_UPDATER_INVALID_SIGNATURE } from 'uhk-common'; +import { ActionTypes as AppUpdateActionTypes } from './store/actions/app-update.action'; import { DoNotUpdateAppAction, UpdateAppAction } from './store/actions/app-update.action'; import { EnableUsbStackTestAction, UpdateFirmwareAction } from './store/actions/device'; import { @@ -102,6 +106,7 @@ import { SecondSideMenuContainerComponent } from './components/side-menu'; }) export class MainAppComponent implements OnDestroy { @ViewChild(SecondSideMenuContainerComponent) secondarySideMenuContainer: SecondSideMenuContainerComponent; + @ViewChild('manuallyUpdateNotification', { static: true }) manuallyUpdateNotificationTmpl; donglePairingState: DonglePairingState; newPairedDevicesState: BleAddingState; @@ -120,6 +125,7 @@ export class MainAppComponent implements OnDestroy { bottom: 0 }; statusBuffer: string; + private actionsSubscription: Subscription; private donglePairingStateSubscription: Subscription; private newPairedDevicesStateSubscription: Subscription; private errorPanelHeightSubscription: Subscription; @@ -136,7 +142,22 @@ export class MainAppComponent implements OnDestroy { constructor(private store: Store, private route: ActivatedRoute, private router: Router, - private cdRef: ChangeDetectorRef) { + private cdRef: ChangeDetectorRef, + private actions$: Actions, + private notificationService: NotifierService, + ) { + this.actionsSubscription = actions$.pipe( + ofType(AppUpdateActionTypes.InvalidCodesignSignature) + ) + .subscribe(() => { + notificationService.show({ + message: '', + type: 'info', + template: this.manuallyUpdateNotificationTmpl, + id: ERR_UPDATER_INVALID_SIGNATURE, + }) + }) + this.donglePairingStateSubscription = store.select(getDonglePairingState) .subscribe(data => { this.donglePairingState = data; @@ -211,6 +232,7 @@ export class MainAppComponent implements OnDestroy { } ngOnDestroy(): void { + this.actionsSubscription.unsubscribe(); this.donglePairingStateSubscription.unsubscribe(); this.newPairedDevicesStateSubscription.unsubscribe(); this.errorPanelHeightSubscription.unsubscribe(); @@ -263,6 +285,10 @@ export class MainAppComponent implements OnDestroy { this.store.dispatch(new CloseErrorPanelAction()); } + dismissUpdateErrorNotification(): void { + this.notificationService.hide(ERR_UPDATER_INVALID_SIGNATURE); + } + showErrorPanel(): void { this.store.dispatch(new ShowErrorPanelAction()); } diff --git a/packages/uhk-web/src/app/store/actions/app-update.action.ts b/packages/uhk-web/src/app/store/actions/app-update.action.ts index 15b266d3429..76d94fb61ce 100644 --- a/packages/uhk-web/src/app/store/actions/app-update.action.ts +++ b/packages/uhk-web/src/app/store/actions/app-update.action.ts @@ -4,6 +4,7 @@ import { UpdateInfo } from '../../models/update-info'; export enum ActionTypes { ForceUpdate = '[app-update] force update', + InvalidCodesignSignature = '[app-update] invalid codesign signature', UpdateAvailable = '[app-update] update available', UpdateApp = '[app-update] update app', DoNotUpdateApp = '[app-update] do not update app', @@ -16,6 +17,10 @@ export class ForceUpdateAction implements Action { type = ActionTypes.ForceUpdate; } +export class InvalidCodesignSignatureAction implements Action { + type = ActionTypes.InvalidCodesignSignature; +} + export class UpdateAvailableAction implements Action { type = ActionTypes.UpdateAvailable; } @@ -48,6 +53,7 @@ export class UpdateErrorAction implements Action { export type Actions = UpdateAvailableAction + | InvalidCodesignSignatureAction | UpdateAppAction | DoNotUpdateAppAction | UpdateDownloadedAction diff --git a/packages/uhk-web/src/app/store/effects/app-update.ts b/packages/uhk-web/src/app/store/effects/app-update.ts index cb62e2ea4d8..f3909cafaae 100644 --- a/packages/uhk-web/src/app/store/effects/app-update.ts +++ b/packages/uhk-web/src/app/store/effects/app-update.ts @@ -4,11 +4,12 @@ import { Actions, createEffect, ofType } from '@ngrx/effects'; import { EMPTY } from 'rxjs'; import { first, map, tap, withLatestFrom } from 'rxjs/operators'; -import { LogService, NotificationType } from 'uhk-common'; +import { ERR_UPDATER_INVALID_SIGNATURE, LogService, NotificationType } from 'uhk-common'; import { ActionTypes, ForceUpdateAction, + InvalidCodesignSignatureAction, UpdateAppAction, UpdateAvailableAction, UpdateErrorAction @@ -74,6 +75,10 @@ export class AppUpdateEffect { ofType(ActionTypes.UpdateError), map(action => action.payload), map((message: string) => { + if (message === ERR_UPDATER_INVALID_SIGNATURE) { + return new InvalidCodesignSignatureAction(); + } + return new ShowNotificationAction({ type: NotificationType.Error, message diff --git a/packages/uhk-web/src/styles/_global.scss b/packages/uhk-web/src/styles/_global.scss index b47f9b1b58c..bd283dfa8c4 100644 --- a/packages/uhk-web/src/styles/_global.scss +++ b/packages/uhk-web/src/styles/_global.scss @@ -321,3 +321,7 @@ mwl-confirmation-popover-window { cursor: not-allowed; } } + +.alert-link { + text-decoration: underline; +}