From 734b481a5e250af46e0da35fa16bdb4362803267 Mon Sep 17 00:00:00 2001 From: Enrico Schwendig Date: Sat, 2 Nov 2024 19:55:20 +0100 Subject: [PATCH] add authentication by user vs client user --- projects/core/src/lib/entities/account.ts | 5 ++ projects/core/src/lib/entities/client-user.ts | 5 ++ projects/core/src/lib/entities/index.ts | 4 + .../src/lib/entities/lobby-media-stream.ts | 1 - projects/core/src/lib/entities/token.ts | 3 + projects/core/src/lib/entities/user.ts | 5 +- .../src/lib/interceptors/auth.interceptor.ts | 4 +- .../core/src/lib/provider/auth.service.ts | 82 +++++++++++++++++++ .../core/src/lib/provider/error.handler.ts | 24 ++++++ projects/core/src/lib/provider/index.ts | 1 + .../core/src/lib/provider/session.service.ts | 81 ++++++------------ projects/core/src/lib/shig-module.ts | 4 +- 12 files changed, 155 insertions(+), 64 deletions(-) create mode 100644 projects/core/src/lib/entities/account.ts create mode 100644 projects/core/src/lib/entities/client-user.ts create mode 100644 projects/core/src/lib/entities/token.ts create mode 100644 projects/core/src/lib/provider/auth.service.ts create mode 100644 projects/core/src/lib/provider/error.handler.ts diff --git a/projects/core/src/lib/entities/account.ts b/projects/core/src/lib/entities/account.ts new file mode 100644 index 0000000..c2d514a --- /dev/null +++ b/projects/core/src/lib/entities/account.ts @@ -0,0 +1,5 @@ +export interface Account { + user: string, + email: string, + password: string, +} diff --git a/projects/core/src/lib/entities/client-user.ts b/projects/core/src/lib/entities/client-user.ts new file mode 100644 index 0000000..b1e2266 --- /dev/null +++ b/projects/core/src/lib/entities/client-user.ts @@ -0,0 +1,5 @@ +export interface ClientUser { + id: string, + name: string, + token: string, +} diff --git a/projects/core/src/lib/entities/index.ts b/projects/core/src/lib/entities/index.ts index ed4f672..3c9aec1 100644 --- a/projects/core/src/lib/entities/index.ts +++ b/projects/core/src/lib/entities/index.ts @@ -1,4 +1,6 @@ +export * from './account'; export * from './channel.msg'; +export * from './client-user'; export * from './constraints'; export * from './device-settings'; export * from './lobby-media.event'; @@ -7,6 +9,7 @@ export * from './lobby-media-muted'; export * from './lobby-media-purpose'; export * from './lobby-media-stream'; export * from './media-devices'; +export * from './token'; export * from './sdp-media-info'; export * from './sdp-media-line'; export * from './space'; @@ -18,3 +21,4 @@ export * from './user'; + diff --git a/projects/core/src/lib/entities/lobby-media-stream.ts b/projects/core/src/lib/entities/lobby-media-stream.ts index daecc51..1de2336 100644 --- a/projects/core/src/lib/entities/lobby-media-stream.ts +++ b/projects/core/src/lib/entities/lobby-media-stream.ts @@ -1,4 +1,3 @@ -import {User} from './user'; import {LobbyMedia} from './lobby-media'; import {LobbyMediaPurpose} from './lobby-media-purpose'; diff --git a/projects/core/src/lib/entities/token.ts b/projects/core/src/lib/entities/token.ts new file mode 100644 index 0000000..862b73c --- /dev/null +++ b/projects/core/src/lib/entities/token.ts @@ -0,0 +1,3 @@ +export interface Token { + jwt: string +} diff --git a/projects/core/src/lib/entities/user.ts b/projects/core/src/lib/entities/user.ts index 922d392..2b85b76 100644 --- a/projects/core/src/lib/entities/user.ts +++ b/projects/core/src/lib/entities/user.ts @@ -1,5 +1,4 @@ export interface User { - id: string, - name: string, - token: string, + name: string, + domain: string, } diff --git a/projects/core/src/lib/interceptors/auth.interceptor.ts b/projects/core/src/lib/interceptors/auth.interceptor.ts index a07b966..533b469 100644 --- a/projects/core/src/lib/interceptors/auth.interceptor.ts +++ b/projects/core/src/lib/interceptors/auth.interceptor.ts @@ -14,10 +14,10 @@ export class AuthInterceptor implements HttpInterceptor { .pipe( map(isActive => { if (isActive) { - const token = this.session.getToken(); + const token = this.session.getAuthenticationToken(); request = request.clone({ setHeaders: { - Authorization: token, + Authorization: `Bearer ${token}`, }, }); } diff --git a/projects/core/src/lib/provider/auth.service.ts b/projects/core/src/lib/provider/auth.service.ts new file mode 100644 index 0000000..b1f774e --- /dev/null +++ b/projects/core/src/lib/provider/auth.service.ts @@ -0,0 +1,82 @@ +import {Injectable} from '@angular/core'; +import {Account, Token, User} from '../entities'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; +import {ParameterService} from './parameter.service'; +import {catchError, lastValueFrom, map, mergeMap, Observable, tap} from 'rxjs'; +import {handleError} from './error.handler'; +import {SessionService} from './session.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + + httpOptions = { + headers: new HttpHeaders({'Content-Type': 'application/json', 'Accept': 'application/json'}), + }; + + constructor(private http: HttpClient, private params: ParameterService, private session: SessionService) { + } + + login(email: string, pass: string): Observable { + const loginUrl = `${this.params.API_PREFIX}/auth/login`; + const body = {email, pass}; + // returns 200 || 403 + return this.http.post(loginUrl, body, this.httpOptions).pipe( + tap((token: Token) => this.session.setAuthenticationToken(token.jwt)), + mergeMap(() => this.getUser()), + map((user) => this.session.setUser(user)) + ); + } + + registerAccount(account: Account): Observable { + const url = `${this.params.API_PREFIX}/auth/register`; + // returns 201 || 400 + return this.http.post(url, account, this.httpOptions); + } + + verifyAccount(token: String): Observable { + const url = `${this.params.API_PREFIX}/auth/verify`; + // returns 200 || 400 + return this.http.put(url, {token}, this.httpOptions); + } + + forgetPassword(email: string) { + const url = `${this.params.API_PREFIX}/auth/forgotPassword`; + const body = {email}; + + return lastValueFrom(this.http.post(url, body, this.httpOptions).pipe( + tap(a => console.log('Forgot Password Finish', a)), + catchError(handleError('forgot password', '')) + ) + ); + } + + newPassword(password: string, token: string) { + const url = `${this.params.API_PREFIX}/auth/newPassword`; + const body = {password, token}; + + return lastValueFrom(this.http.post(url, body, this.httpOptions).pipe( + tap(a => console.log('New Password Finish', a)), + catchError(handleError('new password', '')) + ) + ); + } + + deleteAccount(email: string) { + const url = `${this.params.API_PREFIX}/auth/deleteAccount`; + const body = {email}; + + return lastValueFrom(this.http.post(url, body, this.httpOptions).pipe( + tap(a => console.log('Delete Account', a)), + catchError(handleError('delete account', '')) + ) + ); + } + + private getUser(): Observable { + const userUrl = `${this.params.API_PREFIX}/auth/user`; + // returns 200 || 403 + return this.http.get(userUrl, this.httpOptions); + } +} diff --git a/projects/core/src/lib/provider/error.handler.ts b/projects/core/src/lib/provider/error.handler.ts new file mode 100644 index 0000000..8dcad3d --- /dev/null +++ b/projects/core/src/lib/provider/error.handler.ts @@ -0,0 +1,24 @@ +import {Observable, of} from 'rxjs'; + + +/** + * Handle Http operation that failed. + * Let the app continue. + * + * @param operation - name of the operation that failed + * @param result - optional value to return as the observable result + */ +export const handleError = (operation = 'operation', result?: T): (error: any) => Observable => { + + return (error: any): Observable => { + + // TODO: send the error to remote logging infrastructure + console.error(error); // log to console instead + + // TODO: better job of transforming error for user consumption + console.log(`${operation} failed: ${error.message}`); + + // Let the app keep running by returning an empty result. + return of(result as T); + }; +}; diff --git a/projects/core/src/lib/provider/index.ts b/projects/core/src/lib/provider/index.ts index 09285b2..15fc518 100644 --- a/projects/core/src/lib/provider/index.ts +++ b/projects/core/src/lib/provider/index.ts @@ -1,3 +1,4 @@ +export * from './auth.service'; export * from './channel-messenger'; export * from './lobby.service'; export * from './message.service'; diff --git a/projects/core/src/lib/provider/session.service.ts b/projects/core/src/lib/provider/session.service.ts index 5ef002e..bd93efb 100644 --- a/projects/core/src/lib/provider/session.service.ts +++ b/projects/core/src/lib/provider/session.service.ts @@ -1,89 +1,58 @@ import {Injectable} from '@angular/core'; import {BehaviorSubject, Observable, of, Subject} from 'rxjs'; -import {User} from '../entities'; +import {ClientUser} from '../entities'; +import {User} from '../entities/user'; + +const USER_NAME_KEY = 'user-name'; +const USER_DOMAIN_KEY = 'user-domain'; +const SESSION_TOKEN_KEY = 'jwt'; @Injectable({ providedIn: 'root' }) export class SessionService { - private readonly users: User[] = [ - {id: 'root@localhost:9000', name: 'root@localhost:9000', token: token1}, - {id: 'user123@localhost:9000', name: 'user123@localhost:9000', token: token2}, - {id: 'guest-2', name: 'Guest 2', token: token3}, - {id: 'guest-3', name: 'Guest 3', token: token4}, - {id: 'guest-4', name: 'Guest 4', token: token5}, - ]; - private readonly userName$: Subject; private readonly anonymous = 'anonymous'; - private authToken: string | undefined; - constructor() { - const user = localStorage.getItem('user'); + const user = window.localStorage.getItem(USER_NAME_KEY); this.userName$ = new BehaviorSubject(user === null ? this.anonymous : user); } - setAuthenticationToken(token: string) { - this.authToken = token; + public setAuthenticationToken(token: string) { + window.localStorage.setItem(SESSION_TOKEN_KEY, token); + } + + public getAuthenticationToken(): string | null { + return window.localStorage.getItem(SESSION_TOKEN_KEY); } isActive(): Observable { - if (this.authToken !== undefined) { + if (this.getAuthenticationToken() !== null) { return of(true); } - const user = localStorage.getItem('user'); - return of(user !== null); + return of(false) } getUserName(): Observable { return this.userName$; } - setUserName(user: User): boolean { - const found = this.users.find((list) => list.id === user.id); - if (found === undefined) { - return false; - } - localStorage.setItem('user', user.name); - this.userName$.next(user.name); - return true; - } - - getUsers(): User[] { - return this.users; - } - - public removeUser(key: string) { - localStorage.removeItem('user'); - this.userName$.next(this.anonymous); - } - public clearData() { - localStorage.clear(); + window.localStorage.clear(); this.userName$.next(this.anonymous); } - public getToken(): string { - if (this.authToken !== undefined) { - return this.authToken; - } - let userName = localStorage.getItem('user'); - if (userName === null) { - return ''; - } - const user = this.users.find(items => items.name === userName); - if (user === undefined) { - return ''; - } + public setUser(user: User) { + window.localStorage.setItem(USER_NAME_KEY, user.name); + window.localStorage.setItem(USER_DOMAIN_KEY, user.domain); + this.userName$.next(user.name); + } - return `Bearer ${user.token}`; + public removeUser(key: string) { + window.localStorage.removeItem(USER_NAME_KEY); + window.localStorage.removeItem(USER_DOMAIN_KEY); + this.userName$.next(this.anonymous); } } - -const token1 = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJ1dWlkIjoiMWY1NjA2NmYtYjA1Yi00NDhhLWI3NjUtYjhkYzJiZTU1OTY3In0.GV0ptLGhnA0gwJC5zlKPY5z94KfLYUEpDraQmXPAR4o'; -const token2 = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJ1dWlkIjoiOGRhMGNkYzItZTVmMS00ZmZiLWIwYTktYWYxODI0MzI5OTQ0In0.CVk4I_9BP5yQuFS4X6XNZsvQ7tFStn3eGXGtNVYvSjY'; -const token3 = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJ1dWlkIjoiNzBiZTg2ODItNzk5Yy00MjdmLWI3MjgtZmQwMDhjNTYzYWFjIn0.mLT7DRp50QP6OqqxBQmf4VSx02i3cA0jk89UMdXLhBY'; -const token4 = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJ1dWlkIjoiMjhiYmYzNTctYTFmNy00NDkwLWIxZjItYTYzN2E0YWU5YmFlIn0.wMfmkJ0VBj86tW5NfQnnV91j2YT-mUZeM_E9qbt_bjg'; -const token5 = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJ1dWlkIjoiZmUwMjI2NDgtYTBlOS00NDQ0LTlkNGQtYTRjMGQ5ZWZiNmQ3In0.HgWacVwFeEgYBG6iDJYlQ25VTymM0xHpnppjWGrzOp4'; diff --git a/projects/core/src/lib/shig-module.ts b/projects/core/src/lib/shig-module.ts index 8d4c748..ae6e4dc 100644 --- a/projects/core/src/lib/shig-module.ts +++ b/projects/core/src/lib/shig-module.ts @@ -10,8 +10,8 @@ import { } from './component'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; -import '@material/web/switch/switch'; -import '@material/web/chips/filter-chip'; +// import '@material/web/common'; + @NgModule({ declarations: [