• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / gotrue / 8316299588

17 Mar 2024 02:55PM UTC coverage: 64.923% (-0.3%) from 65.241%
8316299588

Pull #1474

github

J0
fix: remove unneeded if check
Pull Request #1474: feat: add custom sms hook

87 of 197 new or added lines in 13 files covered. (44.16%)

72 existing lines in 3 files now uncovered.

8005 of 12330 relevant lines covered (64.92%)

59.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.18
/internal/api/errors.go
1
package api
2

3
import (
4
        "context"
5
        "fmt"
6
        "net/http"
7
        "os"
8
        "runtime/debug"
9

10
        "github.com/pkg/errors"
11
        "github.com/supabase/auth/internal/observability"
12
        "github.com/supabase/auth/internal/utilities"
13
)
14

15
// Common error messages during signup flow
16
var (
17
        DuplicateEmailMsg       = "A user with this email address has already been registered"
18
        DuplicatePhoneMsg       = "A user with this phone number has already been registered"
19
        UserExistsError   error = errors.New("user already exists")
20
)
21

22
const InvalidChannelError = "Invalid channel, supported values are 'sms' or 'whatsapp'"
23

24
var oauthErrorMap = map[int]string{
25
        http.StatusBadRequest:          "invalid_request",
26
        http.StatusUnauthorized:        "unauthorized_client",
27
        http.StatusForbidden:           "access_denied",
28
        http.StatusInternalServerError: "server_error",
29
        http.StatusServiceUnavailable:  "temporarily_unavailable",
30
}
31

32
// OAuthError is the JSON handler for OAuth2 error responses
33
type OAuthError struct {
34
        Err             string `json:"error"`
35
        Description     string `json:"error_description,omitempty"`
36
        InternalError   error  `json:"-"`
37
        InternalMessage string `json:"-"`
38
}
39

40
func (e *OAuthError) Error() string {
88✔
41
        if e.InternalMessage != "" {
98✔
42
                return e.InternalMessage
10✔
43
        }
10✔
44
        return fmt.Sprintf("%s: %s", e.Err, e.Description)
78✔
45
}
46

47
// WithInternalError adds internal error information to the error
48
func (e *OAuthError) WithInternalError(err error) *OAuthError {
×
49
        e.InternalError = err
×
50
        return e
×
51
}
×
52

53
// WithInternalMessage adds internal message information to the error
54
func (e *OAuthError) WithInternalMessage(fmtString string, args ...interface{}) *OAuthError {
5✔
55
        e.InternalMessage = fmt.Sprintf(fmtString, args...)
5✔
56
        return e
5✔
57
}
5✔
58

59
// Cause returns the root cause error
60
func (e *OAuthError) Cause() error {
44✔
61
        if e.InternalError != nil {
44✔
62
                return e.InternalError
×
63
        }
×
64
        return e
44✔
65
}
66

67
func oauthError(err string, description string) *OAuthError {
44✔
68
        return &OAuthError{Err: err, Description: description}
44✔
69
}
44✔
70

71
func badRequestError(errorCode ErrorCode, fmtString string, args ...interface{}) *HTTPError {
68✔
72
        return httpError(http.StatusBadRequest, errorCode, fmtString, args...)
68✔
73
}
68✔
74

75
func internalServerError(fmtString string, args ...interface{}) *HTTPError {
39✔
76
        return httpError(http.StatusInternalServerError, ErrorCodeUnexpectedFailure, fmtString, args...)
39✔
77
}
39✔
78

79
func notFoundError(errorCode ErrorCode, fmtString string, args ...interface{}) *HTTPError {
42✔
80
        return httpError(http.StatusNotFound, errorCode, fmtString, args...)
42✔
81
}
42✔
82

83
func forbiddenError(errorCode ErrorCode, fmtString string, args ...interface{}) *HTTPError {
19✔
84
        return httpError(http.StatusForbidden, errorCode, fmtString, args...)
19✔
85
}
19✔
86

87
func unprocessableEntityError(errorCode ErrorCode, fmtString string, args ...interface{}) *HTTPError {
32✔
88
        return httpError(http.StatusUnprocessableEntity, errorCode, fmtString, args...)
32✔
89
}
32✔
90

91
func tooManyRequestsError(errorCode ErrorCode, fmtString string, args ...interface{}) *HTTPError {
7✔
92
        return httpError(http.StatusTooManyRequests, errorCode, fmtString, args...)
7✔
93
}
7✔
94

95
func conflictError(fmtString string, args ...interface{}) *HTTPError {
×
96
        return httpError(http.StatusConflict, ErrorCodeConflict, fmtString, args...)
×
97
}
×
98

NEW
UNCOV
99
func gatewayTimeoutError(errorCode ErrorCode, fmtString string, args ...interface{}) *HTTPError {
×
NEW
UNCOV
100
        return httpError(http.StatusGatewayTimeout, errorCode, fmtString, args...)
×
NEW
UNCOV
101
}
×
102

