Skip to content

Commit 3b807b4

Browse files
Lmendoza/9694 capture affiliations during sign in (#2522)
* 9694-capture-affiliations-during-sign-in * 9694-capture-affiliations-during-sign-in * 9694-capture-affiliations-during-sign-in * 9694-capture-affiliations-during-sign-in * 9694-capture-affiliations-during-sign-in * 9694-capture-affiliations-during-sign-in * 9694-capture-affiliations-during-sign-in * 9694-capture-affiliations-during-sign-in * 9694-capture-affiliations-during-sign-in --------- Co-authored-by: Angel Montenegro <a.montenegro@orcid.org>
1 parent 0649c7b commit 3b807b4

37 files changed

Lines changed: 746 additions & 485 deletions

File tree

src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import { MatLegacySelectModule } from '@angular/material/legacy-select'
2222
import { MatLegacyFormFieldModule } from '@angular/material/legacy-form-field'
2323
import { MatLegacyInputModule } from '@angular/material/legacy-input'
24+
import { FormsModule } from '@angular/forms'
2425

2526
@NgModule({
2627
declarations: [AppComponent],
@@ -38,6 +39,7 @@ import { MatLegacyInputModule } from '@angular/material/legacy-input'
3839
PseudoModule, // Remove on angular 10 https://bit.ly/3ezbF4v
3940
// Environmental dependent modules
4041
EnvironmentBannerModule,
42+
FormsModule,
4143
],
4244
providers: [
4345
TitleService,

src/app/authorize/authorize.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { OauthErrorComponent } from './components/oauth-error/oauth-error.compon
1616
import { AuthorizeComponent } from './pages/authorize/authorize.component'
1717
import { FormAuthorizeComponent } from './components/form-authorize/form-authorize.component'
1818
import { InterstitialsModule } from '../cdk/interstitials/interstitials.module'
19+
import { PortalModule } from '@angular/cdk/portal'
1920

2021
@NgModule({
2122
declarations: [
@@ -24,6 +25,7 @@ import { InterstitialsModule } from '../cdk/interstitials/interstitials.module'
2425
FormAuthorizeComponent,
2526
],
2627
imports: [
28+
PortalModule,
2729
CommonModule,
2830
AuthorizeRoutingModule,
2931
MatCardModule,

src/app/authorize/pages/authorize/authorize.component.html

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,7 @@
1212
*ngIf="showAuthorizationComponent"
1313
(redirectUrl)="handleRedirect($event)"
1414
></app-form-authorize>
15-
<app-share-emails-domains
16-
*ngIf="showInterstital"
17-
(finish)="finishRedirect()"
18-
[userEmailsJson]="originalEmailsBackendCopy"
19-
></app-share-emails-domains>
15+
<ng-template #interstitialOutlet cdkPortalOutlet></ng-template>
2016
<app-oauth-error *ngIf="showAuthorizationError"></app-oauth-error>
2117
</mat-card>
2218
</div>

src/app/authorize/pages/authorize/authorize.component.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { Overlay } from '@angular/cdk/overlay'
1313
import { UserService } from '../../../core'
1414

1515
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
16+
import { P } from '@angular/cdk/keycodes'
17+
import { LoginMainInterstitialsManagerService } from 'src/app/core/login-interstitials-manager/login-main-interstitials-manager.service'
1618

1719
describe('AuthorizeComponent', () => {
1820
let component: AuthorizeComponent
@@ -31,6 +33,10 @@ describe('AuthorizeComponent', () => {
3133
MatSnackBar,
3234
MatDialog,
3335
Overlay,
36+
{
37+
provide: LoginMainInterstitialsManagerService,
38+
useValue: {},
39+
},
3440
],
3541
schemas: [CUSTOM_ELEMENTS_SCHEMA],
3642
}).compileComponents()
Lines changed: 82 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,41 @@
1-
import { Component, Inject } from '@angular/core'
1+
import { ComponentType } from '@angular/cdk/overlay'
2+
import { Component, Inject, ViewChild } from '@angular/core'
23
import { cloneDeep } from 'lodash'
34
import { Observable, forkJoin, NEVER, of } from 'rxjs'
4-
import { first, map, switchMap, take, tap } from 'rxjs/operators'
5+
import {
6+
filter,
7+
finalize,
8+
first,
9+
map,
10+
switchMap,
11+
take,
12+
tap,
13+
} from 'rxjs/operators'
514
import { InterstitialsService } from 'src/app/cdk/interstitials/interstitials.service'
615
import { PlatformInfo, PlatformInfoService } from 'src/app/cdk/platform-info'
716
import { WINDOW } from 'src/app/cdk/window'
817
import { UserService } from 'src/app/core'
918
import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service'
1019
import { GoogleTagManagerService } from 'src/app/core/google-tag-manager/google-tag-manager.service'
20+
import { LoginMainInterstitialsManagerService } from 'src/app/core/login-interstitials-manager/login-main-interstitials-manager.service'
1121
import { RecordEmailsService } from 'src/app/core/record-emails/record-emails.service'
22+
import { RecordService } from 'src/app/core/record/record.service'
1223
import { TogglzService } from 'src/app/core/togglz/togglz.service'
1324
import { ERROR_REPORT } from 'src/app/errors'
1425
import { EmailsEndpoint, RequestInfoForm } from 'src/app/types'
26+
import { UserRecord } from 'src/app/types/record.local'
27+
import { UserSession } from 'src/app/types/session.local'
28+
import { CdkPortalOutlet, ComponentPortal, Portal } from '@angular/cdk/portal'
1529

1630
@Component({
1731
templateUrl: './authorize.component.html',
1832
styleUrls: ['./authorize.component.scss'],
1933
preserveWhitespaces: true,
2034
})
2135
export class AuthorizeComponent {
36+
@ViewChild('interstitialOutlet', { static: false, read: CdkPortalOutlet })
37+
outlet!: CdkPortalOutlet
38+
2239
redirectUrl: string
2340

2441
platform: PlatformInfo
@@ -38,58 +55,71 @@ export class AuthorizeComponent {
3855

3956
// User session properties
4057
isNotImpersonating = false
41-
insidePopUpWindows = false
42-
redirectByReportAlreadyAuthorize = false
58+
interstitialComponent: ComponentType<any>
59+
redirectByReportAlreadyAuthorize: boolean
4360

4461
constructor(
4562
private userService: UserService,
4663
private platformInfoService: PlatformInfoService,
47-
private recordEmailsService: RecordEmailsService,
48-
private togglzService: TogglzService,
49-
private interstitialsService: InterstitialsService,
5064
@Inject(WINDOW) private window: Window,
5165
private googleTagManagerService: GoogleTagManagerService,
52-
private errorHandlerService: ErrorHandlerService
66+
private errorHandlerService: ErrorHandlerService,
67+
private recordService: RecordService,
68+
private loginMainInterstitialsManagerService: LoginMainInterstitialsManagerService
5369
) {}
5470

5571
/**
5672
* Lifecycle hook. Initiates data loading and handles session logic.
5773
*/
5874
ngOnInit(): void {
5975
this.loading = true
60-
this.insidePopUpWindows = !!this.window.opener
6176

6277
forkJoin({
63-
userSession: this.loadUserSession(),
6478
platform: this.loadPlatformInfo(),
65-
togglz: this.loadTogglzState(),
66-
emails: this.loadEmails(),
79+
userSession: this.loadUserSession(),
80+
userRecord: this.recordService.getRecord({}).pipe(
81+
filter((userRecord: UserRecord) => {
82+
return (
83+
!!userRecord &&
84+
!!userRecord?.userInfo &&
85+
!!userRecord?.emails &&
86+
!!userRecord?.affiliations?.length
87+
)
88+
}),
89+
take(1)
90+
),
6791
})
6892
.pipe(
69-
switchMap((results) => {
70-
if (!results.userSession?.userInfo) {
71-
return of(results)
72-
} else {
73-
return this.loadInterstitialViewed().pipe(
74-
map(() => {
75-
return results
93+
switchMap((record) => {
94+
return this.loginMainInterstitialsManagerService
95+
.checkLoginInterstitials(record.userRecord, {
96+
returnType: 'component',
97+
togglzPrefix: 'OAUTH',
98+
})
99+
.pipe(
100+
take(1),
101+
map((interstitial) => {
102+
this.interstitialComponent = interstitial
103+
}),
104+
switchMap(() => {
105+
return of(record.userSession)
106+
}),
107+
finalize(() => {
108+
this.handleUserSession(record.userSession)
76109
})
77110
)
78-
}
79111
})
80112
)
81-
.subscribe(({ userSession }) => {
82-
this.handleUserSession(userSession)
83-
})
113+
.subscribe()
84114
}
85115

86116
/**
87117
* Called by template to handle final redirection.
88118
*/
89119
handleRedirect(url: string): void {
90120
this.redirectUrl = url
91-
if (url && this.canShowDomainInterstitial()) {
92-
this.showDomainInterstitial()
121+
if (this.redirectUrl && this.isThereInterstitialToShow()) {
122+
this.showInterstitial()
93123
} else {
94124
this.finishRedirect()
95125
}
@@ -119,35 +149,34 @@ export class AuthorizeComponent {
119149
}
120150

121151
/**
122-
* Determines whether the domain interstitial should be displayed
123-
* based on user domain status, togglz, impersonation, etc.
152+
* Determines whether a interstitial should be displayed
124153
*/
125-
private canShowDomainInterstitial(): boolean {
126-
return (
127-
this.hasPrivateDomains &&
128-
!this.hasPublicDomains &&
129-
this.isOAuthDomainsInterstitialEnabled &&
130-
!this.hasDomainInterstitialBeenViewed &&
131-
this.isNotImpersonating &&
132-
!this.insidePopUpWindows
133-
)
154+
private isThereInterstitialToShow(): boolean {
155+
return !!this.interstitialComponent
134156
}
135157

136158
/**
137-
* Displays the domain interstitial and marks it as viewed.
159+
* Displays the interstitial
138160
*/
139-
private showDomainInterstitial(): void {
161+
private showInterstitial(): void {
162+
const portal = new ComponentPortal(this.interstitialComponent)
163+
164+
const componentRef = this.outlet.attachComponentPortal(portal)
165+
166+
componentRef.instance.finish.subscribe(() => {
167+
this.finishRedirect()
168+
})
169+
170+
componentRef.changeDetectorRef.detectChanges()
171+
140172
this.showAuthorizationComponent = false
141173
this.showInterstital = true
142-
this.interstitialsService
143-
.setInterstitialsViewed('DOMAIN_INTERSTITIAL')
144-
.subscribe()
145174
}
146175

147176
/**
148177
* Loads the user session data.
149178
*/
150-
private loadUserSession(): Observable<any> {
179+
private loadUserSession(): Observable<UserSession> {
151180
return this.userService.getUserSession().pipe(first())
152181
}
153182

@@ -161,91 +190,35 @@ export class AuthorizeComponent {
161190
)
162191
}
163192

164-
/**
165-
* Checks if the domain interstitial was previously viewed by the user.
166-
*/
167-
private loadInterstitialViewed(): Observable<boolean> {
168-
return this.interstitialsService
169-
.getInterstitialsViewed('DOMAIN_INTERSTITIAL')
170-
.pipe(
171-
tap((wasViewed) => {
172-
this.hasDomainInterstitialBeenViewed = wasViewed
173-
})
174-
)
175-
}
176-
177-
/**
178-
* Loads togglz (feature flag) state for the OAuth domains interstitial feature.
179-
*/
180-
private loadTogglzState(): Observable<boolean> {
181-
return this.togglzService.getStateOf('OAUTH_DOMAINS_INTERSTITIAL').pipe(
182-
take(1),
183-
tap((state) => (this.isOAuthDomainsInterstitialEnabled = state))
184-
)
185-
}
186-
187-
/**
188-
* Loads the user emails from the backend and determines public/private domain flags.
189-
*/
190-
private loadEmails(): Observable<EmailsEndpoint> {
191-
return this.userService.getUserSession().pipe(
192-
take(1),
193-
switchMap((session) => {
194-
// Load emails only if user is logged in
195-
if (session.oauthSessionIsLoggedIn) {
196-
return this.recordEmailsService.getEmails().pipe(
197-
first(),
198-
tap((emails) => {
199-
this.originalEmailsBackendCopy = cloneDeep(emails)
200-
this.hasPrivateDomains = this.userHasPrivateEmails(emails)
201-
this.hasPublicDomains = this.userHasPublicEmails(emails)
202-
})
203-
)
204-
} else {
205-
// If user is not logged in, return empty emails object
206-
// This scenario is for users who are not logged in and the OAUTH URL is invalid
207-
// Those will load this component to display the error message by the component `app-oauth-error`
208-
this.originalEmailsBackendCopy = {
209-
emails: [],
210-
emailDomains: [],
211-
errors: [],
212-
}
213-
return of({} as EmailsEndpoint)
214-
}
215-
})
216-
)
217-
}
218-
219193
/**
220194
* After loading forkJoin data, decide on final flow:
221195
* show error, show domain interstitial, or show authorization screen.
222196
*/
223-
private handleUserSession(userSession: any): void {
224-
// Check if user is impersonating
225-
this.isNotImpersonating =
226-
userSession?.userInfo?.REAL_USER_ORCID ===
227-
userSession?.userInfo?.EFFECTIVE_USER_ORCID
228-
197+
private handleUserSession(userSession: UserSession): void {
229198
this.oauthSession = userSession.oauthSession
230199

231200
// 1. If the backend returned an error
232201
if (this.oauthSession && this.oauthSession.error) {
202+
this.debugLog(`Oauth session error: ${this.oauthSession.error}`)
233203
this.showAuthorizationError = true
234204
this.loading = false
235205
return
236206
}
237207

238208
// 2. If the user was already authorized, we might show domain interstitial or just redirect
239209
if (this.isUserAlreadyAuthorized(this.oauthSession)) {
240-
if (this.canShowDomainInterstitial()) {
210+
this.debugLog('User alreay authorized this app')
211+
if (this.isThereInterstitialToShow()) {
241212
this.redirectByReportAlreadyAuthorize = true
242-
this.showDomainInterstitial()
213+
this.showInterstitial()
243214
this.loading = false
244215
this.redirectUrl = this.oauthSession.redirectUrl
245216
} else {
246217
this.finishRedirectObs(this.oauthSession)
247218
}
248219
return
220+
} else {
221+
this.debugLog('User has not authorized this app')
249222
}
250223

251224
// 3. Otherwise, show the standard authorization component
@@ -267,21 +240,9 @@ export class AuthorizeComponent {
267240
return oauthSession.redirectUrl.includes(oauthSession.responseType + '=')
268241
}
269242

270-
/**
271-
* Helper to check if at least one email domain is public.
272-
*/
273-
private userHasPublicEmails(emails: EmailsEndpoint): boolean {
274-
return !!emails.emailDomains?.some(
275-
(domain) => domain.visibility === 'PUBLIC'
276-
)
277-
}
278-
279-
/**
280-
* Helper to check if at least one email domain is private.
281-
*/
282-
private userHasPrivateEmails(emails: EmailsEndpoint): boolean {
283-
return !!emails.emailDomains?.some(
284-
(domain) => domain.visibility !== 'PUBLIC'
285-
)
243+
private debugLog(message: string): void {
244+
if (runtimeEnvironment.debugger) {
245+
console.info('[OAuth]', message)
246+
}
286247
}
287248
}

0 commit comments

Comments
 (0)