From bf413c2e099a130ece536bb71e44255842733644 Mon Sep 17 00:00:00 2001 From: Daniel Ponte Date: Sun, 9 Feb 2025 09:29:08 -0500 Subject: [PATCH 1/3] wip --- client/stillbox/src/app/app.component.html | 4 +- client/stillbox/src/app/app.config.ts | 2 +- client/stillbox/src/app/login/auth.service.ts | 107 ++++++++++++------ .../src/app/login/login.component.html | 2 +- .../stillbox/src/app/login/login.component.ts | 28 +---- .../src/app/talkgroups/talkgroups.service.ts | 2 +- 6 files changed, 82 insertions(+), 63 deletions(-) diff --git a/client/stillbox/src/app/app.component.html b/client/stillbox/src/app/app.component.html index 20725bc..3735317 100644 --- a/client/stillbox/src/app/app.component.html +++ b/client/stillbox/src/app/app.component.html @@ -8,7 +8,7 @@
- @if (auth.loggedIn) { + @if (auth.isAuth()) { @@ -19,7 +19,7 @@
- @if (auth.loggedIn) { + @if (auth.isAuth()) { } @else {
diff --git a/client/stillbox/src/app/app.config.ts b/client/stillbox/src/app/app.config.ts index 0014fed..cb21be6 100644 --- a/client/stillbox/src/app/app.config.ts +++ b/client/stillbox/src/app/app.config.ts @@ -29,7 +29,7 @@ export function authIntercept( next: HttpHandlerFn, ): Observable> { let authSvc: AuthService = inject(AuthService); - if (authSvc.loggedIn) { + if (authSvc.isAuth()) { req = req.clone({ setHeaders: { Authorization: `Bearer ${authSvc.getToken()}`, diff --git a/client/stillbox/src/app/login/auth.service.ts b/client/stillbox/src/app/login/auth.service.ts index 0ec7b97..8749535 100644 --- a/client/stillbox/src/app/login/auth.service.ts +++ b/client/stillbox/src/app/login/auth.service.ts @@ -1,56 +1,93 @@ -import { Injectable } from '@angular/core'; +import { Injectable, signal, computed, effect, inject, DestroyRef } from '@angular/core'; import { HttpClient, HttpResponse } from '@angular/common/http'; import { Router } from '@angular/router'; -import { Observable } from 'rxjs'; +import { Observable, Subject } from 'rxjs'; import { tap } from 'rxjs/operators'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; export class Jwt { constructor(public jwt: string) {} } +type AuthState = { + user: string | null; + token: string | null; + is_auth: boolean; +}; + @Injectable({ providedIn: 'root', }) export class AuthService { - loggedIn: boolean = false; + private _accessTokenKey = 'jwt'; + private _storedToken = localStorage.getItem(this._accessTokenKey); + destroyed = inject(DestroyRef); + + private _state = signal({ + user: null, + token: this._storedToken, + is_auth: this._storedToken !== null, + }); + loginFailed = signal(false); + token = computed(() => this._state().token); + isAuth = computed(() => this._state().is_auth); + user = computed(() => this._state().user); constructor( private http: HttpClient, private _router: Router, ) { - let ssJWT = localStorage.getItem('jwt'); - if (ssJWT) { - this.loggedIn = true; - } + effect(() => { + const token = this.token(); + if (token !== null) { + localStorage.setItem(this._accessTokenKey, token); + } else { + localStorage.removeItem(this._accessTokenKey); + } + }); } - login(username: string, password: string): Observable> { + login(username: string, password: string) { return this.http - .post( - '/api/login', - { username: username, password: password }, - { observe: 'response' }, - ) - .pipe( - tap((event) => { - if (event.status == 200) { - localStorage.setItem('jwt', event.body?.jwt.toString() ?? ''); - this.loggedIn = true; - this._router.navigateByUrl('/home'); - } - }), - ); + .post('/api/login', { username: username, password: password }) + .pipe(takeUntilDestroyed(this.destroyed)) + .subscribe({ + next: (res) => { + let state = { + user: username, + token: res.jwt, + is_auth: true, + }; + this._state.set(state); + this.loginFailed.update(() => false); + this._router.navigateByUrl('/'); + }, + error: (err) => { + this.loginFailed.update(() => true); + }, + }); + } + + _clearState() { +this._state.update((state) => { + state.is_auth = false; + state.token = null; + return state; + }); + } logout() { + console.log("logout!"); this.http - .get('/api/logout', { withCredentials: true, observe: 'response' }) - .subscribe((event) => { - if (event.status == 200) { - this.loggedIn = false; - } - }); - localStorage.removeItem('jwt'); - this.loggedIn = false; + .get('/api/logout', { withCredentials: true}) + .subscribe({ + next: (event) => { + this._clearState(); + }, + error: (err) => { + this._clearState(); + } + }); this._router.navigateByUrl('/login'); } @@ -60,14 +97,18 @@ export class AuthService { .pipe( tap((event) => { if (event.status == 200) { - localStorage.setItem('jwt', event.body?.jwt.toString() ?? ''); - this.loggedIn = true; + let tok = event.body?.jwt.toString(); + this._state.update((state) => { + state.is_auth = true; + state.token = tok ? tok : null; + return state; + }); } }), ); } getToken(): string | null { - return localStorage.getItem('jwt'); + return localStorage.getItem(this._accessTokenKey); } } diff --git a/client/stillbox/src/app/login/login.component.html b/client/stillbox/src/app/login/login.component.html index 293daed..1646e47 100644 --- a/client/stillbox/src/app/login/login.component.html +++ b/client/stillbox/src/app/login/login.component.html @@ -28,7 +28,7 @@
- @if (failed) { + @if (failed()) {
Login Failed!
diff --git a/client/stillbox/src/app/login/login.component.ts b/client/stillbox/src/app/login/login.component.ts index 5a8c5bc..a7adc76 100644 --- a/client/stillbox/src/app/login/login.component.ts +++ b/client/stillbox/src/app/login/login.component.ts @@ -1,4 +1,4 @@ -import { Component, inject } from '@angular/core'; +import { Component, inject, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { AuthService } from '../login/auth.service'; import { catchError, of, Subscription } from 'rxjs'; @@ -17,31 +17,9 @@ export class LoginComponent { router: Router = inject(Router); username: string = ''; password: string = ''; - failed: boolean = false; - private subscriptions = new Subscription(); + failed = this.apiService.loginFailed; onSubmit() { - this.failed = false; - this.subscriptions.add( - this.apiService - .login(this.username, this.password) - .pipe( - catchError(() => { - this.failed = true; - return of(null); - }), - ) - .subscribe((event) => { - if (event?.status == 200) { - this.router.navigateByUrl('/'); - } else { - this.failed = true; - } - }), - ); - } - - ngOnDestroy() { - this.subscriptions.unsubscribe(); + this.apiService.login(this.username, this.password); } } diff --git a/client/stillbox/src/app/talkgroups/talkgroups.service.ts b/client/stillbox/src/app/talkgroups/talkgroups.service.ts index 1184952..d132b23 100644 --- a/client/stillbox/src/app/talkgroups/talkgroups.service.ts +++ b/client/stillbox/src/app/talkgroups/talkgroups.service.ts @@ -51,7 +51,7 @@ export class TalkgroupService { if (sh) { this.shareSvc.getShare(sh).subscribe(this.fetchAll); } else { - if (this.authSvc.loggedIn) { + if (this.authSvc.isAuth()) { this.fetchAll.next(null); } } From 88522f4e241393ebcd152fd201c49a159b8b8340 Mon Sep 17 00:00:00 2001 From: Daniel Ponte Date: Sun, 9 Feb 2025 09:34:43 -0500 Subject: [PATCH 2/3] Immutable signals --- client/stillbox/src/app/login/auth.service.ts | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/client/stillbox/src/app/login/auth.service.ts b/client/stillbox/src/app/login/auth.service.ts index 8749535..46fc5df 100644 --- a/client/stillbox/src/app/login/auth.service.ts +++ b/client/stillbox/src/app/login/auth.service.ts @@ -1,4 +1,11 @@ -import { Injectable, signal, computed, effect, inject, DestroyRef } from '@angular/core'; +import { + Injectable, + signal, + computed, + effect, + inject, + DestroyRef, +} from '@angular/core'; import { HttpClient, HttpResponse } from '@angular/common/http'; import { Router } from '@angular/router'; import { Observable, Subject } from 'rxjs'; @@ -52,11 +59,11 @@ export class AuthService { .pipe(takeUntilDestroyed(this.destroyed)) .subscribe({ next: (res) => { - let state = { - user: username, - token: res.jwt, - is_auth: true, - }; + let state = { + user: username, + token: res.jwt, + is_auth: true, + }; this._state.set(state); this.loginFailed.update(() => false); this._router.navigateByUrl('/'); @@ -68,25 +75,17 @@ export class AuthService { } _clearState() { -this._state.update((state) => { - state.is_auth = false; - state.token = null; - return state; - }); - + this._state.set({}); } logout() { - console.log("logout!"); - this.http - .get('/api/logout', { withCredentials: true}) - .subscribe({ - next: (event) => { - this._clearState(); - }, + this.http.get('/api/logout', { withCredentials: true }).subscribe({ + next: (event) => { + this._clearState(); + }, error: (err) => { - this._clearState(); - } + this._clearState(); + }, }); this._router.navigateByUrl('/login'); } @@ -97,12 +96,14 @@ this._state.update((state) => { .pipe( tap((event) => { if (event.status == 200) { + let ost = this._state(); let tok = event.body?.jwt.toString(); - this._state.update((state) => { - state.is_auth = true; - state.token = tok ? tok : null; - return state; - }); + let state = { + user: ost.user, + token: tok ? tok : null, + is_auth: true, + }; + this._state.set(state); } }), ); From 246c024fc2b165ccb4575462c18042806a4d9b2d Mon Sep 17 00:00:00 2001 From: Daniel Ponte Date: Sun, 9 Feb 2025 09:49:18 -0500 Subject: [PATCH 3/3] Fix #107 --- client/stillbox/src/app/talkgroups/talkgroups.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/stillbox/src/app/talkgroups/talkgroups.service.ts b/client/stillbox/src/app/talkgroups/talkgroups.service.ts index d132b23..fee62a3 100644 --- a/client/stillbox/src/app/talkgroups/talkgroups.service.ts +++ b/client/stillbox/src/app/talkgroups/talkgroups.service.ts @@ -59,7 +59,9 @@ export class TalkgroupService { } setShare(share: Share | null) { - this.fetchAll.next(share); + if (!this.authSvc.isAuth() && share !== null) { + this.fetchAll.next(share); + } } ngOnDestroy() {