diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 37896071..27d13641 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -20,6 +20,7 @@ const routes: Routes = [ { path: 'source/:source_id', component: SourceDetailComponent, canActivate: [ CanActivateAuthGuard] }, { path: 'source/:source_id/resource/:resource_id', component: ResourceDetailComponent, canActivate: [ CanActivateAuthGuard] }, { path: 'sources', component: MedicalSourcesComponent, canActivate: [ CanActivateAuthGuard] }, + { path: 'sources/callback/:source_type', component: MedicalSourcesComponent, canActivate: [ CanActivateAuthGuard] }, diff --git a/frontend/src/app/models/lighthouse/lighthouse-source-metadata.ts b/frontend/src/app/models/lighthouse/lighthouse-source-metadata.ts new file mode 100644 index 00000000..4fd28cbb --- /dev/null +++ b/frontend/src/app/models/lighthouse/lighthouse-source-metadata.ts @@ -0,0 +1,19 @@ +export class LighthouseSourceMetadata { + authorization_endpoint: string + token_endpoint: string + introspection_endpoint: string + userinfo_endpoint: string + + scopes_supported: string[] + issuer: string + grant_types_supported: string[] + response_types_supported: string[] + aud: string + code_challenge_methods_supported: string[] + + api_endpoint_base_url: string + client_id: string + redirect_uri: string + + confidential: boolean +} diff --git a/frontend/src/app/models/lighthouse/lighthouse-source.ts b/frontend/src/app/models/lighthouse/lighthouse-source.ts deleted file mode 100644 index 1dbf5391..00000000 --- a/frontend/src/app/models/lighthouse/lighthouse-source.ts +++ /dev/null @@ -1,17 +0,0 @@ -export class LighthouseSource { - oauth_authorization_endpoint: string - oauth_token_endpoint: string - oauth_registration_endpoint: string - oauth_introspection_endpoint: string - oauth_userinfo_endpoint: string - oauth_token_endpoint_auth_methods_supported: string - - api_endpoint_base_url: string - response_type: string - client_id: string - scopes: string[] - redirect_uri: string - aud: string - - confidential: boolean -} diff --git a/frontend/src/app/models/lighthouse/ligthouse-source.spec.ts b/frontend/src/app/models/lighthouse/ligthouse-source.spec.ts deleted file mode 100644 index bbf41a20..00000000 --- a/frontend/src/app/models/lighthouse/ligthouse-source.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { LighthouseSource } from './lighthouse-source'; - -describe('LighthouseSource', () => { - it('should create an instance', () => { - expect(new LighthouseSource()).toBeTruthy(); - }); -}); diff --git a/frontend/src/app/pages/auth-signup/auth-signup.component.ts b/frontend/src/app/pages/auth-signup/auth-signup.component.ts index b5b76f46..49dcc227 100644 --- a/frontend/src/app/pages/auth-signup/auth-signup.component.ts +++ b/frontend/src/app/pages/auth-signup/auth-signup.component.ts @@ -1,6 +1,5 @@ import { Component, OnInit } from '@angular/core'; import {FastenApiService} from '../../services/fasten-api.service'; -import {LighthouseSource} from '../../models/lighthouse/lighthouse-source'; import {User} from '../../models/fasten/user'; import {Router} from '@angular/router'; diff --git a/frontend/src/app/pages/dashboard/dashboard.component.ts b/frontend/src/app/pages/dashboard/dashboard.component.ts index 464f9ad3..225a9f21 100644 --- a/frontend/src/app/pages/dashboard/dashboard.component.ts +++ b/frontend/src/app/pages/dashboard/dashboard.component.ts @@ -1,6 +1,5 @@ import { Component, OnInit } from '@angular/core'; import {FastenApiService} from '../../services/fasten-api.service'; -import {LighthouseSource} from '../../models/lighthouse/lighthouse-source'; import {Source} from '../../models/fasten/source'; import {Router} from '@angular/router'; import {Summary} from '../../models/fasten/summary'; diff --git a/frontend/src/app/pages/medical-sources/medical-sources.component.ts b/frontend/src/app/pages/medical-sources/medical-sources.component.ts index 2ae5d0b1..1c8f9d9e 100644 --- a/frontend/src/app/pages/medical-sources/medical-sources.component.ts +++ b/frontend/src/app/pages/medical-sources/medical-sources.component.ts @@ -1,7 +1,7 @@ import {Component, HostListener, OnInit} from '@angular/core'; import {LighthouseService} from '../../services/lighthouse.service'; import {FastenApiService} from '../../services/fasten-api.service'; -import {LighthouseSource} from '../../models/lighthouse/lighthouse-source'; +import {LighthouseSourceMetadata} from '../../models/lighthouse/lighthouse-source-metadata'; import * as Oauth from '@panva/oauth4webapi'; import {AuthorizeClaim} from '../../models/lighthouse/authorize-claim'; import {Source} from '../../models/fasten/source'; @@ -12,6 +12,9 @@ import {concatMap, delay, retryWhen, timeout, first, map, filter, catchError} fr import * as FHIR from "fhirclient" import {MetadataSource} from '../../models/fasten/metadata-source'; import {NgbModal, ModalDismissReasons} from '@ng-bootstrap/ng-bootstrap'; +import {ActivatedRoute, Router} from '@angular/router'; +import {Location} from '@angular/common'; +// If you dont import this angular will import the wrong "Location" export const sourceConnectWindowTimeout = 24*5000 //wait 2 minutes (5 * 24 = 120) @@ -26,6 +29,9 @@ export class MedicalSourcesComponent implements OnInit { private lighthouseApi: LighthouseService, private fastenApi: FastenApiService, private modalService: NgbModal, + private route: ActivatedRoute, + private router: Router, + private location: Location, ) { } status: { [name: string]: string } = {} @@ -43,6 +49,11 @@ export class MedicalSourcesComponent implements OnInit { this.fastenApi.getMetadataSources().subscribe((metadataSources: {[name:string]: MetadataSource}) => { this.metadataSources = metadataSources + const callbackSourceType = this.route.snapshot.paramMap.get('source_type') + if(callbackSourceType){ + this.callback(callbackSourceType).then(console.log) + } + this.fastenApi.getSources() .subscribe((sourceList: Source[]) => { @@ -73,112 +84,113 @@ export class MedicalSourcesComponent implements OnInit { this.status[sourceType] = "authorize" this.lighthouseApi.getLighthouseSource(sourceType) - .subscribe(async (connectData: LighthouseSource) => { - console.log(connectData); - - const state = this.uuidV4() - - let authorizationUrl - - //only set if this is not a "confidential" source. - let codeVerifier - let codeChallenge - let codeChallengeMethod - - if(connectData.confidential){ - authorizationUrl = this.lighthouseApi.generateConfidentialSourceAuthorizeUrl(state, connectData) - } else { - // https://github.com/panva/oauth4webapi/blob/8eba19eac408bdec5c1fe8abac2710c50bfadcc3/examples/public.ts - codeVerifier = Oauth.generateRandomCodeVerifier(); - codeChallenge = await Oauth.calculatePKCECodeChallenge(codeVerifier); - codeChallengeMethod = 'S256'; - - authorizationUrl = this.lighthouseApi.generatePKCESourceAuthorizeUrl(codeVerifier, codeChallenge, codeChallengeMethod, state, connectData) - } + .subscribe(async (sourceMetadata: LighthouseSourceMetadata) => { + console.log(sourceMetadata); + let authorizationUrl = await this.lighthouseApi.generateSourceAuthorizeUrl(sourceType, sourceMetadata) console.log('authorize url:', authorizationUrl.toString()); - // open new browser window - let openedWindow = window.open(authorizationUrl.toString(), "_blank"); - - //wait for response - // TODO: throw an error if we timeout or an error occurs during authentication. - this.waitForClaimOrTimeout(openedWindow, sourceType, state).subscribe(async (claimData: AuthorizeClaim) => { - console.log("claim response:", claimData) - this.status[sourceType] = "token" - - let payload: any - if(connectData.confidential){ - - // we should have an access_token (and optionally a refresh_token) in the claim - payload = claimData - - //patient may be returned as patient_id - payload.patient = payload.patient ? payload.patient : payload.patient_id - - } else { - payload = await this.swapOauthPKCEToken(state, codeVerifier, authorizationUrl, connectData, claimData) - } - - - - //If payload.patient is not set, make sure we extract the patient ID from the id_token or make an introspection req - if(!payload.patient && payload.id_token){ - // - console.log("NO PATIENT ID present, decoding jwt to extract patient") - //const introspectionResp = await Oauth.introspectionRequest(as, client, payload.access_token) - //console.log(introspectionResp) - payload.patient = jwtDecode(payload.id_token, new BrowserAdapter())["profile"].replace(/^(Patient\/)/,'') - } - - - - //Create FHIR Client - - const sourceCredential: Source = { - source_type: sourceType, - oauth_authorization_endpoint: connectData.oauth_authorization_endpoint, - oauth_token_endpoint: connectData.oauth_token_endpoint, - oauth_registration_endpoint: connectData.oauth_registration_endpoint, - oauth_introspection_endpoint: connectData.oauth_introspection_endpoint, - oauth_userinfo_endpoint: connectData.oauth_userinfo_endpoint, - oauth_token_endpoint_auth_methods_supported: connectData.oauth_token_endpoint_auth_methods_supported, - api_endpoint_base_url: connectData.api_endpoint_base_url, - client_id: connectData.client_id, - redirect_uri: connectData.redirect_uri, - scopes: connectData.scopes ? connectData.scopes.join(' ') : undefined, - patient_id: payload.patient, - access_token: payload.access_token, - refresh_token: payload.refresh_token, - id_token: payload.id_token, - code_challenge: codeChallenge, - code_verifier: codeVerifier, - - // @ts-ignore - in some cases the getAccessTokenExpiration is a string, which cases failures to store Source in db. - expires_at: parseInt(getAccessTokenExpiration(payload, new BrowserAdapter())), - confidential: connectData.confidential - } - - await this.fastenApi.createSource(sourceCredential).subscribe( - (respData) => { - delete this.status[sourceType] - window.location.reload(); - - console.log("source credential create response:", respData) - }, - (err) => { - delete this.status[sourceType] - window.location.reload(); - - console.log(err) - } - ) - - - }) + // redirect to lighthouse with uri's + this.lighthouseApi.redirectWithOriginAndDestination(authorizationUrl.toString(), sourceType) }); } + async callback(sourceType: string) { + + //get the source metadata again + await this.lighthouseApi.getLighthouseSource(sourceType) + .subscribe(async (sourceMetadata: LighthouseSourceMetadata) => { + + //get required parameters from the URI and local storage + const callbackUrlParts = new URL(window.location.href) + const fragmentParams = new URLSearchParams(callbackUrlParts.hash.substring(1)) + const callbackCode = callbackUrlParts.searchParams.get("code") || fragmentParams.get("code") + const callbackState = callbackUrlParts.searchParams.get("state") || fragmentParams.get("state") + const callbackError = callbackUrlParts.searchParams.get("error") || fragmentParams.get("error") + const callbackErrorDescription = callbackUrlParts.searchParams.get("error_description") || fragmentParams.get("error_description") + + //reset the url, removing the params and fragment from the current url. + const urlTree = this.router.createUrlTree(["/sources"],{ + relativeTo: this.route, + }); + this.location.replaceState(urlTree.toString()); + + const expectedState = localStorage.getItem(`${sourceType}:state`) + localStorage.removeItem(`${sourceType}:state`) + + + if(callbackError && !callbackCode){ + //TOOD: print this message in the UI + console.error("an error occurred while authenticating to this source. Please try again later", callbackErrorDescription) + return + } + + console.log("callback code:", callbackCode) + this.status[sourceType] = "token" + + let payload: any + payload = await this.lighthouseApi.swapOauthToken(sourceType, sourceMetadata,expectedState, callbackState, callbackCode) + + + //If payload.patient is not set, make sure we extract the patient ID from the id_token or make an introspection req + if(!payload.patient && payload.id_token){ + // + console.log("NO PATIENT ID present, decoding jwt to extract patient") + //const introspectionResp = await Oauth.introspectionRequest(as, client, payload.access_token) + //console.log(introspectionResp) + payload.patient = jwtDecode(payload.id_token, new BrowserAdapter())["profile"].replace(/^(Patient\/)/,'') + } + + + + //Create FHIR Client + + const sourceCredential: Source = { + source_type: sourceType, + oauth_authorization_endpoint: sourceMetadata.authorization_endpoint, + oauth_token_endpoint: sourceMetadata.token_endpoint, + oauth_registration_endpoint: "", + oauth_introspection_endpoint: sourceMetadata.introspection_endpoint, + oauth_userinfo_endpoint: sourceMetadata.userinfo_endpoint, + oauth_token_endpoint_auth_methods_supported: "", + api_endpoint_base_url: sourceMetadata.api_endpoint_base_url, + client_id: sourceMetadata.client_id, + redirect_uri: sourceMetadata.redirect_uri, + scopes: sourceMetadata.scopes_supported ? sourceMetadata.scopes_supported.join(' ') : undefined, + patient_id: payload.patient, + access_token: payload.access_token, + refresh_token: payload.refresh_token, + id_token: payload.id_token, + code_challenge: "", + code_verifier: "", + + // @ts-ignore - in some cases the getAccessTokenExpiration is a string, which cases failures to store Source in db. + expires_at: parseInt(getAccessTokenExpiration(payload, new BrowserAdapter())), + confidential: sourceMetadata.confidential + } + + await this.fastenApi.createSource(sourceCredential).subscribe( + (respData) => { + delete this.status[sourceType] + // window.location.reload(); + + console.log("source credential create response:", respData) + //remove item from available sources list, add to connected sources. + this.availableSourceList.splice(this.availableSourceList.indexOf(this.metadataSources[sourceType]), 1); + this.connectedSourceList.push({source: respData, metadata: this.metadataSources[sourceType]}) + + }, + (err) => { + delete this.status[sourceType] + // window.location.reload(); + + console.log(err) + } + ) + }) + } + + uploadSourceBundle(event) { this.uploadedFile = [event.addedFiles[0]] this.fastenApi.createManualSource(event.addedFiles[0]).subscribe( @@ -229,80 +241,4 @@ export class MedicalSourcesComponent implements OnInit { } } - private waitForClaimOrTimeout(openedWindow: Window, sourceType: string, state: string): Observable { - console.log(`waiting for postMessage notification from ${sourceType} window`) - - //new code to listen to post message - return fromEvent(window, 'message') - .pipe( - //throw an error if we wait more than 2 minutes (this will close the window) - timeout(sourceConnectWindowTimeout), - //make sure we're only listening to events from the "opened" window. - filter((event: MessageEvent) => event.source == openedWindow), - //after filtering, we should only have one event to handle. - first(), - map((event) => { - console.log(`received postMessage notification from ${sourceType} window & sending acknowledgment`) - // @ts-ignore - event.source.postMessage(JSON.stringify({close:true}), event.origin); - }), - concatMap(() => { - console.log("requesting authorized claim") - return this.lighthouseApi.getSourceAuthorizeClaim(sourceType, state) - }), - catchError((err) => { - console.warn(`timed out waiting for notification from ${sourceType} (${sourceConnectWindowTimeout/1000}s), closing window`) - openedWindow.self.close() - return throwError(err) - }) - ) - } - - private uuidV4(){ - // @ts-ignore - return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => - (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) - ); - } - - private async swapOauthPKCEToken(state: string, codeVerifier: any, authorizationUrl: URL, connectData: LighthouseSource, claimData: AuthorizeClaim){ - // @ts-expect-error - const client: oauth.Client = { - client_id: connectData.client_id, - token_endpoint_auth_method: 'none', - } - - //check if the oauth_token_endpoint_auth_methods_supported field is set - if(connectData.oauth_token_endpoint_auth_methods_supported){ - let auth_methods = connectData.oauth_token_endpoint_auth_methods_supported.split(",") - client.token_endpoint_auth_method = auth_methods[0] - } - - const as = { - issuer: `${authorizationUrl.protocol}//${authorizationUrl.host}`, - authorization_endpoint: connectData.oauth_authorization_endpoint, - token_endpoint: connectData.oauth_token_endpoint, - introspection_endpoint: connectData.oauth_introspection_endpoint, - } - - console.log("STARTING--- Oauth.validateAuthResponse") - const params = Oauth.validateAuthResponse(as, client, new URLSearchParams(claimData as any), state) - if (Oauth.isOAuth2Error(params)) { - console.log('error', params) - throw new Error() // Handle OAuth 2.0 redirect error - } - console.log("ENDING--- Oauth.validateAuthResponse") - console.log("STARTING--- Oauth.authorizationCodeGrantRequest") - const response = await Oauth.authorizationCodeGrantRequest( - as, - client, - params, - connectData.redirect_uri, - codeVerifier, - ) - let payload = await response.json() - console.log("ENDING--- Oauth.authorizationCodeGrantRequest", payload) - return payload - } - } diff --git a/frontend/src/app/services/fasten-api.service.ts b/frontend/src/app/services/fasten-api.service.ts index 33a312d6..afc4b55d 100644 --- a/frontend/src/app/services/fasten-api.service.ts +++ b/frontend/src/app/services/fasten-api.service.ts @@ -2,8 +2,6 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import {Observable} from 'rxjs'; import { Router } from '@angular/router'; -import {LighthouseSource} from '../models/lighthouse/lighthouse-source'; -import {environment} from '../../environments/environment'; import {map} from 'rxjs/operators'; import {ResponseWrapper} from '../models/response-wrapper'; import {Source} from '../models/fasten/source'; diff --git a/frontend/src/app/services/lighthouse.service.ts b/frontend/src/app/services/lighthouse.service.ts index f5548b31..0ebe3f50 100644 --- a/frontend/src/app/services/lighthouse.service.ts +++ b/frontend/src/app/services/lighthouse.service.ts @@ -4,8 +4,8 @@ import {Observable} from 'rxjs'; import {environment} from '../../environments/environment'; import {map, tap} from 'rxjs/operators'; import {ResponseWrapper} from '../models/response-wrapper'; -import {LighthouseSource} from '../models/lighthouse/lighthouse-source'; -import {AuthorizeClaim} from '../models/lighthouse/authorize-claim'; +import {LighthouseSourceMetadata} from '../models/lighthouse/lighthouse-source-metadata'; +import * as Oauth from '@panva/oauth4webapi'; @Injectable({ providedIn: 'root' @@ -15,57 +15,140 @@ export class LighthouseService { constructor(private _httpClient: HttpClient) { } - getLighthouseSource(sourceType: string): Observable { + getLighthouseSource(sourceType: string): Observable { return this._httpClient.get(`${environment.lighthouse_api_endpoint_base}/connect/${sourceType}`) .pipe( map((response: ResponseWrapper) => { - return response.data as LighthouseSource + return response.data as LighthouseSourceMetadata }) ); } - generatePKCESourceAuthorizeUrl(codeVerifier: string, codeChallenge: string, codeChallengeMethod: string, state: string, lighthouseSource: LighthouseSource): URL { + + async generateSourceAuthorizeUrl(sourceType: string, lighthouseSource: LighthouseSourceMetadata): Promise { + const state = this.uuidV4() + localStorage.setItem(`${sourceType}:state`, state) + // generate the authorization url - const authorizationUrl = new URL(lighthouseSource.oauth_authorization_endpoint); - authorizationUrl.searchParams.set('client_id', lighthouseSource.client_id); - authorizationUrl.searchParams.set('code_challenge', codeChallenge); - authorizationUrl.searchParams.set('code_challenge_method', codeChallengeMethod); + const authorizationUrl = new URL(lighthouseSource.authorization_endpoint); authorizationUrl.searchParams.set('redirect_uri', lighthouseSource.redirect_uri); - authorizationUrl.searchParams.set('response_type', 'code'); + authorizationUrl.searchParams.set('response_type', lighthouseSource.response_types_supported[0]); + authorizationUrl.searchParams.set('response_mode', 'fragment'); authorizationUrl.searchParams.set('state', state); - if(lighthouseSource.scopes && lighthouseSource.scopes.length){ - authorizationUrl.searchParams.set('scope', lighthouseSource.scopes.join(' ')); + authorizationUrl.searchParams.set('client_id', lighthouseSource.client_id); + if(lighthouseSource.scopes_supported && lighthouseSource.scopes_supported.length){ + authorizationUrl.searchParams.set('scope', lighthouseSource.scopes_supported.join(' ')); } if (lighthouseSource.aud) { authorizationUrl.searchParams.set('aud', lighthouseSource.aud); } + + //this is for providers that support CORS and PKCE (public client auth) + if(!lighthouseSource.confidential){ + // https://github.com/panva/oauth4webapi/blob/8eba19eac408bdec5c1fe8abac2710c50bfadcc3/examples/public.ts + const codeVerifier = Oauth.generateRandomCodeVerifier(); + const codeChallenge = await Oauth.calculatePKCECodeChallenge(codeVerifier); + const codeChallengeMethod = lighthouseSource.code_challenge_methods_supported[0]; // 'S256' + + localStorage.setItem(`${sourceType}:code_verifier`, codeVerifier) + localStorage.setItem(`${sourceType}:code_challenge`, codeChallenge) + localStorage.setItem(`${sourceType}:code_challenge_method`, codeChallengeMethod) + + authorizationUrl.searchParams.set('code_challenge', codeChallenge); + authorizationUrl.searchParams.set('code_challenge_method', codeChallengeMethod); + } + return authorizationUrl } - generateConfidentialSourceAuthorizeUrl(state: string, lighthouseSource: LighthouseSource): URL { - // generate the authorization url - const authorizationUrl = new URL(lighthouseSource.oauth_authorization_endpoint); - authorizationUrl.searchParams.set('client_id', lighthouseSource.client_id); - authorizationUrl.searchParams.set('redirect_uri', lighthouseSource.redirect_uri); - authorizationUrl.searchParams.set('response_type', 'code'); - authorizationUrl.searchParams.set('state', state); - if(lighthouseSource.scopes && lighthouseSource.scopes.length){ - authorizationUrl.searchParams.set('scope', lighthouseSource.scopes.join(' ')); + redirectWithOriginAndDestination(destUrl: string, sourceType: string){ + const originUrlParts = new URL(window.location.href) + originUrlParts.hash = "" //reset hash in-case its present. + originUrlParts.pathname = this.pathJoin([originUrlParts.pathname, `callback/${sourceType}`]) + + + const redirectUrlParts = new URL(`${environment.lighthouse_api_endpoint_base}/redirect/${sourceType}`); + const redirectParams = new URLSearchParams() + redirectParams.set("origin_url", originUrlParts.toString()) + redirectParams.set("dest_url", destUrl) + redirectUrlParts.search = redirectParams.toString() + console.log(redirectUrlParts.toString()); + + // Simulate a mouse click: + window.location.href = redirectUrlParts.toString(); + } + + async swapOauthToken(sourceType: string, sourceMetadata: LighthouseSourceMetadata, expectedState: string, state: string, code: string){ + // @ts-expect-error + const client: oauth.Client = { + client_id: sourceMetadata.client_id } - if (lighthouseSource.aud) { - authorizationUrl.searchParams.set('aud', lighthouseSource.aud); + //this is for providers that support CORS & PKCE (public client auth) + let codeVerifier = undefined + if(!sourceMetadata.confidential){ + client.token_endpoint_auth_method = 'none' + codeVerifier = localStorage.getItem(`${sourceType}:code_verifier`) + + localStorage.removeItem(`${sourceType}:code_verifier`) + localStorage.removeItem(`${sourceType}:code_challenge`) + localStorage.removeItem(`${sourceType}:code_challenge_method`) + } else { + console.log("This is a confidential client, using lighthouse token endpoint.") + //if this is a confidential client, we need to "override" token endpoint, and use the Fasten Lighthouse to complete the swap + sourceMetadata.token_endpoint = sourceMetadata.redirect_uri.replace("callback", "token") + //use a placeholder client_secret (the actual secret is stored in Lighthouse) + client.client_secret = "placeholder" + client.token_endpoint_auth_method = "client_secret_basic" + codeVerifier = "placeholder" } - return authorizationUrl + + const as = { + issuer: sourceMetadata.issuer, + authorization_endpoint: sourceMetadata.authorization_endpoint, + token_endpoint: sourceMetadata.token_endpoint, + introspection_endpoint: sourceMetadata.introspection_endpoint, + } + + console.log("STARTING--- Oauth.validateAuthResponse") + const params = Oauth.validateAuthResponse(as, client, new URLSearchParams({"code": code, "state": state}), expectedState) + if (Oauth.isOAuth2Error(params)) { + console.log('error', params) + throw new Error() // Handle OAuth 2.0 redirect error + } + console.log("ENDING--- Oauth.validateAuthResponse") + console.log("STARTING--- Oauth.authorizationCodeGrantRequest") + const response = await Oauth.authorizationCodeGrantRequest( + as, + client, + params, + sourceMetadata.redirect_uri, + codeVerifier, + ) + let payload = await response.json() + console.log("ENDING--- Oauth.authorizationCodeGrantRequest", payload) + return payload } - getSourceAuthorizeClaim(sourceType: string, state: string): Observable { - return this._httpClient.get(`${environment.lighthouse_api_endpoint_base}/claim/${sourceType}`, {params: {"state": state}}) - .pipe( - map((response: ResponseWrapper) => { - return response.data as AuthorizeClaim - }) - ); + + private pathJoin(parts: string[], sep?: string): string{ + const separator = sep || '/'; + parts = parts.map((part, index)=>{ + if (index) { + part = part.replace(new RegExp('^' + separator), ''); + } + if (index !== parts.length - 1) { + part = part.replace(new RegExp(separator + '$'), ''); + } + return part; + }) + return parts.join(separator); } + private uuidV4(){ + // @ts-ignore + return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => + (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) + ); + } }