Skip to content

Commit

Permalink
Merge pull request #2466 from ORCID/lmendoza/9619-show-interstitial-f…
Browse files Browse the repository at this point in the history
…or-re-authorise-requests

Lmendoza/9619 show interstitial for re authorise requests
  • Loading branch information
leomendoza123 authored Feb 11, 2025
2 parents 421066a + d5c3c9d commit 5309332
Show file tree
Hide file tree
Showing 3 changed files with 245 additions and 107 deletions.
2 changes: 1 addition & 1 deletion src/app/authorize/pages/authorize/authorize.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<main id="main">
<main id="main" *ngIf="!loading">
<div class="container">
<div class="row space-around">
<div class="col m6 s4 l6">
Expand Down
318 changes: 244 additions & 74 deletions src/app/authorize/pages/authorize/authorize.component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Component, Inject } from '@angular/core'
import { cloneDeep } from 'lodash'
import { Observable, forkJoin, NEVER } from 'rxjs'
import { first, take, tap } from 'rxjs/operators'
import { InterstitialsService } from 'src/app/cdk/interstitials/interstitials.service'
import { PlatformInfo, PlatformInfoService } from 'src/app/cdk/platform-info'
import { WINDOW } from 'src/app/cdk/window'
import { UserService } from 'src/app/core'
import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service'
import { GoogleTagManagerService } from 'src/app/core/google-tag-manager/google-tag-manager.service'
import { RecordEmailsService } from 'src/app/core/record-emails/record-emails.service'
import { TogglzService } from 'src/app/core/togglz/togglz.service'
import { AssertionVisibilityString, EmailsEndpoint } from 'src/app/types'
import { ERROR_REPORT } from 'src/app/errors'
import { EmailsEndpoint, RequestInfoForm } from 'src/app/types'

