Skip to content

Commit

Permalink
Add AuthManagementKey to config and client (#507)
Browse files Browse the repository at this point in the history
  • Loading branch information
itaihanski authored Feb 3, 2025
1 parent 76cb8cb commit 2db698a
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 15 deletions.
6 changes: 6 additions & 0 deletions descope/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,7 @@ const (
type ClientParams struct {
ProjectID string
BaseURL string
AuthManagementKey string
DefaultClient IHttpClient
CustomDefaultHeaders map[string]string
CertificateVerify CertificateVerifyMode
Expand Down Expand Up @@ -1399,6 +1400,11 @@ func (c *Client) DoRequest(ctx context.Context, method, uriPath string, body io.
if len(pswd) > 0 {
bearer = fmt.Sprintf("%s:%s", bearer, pswd)
}
// append auth management key if available
authKey := c.conf.AuthManagementKey
if len(authKey) > 0 {
bearer = fmt.Sprintf("%s:%s", bearer, authKey)
}
req.Header.Set(AuthorizationHeaderName, BearerAuthorizationPrefix+bearer)
c.addDescopeHeaders(req)

Expand Down
33 changes: 23 additions & 10 deletions descope/api/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,25 @@ import (
"github.com/stretchr/testify/require"
)

func getProjectAndJwt(r *http.Request) (string, string) {
var projectID, jwt string
func parseAuthorizationHeader(r *http.Request) (projectID, jwt, accessKey string) {
reqToken := r.Header.Get(AuthorizationHeaderName)
if splitToken := strings.Split(reqToken, BearerAuthorizationPrefix); len(splitToken) == 2 {
bearer := splitToken[1]
bearers := strings.Split(bearer, ":")
projectID = bearers[0]
if len(bearers) > 1 {
if len(bearers) == 2 {
if strings.Contains(bearers[1], ".") {
jwt = bearers[1]
} else {
accessKey = bearers[1]
}
}
if len(bearers) > 2 {
jwt = bearers[1]
accessKey = bearers[2]
}
}
return projectID, jwt
return
}

func TestClient(t *testing.T) {
Expand Down Expand Up @@ -57,7 +64,7 @@ func TestGetRequest(t *testing.T) {
assert.Nil(t, r.Body)
assert.EqualValues(t, "/path", r.URL.Path)
assert.EqualValues(t, "test=1", r.URL.RawQuery)
actualProject, _ := getProjectAndJwt(r)
actualProject, _, _ := parseAuthorizationHeader(r)
assert.EqualValues(t, projectID, actualProject)
return &http.Response{Body: io.NopCloser(strings.NewReader(expectedResponse)), StatusCode: http.StatusOK}, nil
})})
Expand All @@ -74,13 +81,15 @@ func TestPutRequest(t *testing.T) {
expectedOutput := &dummy{Test: "test"}
expectedHeaders := map[string]string{"header1": "value1"}
projectID := "test"
accesskey := "accessKey"
outputBytes, err := utils.Marshal(expectedOutput)
require.NoError(t, err)
c := NewClient(ClientParams{ProjectID: projectID, DefaultClient: mocks.NewTestClient(func(r *http.Request) (*http.Response, error) {
c := NewClient(ClientParams{ProjectID: projectID, AuthManagementKey: accesskey, DefaultClient: mocks.NewTestClient(func(r *http.Request) (*http.Response, error) {
assert.NotNil(t, r.Body)
actualProject, _ := getProjectAndJwt(r)
actualProject, _, actualAccessKey := parseAuthorizationHeader(r)
assert.EqualValues(t, http.MethodPut, r.Method)
assert.EqualValues(t, projectID, actualProject)
assert.EqualValues(t, accesskey, actualAccessKey)
assert.EqualValues(t, expectedHeaders["header1"], r.Header.Get("header1"))
return &http.Response{Body: io.NopCloser(bytes.NewReader(outputBytes)), StatusCode: http.StatusOK}, nil
})})
Expand All @@ -100,19 +109,23 @@ func TestPostRequest(t *testing.T) {
expectedOutput := &dummy{Test: "test"}
expectedHeaders := map[string]string{"header1": "value1"}
projectID := "test"
accesskey := "accessKey"
jwtStr := "eyJhbGciOiJFUzM4NCIsImtpZCI6IjI4eVRTeDZRMGNpSzU4QWRDU3ZLZkNKcEJJTiIsInR5cCI6IkpXVCJ9.eyJleHAiOi01Njk3NzcxNjg2LCJpc3MiOiIyOHlUU3g2UTBjaUs1OEFkQ1N2S2ZDSnBCSU4iLCJzdWIiOiIyOHlldzQ3NTVLdElSNnhmMk1rV2lITDRYSnEifQ.fm5h2AlyOzUCVMIezSQf8wddE6xhcfqnSAzpG4SoOy6HK387T8hxcpbmCc7qbFOQfaPDdhVhqS7JkX7wessaTznbiK_xiDac6CkENgzrl_V8eMXEHt1HcyCW1s6IQd5D"
outputBytes, err := utils.Marshal(expectedOutput)
require.NoError(t, err)
c := NewClient(ClientParams{ProjectID: projectID, DefaultClient: mocks.NewTestClient(func(r *http.Request) (*http.Response, error) {
c := NewClient(ClientParams{ProjectID: projectID, AuthManagementKey: accesskey, DefaultClient: mocks.NewTestClient(func(r *http.Request) (*http.Response, error) {
assert.NotNil(t, r.Body)
actualProject, _ := getProjectAndJwt(r)
actualProject, actualJwt, actualAccessKey := parseAuthorizationHeader(r)
assert.EqualValues(t, http.MethodPost, r.Method)
assert.EqualValues(t, projectID, actualProject)
assert.EqualValues(t, accesskey, actualAccessKey)
assert.EqualValues(t, jwtStr, actualJwt)
assert.EqualValues(t, expectedHeaders["header1"], r.Header.Get("header1"))
return &http.Response{Body: io.NopCloser(bytes.NewReader(outputBytes)), StatusCode: http.StatusOK}, nil
})})

actualOutput := &dummy{}
res, err := c.DoPostRequest(context.Background(), "path", strings.NewReader("test"), &HTTPRequest{ResBodyObj: actualOutput, Headers: expectedHeaders}, "")
res, err := c.DoPostRequest(context.Background(), "path", strings.NewReader("test"), &HTTPRequest{ResBodyObj: actualOutput, Headers: expectedHeaders}, jwtStr)
require.NoError(t, err)
assert.EqualValues(t, string(outputBytes), res.BodyStr)
assert.EqualValues(t, expectedOutput, actualOutput)
Expand Down
2 changes: 2 additions & 0 deletions descope/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ func NewWithConfig(config *Config) (*DescopeClient, error) {
logger.LogInfo("Provided public key is set, forcing only provided public key validation")
}
config.setManagementKey()
config.setAuthManagementKey()

c := api.NewClient(api.ClientParams{
ProjectID: config.ProjectID,
BaseURL: config.DescopeBaseURL,
AuthManagementKey: config.AuthManagementKey,
DefaultClient: config.DefaultClient,
CustomDefaultHeaders: config.CustomDefaultHeaders,
CertificateVerify: config.CertificateVerify,
Expand Down
15 changes: 15 additions & 0 deletions descope/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ func TestEnvVariableManagementKey(t *testing.T) {
assert.NotNil(t, a.Management)
}

func TestEnvVariableAuthManagementKey(t *testing.T) {
expectedManagementKey := "test"
err := os.Setenv(descope.EnvironmentVariableAuthManagementKey, expectedManagementKey)
defer func() {
err = os.Setenv(descope.EnvironmentVariableAuthManagementKey, "")
require.NoError(t, err)
}()
require.NoError(t, err)
a, err := NewWithConfig(&Config{ProjectID: "a"})
require.NoError(t, err)
assert.EqualValues(t, expectedManagementKey, a.config.AuthManagementKey)
assert.NotNil(t, a.Auth)
assert.NotNil(t, a.Management)
}

func TestConcurrentClients(t *testing.T) {
// This test should be run with the 'race' flag, to ensure that
// creating two client in a concurrent manner is safe
Expand Down
18 changes: 17 additions & 1 deletion descope/client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ type Config struct {
ProjectID string
// ManagementKey (optional, "") - used to provide a management key that's required
// for using any of the Management APIs. If empty, this value is retrieved
// from the DESCOPE_MANAGEMENT_KEY environement variable instead. If neither
// from the DESCOPE_MANAGEMENT_KEY environment variable instead. If neither
// values are set then any Management API call with fail.
ManagementKey string
// AuthManagementKey (optional, "") - used to provide a management key to use
// with Authentication APIs whose public access has been disabled.
// If empty, this value is retrieved from the DESCOPE_AUTH_MANAGEMENT_KEY environment variable instead.
// If neither values are set then any disabled authentication methods API calls with fail.
AuthManagementKey string
// PublicKey (optional, "") - used to override or implicitly use a dedicated public key in order to decrypt and validate the JWT tokens
// during ValidateSessionRequest(). If empty, will attempt to fetch all public keys from the specified project id.
PublicKey string
Expand Down Expand Up @@ -79,3 +84,14 @@ func (c *Config) setManagementKey() string {
}
return c.ManagementKey
}

func (c *Config) setAuthManagementKey() string {
if c.AuthManagementKey == "" {
if authKey := utils.GetAuthManagementKeyEnvVariable(); authKey != "" {
c.AuthManagementKey = authKey
} else {
return ""
}
}
return c.AuthManagementKey
}
1 change: 1 addition & 0 deletions descope/internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const SKEW = time.Second * 5
type AuthParams struct {
ProjectID string
PublicKey string
AuthManagementKey string
SessionJWTViaCookie bool
CookieDomain string
CookieSameSite http.SameSite
Expand Down
4 changes: 4 additions & 0 deletions descope/internal/utils/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func GetManagementKeyEnvVariable() string {
return os.Getenv(descope.EnvironmentVariableManagementKey)
}

func GetAuthManagementKeyEnvVariable() string {
return os.Getenv(descope.EnvironmentVariableAuthManagementKey)
}

func GetPublicKeyEnvVariable() string {
return os.Getenv(descope.EnvironmentVariablePublicKey)
}
9 changes: 5 additions & 4 deletions descope/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -985,10 +985,11 @@ const (
ClaimAuthorizedGlobalPermissions = "permissions"
ClaimDescopeCurrentTenant = "dct"

EnvironmentVariableProjectID = "DESCOPE_PROJECT_ID"
EnvironmentVariablePublicKey = "DESCOPE_PUBLIC_KEY"
EnvironmentVariableManagementKey = "DESCOPE_MANAGEMENT_KEY"
EnvironmentVariableBaseURL = "DESCOPE_BASE_URL"
EnvironmentVariableProjectID = "DESCOPE_PROJECT_ID"
EnvironmentVariablePublicKey = "DESCOPE_PUBLIC_KEY"
EnvironmentVariableManagementKey = "DESCOPE_MANAGEMENT_KEY"
EnvironmentVariableAuthManagementKey = "DESCOPE_AUTH_MANAGEMENT_KEY"
EnvironmentVariableBaseURL = "DESCOPE_BASE_URL"
)

type ThirdPartyApplicationScope struct {
Expand Down

0 comments on commit 2db698a

Please sign in to comment.