working on Provider ui.
This commit is contained in:
parent
adcc0e322b
commit
6413de2977
|
@ -1,25 +1 @@
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * Delete the template below * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * to get started with your project! * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><button>aetna</button></li>
|
|
||||||
<li><button (click)="connect('cigna')">cigna</button></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|
|
@ -1,17 +1,4 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import * as Oauth from '@panva/oauth4webapi';
|
|
||||||
import { concatMap, delay, retryWhen } from 'rxjs/operators';
|
|
||||||
import { Observable, of, throwError } from 'rxjs';
|
|
||||||
import * as FHIR from "fhirclient"
|
|
||||||
import {getAccessTokenExpiration} from 'fhirclient/lib/lib';
|
|
||||||
import BrowserAdapter from 'fhirclient/lib/adapters/BrowserAdapter';
|
|
||||||
import {PassportService} from './services/passport.service';
|
|
||||||
import {ProviderConfig} from './models/passport/provider-config';
|
|
||||||
import {AuthorizeClaim} from './models/passport/authorize-claim';
|
|
||||||
import {FastenApiService} from './services/fasten-api.service';
|
|
||||||
import {ProviderCredential} from './models/fasten/provider-credential';
|
|
||||||
export const retryCount = 24; //wait 2 minutes (5 * 24 = 120)
|
|
||||||
export const retryWaitMilliSeconds = 5000; //wait 5 seconds
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
|
@ -20,148 +7,6 @@ export const retryWaitMilliSeconds = 5000; //wait 5 seconds
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'fastenhealth';
|
title = 'fastenhealth';
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private passportApi: PassportService,
|
|
||||||
private fastenApi: FastenApiService,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
connect(provider: string) {
|
|
||||||
this.passportApi.getProviderConfig(provider)
|
|
||||||
.subscribe(async (connectData: ProviderConfig) => {
|
|
||||||
console.log(connectData);
|
|
||||||
|
|
||||||
// https://github.com/panva/oauth4webapi/blob/8eba19eac408bdec5c1fe8abac2710c50bfadcc3/examples/public.ts
|
|
||||||
const codeVerifier = Oauth.generateRandomCodeVerifier();
|
|
||||||
const codeChallenge = await Oauth.calculatePKCECodeChallenge(codeVerifier);
|
|
||||||
const codeChallengeMethod = 'S256';
|
|
||||||
const state = this.uuidV4()
|
|
||||||
|
|
||||||
const authorizationUrl = this.passportApi.generatePKCEProviderAuthorizeUrl(codeVerifier, codeChallenge, codeChallengeMethod, state, connectData)
|
|
||||||
|
|
||||||
console.log('authorize url:', authorizationUrl.toString());
|
|
||||||
// open new browser window
|
|
||||||
window.open(authorizationUrl.toString(), "_blank");
|
|
||||||
|
|
||||||
//wait for response
|
|
||||||
this.waitForClaimOrTimeout(provider, state).subscribe(async (claimData: AuthorizeClaim) => {
|
|
||||||
console.log("claim response:", claimData)
|
|
||||||
|
|
||||||
|
|
||||||
//swap code for token
|
|
||||||
let sub: string
|
|
||||||
let access_token: string
|
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
const client: oauth.Client = {
|
|
||||||
client_id: connectData.client_id,
|
|
||||||
token_endpoint_auth_method: 'none',
|
|
||||||
}
|
|
||||||
|
|
||||||
const as = {
|
|
||||||
issuer: `${authorizationUrl.protocol}//${authorizationUrl.host}`,
|
|
||||||
authorization_endpoint: `${connectData.oauth_endpoint_base_url}/authorize`,
|
|
||||||
token_endpoint: `${connectData.oauth_endpoint_base_url}/token`,
|
|
||||||
introspect_endpoint: `${connectData.oauth_endpoint_base_url}/introspect`,
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
const payload = await response.json()
|
|
||||||
console.log("ENDING--- Oauth.authorizationCodeGrantRequest", payload)
|
|
||||||
|
|
||||||
|
|
||||||
//Create FHIR Client
|
|
||||||
const providerCredential: ProviderCredential = {
|
|
||||||
oauth_endpoint_base_url: connectData.oauth_endpoint_base_url,
|
|
||||||
api_endpoint_base_url: connectData.api_endpoint_base_url,
|
|
||||||
client_id: connectData.client_id,
|
|
||||||
redirect_uri: connectData.redirect_uri,
|
|
||||||
scopes: connectData.scopes.join(' '),
|
|
||||||
patient: payload.patient,
|
|
||||||
access_token: payload.access_token,
|
|
||||||
refresh_token: payload.refresh_token,
|
|
||||||
id_token: payload.id_token,
|
|
||||||
expires_at: getAccessTokenExpiration(payload, new BrowserAdapter()),
|
|
||||||
code_challenge: codeChallenge,
|
|
||||||
code_verifier: codeVerifier,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fastenApi.createProviderCredential(providerCredential).subscribe( (respData) => {
|
|
||||||
console.log("provider credential create response:", respData)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// console.log("STARTING--- FHIR.client(clientState)", clientState)
|
|
||||||
// const fhirClient = FHIR.client(clientState);
|
|
||||||
//
|
|
||||||
// console.log("STARTING--- client.request(Patient)")
|
|
||||||
// const patientResponse = await fhirClient.request("PatientAccess/v1/$userinfo")
|
|
||||||
// console.log(patientResponse)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// // fetch userinfo response
|
|
||||||
//
|
|
||||||
// const response = await oauth.userInfoRequest(as, client, access_token)
|
|
||||||
//
|
|
||||||
// let challenges: oauth.WWWAuthenticateChallenge[] | undefined
|
|
||||||
// if ((challenges = oauth.parseWwwAuthenticateChallenges(response))) {
|
|
||||||
// for (const challenge of challenges) {
|
|
||||||
// console.log('challenge', challenge)
|
|
||||||
// }
|
|
||||||
// throw new Error() // Handle www-authenticate challenges as needed
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// const result = await oauth.processUserInfoResponse(as, client, sub, response)
|
|
||||||
// console.log('result', result)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
waitForClaimOrTimeout(providerId: string, state: string): Observable<any> {
|
|
||||||
return this.passportApi.getProviderAuthorizeClaim(providerId, state).pipe(
|
|
||||||
retryWhen(error =>
|
|
||||||
error.pipe(
|
|
||||||
concatMap((error, count) => {
|
|
||||||
if (count <= retryCount && error.status == 500) {
|
|
||||||
return of(error);
|
|
||||||
}
|
|
||||||
return throwError(error);
|
|
||||||
}),
|
|
||||||
delay(retryWaitMilliSeconds)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { NgModule } from '@angular/core';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
import { DashboardComponent } from './pages/dashboard/dashboard.component';
|
|
||||||
import { AdminLayoutComponent } from "./layouts/admin-layout/admin-layout.component";
|
import { AdminLayoutComponent } from "./layouts/admin-layout/admin-layout.component";
|
||||||
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
||||||
import { ComponentsModule } from "./components/components.module";
|
import { ComponentsModule } from "./components/components.module";
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { Component, OnInit } from "@angular/core";
|
||||||
declare interface RouteInfo {
|
declare interface RouteInfo {
|
||||||
path: string;
|
path: string;
|
||||||
title: string;
|
title: string;
|
||||||
rtlTitle: string;
|
|
||||||
icon: string;
|
icon: string;
|
||||||
class: string;
|
class: string;
|
||||||
}
|
}
|
||||||
|
@ -11,58 +10,32 @@ export const ROUTES: RouteInfo[] = [
|
||||||
{
|
{
|
||||||
path: "/dashboard",
|
path: "/dashboard",
|
||||||
title: "Dashboard",
|
title: "Dashboard",
|
||||||
rtlTitle: "لوحة القيادة",
|
|
||||||
icon: "icon-chart-pie-36",
|
icon: "icon-chart-pie-36",
|
||||||
class: ""
|
class: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/icons",
|
path: "/providers",
|
||||||
title: "Icons",
|
title: "Medical Providers",
|
||||||
rtlTitle: "الرموز",
|
icon: "icon-cloud-download-93",
|
||||||
icon: "icon-atom",
|
|
||||||
class: ""
|
class: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/maps",
|
path: "/patient",
|
||||||
title: "Maps",
|
title: "Patient Profile",
|
||||||
rtlTitle: "خرائط",
|
|
||||||
icon: "icon-pin",
|
|
||||||
class: "" },
|
|
||||||
{
|
|
||||||
path: "/notifications",
|
|
||||||
title: "Notifications",
|
|
||||||
rtlTitle: "إخطارات",
|
|
||||||
icon: "icon-bell-55",
|
|
||||||
class: ""
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
path: "/user",
|
|
||||||
title: "User Profile",
|
|
||||||
rtlTitle: "ملف تعريفي للمستخدم",
|
|
||||||
icon: "icon-single-02",
|
icon: "icon-single-02",
|
||||||
class: ""
|
class: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/tables",
|
path: "/tables",
|
||||||
title: "Table List",
|
title: "Table List",
|
||||||
rtlTitle: "قائمة الجدول",
|
|
||||||
icon: "icon-puzzle-10",
|
icon: "icon-puzzle-10",
|
||||||
class: ""
|
class: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/typography",
|
path: "/typography",
|
||||||
title: "Typography",
|
title: "Typography",
|
||||||
rtlTitle: "طباعة",
|
|
||||||
icon: "icon-align-center",
|
icon: "icon-align-center",
|
||||||
class: ""
|
class: ""
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/rtl",
|
|
||||||
title: "RTL Support",
|
|
||||||
rtlTitle: "ار تي ال",
|
|
||||||
icon: "icon-world",
|
|
||||||
class: ""
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,9 @@ import { FormsModule } from "@angular/forms";
|
||||||
|
|
||||||
import { AdminLayoutRoutes } from "./admin-layout.routing";
|
import { AdminLayoutRoutes } from "./admin-layout.routing";
|
||||||
import { DashboardComponent } from "../../pages/dashboard/dashboard.component";
|
import { DashboardComponent } from "../../pages/dashboard/dashboard.component";
|
||||||
import { UserComponent } from "../../pages/user/user.component";
|
import { PatientComponent } from "../../pages/patient/patient.component";
|
||||||
// import { RtlComponent } from "../../pages/rtl/rtl.component";
|
import { MedicalProvidersComponent } from "../../pages/medical-providers/medical-providers.component";
|
||||||
|
|
||||||
|
|
||||||
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
||||||
|
|
||||||
|
@ -21,7 +22,8 @@ import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
DashboardComponent,
|
DashboardComponent,
|
||||||
UserComponent,
|
PatientComponent,
|
||||||
|
MedicalProvidersComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AdminLayoutModule {}
|
export class AdminLayoutModule {}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { Routes } from "@angular/router";
|
import { Routes } from "@angular/router";
|
||||||
|
|
||||||
import { DashboardComponent } from "../../pages/dashboard/dashboard.component";
|
import { DashboardComponent } from "../../pages/dashboard/dashboard.component";
|
||||||
import { UserComponent } from "../../pages/user/user.component";
|
import { PatientComponent } from "../../pages/patient/patient.component";
|
||||||
// import { RtlComponent } from "../../pages/rtl/rtl.component";
|
import {MedicalProvidersComponent} from '../../pages/medical-providers/medical-providers.component';
|
||||||
|
|
||||||
export const AdminLayoutRoutes: Routes = [
|
export const AdminLayoutRoutes: Routes = [
|
||||||
{ path: "dashboard", component: DashboardComponent },
|
{ path: "dashboard", component: DashboardComponent },
|
||||||
{ path: "user", component: UserComponent },
|
{ path: "patient", component: PatientComponent },
|
||||||
|
{ path: "providers", component: MedicalProvidersComponent },
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
<div class=" content">
|
||||||
|
<div class=" row">
|
||||||
|
<div class=" col-md-12">
|
||||||
|
<div class=" card">
|
||||||
|
<div class=" card-header">
|
||||||
|
<h5 class=" title">Medical Providers</h5>
|
||||||
|
<p class=" category">
|
||||||
|
The following medical providers have API's which Fasten can use to retrieve your medical history.
|
||||||
|
Please click the logos below to initiate the connection.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class=" card-body all-icons">
|
||||||
|
<div class=" row">
|
||||||
|
|
||||||
|
<div (click)="connect('aetna')" class=" font-icon-list col-lg-2 col-md-3 col-sm-4 col-xs-6 col-xs-6">
|
||||||
|
<div class=" font-icon-detail">
|
||||||
|
<img class=" tim-icons icon-alert-circle-exc" src="assets/img/provider_logos/aetna.png">
|
||||||
|
<p>Aetna</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div (click)="connect('anthem')" class=" font-icon-list col-lg-2 col-md-3 col-sm-4 col-xs-6 col-xs-6">
|
||||||
|
<div class=" font-icon-detail">
|
||||||
|
<img class=" tim-icons icon-alert-circle-exc" src="assets/img/provider_logos/anthem.png">
|
||||||
|
<p>Anthem</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div (click)="connect('cigna')" class=" font-icon-list col-lg-2 col-md-3 col-sm-4 col-xs-6 col-xs-6">
|
||||||
|
<div class=" font-icon-detail">
|
||||||
|
<img class=" tim-icons icon-alert-circle-exc" src="assets/img/provider_logos/cigna.jpg">
|
||||||
|
<p>Cigna</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div (click)="connect('humana')" class=" font-icon-list col-lg-2 col-md-3 col-sm-4 col-xs-6 col-xs-6">
|
||||||
|
<div class=" font-icon-detail">
|
||||||
|
<img class=" tim-icons icon-alert-circle-exc" src="assets/img/provider_logos/humana.webp">
|
||||||
|
<p>Humana</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div (click)="connect('kaiser')" class=" font-icon-list col-lg-2 col-md-3 col-sm-4 col-xs-6 col-xs-6">
|
||||||
|
<div class=" font-icon-detail">
|
||||||
|
<img class=" tim-icons icon-alert-circle-exc" src="assets/img/provider_logos/kaiser.jpg">
|
||||||
|
<p>Kaiser</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div (click)="connect('united_healthcare')" class=" font-icon-list col-lg-2 col-md-3 col-sm-4 col-xs-6 col-xs-6">
|
||||||
|
<div class=" font-icon-detail">
|
||||||
|
<img class=" tim-icons icon-alert-circle-exc" src="assets/img/provider_logos/unitedhealthcare.jpg">
|
||||||
|
<p>United Healthcare</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MedicalProvidersComponent } from './medical-providers.component';
|
||||||
|
|
||||||
|
describe('MedicalProvidersComponent', () => {
|
||||||
|
let component: MedicalProvidersComponent;
|
||||||
|
let fixture: ComponentFixture<MedicalProvidersComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ MedicalProvidersComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(MedicalProvidersComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,167 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import {PassportService} from '../../services/passport.service';
|
||||||
|
import {FastenApiService} from '../../services/fasten-api.service';
|
||||||
|
import {ProviderConfig} from '../../models/passport/provider-config';
|
||||||
|
import * as Oauth from '@panva/oauth4webapi';
|
||||||
|
import {AuthorizeClaim} from '../../models/passport/authorize-claim';
|
||||||
|
import {ProviderCredential} from '../../models/fasten/provider-credential';
|
||||||
|
import {getAccessTokenExpiration} from 'fhirclient/lib/lib';
|
||||||
|
import BrowserAdapter from 'fhirclient/lib/adapters/BrowserAdapter';
|
||||||
|
import {Observable, of, throwError} from 'rxjs';
|
||||||
|
import {concatMap, delay, retryWhen} from 'rxjs/operators';
|
||||||
|
import * as FHIR from "fhirclient"
|
||||||
|
|
||||||
|
export const retryCount = 24; //wait 2 minutes (5 * 24 = 120)
|
||||||
|
export const retryWaitMilliSeconds = 5000; //wait 5 seconds
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-medical-providers',
|
||||||
|
templateUrl: './medical-providers.component.html',
|
||||||
|
styleUrls: ['./medical-providers.component.scss']
|
||||||
|
})
|
||||||
|
export class MedicalProvidersComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private passportApi: PassportService,
|
||||||
|
private fastenApi: FastenApiService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(provider: string) {
|
||||||
|
this.passportApi.getProviderConfig(provider)
|
||||||
|
.subscribe(async (connectData: ProviderConfig) => {
|
||||||
|
console.log(connectData);
|
||||||
|
|
||||||
|
// https://github.com/panva/oauth4webapi/blob/8eba19eac408bdec5c1fe8abac2710c50bfadcc3/examples/public.ts
|
||||||
|
const codeVerifier = Oauth.generateRandomCodeVerifier();
|
||||||
|
const codeChallenge = await Oauth.calculatePKCECodeChallenge(codeVerifier);
|
||||||
|
const codeChallengeMethod = 'S256';
|
||||||
|
const state = this.uuidV4()
|
||||||
|
|
||||||
|
const authorizationUrl = this.passportApi.generatePKCEProviderAuthorizeUrl(codeVerifier, codeChallenge, codeChallengeMethod, state, connectData)
|
||||||
|
|
||||||
|
console.log('authorize url:', authorizationUrl.toString());
|
||||||
|
// open new browser window
|
||||||
|
window.open(authorizationUrl.toString(), "_blank");
|
||||||
|
|
||||||
|
//wait for response
|
||||||
|
this.waitForClaimOrTimeout(provider, state).subscribe(async (claimData: AuthorizeClaim) => {
|
||||||
|
console.log("claim response:", claimData)
|
||||||
|
|
||||||
|
|
||||||
|
//swap code for token
|
||||||
|
let sub: string
|
||||||
|
let access_token: string
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
const client: oauth.Client = {
|
||||||
|
client_id: connectData.client_id,
|
||||||
|
token_endpoint_auth_method: 'none',
|
||||||
|
}
|
||||||
|
|
||||||
|
const as = {
|
||||||
|
issuer: `${authorizationUrl.protocol}//${authorizationUrl.host}`,
|
||||||
|
authorization_endpoint: `${connectData.oauth_endpoint_base_url}/authorize`,
|
||||||
|
token_endpoint: `${connectData.oauth_endpoint_base_url}/token`,
|
||||||
|
introspect_endpoint: `${connectData.oauth_endpoint_base_url}/introspect`,
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
const payload = await response.json()
|
||||||
|
console.log("ENDING--- Oauth.authorizationCodeGrantRequest", payload)
|
||||||
|
|
||||||
|
|
||||||
|
//Create FHIR Client
|
||||||
|
const providerCredential: ProviderCredential = {
|
||||||
|
oauth_endpoint_base_url: connectData.oauth_endpoint_base_url,
|
||||||
|
api_endpoint_base_url: connectData.api_endpoint_base_url,
|
||||||
|
client_id: connectData.client_id,
|
||||||
|
redirect_uri: connectData.redirect_uri,
|
||||||
|
scopes: connectData.scopes.join(' '),
|
||||||
|
patient: payload.patient,
|
||||||
|
access_token: payload.access_token,
|
||||||
|
refresh_token: payload.refresh_token,
|
||||||
|
id_token: payload.id_token,
|
||||||
|
expires_at: getAccessTokenExpiration(payload, new BrowserAdapter()),
|
||||||
|
code_challenge: codeChallenge,
|
||||||
|
code_verifier: codeVerifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fastenApi.createProviderCredential(providerCredential).subscribe( (respData) => {
|
||||||
|
console.log("provider credential create response:", respData)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// console.log("STARTING--- FHIR.client(clientState)", clientState)
|
||||||
|
// const fhirClient = FHIR.client(clientState);
|
||||||
|
//
|
||||||
|
// console.log("STARTING--- client.request(Patient)")
|
||||||
|
// const patientResponse = await fhirClient.request("PatientAccess/v1/$userinfo")
|
||||||
|
// console.log(patientResponse)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// // fetch userinfo response
|
||||||
|
//
|
||||||
|
// const response = await oauth.userInfoRequest(as, client, access_token)
|
||||||
|
//
|
||||||
|
// let challenges: oauth.WWWAuthenticateChallenge[] | undefined
|
||||||
|
// if ((challenges = oauth.parseWwwAuthenticateChallenges(response))) {
|
||||||
|
// for (const challenge of challenges) {
|
||||||
|
// console.log('challenge', challenge)
|
||||||
|
// }
|
||||||
|
// throw new Error() // Handle www-authenticate challenges as needed
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// const result = await oauth.processUserInfoResponse(as, client, sub, response)
|
||||||
|
// console.log('result', result)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
waitForClaimOrTimeout(providerId: string, state: string): Observable<any> {
|
||||||
|
return this.passportApi.getProviderAuthorizeClaim(providerId, state).pipe(
|
||||||
|
retryWhen(error =>
|
||||||
|
error.pipe(
|
||||||
|
concatMap((error, count) => {
|
||||||
|
if (count <= retryCount && error.status == 500) {
|
||||||
|
return of(error);
|
||||||
|
}
|
||||||
|
return throwError(error);
|
||||||
|
}),
|
||||||
|
delay(retryWaitMilliSeconds)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-patient",
|
||||||
|
templateUrl: "patient.component.html"
|
||||||
|
})
|
||||||
|
export class PatientComponent implements OnInit {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
ngOnInit() {}
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
import { Component, OnInit } from "@angular/core";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "app-user",
|
|
||||||
templateUrl: "user.component.html"
|
|
||||||
})
|
|
||||||
export class UserComponent implements OnInit {
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
ngOnInit() {}
|
|
||||||
}
|
|
Loading…
Reference in New Issue