diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 58b587072..be0a70745 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -303,7 +303,7 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { this.subs.add( this.store$ .select(userStore.getUserAuth) - .pipe(filter((userAuth) => !!userAuth.token)) + .pipe(filter((userAuth) => !!userAuth?.token)) .subscribe((_) => this.store$.dispatch(new userStore.LoadProfile())), ); @@ -361,10 +361,13 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { ), ); - const user = this.authService.getUser(); - if (user.id) { - this.store$.dispatch(new userStore.Login(user)); - } + this.subs.add( + this.authService.getUser().subscribe((user) => { + if (user?.id) { + this.store$.dispatch(new userStore.Login(user)); + } + }), + ); this.subs.add( this.actions$ diff --git a/src/app/components/header/header-buttons/header-buttons.component.ts b/src/app/components/header/header-buttons/header-buttons.component.ts index 021276ad5..e09de5402 100644 --- a/src/app/components/header/header-buttons/header-buttons.component.ts +++ b/src/app/components/header/header-buttons/header-buttons.component.ts @@ -26,6 +26,7 @@ import { AsfApiService, EnvironmentService, ScreenSizeService, + UserDataService, } from '@services'; import { CMRProduct, @@ -79,6 +80,7 @@ declare global { }) export class HeaderButtonsComponent implements OnInit, OnDestroy { authService = inject(AuthService); + userData = inject(UserDataService); env = inject(EnvironmentService); private http = inject(HttpClient); asfApiService = inject(AsfApiService); @@ -138,9 +140,9 @@ export class HeaderButtonsComponent implements OnInit, OnDestroy { ngOnInit() { this.subs.add( - this.store$ - .select(userStore.getUserAuth) - .subscribe((user) => (this.userAuth = user)), + this.store$.select(userStore.getUserAuth).subscribe((user) => { + this.userAuth = user; + }), ); this.subs.add( diff --git a/src/app/components/header/header-buttons/preferences/preferences.component.html b/src/app/components/header/header-buttons/preferences/preferences.component.html index e3193a416..c2967b398 100644 --- a/src/app/components/header/header-buttons/preferences/preferences.component.html +++ b/src/app/components/header/header-buttons/preferences/preferences.component.html @@ -1,6 +1,6 @@
- {{ 'PREFERENCES_FOR' | translate }} {{ userAuth.id }} + {{ 'PREFERENCES_FOR' | translate }} {{ userAuth?.id }}
diff --git a/src/app/models/user.model.ts b/src/app/models/user.model.ts index 193ef43e8..303bd1e7d 100644 --- a/src/app/models/user.model.ts +++ b/src/app/models/user.model.ts @@ -4,6 +4,7 @@ export interface UserAuth { id: string | null; token: string | null; groups: URSGroup[]; + exp: number; } export interface UserProfile { diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index 38a8e7def..4a04fa89d 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -2,12 +2,19 @@ import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { interval, Subject, Observable, of } from 'rxjs'; -import { map, takeUntil, take, filter, catchError } from 'rxjs/operators'; +import { + map, + takeUntil, + take, + catchError, + filter, + switchMap, + retry, +} from 'rxjs/operators'; import { Store } from '@ngrx/store'; import { AppState } from '@store'; import { EnvironmentService } from './environment.service'; -import jwt_decode from 'jwt-decode'; import * as userStore from '@store/user'; import * as models from '@models'; @@ -21,18 +28,25 @@ export class AuthService { private http = inject(HttpClient); private notificationService = inject(NotificationService); private store$ = inject>(Store); + private existingUserInfo: models.UserAuth = null; private bc: BroadcastChannel; constructor() { if (typeof BroadcastChannel !== 'undefined') { this.bc = new BroadcastChannel('asf-vertex'); this.bc.onmessage = (_event: MessageEvent) => { - const user = this.getUser(); - if (!user.id) { - this.store$.dispatch(new userStore.Logout()); - } else { - this.store$.dispatch(new userStore.Login(user)); - } + this.getUser() + .pipe( + take(1), + map((user) => { + if (!user?.id) { + this.store$.dispatch(new userStore.Logout()); + } else { + this.store$.dispatch(new userStore.Login(user)); + } + }), + ) + .subscribe(); }; } } @@ -65,29 +79,25 @@ export class AuthService { ); const loginWindowClosed = new Subject(); - return interval(500).pipe( takeUntil(loginWindowClosed), - map((_) => { - let user = null; - + switchMap((_) => { if (loginWindow.closed) { loginWindowClosed.next(); + return this.getUser(); } - try { if (loginWindow.location.host === window.location.host) { loginWindow.close(); - user = this.getUser(); this.bc.postMessage({ event: 'login', }); + return this.getUser(); } } catch (_e) { // Do nothing } - - return user; + return of(null); }), catchError((_) => { this.notificationService.error('Trouble logging in', 'Error', { @@ -103,7 +113,7 @@ export class AuthService { public logout$(): Observable { return this.http - .get(`${this.authUrl}/loginservice/logout`, { + .get(`${this.authUrl}/logout`, { responseType: 'text', withCredentials: true, }) @@ -112,70 +122,65 @@ export class AuthService { this.bc.postMessage({ event: 'logout', }); - return this.getUser(); + return this.existingUserInfo; }), catchError((_) => { - this.notificationService.error('Trouble logging out', 'Error', { - timeOut: 5000, - }); - return of(this.getUser()); + // For now this always throws a CORS error, but it does still logout successfully + // this.notificationService.error('Trouble logging out', 'Error', { + // timeOut: 5000, + // }); + return of(this.existingUserInfo); }), take(1), ); } - public getUser(): models.UserAuth { - const cookies = this.loadCookies(); - const token = cookies['asf-urs']; - - if (!token) { - return this.nullUser(); - } - try { - const user = jwt_decode(token); - - if (this.isExpired(user)) { - return this.nullUser(); - } - - setTimeout( - () => { - this.store$.dispatch(new userStore.Logout()); - this.notificationService.info( - 'Session Expired', - 'Please login again', + public getUser(): Observable { + return this.http + .get(`${this.env.currentEnv.user_data}/info/cookie`, { + withCredentials: true, + }) + .pipe( + retry({ + count: 3, + delay: 100, + }), + map((user) => { + return this.makeUser( + user['urs-user-id'], + user['urs-groups'], + user['token'], + user['exp'], + ); + }), + map((final) => { + this.existingUserInfo = final; + setTimeout( + () => { + this.store$.dispatch(new userStore.Logout()); + this.notificationService.info( + 'Session Expired', + 'Please login again', + ); + }, + final.exp * 1000 - Date.now(), ); - }, - user.exp * 1000 - Date.now(), + return final; + }), + catchError((_error) => { + console.error('Failed to get user info'); + return of(null); + }), + take(1), ); - - return this.makeUser(user['urs-user-id'], user['urs-groups'], token); - } catch (_error) { - return this.nullUser(); - } } private makeUser( id: string, groups: models.URSGroup[], token: string, + exp: number, ): models.UserAuth { - return { id, token, groups }; - } - - private nullUser(): models.UserAuth { - return { id: null, token: null, groups: [] }; - } - - private isExpired(userToken): boolean { - return Date.now() > userToken.exp * 1000; - } - - private loadCookies() { - return document.cookie - .split(';') - .map((s) => s.trim().split('=')) - .map(([name, val]) => ({ [name]: val })) - .reduce((allCookies, cookie) => ({ ...allCookies, ...cookie })); + return { id, token, groups, exp }; } } diff --git a/src/app/services/env.ts b/src/app/services/env.ts index d67a2e094..0993bb406 100644 --- a/src/app/services/env.ts +++ b/src/app/services/env.ts @@ -12,11 +12,11 @@ export const env = { test: { api: 'https://api-test.asf.alaska.edu', api_maturity: 'prod', - auth: 'https://auth.asf.alaska.edu', + auth: 'https://cumulus.asf.alaska.edu', urs: 'https://urs.earthdata.nasa.gov', urs_client_id: 'BO_n7nTIlMljdvU6kRRB3g', banner: 'https://banners.asf.alaska.edu', - user_data: 'https://cgdjuem3wc.execute-api.us-east-1.amazonaws.com/prod/', + user_data: 'https://appdata-test.asf.alaska.edu', unzip: 'https://unzip.asf.alaska.edu', bulk_download: 'https://bulk-download.asf.alaska.edu', }, diff --git a/src/app/services/environment.service.ts b/src/app/services/environment.service.ts index 7ad0f27fc..af18b5051 100644 --- a/src/app/services/environment.service.ts +++ b/src/app/services/environment.service.ts @@ -18,7 +18,7 @@ export interface Environment { api_maturity?: string; urs_client_id: string; unzip: string; - datapool: string; + datapool?: string; banner: string; user_data: string; bulk_download: string; diff --git a/src/app/services/envs/env-devel.ts b/src/app/services/envs/env-devel.ts index d67a2e094..fe38a6043 100644 --- a/src/app/services/envs/env-devel.ts +++ b/src/app/services/envs/env-devel.ts @@ -16,7 +16,7 @@ export const env = { urs: 'https://urs.earthdata.nasa.gov', urs_client_id: 'BO_n7nTIlMljdvU6kRRB3g', banner: 'https://banners.asf.alaska.edu', - user_data: 'https://cgdjuem3wc.execute-api.us-east-1.amazonaws.com/prod/', + user_data: 'https://appdata-test.asf.alaska.edu', unzip: 'https://unzip.asf.alaska.edu', bulk_download: 'https://bulk-download.asf.alaska.edu', }, diff --git a/src/app/services/envs/env-prod.ts b/src/app/services/envs/env-prod.ts index 870283201..12773835e 100644 --- a/src/app/services/envs/env-prod.ts +++ b/src/app/services/envs/env-prod.ts @@ -12,11 +12,11 @@ export const env = { test: { api: 'https://api-test.asf.alaska.edu', api_maturity: 'test', - auth: 'https://auth.asf.alaska.edu', + auth: 'https://cumulus.asf.alaska.edu', urs: 'https://urs.earthdata.nasa.gov', urs_client_id: 'BO_n7nTIlMljdvU6kRRB3g', banner: 'https://banners.asf.alaska.edu', - user_data: 'https://cgdjuem3wc.execute-api.us-east-1.amazonaws.com/prod/', + user_data: 'https://appdata-test.asf.alaska.edu', unzip: 'https://unzip.asf.alaska.edu', bulk_download: 'https://bulk-download.asf.alaska.edu', }, diff --git a/src/app/services/envs/env-test.ts b/src/app/services/envs/env-test.ts index d67a2e094..0993bb406 100644 --- a/src/app/services/envs/env-test.ts +++ b/src/app/services/envs/env-test.ts @@ -12,11 +12,11 @@ export const env = { test: { api: 'https://api-test.asf.alaska.edu', api_maturity: 'prod', - auth: 'https://auth.asf.alaska.edu', + auth: 'https://cumulus.asf.alaska.edu', urs: 'https://urs.earthdata.nasa.gov', urs_client_id: 'BO_n7nTIlMljdvU6kRRB3g', banner: 'https://banners.asf.alaska.edu', - user_data: 'https://cgdjuem3wc.execute-api.us-east-1.amazonaws.com/prod/', + user_data: 'https://appdata-test.asf.alaska.edu', unzip: 'https://unzip.asf.alaska.edu', bulk_download: 'https://bulk-download.asf.alaska.edu', }, diff --git a/src/app/services/user-data.service.ts b/src/app/services/user-data.service.ts index 437ae40d7..117ade3aa 100644 --- a/src/app/services/user-data.service.ts +++ b/src/app/services/user-data.service.ts @@ -1,5 +1,5 @@ import { Injectable, inject } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { HttpClient } from '@angular/common/http'; import { Observable, of } from 'rxjs'; import { catchError } from 'rxjs/operators'; @@ -7,6 +7,15 @@ import { EnvironmentService } from './environment.service'; import { UserAuth } from '@models'; import { NotificationService } from './notification.service'; +interface UserInfo { + uid: string; + first_name: string; + last_name: string; + country: string; + email_address: string; + organization: string; +} + @Injectable({ providedIn: 'root', }) @@ -17,13 +26,11 @@ export class UserDataService { private baseUrl = this.getBaseUrlFrom(); - public getUserInfo$(userAuth: UserAuth): Observable { - const url = this.getUserInfoURL(this.baseUrl, userAuth.id); - const headers = this.makeAuthHeader(userAuth.token); - + public getUserInfo$(_userAuth: UserAuth): Observable { + const url = this.getUserInfoURL(this.baseUrl); return this.http - .get(url, { - headers, + .get(url, { + withCredentials: true, }) .pipe( catchError((error) => { @@ -48,12 +55,11 @@ export class UserDataService { userAuth: UserAuth, attribute: string, ): Observable { - const url = this.makeEndpoint(this.baseUrl, userAuth.id, attribute); - const headers = this.makeAuthHeader(userAuth.token); + const url = this.makeEndpoint(this.baseUrl, userAuth?.id, attribute); return this.http .get(url, { - headers, + withCredentials: true, }) .pipe( catchError((error) => { @@ -79,12 +85,11 @@ export class UserDataService { attribute: string, value: T, ): Observable { - const url = this.makeEndpoint(this.baseUrl, userAuth.id, attribute); - const headers = this.makeAuthHeader(userAuth.token); + const url = this.makeEndpoint(this.baseUrl, userAuth?.id, attribute); return this.http .post(url, value, { - headers, + withCredentials: true, }) .pipe( catchError((_) => { @@ -109,14 +114,10 @@ export class UserDataService { return `${baseUrl}/vertex/${userId}/${attributeName}`; } - private makeAuthHeader(token: string): HttpHeaders { - return new HttpHeaders().set('Authorization', `Bearer ${token}`); - } - private getBaseUrlFrom(): string { return this.env.currentEnv.user_data; } - public getUserInfoURL(baseUrl: string, userId: string): string { - return `${baseUrl}/vertex/${userId}/`; + public getUserInfoURL(baseUrl: string): string { + return `${baseUrl}/info/`; } } diff --git a/src/app/store/user/user.effect.ts b/src/app/store/user/user.effect.ts index 531694f16..3e43ec988 100644 --- a/src/app/store/user/user.effect.ts +++ b/src/app/store/user/user.effect.ts @@ -147,7 +147,7 @@ export class UserEffects { this.store$.select(userReducer.getSearchHistory), ]), ), - filter(([_, [userAuth, _searches]]) => userAuth.id !== null), + filter(([_, [userAuth, _searches]]) => userAuth?.id !== null), switchMap(([_, [userAuth, searches]]) => this.userDataService.setAttribute$(userAuth, 'History', searches), ), diff --git a/src/app/store/user/user.reducer.ts b/src/app/store/user/user.reducer.ts index c06dfe545..49d68b4ce 100644 --- a/src/app/store/user/user.reducer.ts +++ b/src/app/store/user/user.reducer.ts @@ -20,6 +20,7 @@ export const initState: UserState = { id: null, token: null, groups: [], + exp: null, }, profile: { defaultDataset: 'SENTINEL-1', @@ -62,6 +63,7 @@ export function userReducer(state = initState, action: UserActions): UserState { id: null, token: null, groups: [], + exp: null, }, }; }