diff --git a/Singer.API/ClientApp/package-lock.json b/Singer.API/ClientApp/package-lock.json index 4728edff..beb974a0 100644 --- a/Singer.API/ClientApp/package-lock.json +++ b/Singer.API/ClientApp/package-lock.json @@ -1,12 +1,12 @@ { "name": "Singer", - "version": "0.1.0-issue-msal.1584", + "version": "0.1.0-issue-msal.1585", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Singer", - "version": "0.1.0-issue-msal.1584", + "version": "0.1.0-issue-msal.1585", "hasInstallScript": true, "dependencies": { "@angular/animations": "^14.3.0", diff --git a/Singer.API/ClientApp/package.json b/Singer.API/ClientApp/package.json index 60d397e6..5ea7ac6e 100644 --- a/Singer.API/ClientApp/package.json +++ b/Singer.API/ClientApp/package.json @@ -1,6 +1,6 @@ { "name": "Singer", - "version": "0.1.0-issue-msal.1584", + "version": "0.1.0-issue-msal.1585", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/Singer.API/ClientApp/src/app/app.component.ts b/Singer.API/ClientApp/src/app/app.component.ts index 3e0a7014..8cc57da8 100644 --- a/Singer.API/ClientApp/src/app/app.component.ts +++ b/Singer.API/ClientApp/src/app/app.component.ts @@ -12,7 +12,7 @@ import { RedirectRequest, } from '@azure/msal-browser'; import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular'; -import { b2cPolicyNames } from './modules/core/services/auth-config'; + import { B2PCPolicyStore } from './modules/core/services/b2cpolicy.state.store'; interface Payload extends AuthenticationResult { @@ -69,7 +69,7 @@ export class AppComponent implements OnInit, OnDestroy { * from SUSI flow. "tfp" claim in the id token tells us the policy (NOTE: legacy policies may use "acr" instead of "tfp"). * To learn more about B2C tokens, visit https://docs.microsoft.com/en-us/azure/active-directory-b2c/tokens-overview */ - if (payload.idTokenClaims['tfp'] === b2cPolicyNames.editProfile) { + if (payload.idTokenClaims['tfp'] === this.b2cPolicyStateStore.getPolicies().b2cPolicyNames.editProfile) { window.alert('Profile has been updated successfully. \nPlease sign-in again.'); return this.logout(); } diff --git a/Singer.API/ClientApp/src/app/modules/core/services/auth-config.ts b/Singer.API/ClientApp/src/app/modules/core/services/auth-config.ts index 90edfb11..615ce963 100644 --- a/Singer.API/ClientApp/src/app/modules/core/services/auth-config.ts +++ b/Singer.API/ClientApp/src/app/modules/core/services/auth-config.ts @@ -1,10 +1,11 @@ export const isIE = window.navigator.userAgent.indexOf('MSIE ') > -1 || window.navigator.userAgent.indexOf('Trident/') > -1; -export const b2cPolicyNames = { - signUpSignIn: '', - editProfile: '', - resetPassword: '', -}; + +export interface IB2CPolicyNames { + signUpSignIn: string; + editProfile: string; + resetPassword: string; +} export interface IB2CPolicies { authorities: { @@ -19,16 +20,17 @@ export interface IB2CPolicies { }; }; authorityDomain: string; + b2cPolicyNames: IB2CPolicyNames; } -export const endpoint = 'https://localhost:5001/api/'; +export const endpoint = 'https://localhost:5001/'; -export const protectedResources = { - default: { - endpoint: `${endpoint}*`, - scopes: ['https://vzwstijn.onmicrosoft.com/4ea3a07f-5db9-4290-b930-88806df40e9d/Events.Read'], - }, -}; +export interface IProtectedResources { + [name: string]: { + endpoint: string; + scopes: string[]; + }; +} export const loginRequest = { scopes: [], diff --git a/Singer.API/ClientApp/src/app/modules/core/services/b2cpolicy.state.store.ts b/Singer.API/ClientApp/src/app/modules/core/services/b2cpolicy.state.store.ts index 856d7b49..d0d6414d 100644 --- a/Singer.API/ClientApp/src/app/modules/core/services/b2cpolicy.state.store.ts +++ b/Singer.API/ClientApp/src/app/modules/core/services/b2cpolicy.state.store.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { ObservableStore } from '@codewithdan/observable-store'; -import { IB2CPolicies, b2cPolicyNames } from './auth-config'; +import { IB2CPolicies, IB2CPolicyNames } from './auth-config'; @Injectable({ providedIn: 'root' }) export class B2PCPolicyStore extends ObservableStore { @@ -8,7 +8,7 @@ export class B2PCPolicyStore extends ObservableStore { super({}); } - load(authority: string, tenant: string) { + load(authority: string, tenant: string, b2cPolicyNames: IB2CPolicyNames) { this.setState({ authorities: { signUpSignIn: { @@ -21,8 +21,8 @@ export class B2PCPolicyStore extends ObservableStore { authority: `https://${authority}/${tenant}.onmicrosoft.com/${b2cPolicyNames.resetPassword}`, }, }, - // authorityDomain: 'vzwstijn.b2clogin.com', authorityDomain: authority, + b2cPolicyNames: b2cPolicyNames, }); } diff --git a/Singer.API/ClientApp/src/app/modules/core/services/clientconfiguration.service.ts b/Singer.API/ClientApp/src/app/modules/core/services/clientconfiguration.service.ts index 5b56e000..6f099adf 100644 --- a/Singer.API/ClientApp/src/app/modules/core/services/clientconfiguration.service.ts +++ b/Singer.API/ClientApp/src/app/modules/core/services/clientconfiguration.service.ts @@ -1,7 +1,5 @@ -import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { IB2CPolicyNames, IProtectedResources } from './auth-config'; @Injectable({ providedIn: 'root', }) @@ -25,4 +23,6 @@ export interface Configuration { tenant: string; client_id: string; applicationinsights_intrumentationkey: string; + b2cPolicies: IB2CPolicyNames; + protectedResources: IProtectedResources; } diff --git a/Singer.API/ClientApp/src/app/modules/core/services/registration-api/registration-service.ts b/Singer.API/ClientApp/src/app/modules/core/services/registration-api/registration-service.ts index 0fc032dc..d25b5045 100644 --- a/Singer.API/ClientApp/src/app/modules/core/services/registration-api/registration-service.ts +++ b/Singer.API/ClientApp/src/app/modules/core/services/registration-api/registration-service.ts @@ -4,7 +4,7 @@ import { RegistrationOverviewDTO } from '../../DTOs/registration.dto'; import { GenericService } from '../generic-service'; import { HttpClient } from '@angular/common/http'; import { RegistrationSearchDTO } from '../../DTOs/registration.dto'; -import { endpoint, protectedResources } from '../auth-config'; +import { endpoint } from '../auth-config'; @Injectable({ providedIn: 'root', diff --git a/Singer.API/ClientApp/src/app/modules/core/services/singerevents-api/pending-registrations-service.ts b/Singer.API/ClientApp/src/app/modules/core/services/singerevents-api/pending-registrations-service.ts index ec9208e5..89523702 100644 --- a/Singer.API/ClientApp/src/app/modules/core/services/singerevents-api/pending-registrations-service.ts +++ b/Singer.API/ClientApp/src/app/modules/core/services/singerevents-api/pending-registrations-service.ts @@ -32,7 +32,7 @@ export class PendingRegistrationsService extends GenericService> { diff --git a/Singer.API/ClientApp/src/app/modules/core/services/singerevents-api/singerevents.proxy.ts b/Singer.API/ClientApp/src/app/modules/core/services/singerevents-api/singerevents.proxy.ts index 5f5ec2f4..00793ac3 100644 --- a/Singer.API/ClientApp/src/app/modules/core/services/singerevents-api/singerevents.proxy.ts +++ b/Singer.API/ClientApp/src/app/modules/core/services/singerevents-api/singerevents.proxy.ts @@ -35,27 +35,27 @@ export class SingerEventsProxy { .set('pageIndex', pageIndex.toString()) .set('pageSize', pageSize.toString()) .set('filter', filter); - return this.apiService.get('api/event', searchParams).pipe(map(res => res)); + return this.apiService.get('api/event', searchParams).pipe(map((res) => res)); } updateSingerEvents(id: string, updateSingerEventDTO: UpdateSingerEventDTO) { - return this.apiService.put(`api/event/${id}`, updateSingerEventDTO).pipe(map(res => res)); + return this.apiService.put(`api/event/${id}`, updateSingerEventDTO).pipe(map((res) => res)); } createSingerEvents(createSingerEventDTO: CreateSingerEventDTO): Observable { - return this.apiService.post('api/event', createSingerEventDTO).pipe(map(res => res)); + return this.apiService.post('api/event', createSingerEventDTO).pipe(map((res) => res)); } deleteSingerEvent(eventId: string): Observable { - return this.apiService.delete(`api/event/${eventId}`).pipe(map(res => res)); + return this.apiService.delete(`api/event/${eventId}`).pipe(map((res) => res)); } getEventRegisterDetails(eventId: string): Observable { - return this.apiService.get(`api/event/${eventId}/geteventregisterdetails`).pipe(map(res => res)); + return this.apiService.get(`api/event/${eventId}/geteventregisterdetails`).pipe(map((res) => res)); } registerCareUserOnEvent(eventId: string, dto: CreateRegistrationDTO): Observable { - return this.apiService.post(`api/event/${eventId}/registrations`, dto).pipe(map(res => res)); + return this.apiService.post(`api/event/${eventId}/registrations`, dto).pipe(map((res) => res)); } registerCareUserOnEventSlot( @@ -65,11 +65,11 @@ export class SingerEventsProxy { ): Observable { return this.apiService .post(`api/event/${eventId}/eventslot/${eventSlotId}/registrations`, dto) - .pipe(map(res => res)); + .pipe(map((res) => res)); } isUserRegisteredForEvent(eventId: string, careUserId: string): Observable { - return this.apiService.get(`api/event/${eventId}/isuserregistered/${careUserId}`).pipe(map(res => res)); + return this.apiService.get(`api/event/${eventId}/isuserregistered/${careUserId}`).pipe(map((res) => res)); } getRegistrations( @@ -86,7 +86,7 @@ export class SingerEventsProxy { .set('pageIndex', pageIndex.toString()) .set('pageSize', pageSize.toString()) .set('filter', filter); - return this.apiService.get(`api/event/${eventId}/registrations`, searchParams).pipe(map(res => res)); + return this.apiService.get(`api/event/${eventId}/registrations`, searchParams).pipe(map((res) => res)); } getPublicEvents(eventFilterData: EventFilterParameters): Observable { @@ -97,7 +97,7 @@ export class SingerEventsProxy { allowedAgeGroups: eventFilterData.allowedAgeGroups, text: eventFilterData.text, }; - return this.apiService.post('api/event/search', filterParams).pipe(map(res => res)); + return this.apiService.post('api/event/search', filterParams).pipe(map((res) => res)); } downloadEventSlotRegistartionCsv(eventId: string, eventSlotId: string): Observable { diff --git a/Singer.API/ClientApp/src/app/modules/login/components/auth/auth.component.ts b/Singer.API/ClientApp/src/app/modules/login/components/auth/auth.component.ts index cb33382a..5b4e9f98 100644 --- a/Singer.API/ClientApp/src/app/modules/login/components/auth/auth.component.ts +++ b/Singer.API/ClientApp/src/app/modules/login/components/auth/auth.component.ts @@ -16,7 +16,6 @@ import { } from '@azure/msal-browser'; import { filter, takeUntil } from 'rxjs/operators'; import { B2PCPolicyStore } from 'src/app/modules/core/services/b2cpolicy.state.store'; -import { b2cPolicyNames } from 'src/app/modules/core/services/auth-config'; type IdTokenClaimsWithPolicyId = IdTokenClaims & { acr?: string; @@ -88,7 +87,10 @@ export class AuthComponent implements OnInit { let payload = result.payload as AuthenticationResult; let idtoken = payload.idTokenClaims as IdTokenClaimsWithPolicyId; - if (idtoken.acr === b2cPolicyNames.signUpSignIn || idtoken.tfp === b2cPolicyNames.signUpSignIn) { + if ( + idtoken.acr === this.b2cPolicyStore.getPolicies().b2cPolicyNames.signUpSignIn || + idtoken.tfp === this.b2cPolicyStore.getPolicies().b2cPolicyNames.signUpSignIn + ) { this.authService.instance.setActiveAccount(payload.account); } @@ -97,7 +99,10 @@ export class AuthComponent implements OnInit { * from SUSI flow. "acr" claim in the id token tells us the policy (NOTE: newer policies may use the "tfp" claim instead). * To learn more about B2C tokens, visit https://docs.microsoft.com/en-us/azure/active-directory-b2c/tokens-overview */ - if (idtoken.acr === b2cPolicyNames.editProfile || idtoken.tfp === b2cPolicyNames.editProfile) { + if ( + idtoken.acr === this.b2cPolicyStore.getPolicies().b2cPolicyNames.editProfile || + idtoken.tfp === this.b2cPolicyStore.getPolicies().b2cPolicyNames.editProfile + ) { // retrieve the account from initial sing-in to the app const originalSignInAccount = this.authService.instance .getAllAccounts() @@ -105,8 +110,10 @@ export class AuthComponent implements OnInit { (account: AccountInfo) => account.idTokenClaims?.oid === idtoken.oid && account.idTokenClaims?.sub === idtoken.sub && - ((account.idTokenClaims as IdTokenClaimsWithPolicyId).acr === b2cPolicyNames.signUpSignIn || - (account.idTokenClaims as IdTokenClaimsWithPolicyId).tfp === b2cPolicyNames.signUpSignIn) + ((account.idTokenClaims as IdTokenClaimsWithPolicyId).acr === + this.b2cPolicyStore.getPolicies().b2cPolicyNames.signUpSignIn || + (account.idTokenClaims as IdTokenClaimsWithPolicyId).tfp === + this.b2cPolicyStore.getPolicies().b2cPolicyNames.signUpSignIn) ); let signUpSignInFlowRequest: SsoSilentRequest = { @@ -125,7 +132,10 @@ export class AuthComponent implements OnInit { * you can replace the code below with the same pattern used for handling the return from * profile edit flow (see above ln. 74-92). */ - if (idtoken.acr === b2cPolicyNames.resetPassword || idtoken.tfp === b2cPolicyNames.resetPassword) { + if ( + idtoken.acr === this.b2cPolicyStore.getPolicies().b2cPolicyNames.resetPassword || + idtoken.tfp === this.b2cPolicyStore.getPolicies().b2cPolicyNames.resetPassword + ) { let signUpSignInFlowRequest: RedirectRequest | PopupRequest = { authority: this.b2cPolicyStore.getPolicies().authorities.signUpSignIn.authority, prompt: PromptValue.LOGIN, // force user to reauthenticate with their new password diff --git a/Singer.API/ClientApp/src/app/msal-config.dynamic.module.ts b/Singer.API/ClientApp/src/app/msal-config.dynamic.module.ts index bbef7df6..cd635a05 100644 --- a/Singer.API/ClientApp/src/app/msal-config.dynamic.module.ts +++ b/Singer.API/ClientApp/src/app/msal-config.dynamic.module.ts @@ -15,7 +15,6 @@ import { } from '@azure/msal-angular'; import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { MSALStateStore } from './modules/core/services/msal.state.store'; -import { protectedResources } from './modules/core/services/auth-config'; import { ApplicationInsightsService } from './modules/core/services/applicationinsights.service'; import { ConfigurationService } from './modules/core/services/clientconfiguration.service'; import { B2PCPolicyStore } from './modules/core/services/b2cpolicy.state.store'; @@ -40,7 +39,8 @@ export function MSALInstanceFactory(config: MSALStateStore): IPublicClientApplic }); } -export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration { +export function MSALInterceptorConfigFactory(configurationService: ConfigurationService): MsalInterceptorConfiguration { + const protectedResources = configurationService.configuration.protectedResources; const protectedResourceMap = new Map | null>(); protectedResourceMap.set(protectedResources.default.endpoint, [...protectedResources.default.scopes]); // protectedResourceMap.set(protectedResources.registrationsList.endpoint, [ @@ -92,6 +92,7 @@ export class MsalConfigDynamicModule { { provide: MSAL_INTERCEPTOR_CONFIG, useFactory: MSALInterceptorConfigFactory, + deps: [ConfigurationService], }, MsalService, MsalGuard, @@ -106,21 +107,6 @@ export class MsalConfigDynamicModule { } } -export function initializeApp( - configurationService: ConfigurationService, - applicationInsightService: ApplicationInsightsService, - msalStateStore: MSALStateStore, - b2PCPolicyStore: B2PCPolicyStore -) { - return () => { - configurationService.load().then((c) => { - b2PCPolicyStore.load(c.authority, c.tenant); - msalStateStore.load(b2PCPolicyStore.getPolicies(), c.client_id, '/'); - applicationInsightService.init(); - }); - }; -} - export function initializerFactory( env: ConfigurationService, msalStateStore: MSALStateStore, @@ -132,7 +118,7 @@ export function initializerFactory( msalStateStore.load(x, value.client_id, '/'); } }); - b2cPolicyStateStore.load(value.authority, value.tenant); + b2cPolicyStateStore.load(value.authority, value.tenant, value.b2cPolicies); console.log('finished getting configurations dynamically.'); }); diff --git a/Singer.API/ClientApp/src/assets/config.json b/Singer.API/ClientApp/src/assets/config.json index ab973d75..fc990c5f 100644 --- a/Singer.API/ClientApp/src/assets/config.json +++ b/Singer.API/ClientApp/src/assets/config.json @@ -2,5 +2,16 @@ "tenant": "", "authority": "", "client_id": "", - "applicationinsights_intrumentationkey": "" + "applicationinsights_intrumentationkey": "", + "b2cPolicies": { + "signUpSignIn": "", + "editProfile": "", + "resetPassword": "" + }, + "protectedResources": { + "default": { + "endpoint": "", + "scopes": [] + } + } }