File: error.go

package info (click to toggle)
golang-github-zitadel-oidc 3.37.0-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental, sid, trixie
  • size: 1,484 kB
  • sloc: makefile: 5
file content (256 lines) | stat: -rw-r--r-- 7,007 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
package oidc

import (
	"encoding/json"
	"errors"
	"fmt"
	"log/slog"
)

type errorType string

const (
	InvalidRequest       errorType = "invalid_request"
	InvalidScope         errorType = "invalid_scope"
	InvalidClient        errorType = "invalid_client"
	InvalidGrant         errorType = "invalid_grant"
	UnauthorizedClient   errorType = "unauthorized_client"
	UnsupportedGrantType errorType = "unsupported_grant_type"
	ServerError          errorType = "server_error"
	InteractionRequired  errorType = "interaction_required"
	LoginRequired        errorType = "login_required"
	RequestNotSupported  errorType = "request_not_supported"

	// Additional error codes as defined in
	// https://www.rfc-editor.org/rfc/rfc8628#section-3.5
	// Device Access Token Response
	AuthorizationPending errorType = "authorization_pending"
	SlowDown             errorType = "slow_down"
	AccessDenied         errorType = "access_denied"
	ExpiredToken         errorType = "expired_token"

	// InvalidTarget error is returned by Token Exchange if
	// the requested target or audience is invalid.
	// [RFC 8693, Section 2.2.2: Error Response](https://www.rfc-editor.org/rfc/rfc8693#section-2.2.2)
	InvalidTarget errorType = "invalid_target"
)

var (
	ErrInvalidRequest = func() *Error {
		return &Error{
			ErrorType: InvalidRequest,
		}
	}
	ErrInvalidRequestRedirectURI = func() *Error {
		return &Error{
			ErrorType:        InvalidRequest,
			redirectDisabled: true,
		}
	}
	ErrInvalidScope = func() *Error {
		return &Error{
			ErrorType: InvalidScope,
		}
	}
	ErrInvalidClient = func() *Error {
		return &Error{
			ErrorType: InvalidClient,
		}
	}
	ErrInvalidGrant = func() *Error {
		return &Error{
			ErrorType: InvalidGrant,
		}
	}
	ErrUnauthorizedClient = func() *Error {
		return &Error{
			ErrorType: UnauthorizedClient,
		}
	}
	ErrUnsupportedGrantType = func() *Error {
		return &Error{
			ErrorType: UnsupportedGrantType,
		}
	}
	ErrServerError = func() *Error {
		return &Error{
			ErrorType: ServerError,
		}
	}
	ErrInteractionRequired = func() *Error {
		return &Error{
			ErrorType: InteractionRequired,
		}
	}
	ErrLoginRequired = func() *Error {
		return &Error{
			ErrorType: LoginRequired,
		}
	}
	ErrRequestNotSupported = func() *Error {
		return &Error{
			ErrorType: RequestNotSupported,
		}
	}

	// Device Access Token errors:
	ErrAuthorizationPending = func() *Error {
		return &Error{
			ErrorType:   AuthorizationPending,
			Description: "The client SHOULD repeat the access token request to the token endpoint, after interval from device authorization response.",
		}
	}
	ErrSlowDown = func() *Error {
		return &Error{
			ErrorType:   SlowDown,
			Description: "Polling should continue, but the interval MUST be increased by 5 seconds for this and all subsequent requests.",
		}
	}
	ErrAccessDenied = func() *Error {
		return &Error{
			ErrorType:   AccessDenied,
			Description: "The authorization request was denied.",
		}
	}
	ErrExpiredDeviceCode = func() *Error {
		return &Error{
			ErrorType:   ExpiredToken,
			Description: "The \"device_code\" has expired.",
		}
	}

	// Token exchange error
	ErrInvalidTarget = func() *Error {
		return &Error{
			ErrorType:   InvalidTarget,
			Description: "The requested audience or target is invalid.",
		}
	}
)

