Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pfda #551

Open
wants to merge 32 commits into
base: development_3.0
Choose a base branch
from
Open

Pfda #551

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f47634f
Pfda session extension
bartapes Sep 17, 2024
019dfe2
Config sessionRefreshOnActiveUserOnly field
bartapes Sep 18, 2024
53d71de
Session expiration dialog fix
bartapes Sep 19, 2024
82cb43a
PFDA-5656 Substance registration pages do not require pFDA login to a…
bartapes Oct 10, 2024
a78b7bc
Request CSRF token before every POST request
bartapes Oct 22, 2024
f5d7639
CSRF token uri
bartapes Oct 23, 2024
bc656cc
Login, logout fix; improvements
bartapes Nov 20, 2024
65e4f6b
PFDA auth update
bartapes Nov 26, 2024
e233a8d
Public GSRS
bartapes Nov 28, 2024
dcd32f0
proceedAsGuest fix
bartapes Dec 11, 2024
e1787cb
Force FDA SSO login (if available)
bartapes Dec 28, 2024
3de76fb
Pfda session extension
bartapes Sep 17, 2024
84c38de
switching to only one package file
NikoAnderson Nov 6, 2024
9a0e31a
reverting alt package, updating readme, commands
NikoAnderson Nov 7, 2024
f2f59da
un-reverting changes to alt files
NikoAnderson Nov 7, 2024
c700c1f
Update README.md
alx652 Nov 18, 2024
fe644bb
Update README.md
alx652 Nov 19, 2024
8846af7
exploring pausing structure earch results
NikoAnderson Dec 11, 2024
2e66cf3
final version for testing
NikoAnderson Dec 23, 2024
bb02e75
adding service
NikoAnderson Dec 23, 2024
be0b95e
removing local changes
NikoAnderson Dec 23, 2024
1588625
adding formulation autofill option for ssg1
NikoAnderson Jan 21, 2025
fee2e10
adding formulation percentage for G1ss, det.pagin
NikoAnderson Jan 23, 2025
efde911
PFDA-5656 Substance registration pages do not require pFDA login to a…
bartapes Oct 10, 2024
ab56656
Request CSRF token before every POST request
bartapes Oct 22, 2024
db59b0e
Login, logout fix; improvements
bartapes Nov 20, 2024
373b11d
PFDA auth update
bartapes Nov 26, 2024
b12d9cb
Public GSRS
bartapes Nov 28, 2024
6324fa7
Prototype
bartapes Feb 4, 2025
40ed857
Success/error notifications
bartapes Feb 5, 2025
c2cbe34
remove bad return
bartapes Feb 5, 2025
5bd6f86
Loading spinners for substance counters on home page;
bartapes Feb 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app/core/app.component.html
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
<app-main-notification></app-main-notification>
<router-outlet></router-outlet>
57 changes: 50 additions & 7 deletions src/app/core/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Injectable, PLATFORM_ID, Inject } from '@angular/core';
import { ConfigService } from '../config/config.service';
import { Auth, Role, UserGroup } from './auth.model';
import { Observable, Subject, of } from 'rxjs';
import { map, take, catchError } from 'rxjs/operators';
import { interval, Observable, Subject, of } from 'rxjs';
import { catchError, concatMap, filter, map, take, takeWhile } from 'rxjs/operators';
import { HttpClient, HttpParams } from '@angular/common/http';
import { isPlatformBrowser } from '@angular/common';
import { UserDownload, AllUserDownloads } from '@gsrs-core/auth/user-downloads/download.model';
Expand Down Expand Up @@ -96,6 +96,36 @@ export class AuthService {
);
}

// Helper function to create an Observable that emits when the popup login window closes
private waitForPopupToClose(popupWindow: Window): Observable<number> {
return interval(1000).pipe(
takeWhile(() => !popupWindow.closed, true),
filter(() => popupWindow.closed)
);
}

// Method to handle pFDA login (using popup window) and return success/unsuccess flag
pfdaLogin(): Observable<boolean> {
const height = 700;
const width = 700;
const left = (screen.width / 2) - (width / 2);
const top = (screen.height / 2) - (height / 2);
const loginWindow = window.open(
'/login?force_fda_sso_login=true&user_return_to=%2Fginas%2Fclose-pfda-login-window',
'pFDA Login',
`height=${height},width=${width},top=${top},left=${left}`
);

return this.waitForPopupToClose(loginWindow).pipe(
concatMap(() =>
this.getAuth().pipe(
map(authAfterLogin => !!authAfterLogin), // Convert to boolean (true = success)
catchError(() => of(false)) // Return false if there's an error
)
)
);
}

