CORS Proxy for Athena providers (#398)
Handle cors_relay_required = true for Providers in UI. Added a safe CORS Proxy, which support for Whitelisted endpoints only.
This commit is contained in:
parent
8ee98845cc
commit
c48af66f00
|
@ -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{}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
2
go.mod
2
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
|
||||
|
|
4
go.sum
4
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=
|
||||
|
|
Loading…
Reference in New Issue