@Component({
templateUrl: './authorize.component.html',
Expand All @@ -18,99 +22,265 @@ export class AuthorizeComponent {
redirectUrl: string

platform: PlatformInfo
showAuthorizationComponent: boolean
showAuthorizationError: boolean
showInterstital: boolean
showAuthorizationComponent = false
showAuthorizationError = false
showInterstital = false
loading = true

originalEmailsBackendCopy: EmailsEndpoint
userHasPrivateDomains = false
oauthDomainsInterstitialEnabled: boolean
oauthSession: any

organizationName: string
domainInterstitialHasBeenViewed: boolean
userIsNotImpersonating: boolean
insidePopUpWindows: boolean
userHasPublicDomains: boolean

// Domain / Interstitial properties
hasPrivateDomains = false
hasPublicDomains = false
isOAuthDomainsInterstitialEnabled = false
hasDomainInterstitialBeenViewed = false

// User session properties
isNotImpersonating = false
insidePopUpWindows = false
redirectByReportAlreadyAuthorize = false

constructor(
_user: UserService,
private _platformInfo: PlatformInfoService,
private _recordEmails: RecordEmailsService,
private _togglz: TogglzService,
private _interstitials: InterstitialsService,
private userService: UserService,
private platformInfoService: PlatformInfoService,
private recordEmailsService: RecordEmailsService,
private togglzService: TogglzService,
private interstitialsService: InterstitialsService,
@Inject(WINDOW) private window: Window,
private _userInfo: UserService
) {
_user.getUserSession().subscribe((session) => {
if (session.oauthSession && session.oauthSession.error) {
this.showAuthorizationError = true
} else {
this.showAuthorizationComponent = true
}
})
_platformInfo.get().subscribe((platformInfo) => {
this.platform = platformInfo
private googleTagManagerService: GoogleTagManagerService,
private errorHandlerService: ErrorHandlerService
) {}

/**
* Lifecycle hook. Initiates data loading and handles session logic.
*/
ngOnInit(): void {
this.loading = true
this.insidePopUpWindows = !!this.window.opener

forkJoin({
userSession: this.loadUserSession(),
platform: this.loadPlatformInfo(),
interstitial: this.loadInterstitialViewed(),
togglz: this.loadTogglzState(),
emails: this.loadEmails(),
}).subscribe(({ userSession }) => {
this.handleUserSession(userSession)
})
}

ngOnInit() {
this.insidePopUpWindows = this.window.opener !== null
this._userInfo.getUserSession().subscribe((userInfo) => {
this.userIsNotImpersonating =
userInfo.userInfo.REAL_USER_ORCID ===
userInfo.userInfo.EFFECTIVE_USER_ORCID
/**
* Called by template to handle final redirection.
*/
handleRedirect(url: string): void {
this.redirectUrl = url
if (url && this.canShowDomainInterstitial()) {
this.showDomainInterstitial()
} else {
this.finishRedirect()
}
}

/**
* Reports re-authorization to Google Tag Manager (async),
* then triggers a redirect after the report completes or on error.
*/
reportReAuthorization(request: RequestInfoForm): void {
const analyticsReport: Observable<void> =
this.googleTagManagerService.reportEvent('Reauthorize', request)

forkJoin([analyticsReport]).subscribe({
next: () => {
// After successful reporting, proceed
this.finishRedirectObs(request)
},
error: (error) => {
// If error happens, handle it, then proceed
this.errorHandlerService.handleError(
error,
ERROR_REPORT.STANDARD_NO_VERBOSE_NO_GA
)
this.finishRedirectObs(request)
},
})
this._interstitials
.getInterstitialsViewed('DOMAIN_INTERSTITIAL')
.subscribe((value) => {
return (this.domainInterstitialHasBeenViewed = value)
})
}

this._togglz
.getStateOf('OAUTH_DOMAINS_INTERSTITIAL')
.pipe(take(1))
.subscribe((value) => {
this.oauthDomainsInterstitialEnabled = value
})
this._recordEmails
.getEmails()
/**
* Internal method to finalize redirection (non-observable variant).
*/
finishRedirect(): void {
if (this.redirectByReportAlreadyAuthorize) {
this.reportReAuthorization(this.oauthSession)
} else {
;(this.window as any).outOfRouterNavigation(this.redirectUrl)
}
}

/*
* ─────────────────────────────────────────────────────────────
* Private methods below
* ─────────────────────────────────────────────────────────────
*/

/**
* Finalize redirection returning an Observable (for chaining).
*/
private finishRedirectObs(
oauthSession: RequestInfoForm
): Observable<boolean> {
;(this.window as any).outOfRouterNavigation(oauthSession.redirectUrl)
return NEVER
}

/**
* Determines whether the domain interstitial should be displayed
* based on user domain status, togglz, impersonation, etc.
*/
private canShowDomainInterstitial(): boolean {
return (
this.hasPrivateDomains &&
!this.hasPublicDomains &&
this.isOAuthDomainsInterstitialEnabled &&
!this.hasDomainInterstitialBeenViewed &&
this.isNotImpersonating &&
!this.insidePopUpWindows
)
}

/**
* Displays the domain interstitial and marks it as viewed.
*/
private showDomainInterstitial(): void {
this.showAuthorizationComponent = false
this.showInterstital = true
this.interstitialsService
.setInterstitialsViewed('DOMAIN_INTERSTITIAL')
.subscribe()
}

/**
* Loads the user session data.
*/
private loadUserSession(): Observable<any> {
return this.userService.getUserSession().pipe(first())
}

/**
* Loads the platform information and stores it in a component property.
*/
private loadPlatformInfo(): Observable<PlatformInfo> {
return this.platformInfoService.get().pipe(
first(),
tap((platform) => (this.platform = platform))
)
}

/**
* Checks if the domain interstitial was previously viewed by the user.
*/
private loadInterstitialViewed(): Observable<boolean> {
return this.interstitialsService
.getInterstitialsViewed('DOMAIN_INTERSTITIAL')
.pipe(
tap((value) => {
this.originalEmailsBackendCopy = cloneDeep(value)
this.userHasPrivateDomains = this.userHasPrivateEmails(value)
this.userHasPublicDomains = this.userHasPublicEmails(value)
}),
first()
first(),
tap((wasViewed) => {
this.hasDomainInterstitialBeenViewed = wasViewed
})
)
.subscribe()
}
userHasPublicEmails(value: EmailsEndpoint): any {
return !!value.emailDomains.find((domain) => domain.visibility === 'PUBLIC')

/**
* Loads togglz (feature flag) state for the OAuth domains interstitial feature.
*/
private loadTogglzState(): Observable<boolean> {
return this.togglzService.getStateOf('OAUTH_DOMAINS_INTERSTITIAL').pipe(
take(1),
tap((state) => (this.isOAuthDomainsInterstitialEnabled = state))
)
}

userHasPrivateEmails(value: EmailsEndpoint): boolean {
return !!value.emailDomains.find((domain) => domain.visibility !== 'PUBLIC')
/**
* Loads the user emails from the backend and determines public/private domain flags.
*/
private loadEmails(): Observable<EmailsEndpoint> {
return this.recordEmailsService.getEmails().pipe(
first(),
tap((emails) => {
this.originalEmailsBackendCopy = cloneDeep(emails)
this.hasPrivateDomains = this.userHasPrivateEmails(emails)
this.hasPublicDomains = this.userHasPublicEmails(emails)
})
)
}

handleRedirect(url: string) {
this.redirectUrl = url
/**
* After loading forkJoin data, decide on final flow:
* show error, show domain interstitial, or show authorization screen.
*/
private handleUserSession(userSession: any): void {
// Check if user is impersonating
this.isNotImpersonating =
userSession?.userInfo?.REAL_USER_ORCID ===
userSession?.userInfo?.EFFECTIVE_USER_ORCID

this.oauthSession = userSession.oauthSession

// 1. If the user was already authorized, we might show domain interstitial or just redirect
if (this.isUserAlreadyAuthorized(this.oauthSession)) {
if (this.canShowDomainInterstitial()) {
this.redirectByReportAlreadyAuthorize = true
this.showDomainInterstitial()
this.loading = false
} else {
this.reportReAuthorization(this.oauthSession)
}
return
}

// 2. If the backend returned an error
if (this.oauthSession && this.oauthSession.error) {
this.showAuthorizationError = true
this.loading = false
return
}

// 3. Otherwise, show the standard authorization component
this.showAuthorizationComponent = true
this.loading = false
}

/**
* Determines if the user was already authorized based on OAuth session data.
*/
private isUserAlreadyAuthorized(oauthSession: any): boolean {
if (
url &&
this.userHasPrivateDomains &&
!this.userHasPublicDomains &&
this.oauthDomainsInterstitialEnabled &&
!this.domainInterstitialHasBeenViewed &&
this.userIsNotImpersonating &&
!this.insidePopUpWindows
!oauthSession ||
!oauthSession.redirectUrl ||
!oauthSession.responseType
) {
this.showAuthorizationComponent = false
this.showInterstital = true
this._interstitials
.setInterstitialsViewed('DOMAIN_INTERSTITIAL')
.subscribe()
} else {
this.finishRedirect()
return false
}
return oauthSession.redirectUrl.includes(oauthSession.responseType + '=')
}
finishRedirect() {
;(this.window as any).outOfRouterNavigation(this.redirectUrl)

/**
* Helper to check if at least one email domain is public.
*/
private userHasPublicEmails(emails: EmailsEndpoint): boolean {
return !!emails.emailDomains?.some(
(domain) => domain.visibility === 'PUBLIC'
)
}

/**
* Helper to check if at least one email domain is private.
*/
private userHasPrivateEmails(emails: EmailsEndpoint): boolean {
return !!emails.emailDomains?.some(
(domain) => domain.visibility !== 'PUBLIC'
)
}
}
Loading

0 comments on commit 5309332

Please sign in to comment.