Merge pull request 'Fix #107' (#108) from authSignals into shareUI

Reviewed-on: #108
This commit is contained in:
Daniel Ponte 2025-02-09 09:51:19 -05:00
commit cd75967b4f
6 changed files with 87 additions and 65 deletions

View file

@ -8,7 +8,7 @@
</div> </div>
<div class="centerNav"></div> <div class="centerNav"></div>
<div class="rightNav"> <div class="rightNav">
@if (auth.loggedIn) { @if (auth.isAuth()) {
<button class="ybtn sbButton"> <button class="ybtn sbButton">
<a (click)="logout()" class="logout">Logout</a> <a (click)="logout()" class="logout">Logout</a>
</button> </button>
@ -19,7 +19,7 @@
</header> </header>
<div class="container"> <div class="container">
@if (auth.loggedIn) { @if (auth.isAuth()) {
<app-navigation [events]="toggleNavSubject.asObservable()"></app-navigation> <app-navigation [events]="toggleNavSubject.asObservable()"></app-navigation>
} @else { } @else {
<div class="content"> <div class="content">

View file

@ -29,7 +29,7 @@ export function authIntercept(
next: HttpHandlerFn, next: HttpHandlerFn,
): Observable<HttpEvent<unknown>> { ): Observable<HttpEvent<unknown>> {
let authSvc: AuthService = inject(AuthService); let authSvc: AuthService = inject(AuthService);
if (authSvc.loggedIn) { if (authSvc.isAuth()) {
req = req.clone({ req = req.clone({
setHeaders: { setHeaders: {
Authorization: `Bearer ${authSvc.getToken()}`, Authorization: `Bearer ${authSvc.getToken()}`,

View file

@ -1,56 +1,92 @@
import { Injectable } from '@angular/core'; import {
Injectable,
signal,
computed,
effect,
inject,
DestroyRef,
} from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http'; import { HttpClient, HttpResponse } from '@angular/common/http';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export class Jwt { export class Jwt {
constructor(public jwt: string) {} constructor(public jwt: string) {}
} }
type AuthState = {
user: string | null;
token: string | null;
is_auth: boolean;
};
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class AuthService { export class AuthService {
loggedIn: boolean = false; private _accessTokenKey = 'jwt';
private _storedToken = localStorage.getItem(this._accessTokenKey);
destroyed = inject(DestroyRef);
private _state = signal<AuthState>({
user: null,
token: this._storedToken,
is_auth: this._storedToken !== null,
});
loginFailed = signal<boolean>(false);
token = computed(() => this._state().token);
isAuth = computed(() => this._state().is_auth);
user = computed(() => this._state().user);
constructor( constructor(
private http: HttpClient, private http: HttpClient,
private _router: Router, private _router: Router,
) { ) {
let ssJWT = localStorage.getItem('jwt'); effect(() => {
if (ssJWT) { const token = this.token();
this.loggedIn = true; if (token !== null) {
} localStorage.setItem(this._accessTokenKey, token);
} else {
localStorage.removeItem(this._accessTokenKey);
}
});
} }
login(username: string, password: string): Observable<HttpResponse<Jwt>> { login(username: string, password: string) {
return this.http return this.http
.post<Jwt>( .post<Jwt>('/api/login', { username: username, password: password })
'/api/login', .pipe(takeUntilDestroyed(this.destroyed))
{ username: username, password: password }, .subscribe({
{ observe: 'response' }, next: (res) => {
) let state = <AuthState>{
.pipe( user: username,
tap((event) => { token: res.jwt,
if (event.status == 200) { is_auth: true,
localStorage.setItem('jwt', event.body?.jwt.toString() ?? ''); };
this.loggedIn = true; this._state.set(state);
this._router.navigateByUrl('/home'); this.loginFailed.update(() => false);
} this._router.navigateByUrl('/');
}), },
); error: (err) => {
this.loginFailed.update(() => true);
},
});
}
_clearState() {
this._state.set(<AuthState>{});
} }
logout() { logout() {
this.http this.http.get('/api/logout', { withCredentials: true }).subscribe({
.get('/api/logout', { withCredentials: true, observe: 'response' }) next: (event) => {
.subscribe((event) => { this._clearState();
if (event.status == 200) { },
this.loggedIn = false; error: (err) => {
} this._clearState();
}); },
localStorage.removeItem('jwt'); });
this.loggedIn = false;
this._router.navigateByUrl('/login'); this._router.navigateByUrl('/login');
} }
@ -60,14 +96,20 @@ export class AuthService {
.pipe( .pipe(
tap((event) => { tap((event) => {
if (event.status == 200) { if (event.status == 200) {
localStorage.setItem('jwt', event.body?.jwt.toString() ?? ''); let ost = this._state();
this.loggedIn = true; let tok = event.body?.jwt.toString();
let state = <AuthState>{
user: ost.user,
token: tok ? tok : null,
is_auth: true,
};
this._state.set(state);
} }
}), }),
); );
} }
getToken(): string | null { getToken(): string | null {
return localStorage.getItem('jwt'); return localStorage.getItem(this._accessTokenKey);
} }
} }

View file

@ -28,7 +28,7 @@
<button class="login sbButton" (click)="onSubmit()">Login</button> <button class="login sbButton" (click)="onSubmit()">Login</button>
</div> </div>
</form> </form>
@if (failed) { @if (failed()) {
<div role="alert"> <div role="alert">
<span>Login Failed!</span> <span>Login Failed!</span>
</div> </div>

View file

@ -1,4 +1,4 @@
import { Component, inject } from '@angular/core'; import { Component, inject, signal } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { AuthService } from '../login/auth.service'; import { AuthService } from '../login/auth.service';
import { catchError, of, Subscription } from 'rxjs'; import { catchError, of, Subscription } from 'rxjs';
@ -17,31 +17,9 @@ export class LoginComponent {
router: Router = inject(Router); router: Router = inject(Router);
username: string = ''; username: string = '';
password: string = ''; password: string = '';
failed: boolean = false; failed = this.apiService.loginFailed;
private subscriptions = new Subscription();
onSubmit() { onSubmit() {
this.failed = false; this.apiService.login(this.username, this.password);
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();
} }
} }

View file

@ -51,7 +51,7 @@ export class TalkgroupService {
if (sh) { if (sh) {
this.shareSvc.getShare(sh).subscribe(this.fetchAll); this.shareSvc.getShare(sh).subscribe(this.fetchAll);
} else { } else {
if (this.authSvc.loggedIn) { if (this.authSvc.isAuth()) {
this.fetchAll.next(null); this.fetchAll.next(null);
} }
} }
@ -59,7 +59,9 @@ export class TalkgroupService {
} }
setShare(share: Share | null) { setShare(share: Share | null) {
this.fetchAll.next(share); if (!this.authSvc.isAuth() && share !== null) {
this.fetchAll.next(share);
}
} }
ngOnDestroy() { ngOnDestroy() {