mirror of
https://github.com/netbirdio/netbird.git
synced 2025-06-20 01:38:41 +02:00
[management] improve zitadel idp error response detail by decoding errors (#2634)
* [management] improve zitadel idp error response detail by decoding errors * [management] extend readZitadelError to be used for requestJWTToken more generically parse the error returned by zitadel. * fix lint --------- Co-authored-by: bcmmbaga <bethuelmbaga12@gmail.com>
This commit is contained in:
parent
acb73bd64a
commit
58ff7ab797
@ -2,10 +2,12 @@ package idp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -97,6 +99,42 @@ type zitadelUserResponse struct {
|
|||||||
PasswordlessRegistration zitadelPasswordlessRegistration `json:"passwordlessRegistration"`
|
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 errors.New("unknown error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New(strings.Join(errsOut, " "))
|
||||||
|
}
|
||||||
|
|
||||||
// NewZitadelManager creates a new instance of the ZitadelManager.
|
// NewZitadelManager creates a new instance of the ZitadelManager.
|
||||||
func NewZitadelManager(config ZitadelClientConfig, appMetrics telemetry.AppMetrics) (*ZitadelManager, error) {
|
func NewZitadelManager(config ZitadelClientConfig, appMetrics telemetry.AppMetrics) (*ZitadelManager, error) {
|
||||||
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
|
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
@ -176,7 +214,8 @@ func (zc *ZitadelCredentials) requestJWTToken(ctx context.Context) (*http.Respon
|
|||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
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
|
return resp, nil
|
||||||
@ -489,7 +528,9 @@ func (zm *ZitadelManager) post(ctx context.Context, resource string, body string
|
|||||||
zm.appMetrics.IDPMetrics().CountRequestStatusError()
|
zm.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unable to post %s, statusCode %d", reqURL, resp.StatusCode)
|
zErr := readZitadelError(resp.Body)
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unable to post %s, statusCode %d, zitadel: %w", reqURL, resp.StatusCode, zErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return io.ReadAll(resp.Body)
|
return io.ReadAll(resp.Body)
|
||||||
@ -561,7 +602,9 @@ func (zm *ZitadelManager) get(ctx context.Context, resource string, q url.Values
|
|||||||
zm.appMetrics.IDPMetrics().CountRequestStatusError()
|
zm.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unable to get %s, statusCode %d", reqURL, resp.StatusCode)
|
zErr := readZitadelError(resp.Body)
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unable to get %s, statusCode %d, zitadel: %w", reqURL, resp.StatusCode, zErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return io.ReadAll(resp.Body)
|
return io.ReadAll(resp.Body)
|
||||||
|
@ -66,7 +66,6 @@ func TestNewZitadelManager(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestZitadelRequestJWTToken(t *testing.T) {
|
func TestZitadelRequestJWTToken(t *testing.T) {
|
||||||
|
|
||||||
type requestJWTTokenTest struct {
|
type requestJWTTokenTest struct {
|
||||||
name string
|
name string
|
||||||
inputCode int
|
inputCode int
|
||||||
@ -88,15 +87,14 @@ func TestZitadelRequestJWTToken(t *testing.T) {
|
|||||||
requestJWTTokenTestCase2 := requestJWTTokenTest{
|
requestJWTTokenTestCase2 := requestJWTTokenTest{
|
||||||
name: "Request Bad Status Code",
|
name: "Request Bad Status Code",
|
||||||
inputCode: 400,
|
inputCode: 400,
|
||||||
inputRespBody: "{}",
|
inputRespBody: "{\"error\": \"invalid_scope\", \"error_description\":\"openid missing\"}",
|
||||||
helper: JsonParser{},
|
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: "",
|
expectedToken: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range []requestJWTTokenTest{requestJWTTokenTesttCase1, requestJWTTokenTestCase2} {
|
for _, testCase := range []requestJWTTokenTest{requestJWTTokenTesttCase1, requestJWTTokenTestCase2} {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
jwtReqClient := mockHTTPClient{
|
jwtReqClient := mockHTTPClient{
|
||||||
resBody: testCase.inputRespBody,
|
resBody: testCase.inputRespBody,
|
||||||
code: testCase.inputCode,
|
code: testCase.inputCode,
|
||||||
@ -156,7 +154,7 @@ func TestZitadelParseRequestJWTResponse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
parseRequestJWTResponseTestCase2 := parseRequestJWTResponseTest{
|
parseRequestJWTResponseTestCase2 := parseRequestJWTResponseTest{
|
||||||
name: "Parse Bad json JWT Body",
|
name: "Parse Bad json JWT Body",
|
||||||
inputRespBody: "",
|
inputRespBody: "{}",
|
||||||
helper: JsonParser{},
|
helper: JsonParser{},
|
||||||
expectedToken: "",
|
expectedToken: "",
|
||||||
expectedExpiresIn: 0,
|
expectedExpiresIn: 0,
|
||||||
@ -254,7 +252,7 @@ func TestZitadelAuthenticate(t *testing.T) {
|
|||||||
inputCode: 400,
|
inputCode: 400,
|
||||||
inputResBody: "{}",
|
inputResBody: "{}",
|
||||||
helper: JsonParser{},
|
helper: JsonParser{},
|
||||||
expectedFuncExitErrDiff: fmt.Errorf("unable to get zitadel token, statusCode 400"),
|
expectedFuncExitErrDiff: fmt.Errorf("unable to get zitadel token, statusCode 400, zitadel: unknown error"),
|
||||||
expectedCode: 200,
|
expectedCode: 200,
|
||||||
expectedToken: "",
|
expectedToken: "",
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user