diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index 29e2e6502..c12509d58 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -101,7 +101,7 @@ export class GfHeaderComponent implements OnChanges { protected hasPermissionToAccessAssistant: boolean; protected hasPermissionToAccessFearAndGreedIndex: boolean; protected hasPermissionToCreateUser: boolean; - protected impersonationId: string; + protected impersonationId: string | null; protected readonly internalRoutes = internalRoutes; protected isMenuOpen: boolean; protected readonly routeAbout = publicRoutes.about.path; diff --git a/apps/client/src/app/pages/admin/admin-page.component.ts b/apps/client/src/app/pages/admin/admin-page.component.ts index c5da0fb0c..eb4ddb95c 100644 --- a/apps/client/src/app/pages/admin/admin-page.component.ts +++ b/apps/client/src/app/pages/admin/admin-page.component.ts @@ -103,7 +103,7 @@ export class AdminPageComponent { const token = this.tokenStorageService.getToken(); document.cookie = [ - `${BULL_BOARD_COOKIE_NAME}=${encodeURIComponent(token)}`, + `${BULL_BOARD_COOKIE_NAME}=${encodeURIComponent(token ?? '')}`, 'path=/', 'SameSite=Strict' ].join('; '); diff --git a/apps/client/src/app/pages/demo/demo-page.component.ts b/apps/client/src/app/pages/demo/demo-page.component.ts index 235805dcc..1f49039ce 100644 --- a/apps/client/src/app/pages/demo/demo-page.component.ts +++ b/apps/client/src/app/pages/demo/demo-page.component.ts @@ -25,7 +25,7 @@ export class GfDemoPageComponent { } public ngOnInit() { - const hasToken = this.tokenStorageService.getToken()?.length > 0; + const hasToken = !!this.tokenStorageService.getToken(); if (hasToken) { this.notificationService.alert({ diff --git a/apps/client/src/app/services/impersonation-storage.service.ts b/apps/client/src/app/services/impersonation-storage.service.ts index fccbc8570..fb101e220 100644 --- a/apps/client/src/app/services/impersonation-storage.service.ts +++ b/apps/client/src/app/services/impersonation-storage.service.ts @@ -7,11 +7,11 @@ export const IMPERSONATION_KEY = 'impersonationId'; providedIn: 'root' }) export class ImpersonationStorageService { - private hasImpersonationChangeSubject = new BehaviorSubject( + private hasImpersonationChangeSubject = new BehaviorSubject( this.getId() ); - public getId(): string { + public getId(): string | null { return window.localStorage.getItem(IMPERSONATION_KEY); } diff --git a/apps/client/src/app/services/settings-storage.service.ts b/apps/client/src/app/services/settings-storage.service.ts index 87d995d76..681552cf7 100644 --- a/apps/client/src/app/services/settings-storage.service.ts +++ b/apps/client/src/app/services/settings-storage.service.ts @@ -8,7 +8,7 @@ export const KEY_TOKEN = 'auth-token'; providedIn: 'root' }) export class SettingsStorageService { - public getSetting(aKey: string): string { + public getSetting(aKey: string): string | null { return window.localStorage.getItem(aKey); } diff --git a/apps/client/src/app/services/token-storage.service.ts b/apps/client/src/app/services/token-storage.service.ts index f54aab828..b29fd207b 100644 --- a/apps/client/src/app/services/token-storage.service.ts +++ b/apps/client/src/app/services/token-storage.service.ts @@ -6,9 +6,9 @@ import { KEY_TOKEN } from './settings-storage.service'; providedIn: 'root' }) export class TokenStorageService { - public getToken(): string { + public getToken(): string | null { return ( - window.sessionStorage.getItem(KEY_TOKEN) || + window.sessionStorage.getItem(KEY_TOKEN) ?? window.localStorage.getItem(KEY_TOKEN) ); } diff --git a/apps/client/src/app/services/user/user.service.ts b/apps/client/src/app/services/user/user.service.ts index caeea775e..52f33dc5a 100644 --- a/apps/client/src/app/services/user/user.service.ts +++ b/apps/client/src/app/services/user/user.service.ts @@ -3,7 +3,7 @@ import { Filter, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { HttpClient } from '@angular/common/http'; -import { DestroyRef, Injectable } from '@angular/core'; +import { computed, DestroyRef, inject, Injectable } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatDialog } from '@angular/material/dialog'; import { ObservableStore } from '@codewithdan/observable-store'; @@ -22,20 +22,20 @@ import { UserStoreState } from './user-store.state'; providedIn: 'root' }) export class UserService extends ObservableStore { - private deviceType: string; - - public constructor( - private destroyRef: DestroyRef, - private deviceDetectorService: DeviceDetectorService, - private dialog: MatDialog, - private http: HttpClient, - private webAuthnService: WebAuthnService - ) { + private readonly deviceType = computed( + () => this.deviceDetectorService.deviceInfo().deviceType + ); + + private readonly destroyRef = inject(DestroyRef); + private readonly deviceDetectorService = inject(DeviceDetectorService); + private readonly dialog = inject(MatDialog); + private readonly http = inject(HttpClient); + private readonly webAuthnService = inject(WebAuthnService); + + public constructor() { super({ trackStateHistory: true }); this.setState({ user: undefined }, UserStoreActions.Initialize); - - this.deviceType = this.deviceDetectorService.getDeviceInfo().deviceType; } public get(force = false) { @@ -97,7 +97,7 @@ export class UserService extends ObservableStore { } public reset() { - this.setState({ user: null }, UserStoreActions.RemoveUser); + this.setState({ user: undefined }, UserStoreActions.RemoveUser); } public signOut() { @@ -127,7 +127,19 @@ export class UserService extends ObservableStore { const cookies = await cookieStore.getAll(); - await Promise.all(cookies.map(({ name }) => cookieStore.delete(name))); + const cookieNames = cookies + .map(({ name }) => { + return name; + }) + .filter((name): name is string => { + return !!name; + }); + + await Promise.all( + cookieNames.map((name) => { + return cookieStore.delete(name); + }) + ); } private fetchUser(): Observable { @@ -158,8 +170,8 @@ export class UserService extends ObservableStore { user }, disableClose: true, - height: this.deviceType === 'mobile' ? '98vh' : '80vh', - width: this.deviceType === 'mobile' ? '100vw' : '50rem' + height: this.deviceType() === 'mobile' ? '98vh' : '80vh', + width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); dialogRef diff --git a/apps/client/src/app/services/web-authn.service.ts b/apps/client/src/app/services/web-authn.service.ts index 95c264310..24eee5dc1 100644 --- a/apps/client/src/app/services/web-authn.service.ts +++ b/apps/client/src/app/services/web-authn.service.ts @@ -6,7 +6,7 @@ import { } from '@ghostfolio/common/interfaces'; import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { startAuthentication, startRegistration @@ -20,10 +20,8 @@ import { catchError, switchMap, tap } from 'rxjs/operators'; export class WebAuthnService { private static readonly WEB_AUTH_N_DEVICE_ID = 'WEB_AUTH_N_DEVICE_ID'; - public constructor( - private http: HttpClient, - private settingsStorageService: SettingsStorageService - ) {} + private readonly http = inject(HttpClient); + private readonly settingsStorageService = inject(SettingsStorageService); public isSupported() { return typeof PublicKeyCredential !== 'undefined'; @@ -44,8 +42,12 @@ export class WebAuthnService { console.warn('Could not register device', error); return of(null); }), - switchMap((attOps) => { - return startRegistration({ optionsJSON: attOps }); + switchMap((registrationOptions) => { + if (!registrationOptions) { + throw new Error('Could not generate registration options'); + } + + return startRegistration({ optionsJSON: registrationOptions }); }), switchMap((credential) => { return this.http.post( @@ -53,10 +55,10 @@ export class WebAuthnService { { credential } ); }), - tap((authDevice) => + tap(({ id }) => this.settingsStorageService.setSetting( WebAuthnService.WEB_AUTH_N_DEVICE_ID, - authDevice.id + id ) ) );