getAuth(): Observable<Auth> {
return new Observable(observer => {

Expand Down Expand Up @@ -143,6 +173,10 @@ export class AuthService {
});
}

private deleteCookie(name: string) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/`;
}

logout(): void {
// if (
// !this.configService.configData
Expand All @@ -161,13 +195,22 @@ export class AuthService {
document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT';
}
}
const url = `${this.configService.configData.apiBaseUrl}logout`;
this.http.get(url).subscribe(() => {
let url = `${this.configService.configData.apiBaseUrl}logout`;
let method = 'GET';

if (this.configService.configData.isPfdaVersion) {
url = '/logout';
method = 'DELETE';
}

this.http.request(method, url).subscribe(() => {
this._auth = null;
this._authUpdate.next(null);
this.deleteCookie('sessionExpiredAt');
}, error => {
this._auth = null;
this._authUpdate.next(null);
this.deleteCookie('sessionExpiredAt');
});
}

Expand Down Expand Up @@ -307,11 +350,11 @@ export class AuthService {
if (this.configService.configData && this.configService.configData.dummyWhoami) {
observer.next(this.configService.configData.dummyWhoami);
} else {
this.http.get<Auth>(`${url}whoami`)
this.http.get<Auth>(`${url}whoami`)
.subscribe(
auth => {
// console.log("Authorized as");
// console.log(auth);
// console.log("Authorized as");
// console.log(auth);
observer.next(auth);
},
err => {
Expand Down
40 changes: 21 additions & 19 deletions src/app/core/auth/csrf-token.interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpClient } from '@angular/common/http';
import { from, Observable, switchMap } from 'rxjs';
import { ConfigService } from "@gsrs-core/config";

@Injectable()
export class CsrfTokenInterceptor implements HttpInterceptor {

constructor() {}
constructor(private http: HttpClient, private configService: ConfigService) {}

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

// CSRF token for GET and HEAD is not needed
if (['GET', 'HEAD'].includes(request.method)) {
// CSRF token request needed in pFDA version only
if (['GET', 'HEAD'].includes(request.method)
|| !(this.configService.configData?.isPfdaVersion)) {
return next.handle(request);
}

// Parse CSRF token from HTML meta tag
const metaTag: HTMLMetaElement | null = document.querySelector('meta[name=csrf-token]');
let csrfToken = metaTag?.content;
if (csrfToken === undefined) {
csrfToken = 'CSRF-TOKEN-NOT-PARSED';
}
return from(this.fetchCsrfToken()).pipe(
switchMap((token: string) => {
const modifiedRequest = this.addCsrfToken(request, token);
return next.handle(modifiedRequest);
})
);
}

// Clone the request and add the CSRF token to the headers
const modifiedRequest = request.clone({
private fetchCsrfToken(): Promise<string> {
return this.http.get(`/csrf-token`, { responseType: 'text' }).toPromise();
}

private addCsrfToken(request: HttpRequest<any>, token: string): HttpRequest<any> {
return request.clone({
setHeaders: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'X-CSRF-Token': csrfToken
'X-CSRF-Token': token
}
});

// Pass the modified request to the next handler
return next.handle(modifiedRequest);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ <h2 mat-dialog-title class="dialog-title">
</div>
<div class="dialog-actions flex-row" mat-dialog-actions>
<span class="middle-fill"></span>
<button mat-flat-button color="primary" (click)="proceedAsGuest()">Proceed as a guest</button>
<button *ngIf="timeRemainingSeconds>0" mat-flat-button color="primary" (click)="extendSession()">Extend Session</button>
<button *ngIf="timeRemainingSeconds<=0" mat-flat-button color="primary" (click)="login()">Login</button>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { Component, OnInit, Inject } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { ConfigService, SessionExpirationWarning } from '@gsrs-core/config';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { AnyNsRecord } from 'dns';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { AuthService } from '@gsrs-core/auth';
import { concatMap } from "rxjs"

@Component({
selector: 'app-session-expiration-dialog',
Expand All @@ -23,7 +24,9 @@ export class SessionExpirationDialogComponent implements OnInit {
@Inject(MAT_DIALOG_DATA) public data: any,
// N.B. injected services has to come after data
private router: Router,
private http: HttpClient
private http: HttpClient,
private authService: AuthService,
public configService: ConfigService
) {
this.sessionExpirationWarning = data.sessionExpirationWarning;
this.sessionExpiringAt = data.sessionExpiringAt;
Expand All @@ -49,11 +52,10 @@ export class SessionExpirationDialogComponent implements OnInit {

if (this.timeRemainingSeconds > 0) {
const remainingMinutes = Math.floor(this.timeRemainingSeconds / 60);
const reminaingSeconds = String(this.timeRemainingSeconds % 60).padStart(2, '0');
const remainingSeconds = String(this.timeRemainingSeconds % 60).padStart(2, '0');
this.dialogTitle = "Session Ending Soon"
this.dialogMessage = `You will be logged out in ${remainingMinutes}:${reminaingSeconds}`
}
else {
this.dialogMessage = `You will be logged out in ${remainingMinutes}:${remainingSeconds}`
} else {
this.dialogTitle = "Session Ended"
this.dialogMessage = "Your session has expired, please login again."
}
Expand All @@ -75,6 +77,23 @@ export class SessionExpirationDialogComponent implements OnInit {
}

login() {
window.location.assign('/login');
if (this.configService.configData.isPfdaVersion) {
this.authService.pfdaLogin().pipe(
concatMap(success => {
console.log('success: ', success);
if (success) {
this.closeDialog();
return this.authService.getAuth();
}
})).subscribe();
} else {
window.location.assign('/login');
}
}

proceedAsGuest() {
clearInterval(this.updateDialogInterval);
this.authService.logout();
this.closeDialog();
}
}
Loading