diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8d6c680f8..5895a0e9a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,17 +3,106 @@ > In the meantime, please consider looking at the extensive docs in the [Fasten Docs Repository](https://github.com/fastenhealth/docs/tree/main/technical) -The Fasten Sources is a library that defines medical provider metadata (`definitions` - OpenID Metadata documents) -and http clients (OAuth2/Smart-on-FHIR clients) which can be used to retrieve data from various Medical -Providers (`clients`). +The Fasten Sources is a library that defines healthcare institution API metadata (`definitions` - OpenID Metadata documents) +and http clients (OAuth2/Smart-on-FHIR clients) which can be used to retrieve data from various healthcare institutions (`clients`). -Development in this repositry is complicated for a couple of reasons: +> In general Healthcare Institutions don't develop their own API's, instead they run a supported EHR Platform (Epic, Cerner, etc) with a custom domain. +> This allows Fasten to write a single client for each Platform, and then use that client for all institutions running that Platform. + +Development in this repository is complicated for a couple of reasons: - writing a new OAuth client requires a client application registration & `client-id` (and sometimes a `client-secret`). + - this registration can differ significantly between EHR systems. Some EHR's make the registration process easy, while others are incredibly convoluted. + - as a developer intending to contribute your medical provider's OAuth2 client, you should document the registration process, and include it in your PR. - most files in this repository are generated classes - created via automation in `fasten-sources-gen` + - This library can take a long time to build (the first time), as there are thousand's of generated go files. + ```bash + go clean -cache + time go build ./... + # 1079.77s user 9.87s system 205% cpu 8:50.45 total + ``` However, it is still possible to develop & test OAuth2 clients. +# Getting Started + +Before developing in this repository, you will need the following tools installed: + +- go 1.18+ +- make +- git + +Next, you can run the following commands: + +```bash +# clone the repository +git clone https://github.com/fastenhealth/fasten-sources.git` + +# change into the directory +cd fasten-sources + +# start the "backend" server +make serve-backend +``` + +`make serve-backend` should automatically start a webserver and open a browser to [http://localhost:9999](http://localhost:9999) + +# Testing Connections + +Once the backend server is running, you can test connections to various EHR systems by selecting the "Sandbox" or "Production" radio button, and selecting the EHR you'd like to test. + +> When using "Sandbox", you will need to login using test account credentials. See [BETA.md](https://github.com/fastenhealth/docs/blob/main/BETA.md#sandbox-flavor) for credentials. +> +> When using "Production", you will need to login using your own personal credentials. + +![](./screenshots/connect.png) + +After completing the OAuth flow with your selected EHR, you will be redirected to the Callback URL, which will display the Authorization Code, Access Token Exchange Response and Patient ID. + +![](./screenshots/callback.png) + + +# Provider Development + +When developing a new provider, the first step is to find the developer documentation for the provider. +Usually this is done by searching ` developer documentation` or ` FHIR API` in your favorite search engine. + +However, Fasten may have already done this for you, as we keep track of EHR integration status in the [PLATFORM_LIST.md](./PLATFORM_LIST.md) file. + +## Registering a Client Application + +Once you've found the developer documentation for your EHR, you should follow their instructions for registering a client application. + +> During development, use your personal email & information for registering the client application. Once you've finished testing & development, +> a Fasten maintainer will create another client application registration using the Fasten email & information, and then request production access. + +- **Scopes** - if you are required to request scopes during application registration, try to request the following `openid fhirUser profile patient/*.read offline_access` +- **Confidential** - if given the option to register a public vs confidential client, choose **public** +- **PKCE** - if given the option to register a client with or without PKCE, choose with **PKCE** +- **Offline Access** - if given the option to register a client with or without offline access, choose with **offline access** + +## Testing new Client Application + +Once you've registered a client application, you should be able to test the OAuth2 flow using the backend server. + +- Open [http://localhost:9999](http://localhost:9999) in your browser +- Scroll down to the "Define Custom Source" section and click it + +![](./screenshots/connect-custom.png) + +Use the information you received during client application registration to fill out the form. + +- **Source Type** - this should be a url-friendly short name for your EHR. For example, if you're developing a client for Kaiser Permanente, you might use `kaiser` or `kaiser_permanente` +- **Authorization Endpoint** - this is the OAuth2 Authorization URL for your EHR. It should be listed in the developer documentation. +- **Response Mode** - this is the OAuth2 Response Mode for your EHR. When supported use "fragment", otherwise use "query" (which should be supported by all EHR's) +- **Client ID** - this is the OAuth2 Client ID for newly registered EHR application. +- **Scopes** - these are the OAuth2 Scopes for your EHR. They should be listed in the developer documentation. + - If you're unsure, use `openid fhirUser profile patient/*.read offline_access` +- **Confidential** - this should be `true` if your EHR requires a `client-secret` for the OAuth2 flow. For Public apps, this should be `false` + - If your app is Confidential, you will need to contact a Fasten maintainer to add the `client-secret` to the Lighthouse server for testing. `hello@fastenhealth.com` + +# Architecture + > In general, Fasten Sources follow an inheritance model. Base and FHIR version specific classes/structs > define most complicated logic, while Platform logic is mostly related to required Headers or API overrides > @@ -28,11 +117,11 @@ classDiagram SourceClientFHIR401 <|-- SourceClientCerner SourceClientFHIR401 <|-- SourceClientEpic - note for SourceClientEpic "platform specific logic" + note for SourceClientEpic "EHR/platform specific logic" SourceClientEpic <|-- SourceClientUcsfHealth SourceClientEpic <|-- SourceClientTuftsMedicine - note for SourceClientUcsfHealth "no custom logic" + note for SourceClientUcsfHealth "institution - no custom logic" SourceClientCerner <|-- SourceClientTheDermatologyClinic SourceClientCerner <|-- SourceClientStephenVileMD @@ -61,108 +150,6 @@ classDiagram } ``` -# Setup Testing Environment - -``` -git clone https://github.com/fastenhealth/fasten-sources.git -cd fasten-sources -go mod tidy -go mod vendor -go test ./... -``` - - - -## Writing Tests or Creating Recordings for Existing Client - - -If we're testing or making changes to an existing client, we can do the following: - -1. Determine the Platform source we'd like to modify (Institution sources are sparse and contain no custom logic). -2. Open browser tool to which will allow us to generate a access-token for our source. -```bash - -make serve-backend - -# this should start a simple webserver and open a browser to http://localhost:9999 -``` - -3. Select "Sandbox" radio button -4. Select Platform you'd like to generate an Access Token for. -5. Login with Sandbox credentials for the platform - https://github.com/fastenhealth/docs/blob/main/BETA.md#sandbox-flavor -6. Continue through all prompts, until redirected back to Fasten Lighthouse and localhost. -7. Wait for OAuth handshake to complete, note **Access Token** and **Patient ID**. -8. Find the [test file](./clients/internal/platform) for the source you'd like to change. -9. Create a new test function. **Make sure to specify Patient ID and Access Token you noted earlier** - -> Make sure `fakeSourceCredential.EXPECT().GetPatientId().AnyTimes().Return(<>)` is populated -> -> Make sure `httpClient := base.OAuthVcrSetup(t, true, <>)` is populated, and 2nd parameter is `true` - -```go - -func TestGetSourceClientCerner_SyncAll(t *testing.T) { - t.Parallel() - //setup - testLogger := logrus.WithFields(logrus.Fields{ - "type": "test", - }) - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - fakeDatabase := mock_models.NewMockDatabaseRepository(mockCtrl) - fakeDatabase.EXPECT().UpsertRawResource(gomock.Any(), gomock.Any(), gomock.Any()).Times(694).Return(true, nil) - - fakeSourceCredential := mock_models.NewMockSourceCredential(mockCtrl) - fakeSourceCredential.EXPECT().GetPatientId().AnyTimes().Return(<>) - fakeSourceCredential.EXPECT().GetSourceType().AnyTimes().Return(pkg.SourceTypeCerner) - fakeSourceCredential.EXPECT().GetApiEndpointBaseUrl().AnyTimes().Return("https://fhir-myrecord.cerner.com/r4/ec2458f2-1e24-41c8-b71b-0e701af7583d") - - httpClient := base.OAuthVcrSetup(t, true, <>) - client, _, err := GetSourceClientCerner(pkg.FastenLighthouseEnvSandbox, context.Background(), testLogger, fakeSourceCredential, httpClient) - - //test - resp, err := client.SyncAll(fakeDatabase) - require.NoError(t, err) - - //assert - require.NoError(t, err) - require.Equal(t, 931, resp.TotalResources) - require.Equal(t, 694, len(resp.UpdatedResources)) -} -``` - -10. Run your new test (and generate a recording) by running the following command `go test ./...` -11. Disable recording mode -```go -httpClient := base.OAuthVcrSetup(t, true, <>) - -//should become -httpClient := base.OAuthVcrSetup(t, false) - -``` -12. Make sure your new recording is added to git -```bash -git add clients/internal/platform/testdata -``` - -13. Once you're satisfied with your changes to the Platform client, open a pull request. A maintainer will verify your changes & recordings, apply your changes to the `fasten-sources-gen` repository, regenerate the class and verify using your recordings. :partying_face: - - ---- - -## Notes - -This library can take a long time to build, as there are thousand's of generated go files. - -https://github.com/golang/go/issues/45474 -https://groups.google.com/g/golang-nuts/c/NJj9nP2Xc0I - -```bash -go clean -cache -time go build ./... -# 129.83s user 5.78s system 204% cpu 1:06.23 total -``` - # Docker diff --git a/Makefile b/Makefile index 17a8b74ff..e629c8c4e 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,7 @@ +.PHONY: dep-backend +dep-backend: + go mod vendor + .PHONY: serve-backend -serve-backend: +serve-backend: dep-backend cd testutils && go run oauth_cli.go diff --git a/screenshots/callback-patient.png b/screenshots/callback-patient.png new file mode 100644 index 000000000..7f0608717 Binary files /dev/null and b/screenshots/callback-patient.png differ diff --git a/screenshots/callback-raw-request.png b/screenshots/callback-raw-request.png new file mode 100644 index 000000000..8e6a503a9 Binary files /dev/null and b/screenshots/callback-raw-request.png differ diff --git a/screenshots/callback.png b/screenshots/callback.png new file mode 100644 index 000000000..81cbb7add Binary files /dev/null and b/screenshots/callback.png differ diff --git a/screenshots/connect-custom.png b/screenshots/connect-custom.png new file mode 100644 index 000000000..5fded8149 Binary files /dev/null and b/screenshots/connect-custom.png differ diff --git a/screenshots/connect.png b/screenshots/connect.png new file mode 100644 index 000000000..c4713244d Binary files /dev/null and b/screenshots/connect.png differ