Skip to content

Commit

Permalink
feat: add middleware to validate mycarehub tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
Muchogoc committed Apr 25, 2023
1 parent 104e5b1 commit 90d031d
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ env:
AUTH_USERNAME: ${{ secrets.AUTH_USERNAME }}
AUTH_PASSWORD: ${{ secrets.AUTH_PASSWORD }}
GRANT_TYPE: ${{ secrets.GRANT_TYPE }}
MYCAREHUB_CLIENT_ID: ${{ secrets.MYCAREHUB_CLIENT_ID }}
MYCAREHUB_CLIENT_SECRET: ${{ secrets.MYCAREHUB_CLIENT_SECRET }}
MYCAREHUB_INTROSPECT_URL: ${{ secrets.MYCAREHUB_INTROSPECT_URL }}


jobs:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/staging_multitenant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ env:
AUTH_USERNAME: ${{ secrets.AUTH_USERNAME }}
AUTH_PASSWORD: ${{ secrets.AUTH_PASSWORD }}
GRANT_TYPE: ${{ secrets.GRANT_TYPE }}
MYCAREHUB_CLIENT_ID: ${{ secrets.MYCAREHUB_CLIENT_ID }}
MYCAREHUB_CLIENT_SECRET: ${{ secrets.MYCAREHUB_CLIENT_SECRET }}
MYCAREHUB_INTROSPECT_URL: ${{ secrets.MYCAREHUB_INTROSPECT_URL }}

jobs:
deploy_to_multitenant_staging:
Expand Down
9 changes: 9 additions & 0 deletions deploy/charts/clinical/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ spec:
- name: GRANT_TYPE
value: {{ .Values.app.container.env.grantType | quote }}

- name: MYCAREHUB_CLIENT_ID
value: {{ .Values.app.container.env.mycarehubClientID | quote }}

- name: MYCAREHUB_CLIENT_SECRET
value: {{ .Values.app.container.env.mycarehubClientSecret | quote}}

- name: MYCAREHUB_INTROSPECT_URL
value: {{ .Values.app.container.env.mycarehubIntrospectURL | quote }}