103
// HTTPError is an error with a message and an HTTP status code.
104
type HTTPError struct {
105
        HTTPStatus      int    `json:"code"`                 // do not rename the JSON tags!
106
        ErrorCode       string `json:"error_code,omitempty"` // do not rename the JSON tags!
107
        Message         string `json:"msg"`                  // do not rename the JSON tags!
108
        InternalError   error  `json:"-"`
109
        InternalMessage string `json:"-"`
110
        ErrorID         string `json:"error_id,omitempty"`
111
}
112

113
func (e *HTTPError) Error() string {
362✔
114
        if e.InternalMessage != "" {
406✔
115
                return e.InternalMessage
44✔
116
        }
44✔
117
        return fmt.Sprintf("%d: %s", e.HTTPStatus, e.Message)
318✔
118
}
119

120
func (e *HTTPError) Is(target error) bool {
15✔
121
        return e.Error() == target.Error()
15✔
122
}
15✔
123

124
// Cause returns the root cause error
125
func (e *HTTPError) Cause() error {
180✔
126
        if e.InternalError != nil {
200✔
127
                return e.InternalError
20✔
128
        }
20✔
129
        return e
160✔
130
}
131

132
// WithInternalError adds internal error information to the error
133
func (e *HTTPError) WithInternalError(err error) *HTTPError {
25✔
134
        e.InternalError = err
25✔
135
        return e
25✔
136
}
25✔
137

138
// WithInternalMessage adds internal message information to the error
139
func (e *HTTPError) WithInternalMessage(fmtString string, args ...interface{}) *HTTPError {
22✔
140
        e.InternalMessage = fmt.Sprintf(fmtString, args...)
22✔
141
        return e
22✔
142
}
22✔
143

144
func httpError(httpStatus int, errorCode ErrorCode, fmtString string, args ...interface{}) *HTTPError {
213✔
145
        return &HTTPError{
213✔
146
                HTTPStatus: httpStatus,
213✔
147
                ErrorCode:  errorCode,
213✔
148
                Message:    fmt.Sprintf(fmtString, args...),
213✔
149
        }
213✔
150
}
213✔
151

152
// Recoverer is a middleware that recovers from panics, logs the panic (and a
153
// backtrace), and returns a HTTP 500 (Internal Server Error) status if
154
// possible. Recoverer prints a request ID if one is provided.
155
func recoverer(w http.ResponseWriter, r *http.Request) (context.Context, error) {
640✔
156
        defer func() {
1,280✔
157
                if rvr := recover(); rvr != nil {
640✔
158

×
159
                        logEntry := observability.GetLogEntry(r)
×
160
                        if logEntry != nil {
×
161
                                logEntry.Panic(rvr, debug.Stack())
×
UNCOV
162
                        } else {
×
163
                                fmt.Fprintf(os.Stderr, "Panic: %+v\n", rvr)
×
164
                                debug.PrintStack()
×
165
                        }
×
166

167
                        se := &HTTPError{
×
UNCOV
168
                                HTTPStatus: http.StatusInternalServerError,
×
UNCOV
169
                                Message:    http.StatusText(http.StatusInternalServerError),
×
UNCOV
170
                        }
×
UNCOV
171
                        HandleResponseError(se, w, r)
×
172
                }
173
        }()
174

175
        return nil, nil
640✔
176
}
177

178
// ErrorCause is an error interface that contains the method Cause() for returning root cause errors
179
type ErrorCause interface {
180
        Cause() error
181
}
182

183
type HTTPErrorResponse20240101 struct {
184
        Code    ErrorCode `json:"code"`
185
        Message string    `json:"message"`
186
}
187

