diff --git a/backend/pkg/web/handler/cors_proxy.go b/backend/pkg/web/handler/cors_proxy.go index 31fc2ea5..2eee5e12 100644 --- a/backend/pkg/web/handler/cors_proxy.go +++ b/backend/pkg/web/handler/cors_proxy.go @@ -2,6 +2,7 @@ package handler import ( "fmt" + sourceDefinitions "github.com/fastenhealth/fasten-sources/definitions" "github.com/gin-gonic/gin" "log" "net/http" @@ -10,15 +11,50 @@ import ( "strings" ) -//TODO, there are security implications to this, we need to make sure we lock this down. +// SECURITY: there are security implications to this, this may require some additional authentication to limit misuse +// this is a whitelisted CORS proxy, it is only used to proxy requests to Token Exchange urls for specified endpoint func CORSProxy(c *gin.Context) { - //appConfig := c.MustGet("CONFIG").(config.Interface) + + endpointId := strings.Trim(c.Param("endpointId"), "/") + + //get the endpoint definition + endpointDefinition, err := sourceDefinitions.GetSourceDefinition(sourceDefinitions.GetSourceConfigOptions{ + EndpointId: endpointId, + }) + + if err != nil { + c.JSON(http.StatusNotFound, gin.H{ + "error": fmt.Sprintf("endpoint not found: %s", endpointId), + }) + return + } + + //SECURITY: if the endpoint definition does not have CORSRelayRequired set to true, then return a 404 + if endpointDefinition.CORSRelayRequired != true { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "endpoint does not require CORS Relay.", + }) + return + } + + //SECURITY: the proxy URL must start with the same URL as the endpoint.TokenUri corsUrl := fmt.Sprintf("https://%s", strings.TrimPrefix(c.Param("proxyPath"), "/")) + //we'll lowercase to normalize the comparison + if !strings.HasPrefix(strings.ToLower(corsUrl), strings.ToLower(endpointDefinition.TokenEndpoint)) { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "invalid proxy URL, must match TokenEndpoint", + }) + return + } + remote, err := url.Parse(corsUrl) remote.RawQuery = c.Request.URL.Query().Encode() if err != nil { - panic(err) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "invalid proxy URL, could not parse", + }) + return } proxy := httputil.ReverseProxy{} diff --git a/backend/pkg/web/server.go b/backend/pkg/web/server.go index cdb21a16..9c55eb82 100644 --- a/backend/pkg/web/server.go +++ b/backend/pkg/web/server.go @@ -53,10 +53,12 @@ func (ae *AppEngine) Setup() (*gin.RouterGroup, *gin.Engine) { api.POST("/auth/signup", handler.AuthSignup) api.POST("/auth/signin", handler.AuthSignin) - // - //r.Any("/database/*proxyPath", handler.CouchDBProxy) - //r.GET("/cors/*proxyPath", handler.CORSProxy) - //r.OPTIONS("/cors/*proxyPath", handler.CORSProxy) + + //whitelisted CORS PROXY + api.GET("/cors/:endpointId/*proxyPath", handler.CORSProxy) + api.POST("/cors/:endpointId/*proxyPath", handler.CORSProxy) + api.OPTIONS("/cors/:endpointId/*proxyPath", handler.CORSProxy) + api.GET("/glossary/code", handler.GlossarySearchByCode) api.POST("/support/request", handler.SupportRequest) diff --git a/frontend/src/app/services/lighthouse.service.ts b/frontend/src/app/services/lighthouse.service.ts index e258e7e2..cebfbb2d 100644 --- a/frontend/src/app/services/lighthouse.service.ts +++ b/frontend/src/app/services/lighthouse.service.ts @@ -15,6 +15,7 @@ import {OpenExternalLink} from '../../lib/utils/external_link'; import {Router, UrlSerializer} from '@angular/router'; import {Location} from '@angular/common'; import {PatientAccessBrand, PatientAccessEndpoint, PatientAccessPortal} from '../models/patient-access-brands'; +import {GetEndpointAbsolutePath} from '../../lib/utils/endpoint_absolute_path'; export const sourceConnectDesktopTimeout = 24*5000 //wait 2 minutes (5 * 24 = 120) @@ -242,11 +243,10 @@ export class LighthouseService { } //this is for providers that support CORS & PKCE (public client auth) let codeVerifier = undefined - if(!sourceMetadata.confidential){ - client.token_endpoint_auth_method = 'none' - codeVerifier = expectedSourceStateInfo.code_verifier - } else { + let tokenEndpointUrl = sourceMetadata.token_endpoint + + if(sourceMetadata.confidential) { 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 = this.pathJoin([environment.lighthouse_api_endpoint_base, `token/${expectedSourceStateInfo.endpoint_id}`]) @@ -259,12 +259,28 @@ export class LighthouseService { } else { codeVerifier = "placeholder" } + } else { + //is not confidential + client.token_endpoint_auth_method = 'none' + codeVerifier = expectedSourceStateInfo.code_verifier + + //check if source requires a CORS relay + if(sourceMetadata.cors_relay_required){ + let corsProxyBaseUrl = `${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/cors/${sourceMetadata.id}/` + + //this endpoint requires a CORS relay + //get the path to the Fasten server, and append `cors/` and then append the request url + let tokenEndpointUrlParts = new URL(tokenEndpointUrl) + tokenEndpointUrl = corsProxyBaseUrl + `${tokenEndpointUrlParts.hostname}${tokenEndpointUrlParts.pathname}${tokenEndpointUrlParts.search}` + console.warn("Using local CORS proxy for token endpoint", tokenEndpointUrl) + } + } const as = { issuer: sourceMetadata.issuer, authorization_endpoint: sourceMetadata.authorization_endpoint, - token_endpoint: sourceMetadata.token_endpoint, + token_endpoint: tokenEndpointUrl, introspection_endpoint: sourceMetadata.introspection_endpoint, } diff --git a/go.mod b/go.mod index c3c331ab..2c5e2f39 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/dave/jennifer v1.6.1 github.com/dominikbraun/graph v0.15.0 github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 - github.com/fastenhealth/fasten-sources v0.5.6 + github.com/fastenhealth/fasten-sources v0.5.8 github.com/fastenhealth/gofhir-models v0.0.6 github.com/gin-gonic/gin v1.9.0 github.com/go-gormigrate/gormigrate/v2 v2.1.1 diff --git a/go.sum b/go.sum index f1e1a216..8e248d3f 100644 --- a/go.sum +++ b/go.sum @@ -101,8 +101,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fastenhealth/fasten-sources v0.5.6 h1:F4Qmw9ABLSkqkWncoSnChBRDVWLrzkJv+z4z/Ue/fdc= -github.com/fastenhealth/fasten-sources v0.5.6/go.mod h1:hUQATAu5KrxKbACJoVt4iEKIGnRtmiOmHz+5TLfyiCM= +github.com/fastenhealth/fasten-sources v0.5.8 h1:zcohdfd7QBWxPcD4TTniHBiD+x4tqi6YIXAjLu72AY0= +github.com/fastenhealth/fasten-sources v0.5.8/go.mod h1:hUQATAu5KrxKbACJoVt4iEKIGnRtmiOmHz+5TLfyiCM= github.com/fastenhealth/gofhir-models v0.0.6 h1:yJYYaV1eJtHiGEfA1rXLsyOm/9hIi6s2cGoZzGfW1tM= github.com/fastenhealth/gofhir-models v0.0.6/go.mod h1:xB8ikGxu3bUq2b1JYV+CZpHqBaLXpOizFR0eFBCunis= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=