volumeMounts:
- name: {{ .Values.app.container.env.googleApplicationCredentialsSecret.name }}
mountPath: {{ .Values.app.container.env.googleApplicationCredentialsSecret.mountPath }}
Expand Down
3 changes: 3 additions & 0 deletions deploy/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ helm upgrade \
--set app.container.env.authUsername="${AUTH_USERNAME}"\
--set app.container.env.authPassword="${AUTH_PASSWORD}"\
--set app.container.env.grantType="${GRANT_TYPE}"\
--set app.container.env.mycarehubClientID="${MYCAREHUB_CLIENT_ID}"\
--set app.container.env.mycarehubClientSecret="${MYCAREHUB_CLIENT_SECRET}"\
--set app.container.env.mycarehubIntrospectURL="${MYCAREHUB_INTROSPECT_URL}"\
--set networking.issuer.name="letsencrypt-prod"\
--set networking.issuer.privateKeySecretRef="letsencrypt-prod"\
--set networking.ingress.host="${APP_DOMAIN}"\
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ require (
github.com/labstack/gommon v0.4.0
github.com/mitchellh/mapstructure v1.5.0
github.com/rs/xid v1.4.0
github.com/savannahghi/authutils v0.0.9
github.com/savannahghi/authutils v0.0.10
github.com/savannahghi/converterandformatter v0.0.11
github.com/savannahghi/enumutils v0.0.3
github.com/savannahghi/errorcodeutil v0.0.6
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -460,8 +460,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/savannahghi/authutils v0.0.9 h1:G2v2LMJYoaNyLJY39cW4DrHRJMeg+JlGZ7Gzn7sihII=
github.com/savannahghi/authutils v0.0.9/go.mod h1:msRa8zWvgf9r3/eZB+cV6Fol76C0pZ+IUt5YGb53dwc=
github.com/savannahghi/authutils v0.0.10 h1:FgrTsqZ+v9OMYVtB32zmfAnnR1iL9FIHNPRTgXLVJcc=
github.com/savannahghi/authutils v0.0.10/go.mod h1:msRa8zWvgf9r3/eZB+cV6Fol76C0pZ+IUt5YGb53dwc=
github.com/savannahghi/converterandformatter v0.0.3/go.mod h1:0o7yieYU10WabPqKuqj+5QL52eTL1eGElxjb+A68bbA=
github.com/savannahghi/converterandformatter v0.0.10/go.mod h1:DNqyfHojHOrll1/l6Y9UUSl97/TBiB08zcRWjaXbXRM=
github.com/savannahghi/converterandformatter v0.0.11 h1:N9UPNhabmrxKAnM4E68qrP6/urijMBr0/1EzSWPX3C4=
Expand Down
2 changes: 1 addition & 1 deletion pkg/clinical/application/utils/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

const (
// TopicVersion defines the topic version. That standard one is `v1`
TopicVersion = "v1"
TopicVersion = "v2"
)

// ContextKey is a custom type used as a key value when adding IDs to the context
Expand Down
2 changes: 1 addition & 1 deletion pkg/clinical/application/utils/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func TestAddPubSubNamespace(t *testing.T) {
topicName: "test",
serviceName: "service",
},
want: "service-test-staging-v1",
want: "service-test-staging-v2",
},
}
for _, tt := range tests {
Expand Down
4 changes: 2 additions & 2 deletions pkg/clinical/presentation/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func SetupRoutes(r *gin.Engine, authclient *authutils.Client, usecases usecases.
handlers := rest.NewPresentationHandlers(usecases, infra.BaseExtension)

graphQL := r.Group("/graphql")
graphQL.Use(authutils.SladeAuthenticationGinMiddleware(*authclient))
graphQL.Use(rest.AuthenticationGinMiddleware(*authclient))
graphQL.Use(rest.TenantIdentifierExtractionMiddleware(infra.FHIR))
graphQL.Any("", GQLHandler(usecases))

Expand All @@ -163,7 +163,7 @@ func SetupRoutes(r *gin.Engine, authclient *authutils.Client, usecases usecases.
r.POST("/pubsub", handlers.ReceivePubSubPushMessage)

apis := r.Group("/api")
apis.Use(authutils.SladeAuthenticationGinMiddleware(*authclient))
apis.Use(rest.AuthenticationGinMiddleware(*authclient))

v1 := apis.Group("/v1")

Expand Down
129 changes: 129 additions & 0 deletions pkg/clinical/presentation/rest/auth_middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package rest

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"

"github.com/gin-gonic/gin"
"github.com/savannahghi/authutils"
"github.com/savannahghi/firebasetools"
"github.com/savannahghi/serverutils"
)

var (
clientID = serverutils.MustGetEnvVar("MYCAREHUB_CLIENT_ID")
clientSecret = serverutils.MustGetEnvVar("MYCAREHUB_CLIENT_SECRET")
introspectURL = serverutils.MustGetEnvVar("MYCAREHUB_INTROSPECT_URL")
)

type IntrospectResponse struct {
Active bool `json:"active"`
UserID string `json:"user_id"`
}

func HasValidMycarehubBearerToken(_ context.Context, r *http.Request) (bool, map[string]string, *authutils.TokenIntrospectionResponse) {
token, err := firebasetools.ExtractBearerToken(r)
if err != nil {
return false, serverutils.ErrorMap(err), nil
}

formData := url.Values{
"token": []string{token},
}

client := &http.Client{
Timeout: 5 * time.Second,
}

req, err := http.NewRequest(http.MethodPost, introspectURL, strings.NewReader(formData.Encode()))
if err != nil {
return false, serverutils.ErrorMap(err), nil
}

req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(clientID, clientSecret)

resp, err := client.Do(req)
if err != nil {
return false, serverutils.ErrorMap(err), nil
}

defer func() {
err = resp.Body.Close()
if err != nil {
fmt.Printf("Introspector() failed to close body:%s", err)
}
}()

if resp.StatusCode != http.StatusOK {
err := fmt.Errorf("failed to introspect token")
return false, serverutils.ErrorMap(err), nil
}

var introspection IntrospectResponse

bs, err := io.ReadAll(resp.Body)
if err != nil {
return false, serverutils.ErrorMap(err), nil
}

if err := json.Unmarshal(bs, &introspection); err != nil {
return false, serverutils.ErrorMap(err), nil
}

if !introspection.Active {
err := fmt.Errorf("the supplied access token is invalid")
return false, serverutils.ErrorMap(err), nil
}

return true, nil, &authutils.TokenIntrospectionResponse{Token: token, UserGUID: introspection.UserID, IsValid: introspection.Active}
}

// authCheckFn is a function type for authorization and authentication checks
// there can be several e.g an authentication check runs first then an authorization
// check runs next if the authentication passes etc
type authCheckFn = func(
ctx context.Context,
r *http.Request,
) (bool, map[string]string, *authutils.TokenIntrospectionResponse)

// AuthenticationGinMiddleware is an authentication middleware for servers using Gin. It checks the user token and ensures
// that it is valid
func AuthenticationGinMiddleware(cl authutils.Client) gin.HandlerFunc {
checkFuncs := []authCheckFn{cl.HasValidSlade360BearerToken, HasValidMycarehubBearerToken}

return func(c *gin.Context) {
var successful bool

var tokenResponse *authutils.TokenIntrospectionResponse

errs := []map[string]string{}

for _, checkFunc := range checkFuncs {
valid, errMap, authToken := checkFunc(c.Request.Context(), c.Request)
if valid {
successful = true
tokenResponse = authToken

break
}

errs = append(errs, errMap)
}

if !successful {
serverutils.WriteJSONResponse(c.Writer, errs, http.StatusUnauthorized)
c.Abort()
}

ctx := context.WithValue(c.Request.Context(), authutils.AuthTokenContextKey, tokenResponse)
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}

0 comments on commit 90d031d

Please sign in to comment.