188
func HandleResponseError(err error, w http.ResponseWriter, r *http.Request) {
164✔
189
        log := observability.GetLogEntry(r)
164✔
190
        errorID := getRequestID(r.Context())
164✔
191

164✔
192
        apiVersion, averr := DetermineClosestAPIVersion(r.Header.Get(APIVersionHeaderName))
164✔
193
        if averr != nil {
164✔
UNCOV
194
                log.WithError(averr).Warn("Invalid version passed to " + APIVersionHeaderName + " header, defaulting to initial version")
×
195
        } else if apiVersion != APIVersionInitial {
167✔
196
                // Echo back the determined API version from the request
3✔
197
                w.Header().Set(APIVersionHeaderName, FormatAPIVersion(apiVersion))
3✔
198
        }
3✔
199

200
        switch e := err.(type) {
164✔
201
        case *WeakPasswordError:
2✔
202
                if apiVersion.Compare(APIVersion20240101) >= 0 {
2✔
203
                        var output struct {
×
204
                                HTTPErrorResponse20240101
×
205
                                Payload struct {
×
206
                                        Reasons []string `json:"reasons,omitempty"`
×
207
                                } `json:"weak_password,omitempty"`
×
208
                        }
×
209

×
210
                        output.Code = ErrorCodeWeakPassword
×
211
                        output.Message = e.Message
×
212
                        output.Payload.Reasons = e.Reasons
×
UNCOV
213

×
UNCOV
214
                        if jsonErr := sendJSON(w, http.StatusUnprocessableEntity, output); jsonErr != nil {
×
UNCOV
215
                                HandleResponseError(jsonErr, w, r)
×
UNCOV
216
                        }
×
217

218
                } else {
2✔
219
                        var output struct {
2✔
220
                                HTTPError
2✔
221
                                Payload struct {
2✔
222
                                        Reasons []string `json:"reasons,omitempty"`
2✔
223
                                } `json:"weak_password,omitempty"`
2✔
224
                        }
2✔
225

2✔
226
                        output.HTTPStatus = http.StatusUnprocessableEntity
2✔
227
                        output.ErrorCode = ErrorCodeWeakPassword
2✔
228
                        output.Message = e.Message
2✔
229
                        output.Payload.Reasons = e.Reasons
2✔
230

2✔
231
                        if jsonErr := sendJSON(w, output.HTTPStatus, output); jsonErr != nil {
2✔
UNCOV
232
                                HandleResponseError(jsonErr, w, r)
×
UNCOV
233
                        }
×
234
                }
235

236
        case *HTTPError:
113✔
237
                if e.HTTPStatus >= http.StatusInternalServerError {
134✔
238
                        e.ErrorID = errorID
21✔
239
                        // this will get us the stack trace too
21✔
240
                        log.WithError(e.Cause()).Error(e.Error())
21✔
241
                } else {
113✔
242
                        log.WithError(e.Cause()).Info(e.Error())
92✔
243
                }
92✔
244

245
                if apiVersion.Compare(APIVersion20240101) >= 0 {
116✔
246
                        resp := HTTPErrorResponse20240101{
3✔
247
                                Code:    e.ErrorCode,
3✔
248
                                Message: e.Message,
3✔
249
                        }
3✔
250

3✔
251
                        if resp.Code == "" {
5✔
252
                                if e.HTTPStatus == http.StatusInternalServerError {
3✔
253
                                        resp.Code = ErrorCodeUnexpectedFailure
1✔
254
                                } else {
2✔
255
                                        resp.Code = ErrorCodeUnknown
1✔
256
                                }
1✔
257
                        }
258

259
                        if jsonErr := sendJSON(w, e.HTTPStatus, resp); jsonErr != nil {
3✔
UNCOV
260
                                HandleResponseError(jsonErr, w, r)
×
UNCOV
261
                        }
×
262
                } else {
110✔
263
                        if e.ErrorCode == "" {
112✔
264
                                if e.HTTPStatus == http.StatusInternalServerError {
3✔
265
                                        e.ErrorCode = ErrorCodeUnexpectedFailure
1✔
266
                                } else {
2✔
267
                                        e.ErrorCode = ErrorCodeUnknown
1✔
268
                                }
1✔
269
                        }
270

271
                        // Provide better error messages for certain user-triggered Postgres errors.
272
                        if pgErr := utilities.NewPostgresError(e.InternalError); pgErr != nil {
111✔
273
                                if jsonErr := sendJSON(w, pgErr.HttpStatusCode, pgErr); jsonErr != nil {
1✔
UNCOV
274
                                        HandleResponseError(jsonErr, w, r)
×
UNCOV
275
                                }
×
276
                                return
1✔
277
                        }
278

279
                        if jsonErr := sendJSON(w, e.HTTPStatus, e); jsonErr != nil {
109✔
UNCOV
280
                                HandleResponseError(jsonErr, w, r)
×
UNCOV
281
                        }
×
282
                }
283

284
        case *OAuthError:
44✔
285
                log.WithError(e.Cause()).Info(e.Error())
44✔
286
                if jsonErr := sendJSON(w, http.StatusBadRequest, e); jsonErr != nil {
44✔
UNCOV
287
                        HandleResponseError(jsonErr, w, r)
×
UNCOV
288
                }
×
289

290
        case ErrorCause:
5✔
291
                HandleResponseError(e.Cause(), w, r)
5✔
292

293
        default:
×
294
                log.WithError(e).Errorf("Unhandled server error: %s", e.Error())
×
295

×
296
                if apiVersion.Compare(APIVersion20240101) >= 0 {
×
297
                        resp := HTTPErrorResponse20240101{
×
298
                                Code:    ErrorCodeUnexpectedFailure,
×
299
                                Message: "Unexpected failure, please check server logs for more information",
×
300
                        }
×
301

×
302
                        if jsonErr := sendJSON(w, http.StatusInternalServerError, resp); jsonErr != nil {
×
303
                                HandleResponseError(jsonErr, w, r)
×
304
                        }
×
305
                } else {
×
306
                        httpError := HTTPError{
×
307
                                HTTPStatus: http.StatusInternalServerError,
×
308
                                ErrorCode:  ErrorCodeUnexpectedFailure,
×
309
                                Message:    "Unexpected failure, please check server logs for more information",
×
310
                        }
×
UNCOV
311

×
UNCOV
312
                        if jsonErr := sendJSON(w, http.StatusInternalServerError, httpError); jsonErr != nil {
×
UNCOV
313
                                HandleResponseError(jsonErr, w, r)
×
UNCOV
314
                        }
×
315
                }
316
        }
317
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc