Skip to content

Commit

Permalink
Add Auth0 AppMetdata Struct (#632)
Browse files Browse the repository at this point in the history
  • Loading branch information
bbengfort authored Jul 12, 2022
1 parent ff8e01f commit dd5455e
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 158 deletions.
8 changes: 8 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ GDS_BFF_TESTNET_MEMBERS_TIMEOUT=5s
GDS_BFF_TESTNET_MEMBERS_MTLS_CERT_PATH=
GDS_BFF_TESTNET_MEMBERS_MTLS_POOL_PATH=

GDS_BFF_TESTNET_ADMIN_ENDPOINT=http://localhost:4434
GDS_BFF_TESTNET_ADMIN_AUDIENCE=http://localhost:4433
GDS_BFF_TESTNET_ADMIN_TOKEN_KEYS=

GDS_BFF_MAINNET_DIRECTORY_INSECURE=true
GDS_BFF_MAINNET_DIRECTORY_ENDPOINT=localhost:5436
GDS_BFF_MAINNET_DIRECTORY_TIMEOUT=5s
Expand All @@ -167,6 +171,10 @@ GDS_BFF_MAINNET_MEMBERS_TIMEOUT=5s
GDS_BFF_MAINNET_MEMBERS_MTLS_CERT_PATH=
GDS_BFF_MAINNET_MEMBERS_MTLS_POOL_PATH=

GDS_BFF_MAINNET_ADMIN_ENDPOINT=http://localhost:5434
GDS_BFF_MAINNET_ADMIN_AUDIENCE=http://localhost:4433
GDS_BFF_MAINNET_ADMIN_TOKEN_KEYS=

GDS_BFF_DATABASE_URL=trtl://localhost:4436/
GDS_BFF_DATABASE_REINDEX_ON_BOOT=false
GDS_BFF_DATABASE_INSECURE=true
Expand Down
6 changes: 3 additions & 3 deletions pkg/bff/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ func (s *Server) Certificates(c *gin.Context) {
}

// Extract the VASP IDs from the claims
// Note that if testnet or mainnet are absent from the VASPs map, the ID will
// Note that if testnet or mainnet are absent from the VASPs struct, the ID will
// default to an empty string, and GetCertificates will return nil for that network
// instead of an error.
testnetID := claims.VASPs[testnet]
mainnetID := claims.VASPs[mainnet]
testnetID := claims.VASPs.TestNet
mainnetID := claims.VASPs.MainNet

// Get the certificate replies from the admin APIs
testnet, mainnet, testnetErr, mainnetErr := s.GetCertificates(c.Request.Context(), testnetID, mainnetID)
Expand Down
10 changes: 5 additions & 5 deletions pkg/bff/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ var AnonymousClaims = Claims{Scope: ScopeAnonymous, Permissions: nil}

// Claims extracts custom data from the JWT token provided by Auth0
type Claims struct {
Scope string `json:"scope"`
Permissions []string `json:"permissions"`
OrgID string `json:"https://vaspdirectory.net/orgid"`
VASPs map[string]string `json:"https://vaspdirectory.net/vasps"`
Email string `json:"https://vaspdirectory.net/email"`
Scope string `json:"scope"`
Permissions []string `json:"permissions"`
OrgID string `json:"https://vaspdirectory.net/orgid"`
VASPs VASPs `json:"https://vaspdirectory.net/vasps"`
Email string `json:"https://vaspdirectory.net/email"`
}

// Validate implements the validator.CustomClaims interface for Auth0 parsing.
Expand Down
2 changes: 1 addition & 1 deletion pkg/bff/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func TestClaimsContext(t *testing.T) {
bclaims, err := auth.GetClaims(c)
require.NoError(t, err, "could not fetch bff claims")
require.Equal(t, "6f0d943d-6cd7-4745-bc9d-6d65e32c70e9", bclaims.OrgID)
require.Equal(t, "eee784b5-49b3-452e-97d5-1b01e79f5e62", bclaims.VASPs["testnet"])
require.Equal(t, "eee784b5-49b3-452e-97d5-1b01e79f5e62", bclaims.VASPs.TestNet)
require.True(t, bclaims.HasAllPermissions("add:collaborators", "read:certificates"))

rclaims, err := auth.GetRegisteredClaims(c)
Expand Down
45 changes: 45 additions & 0 deletions pkg/bff/auth/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package auth

import "encoding/json"

// AppMetadata makes it easier to serialize and deserialize JSON from the auth0
// app_metadata assigned to the user by the BFF (and ensures the data is structured).
type AppMetadata struct {
OrgID string `json:"orgid"`
VASPs VASPs `json:"vasps"`
}

type VASPs struct {
MainNet string `json:"mainnet"`
TestNet string `json:"testnet"`
}

func (meta *AppMetadata) Load(appdata map[string]interface{}) (err error) {
// Serialize appdata back to JSON
var data []byte
if data, err = json.Marshal(appdata); err != nil {
return err
}

// Deserialize app metadata from struct
if err = json.Unmarshal(data, meta); err != nil {
return err
}

return nil
}

func (meta *AppMetadata) Dump() (appdata map[string]interface{}, err error) {
// Serialize meta back to JSON
var data []byte
if data, err = json.Marshal(meta); err != nil {
return nil, err
}

appdata = make(map[string]interface{})
if err = json.Unmarshal(data, &appdata); err != nil {
return nil, err
}

return appdata, nil
}
109 changes: 109 additions & 0 deletions pkg/bff/auth/metadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package auth_test

import (
"testing"

"github.com/stretchr/testify/require"
. "github.com/trisacrypto/directory/pkg/bff/auth"
)

func TestAppMetadata(t *testing.T) {
// Test loading and dumping app_metadata to/from the auth0 response.
testCases := []struct {
appdata map[string]interface{}
expected *AppMetadata
}{
{
nil, &AppMetadata{},
},
{
map[string]interface{}{}, &AppMetadata{},
},
{
map[string]interface{}{
"orgid": "67428be4-3fa4-4bf2-9e15-edbf043f8670",
},
&AppMetadata{
OrgID: "67428be4-3fa4-4bf2-9e15-edbf043f8670",
},
},
{
map[string]interface{}{
"vasps": map[string]string{
"testnet": "1bcacaf5-4b43-4e14-b70c-a47107d3a56c",
},
},
&AppMetadata{
VASPs: VASPs{
TestNet: "1bcacaf5-4b43-4e14-b70c-a47107d3a56c",
},
},
},
{
map[string]interface{}{
"vasps": map[string]string{
"mainnet": "2ac8d50a-ff4c-479e-8eec-a35d96d90911",
},
},
&AppMetadata{
VASPs: VASPs{
MainNet: "2ac8d50a-ff4c-479e-8eec-a35d96d90911",
},
},
},
{
map[string]interface{}{
"vasps": map[string]string{
"testnet": "1bcacaf5-4b43-4e14-b70c-a47107d3a56c",
"mainnet": "2ac8d50a-ff4c-479e-8eec-a35d96d90911",
},
},
&AppMetadata{
VASPs: VASPs{
TestNet: "1bcacaf5-4b43-4e14-b70c-a47107d3a56c",
MainNet: "2ac8d50a-ff4c-479e-8eec-a35d96d90911",
},
},
},
{
map[string]interface{}{
"orgid": "67428be4-3fa4-4bf2-9e15-edbf043f8670",
"vasps": map[string]string{
"testnet": "1bcacaf5-4b43-4e14-b70c-a47107d3a56c",
"mainnet": "2ac8d50a-ff4c-479e-8eec-a35d96d90911",
},
},
&AppMetadata{
OrgID: "67428be4-3fa4-4bf2-9e15-edbf043f8670",
VASPs: VASPs{
TestNet: "1bcacaf5-4b43-4e14-b70c-a47107d3a56c",
MainNet: "2ac8d50a-ff4c-479e-8eec-a35d96d90911",
},
},
},
}

for _, tc := range testCases {
actual := &AppMetadata{}

err := actual.Load(tc.appdata)
require.NoError(t, err, "could not load appdata")
require.Equal(t, tc.expected, actual, "app_metadata did not load correctly")

appdata, err := actual.Dump()
require.NoError(t, err, "could not dump app_metdata")

require.Contains(t, appdata, "orgid")
require.Equal(t, actual.OrgID, appdata["orgid"])

require.Contains(t, appdata, "vasps")
vasps, ok := appdata["vasps"].(map[string]interface{})
require.True(t, ok, "appdata vasps is wrong type")

require.Contains(t, appdata["vasps"], "testnet")
require.Equal(t, actual.VASPs.TestNet, vasps["testnet"])
require.Contains(t, appdata["vasps"], "mainnet")
require.Equal(t, actual.VASPs.MainNet, vasps["mainnet"])
}

}
4 changes: 2 additions & 2 deletions pkg/bff/members.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ func (s *Server) Overview(c *gin.Context) {
}

// Extract the VASP IDs from the claims
testnetID := claims.VASPs[testnet]
mainnetID := claims.VASPs[mainnet]
testnetID := claims.VASPs.TestNet
mainnetID := claims.VASPs.MainNet

out := api.OverviewReply{
OrgID: claims.OrgID,
Expand Down
90 changes: 29 additions & 61 deletions pkg/bff/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,14 @@ func (s *Server) Login(c *gin.Context) {

// Ensure the user resources are correctly populated.
// If the user is not associated with an organization, create it.
if orgID, ok := GetOrgID(user.AppMetadata); !ok || orgID == "" {
appdata := &auth.AppMetadata{}
if err = appdata.Load(user.AppMetadata); err != nil {
log.Error().Err(err).Msg("could not parse user app metadata")
c.JSON(http.StatusInternalServerError, "could not parse user app metadata")
return
}

if appdata.OrgID == "" {
// Create the organization
org, err := s.db.Organizations().Create(c.Request.Context())
if err != nil {
Expand All @@ -78,39 +85,29 @@ func (s *Server) Login(c *gin.Context) {
}

// Set the organization ID in the user app metadata
user.AppMetadata[OrgIDKey] = org.Id
if err = s.auth0.User.Update(*user.ID, user); err != nil {
log.Error().Err(err).Msg("could not update user app_metadata")
c.JSON(http.StatusInternalServerError, "could not complete user login")
return
}
appdata.OrgID = org.Id
} else {
// Get the organization for the specified user
org, err := s.db.Organizations().Retrieve(c.Request.Context(), orgID)
org, err := s.db.Organizations().Retrieve(c.Request.Context(), appdata.OrgID)
if err != nil {
log.Error().Err(err).Msg("could not retrieve organization for user VASP verification")
log.Error().Err(err).Str("orgid", appdata.OrgID).Msg("could not retrieve organization for user VASP verification")
c.JSON(http.StatusInternalServerError, "could not complete user login")
return
}

// Create the actual VASP table
directory := make(map[string]string)
// Ensure the VASP record is correct for the user
if org.Testnet != nil && org.Testnet.Id != "" {
directory[testnet] = org.Testnet.Id
appdata.VASPs.TestNet = org.Testnet.Id
}
if org.Mainnet != nil && org.Mainnet.Id != "" {
directory[mainnet] = org.Mainnet.Id
appdata.VASPs.MainNet = org.Mainnet.Id
}
}

// Ensure the VASP record is correct for the user
if vasps, ok := GetVASPs(user.AppMetadata); !ok || !MapEqual(vasps, directory) {
user.AppMetadata[VASPsKey] = directory
if err = s.auth0.User.Update(*user.ID, user); err != nil {
log.Error().Err(err).Msg("could not update user app_metadata")
c.JSON(http.StatusInternalServerError, "could not complete user login")
return
}
}
if err = s.SaveAuth0AppMetadata(*user.ID, *appdata); err != nil {
log.Error().Err(err).Str("user_id", *user.ID).Msg("could not save user app_metadata")
c.JSON(http.StatusInternalServerError, "could not complete user login")
return
}

// Protect the front-end by setting double cookie tokens for CSRF protection.
Expand Down Expand Up @@ -140,48 +137,19 @@ func (s *Server) FindRoleByName(name string) (*management.Role, error) {
return nil, fmt.Errorf("could not find role %q in %d available roles", name, len(roles.Roles))
}

func GetOrgID(appdata map[string]interface{}) (orgID string, ok bool) {
var val interface{}
if val, ok = appdata[OrgIDKey]; !ok {
return "", false
}
func (s *Server) SaveAuth0AppMetadata(uid string, appdata auth.AppMetadata) (err error) {
// Create a blank user with no data but the appdata
user := &management.User{}

if orgID, ok = val.(string); !ok {
return "", false
// Send the updated user app_metadata back to auth0
if user.AppMetadata, err = appdata.Dump(); err != nil {
return err
}

return orgID, true
}

func GetVASPs(appdata map[string]interface{}) (vasps map[string]string, ok bool) {
var val interface{}
if val, ok = appdata[VASPsKey]; !ok {
return nil, false
}

if vasps, ok = val.(map[string]string); !ok {
return nil, false
}

return vasps, true
}

func MapEqual(a, b map[string]string) bool {
if len(a) != len(b) {
return false
}

for key, val := range a {
if alt, ok := b[key]; !ok || val != alt {
return false
}
}

for key, val := range b {
if alt, ok := a[key]; !ok || val != alt {
return false
}
// Patch the user with the specified user ID
if err = s.auth0.User.Update(uid, user); err != nil {
return err
}

return true
return nil
}
Loading

0 comments on commit dd5455e

Please sign in to comment.