Skip to content

Commit

Permalink
CORS Proxy for Athena providers (#398)
Browse files Browse the repository at this point in the history
Handle cors_relay_required = true for Providers in UI.
Added a safe CORS Proxy, which support for Whitelisted endpoints only.
  • Loading branch information
AnalogJ authored Jan 30, 2024
1 parent 8ee9884 commit c48af66
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 15 deletions.
42 changes: 39 additions & 3 deletions backend/pkg/web/handler/cors_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handler

import (
"fmt"
sourceDefinitions "github.com/fastenhealth/fasten-sources/definitions"
"github.com/gin-gonic/gin"
"log"
"net/http"
Expand All @@ -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{}
Expand Down
10 changes: 6 additions & 4 deletions backend/pkg/web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
26 changes: 21 additions & 5 deletions frontend/src/app/services/lighthouse.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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}`])
Expand All @@ -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,
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down

0 comments on commit c48af66

Please sign in to comment.