Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

CORS Proxy for Athena providers #398

Merged
merged 1 commit into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/#", handler.Auth#)
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
Loading