import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { first, firstValueFrom, map, Observable, of, Subject, take, tap, } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { environment } from 'src/environments/environment'; import { HttpError } from '../model/httpError/httpError.model'; import { User } from '../model/user/user.model'; import * as http from 'http'; @Injectable({ providedIn: 'root', }) export class AuthService { private userAuthenticated!: User; authSubject = new Subject(); readonly BACKEND_PATH = environment.backendPath; readonly BACKEND_OAUTH_PATH = environment.backendOAuthPath; constructor(private http: HttpClient) {} login(userAuthAtempt: User): void { this.validateUser(this.loginUser(userAuthAtempt)); } googleLogin() { window.open( this.BACKEND_OAUTH_PATH + '/oauth2/authorization/google', '_self', ); } githubLogin() { window.open( this.BACKEND_OAUTH_PATH + '/oauth2/authorization/github', '_self', ); } loginGoogleUser(p: any): void { this.validateUser(this.fetchGoogleOAuthToken(p)); } loginGithubUser(p: any): void { this.validateUser(this.fetchGithubOAuthToken(p)); } signup(userAuthAtempt: User): void { this.validateUser(this.createUser(userAuthAtempt)); } refresh(): void { this.validateUser(this.refreshAccessToken()); } autoLogin(): void { this.validateUser(this.validateSession()); } logout() { this.authSubject.next(null); this.destroySessions().subscribe(); } deleteAccount() { return this.deleteAccountRequest(); } addProfilePicture(file: File): void { const fileType = file.type.split('/')[1]; this.getAddProfilePictureUrl(fileType).subscribe({ next: (url: string | null) => { if (url != null) { this.uploadProfilePicture(url, file).then( (response: Observable) => { response.subscribe({ next: (response: any) => { this.processProfilePicture().subscribe( () => { this.refresh(); }, ); }, }); }, ); } }, }); } private loginUser(userAuthAtempt: User): Observable { let loginParams = new URLSearchParams(); loginParams.set('username', userAuthAtempt.username!); loginParams.set('password', userAuthAtempt.password!); let headers = new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded', }); return this.http .post(this.BACKEND_PATH + '/user/login', loginParams, { headers: headers, withCredentials: true, }) .pipe(first()); } private fetchGoogleOAuthToken(p: any): Observable { let params = new HttpParams({ fromObject: p, }); return this.http .get(this.BACKEND_OAUTH_PATH + '/login/oauth2/code/google', { withCredentials: true, params: params, }) .pipe(first()); } private fetchGithubOAuthToken(p: any): Observable { let params = new HttpParams({ fromObject: p, }); return this.http .get(this.BACKEND_OAUTH_PATH + '/login/oauth2/code/github', { withCredentials: true, params: params, }) .pipe(first()); } private createUser(newUser: User) { return this.http .post(this.BACKEND_PATH + '/user/signup', newUser, { withCredentials: true, }) .pipe(first()); } private refreshAccessToken() { return this.http.post( this.BACKEND_PATH + '/user/login/refresh', this.userAuthenticated.refreshToken, { withCredentials: true }, ); } private validateSession(): Observable { return this.http.get(this.BACKEND_PATH + '/session/validate', { withCredentials: true, }); } private destroySessions() { return this.http.delete(this.BACKEND_PATH + '/session/destroy', { withCredentials: true, }); } private deleteAccountRequest() { let headers = this.createAuthorizationHeader(); return this.http.delete(this.BACKEND_PATH + `/user/delete`, { headers: headers, withCredentials: true, }); } private validateUser(userAuthAtempt: Observable) { userAuthAtempt .pipe( catchError((error) => { if (error.status == 0) { return of({ title: 'Service Unavailable', status: 500, details: 'Service Unavailable, please try again later.', developerMessage: 'Service Unavailable, please try again later.', timestamp: new Date().toISOString(), }); } return of(error.error); }), first(), ) .subscribe({ next: (userAuthentication) => { this.userAuthenticated = userAuthentication; this.authSubject.next(this.userAuthenticated); }, }); } private getAddProfilePictureUrl( fileType: string, ): Observable { return this.http .post<{ presigned_url: string; file_key: string }>( this.BACKEND_PATH + '/user/profile-picture?fileType=' + fileType, null, { headers: this.createAuthorizationHeader(), withCredentials: true, }, ) .pipe( first(), map((res) => { if (!!res && !!res.presigned_url) { return res.presigned_url; } return null; }), ); } private async uploadProfilePicture( url: string, file: File, ): Promise> { const fileData = await this.readAsArrayBuffer(file); let headers = new HttpHeaders({ 'Content-Type': file.type, }); return this.http.put(url, fileData, { headers: headers, }); } private processProfilePicture() { return this.http.post( this.BACKEND_PATH + '/user/profile-picture/proccess', null, { headers: this.createAuthorizationHeader(), withCredentials: true, }, ); } private createAuthorizationHeader(): HttpHeaders { return new HttpHeaders({ 'Content-Type': 'application/json', Authorization: 'Bearer ' + this.userAuthenticated.accessToken?.token, }); } private async readAsArrayBuffer(file: File): Promise { const reader = new FileReader(); reader.readAsArrayBuffer(file); return new Promise((resolve, reject) => { reader.onload = () => { resolve(reader.result as ArrayBuffer); }; reader.onerror = () => { reject(reader.error); }; }); } }