type Error struct {
	Parent           error     `json:"-" schema:"-"`
	ErrorType        errorType `json:"error" schema:"error"`
	Description      string    `json:"error_description,omitempty" schema:"error_description,omitempty"`
	State            string    `json:"state,omitempty" schema:"state,omitempty"`
	SessionState     string    `json:"session_state,omitempty" schema:"session_state,omitempty"`
	redirectDisabled bool      `schema:"-"`
	returnParent     bool      `schema:"-"`
}

func (e *Error) MarshalJSON() ([]byte, error) {
	m := struct {
		Error            errorType `json:"error"`
		ErrorDescription string    `json:"error_description,omitempty"`
		State            string    `json:"state,omitempty"`
		SessionState     string    `json:"session_state,omitempty"`
		Parent           string    `json:"parent,omitempty"`
	}{
		Error:            e.ErrorType,
		ErrorDescription: e.Description,
		State:            e.State,
		SessionState:     e.SessionState,
	}
	if e.returnParent {
		m.Parent = e.Parent.Error()
	}
	return json.Marshal(m)
}

func (e *Error) Error() string {
	message := "ErrorType=" + string(e.ErrorType)
	if e.Description != "" {
		message += " Description=" + e.Description
	}
	if e.Parent != nil {
		message += " Parent=" + e.Parent.Error()
	}
	return message
}

func (e *Error) Unwrap() error {
	return e.Parent
}

func (e *Error) Is(target error) bool {
	t, ok := target.(*Error)
	if !ok {
		return false
	}
	return e.ErrorType == t.ErrorType &&
		(e.Description == t.Description || t.Description == "") &&
		(e.State == t.State || t.State == "") &&
		(e.SessionState == t.SessionState || t.SessionState == "")
}

func (e *Error) WithParent(err error) *Error {
	e.Parent = err
	return e
}

// WithReturnParentToClient allows returning the set parent error to the HTTP client.
// Currently it only supports setting the parent inside JSON responses, not redirect URLs.
// As Go errors don't unmarshal well, only the marshaller is implemented for the moment.
//
// Warning: parent errors may contain sensitive data or unwanted details about the server status.
// Also, the `parent` field is not a standard error field and might confuse certain clients
// that require fully compliant responses.
func (e *Error) WithReturnParentToClient(b bool) *Error {
	e.returnParent = b
	return e
}

func (e *Error) WithDescription(desc string, args ...any) *Error {
	e.Description = fmt.Sprintf(desc, args...)
	return e
}

func (e *Error) IsRedirectDisabled() bool {
	return e.redirectDisabled
}

// DefaultToServerError checks if the error is an Error
// if not the provided error will be wrapped into a ServerError
func DefaultToServerError(err error, description string) *Error {
	oauth := new(Error)
	if ok := errors.As(err, &oauth); !ok {
		oauth.ErrorType = ServerError
		oauth.Description = description
		oauth.Parent = err
	}
	return oauth
}

func (e *Error) LogLevel() slog.Level {
	level := slog.LevelWarn
	if e.ErrorType == ServerError {
		level = slog.LevelError
	}
	if e.ErrorType == AuthorizationPending {
		level = slog.LevelInfo
	}
	return level
}

func (e *Error) LogValue() slog.Value {
	attrs := make([]slog.Attr, 0, 5)
	if e.Parent != nil {
		attrs = append(attrs, slog.Any("parent", e.Parent))
	}
	if e.Description != "" {
		attrs = append(attrs, slog.String("description", e.Description))
	}
	if e.ErrorType != "" {
		attrs = append(attrs, slog.String("type", string(e.ErrorType)))
	}
	if e.State != "" {
		attrs = append(attrs, slog.String("state", e.State))
	}
	if e.SessionState != "" {
		attrs = append(attrs, slog.String("session_state", e.SessionState))
	}
	if e.redirectDisabled {
		attrs = append(attrs, slog.Bool("redirect_disabled", e.redirectDisabled))
	}
	return slog.GroupValue(attrs...)
}