Skip to content

Commit

Permalink
[management] extend readZitadelError to be used for requestJWTToken
Browse files Browse the repository at this point in the history
more generically parse the error returned by zitadel.
  • Loading branch information
adasauce committed Sep 27, 2024
1 parent 1176f2a commit e3cf807
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 28 deletions.
65 changes: 43 additions & 22 deletions management/server/idp/zitadel.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"net/http"
"net/url"
"slices"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -44,11 +45,6 @@ type ZitadelCredentials struct {
appMetrics telemetry.AppMetrics
}

type zitadelErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}

// zitadelEmail specifies details of a user email.
type zitadelEmail struct {
Email string `json:"email"`
Expand Down Expand Up @@ -102,6 +98,42 @@ type zitadelUserResponse struct {
PasswordlessRegistration zitadelPasswordlessRegistration `json:"passwordlessRegistration"`
}

// readZitadelError parses errors returned by the zitadel APIs from a response.
func readZitadelError(body io.ReadCloser) error {
bodyBytes, err := io.ReadAll(body)
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}

helper := JsonParser{}
var target map[string]interface{}
err = helper.Unmarshal(bodyBytes, &target)
if err != nil {
return fmt.Errorf("error unparsable body: %s", string(bodyBytes))
}

// ensure keys are ordered for consistent logging behaviour.
errorKeys := make([]string, 0, len(target))
for k := range target {
errorKeys = append(errorKeys, k)
}
slices.Sort(errorKeys)

var errsOut []string
for _, k := range errorKeys {
if _, isEmbedded := target[k].(map[string]interface{}); isEmbedded {
continue
}
errsOut = append(errsOut, fmt.Sprintf("%s: %v", k, target[k]))
}

if len(errsOut) == 0 {
return fmt.Errorf("data missing")
}

return fmt.Errorf(strings.Join(errsOut, " "))

Check failure on line 134 in management/server/idp/zitadel.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

printf: non-constant format string in call to fmt.Errorf (govet)

Check failure on line 134 in management/server/idp/zitadel.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

printf: non-constant format string in call to fmt.Errorf (govet)
}

// NewZitadelManager creates a new instance of the ZitadelManager.
func NewZitadelManager(config ZitadelClientConfig, appMetrics telemetry.AppMetrics) (*ZitadelManager, error) {
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
Expand Down Expand Up @@ -181,7 +213,8 @@ func (zc *ZitadelCredentials) requestJWTToken(ctx context.Context) (*http.Respon
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unable to get zitadel token, statusCode %d", resp.StatusCode)
zErr := readZitadelError(resp.Body)
return nil, fmt.Errorf("unable to get zitadel token, statusCode %d, zitadel: %w", resp.StatusCode, zErr)
}

return resp, nil
Expand Down Expand Up @@ -494,10 +527,9 @@ func (zm *ZitadelManager) post(ctx context.Context, resource string, body string
zm.appMetrics.IDPMetrics().CountRequestStatusError()
}

bodyBytes, _ := io.ReadAll(resp.Body)
zErr := zm.readZitadelError(bodyBytes)
zErr := readZitadelError(resp.Body)

return bodyBytes, fmt.Errorf("unable to post %s, statusCode %d, zitadel: %w", reqURL, resp.StatusCode, zErr)
return nil, fmt.Errorf("unable to post %s, statusCode %d, zitadel: %w", reqURL, resp.StatusCode, zErr)
}

return io.ReadAll(resp.Body)
Expand Down Expand Up @@ -569,25 +601,14 @@ func (zm *ZitadelManager) get(ctx context.Context, resource string, q url.Values
zm.appMetrics.IDPMetrics().CountRequestStatusError()
}

bodyBytes, _ := io.ReadAll(resp.Body)
zErr := zm.readZitadelError(bodyBytes)
zErr := readZitadelError(resp.Body)

return bodyBytes, fmt.Errorf("unable to get %s, statusCode %d, zitadel: %w", reqURL, resp.StatusCode, zErr)
return nil, fmt.Errorf("unable to get %s, statusCode %d, zitadel: %w", reqURL, resp.StatusCode, zErr)
}

return io.ReadAll(resp.Body)
}

func (zm *ZitadelManager) readZitadelError(errorBody []byte) error {
var zitadelErr zitadelErrorResponse
err := zm.helper.Unmarshal(errorBody, &zitadelErr)
if err != nil {
return fmt.Errorf("error unparsable body: %s", errorBody)
}

return fmt.Errorf("error code: %d message: %s", zitadelErr.Code, zitadelErr.Message)
}

// userData construct user data from zitadel profile.
func (zp zitadelProfile) userData() *UserData {
var (
Expand Down
10 changes: 4 additions & 6 deletions management/server/idp/zitadel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ func TestNewZitadelManager(t *testing.T) {
}

func TestZitadelRequestJWTToken(t *testing.T) {

type requestJWTTokenTest struct {
name string
inputCode int
Expand All @@ -88,15 +87,14 @@ func TestZitadelRequestJWTToken(t *testing.T) {
requestJWTTokenTestCase2 := requestJWTTokenTest{
name: "Request Bad Status Code",
inputCode: 400,
inputRespBody: "{}",
inputRespBody: "{\"error\": \"invalid_scope\", \"error_description\":\"openid missing\"}",
helper: JsonParser{},
expectedFuncExitErrDiff: fmt.Errorf("unable to get zitadel token, statusCode 400"),
expectedFuncExitErrDiff: fmt.Errorf("unable to get zitadel token, statusCode 400, zitadel: error: invalid_scope error_description: openid missing"),
expectedToken: "",
}

for _, testCase := range []requestJWTTokenTest{requestJWTTokenTesttCase1, requestJWTTokenTestCase2} {
t.Run(testCase.name, func(t *testing.T) {

jwtReqClient := mockHTTPClient{
resBody: testCase.inputRespBody,
code: testCase.inputCode,
Expand Down Expand Up @@ -156,7 +154,7 @@ func TestZitadelParseRequestJWTResponse(t *testing.T) {
}
parseRequestJWTResponseTestCase2 := parseRequestJWTResponseTest{
name: "Parse Bad json JWT Body",
inputRespBody: "",
inputRespBody: "{}",
helper: JsonParser{},
expectedToken: "",
expectedExpiresIn: 0,
Expand Down Expand Up @@ -254,7 +252,7 @@ func TestZitadelAuthenticate(t *testing.T) {
inputCode: 400,
inputResBody: "{}",
helper: JsonParser{},
expectedFuncExitErrDiff: fmt.Errorf("unable to get zitadel token, statusCode 400"),
expectedFuncExitErrDiff: fmt.Errorf("unable to get zitadel token, statusCode 400, zitadel: data missing"),
expectedCode: 200,
expectedToken: "",
}
Expand Down

0 comments on commit e3cf807

Please sign in to comment.