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

supabase / gotrue / 8135412125

04 Mar 2024 03:34AM UTC coverage: 65.142% (+0.1%) from 65.009%
8135412125

push

github

web-flow
fix: refactor request params to use generics (#1464)

## What kind of change does this PR introduce?
* Introduce a new method `retrieveRequestParams` which makes use of
generics to parse a request
* This will help to simplify parsing a request from:
```go

params := RequestParams{}
body, err := getBodyBytes(r)
if err != nil {
  return nil, badRequestError("Could not read body").WithInternalError(err)
}

if err := json.Unmarshal(body, &params); err != nil {
  return nil, badRequestError("Could not decode request params: %v", err)
}
```
to 
```go
params := &Request{}
err := retrieveRequestParams(req, params)
```

## TODO
- [x] Add type constraint instead of using `any`

48 of 69 new or added lines in 19 files covered. (69.57%)

19 existing lines in 14 files now uncovered.

7806 of 11983 relevant lines covered (65.14%)

59.29 hits per line

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

74.11
/internal/api/token.go
1
package api
2

3
import (
4
        "context"
5
        "errors"
6
        "net/http"
7
        "net/url"
8
        "strconv"
9
        "time"
10

11
        "fmt"
12

13
        "github.com/gofrs/uuid"
14
        "github.com/golang-jwt/jwt"
15
        "github.com/xeipuuv/gojsonschema"
16

17
        "github.com/supabase/auth/internal/conf"
18
        "github.com/supabase/auth/internal/hooks"
19
        "github.com/supabase/auth/internal/metering"
20
        "github.com/supabase/auth/internal/models"
21
        "github.com/supabase/auth/internal/observability"
22
        "github.com/supabase/auth/internal/storage"
23
)
24

25
// AccessTokenClaims is a struct thats used for JWT claims
26
type AccessTokenClaims struct {
27
        jwt.StandardClaims
28
        Email                         string                 `json:"email"`
29
        Phone                         string                 `json:"phone"`
30
        AppMetaData                   map[string]interface{} `json:"app_metadata"`
31
        UserMetaData                  map[string]interface{} `json:"user_metadata"`
32
        Role                          string                 `json:"role"`
33
        AuthenticatorAssuranceLevel   string                 `json:"aal,omitempty"`
34
        AuthenticationMethodReference []models.AMREntry      `json:"amr,omitempty"`
35
        SessionId                     string                 `json:"session_id,omitempty"`
36
        IsAnonymous                   bool                   `json:"is_anonymous"`
37
}
38

39
// AccessTokenResponse represents an OAuth2 success response
40
type AccessTokenResponse struct {
41
        Token                string             `json:"access_token"`
42
        TokenType            string             `json:"token_type"` // Bearer
43
        ExpiresIn            int                `json:"expires_in"`
44
        ExpiresAt            int64              `json:"expires_at"`
45
        RefreshToken         string             `json:"refresh_token"`
46
        User                 *models.User       `json:"user"`
47
        ProviderAccessToken  string             `json:"provider_token,omitempty"`
48
        ProviderRefreshToken string             `json:"provider_refresh_token,omitempty"`
49
        WeakPassword         *WeakPasswordError `json:"weak_password,omitempty"`
50
}
51

52
// AsRedirectURL encodes the AccessTokenResponse as a redirect URL that
53
// includes the access token response data in a URL fragment.
54
func (r *AccessTokenResponse) AsRedirectURL(redirectURL string, extraParams url.Values) string {
74✔
55
        extraParams.Set("access_token", r.Token)
74✔
56
        extraParams.Set("token_type", r.TokenType)
74✔
57
        extraParams.Set("expires_in", strconv.Itoa(r.ExpiresIn))
74✔
58
        extraParams.Set("expires_at", strconv.FormatInt(r.ExpiresAt, 10))
74✔
59
        extraParams.Set("refresh_token", r.RefreshToken)
74✔
60

74✔
61
        return redirectURL + "#" + extraParams.Encode()
74✔
62
}
74✔
63

64
// PasswordGrantParams are the parameters the ResourceOwnerPasswordGrant method accepts
65
type PasswordGrantParams struct {
66
        Email    string `json:"email"`
67
        Phone    string `json:"phone"`
68
        Password string `json:"password"`
69
}
70

71
// PKCEGrantParams are the parameters the PKCEGrant method accepts
72
type PKCEGrantParams struct {
73
        AuthCode     string `json:"auth_code"`
74
        CodeVerifier string `json:"code_verifier"`
75
}
76

77
const useCookieHeader = "x-use-cookie"
78
const InvalidLoginMessage = "Invalid login credentials"
79

80
// Token is the endpoint for OAuth access token requests
81
func (a *API) Token(w http.ResponseWriter, r *http.Request) error {
71✔
82
        ctx := r.Context()
71✔
83
        grantType := r.FormValue("grant_type")
71✔
84
        switch grantType {
71✔
85
        case "password":
9✔
86
                return a.ResourceOwnerPasswordGrant(ctx, w, r)
9✔
87
        case "refresh_token":
23✔
88
                return a.RefreshTokenGrant(ctx, w, r)
23✔
89
        case "id_token":
×
90
                return a.IdTokenGrant(ctx, w, r)
×
91
        case "pkce":
8✔
92
                return a.PKCE(ctx, w, r)
8✔
93
        default:
31✔
94
                return oauthError("unsupported_grant_type", "")
31✔
95
        }
96
}
97

98
// ResourceOwnerPasswordGrant implements the password grant type flow
99
func (a *API) ResourceOwnerPasswordGrant(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
9✔
100
        db := a.db.WithContext(ctx)
9✔
101

9✔
102
        params := &PasswordGrantParams{}
9✔
103
        if err := retrieveRequestParams(r, params); err != nil {
9✔
NEW
104
                return err
×
UNCOV
105
        }
×
106

107
        aud := a.requestAud(ctx, r)
9✔
108
        config := a.config
9✔
109

9✔
110
        if params.Email != "" && params.Phone != "" {
9✔
111
                return unprocessableEntityError("Only an email address or phone number should be provided on login.")
×
112
        }
×
113
        var user *models.User
9✔
114
        var grantParams models.GrantParams
9✔
115
        var provider string
9✔
116
        var err error
9✔
117

9✔
118
        grantParams.FillGrantParams(r)
9✔
119

9✔
120
        if params.Email != "" {
18✔
121
                provider = "email"
9✔
122
                if !config.External.Email.Enabled {
9✔
123
                        return badRequestError("Email logins are disabled")
×
124
                }
×
125
                user, err = models.FindUserByEmailAndAudience(db, params.Email, aud)
9✔
126
        } else if params.Phone != "" {
×
127
                provider = "phone"
×
128
                if !config.External.Phone.Enabled {
×
129
                        return badRequestError("Phone logins are disabled")
×
130
                }
×
131
                params.Phone = formatPhoneNumber(params.Phone)
×
132
                user, err = models.FindUserByPhoneAndAudience(db, params.Phone, aud)
×
133
        } else {
×
134
                return oauthError("invalid_grant", InvalidLoginMessage)
×
135
        }
×
136

137
        if err != nil {
9✔
138
                if models.IsNotFoundError(err) {
×
139
                        return oauthError("invalid_grant", InvalidLoginMessage)
×
140
                }
×
141
                return internalServerError("Database error querying schema").WithInternalError(err)
×
142
        }
143

144
        if user.IsBanned() {
10✔
145
                return oauthError("invalid_grant", InvalidLoginMessage)
1✔
146
        }
1✔
147

148
        isValidPassword := user.Authenticate(ctx, params.Password)
8✔
149

8✔
150
        var weakPasswordError *WeakPasswordError
8✔
151
        if isValidPassword {
16✔
152
                if err := a.checkPasswordStrength(ctx, params.Password); err != nil {
8✔
153
                        if wpe, ok := err.(*WeakPasswordError); ok {
×
154
                                weakPasswordError = wpe
×
155
                        } else {
×
156
                                observability.GetLogEntry(r).WithError(err).Warn("Password strength check on sign-in failed")
×
157
                        }
×
158
                }
159
        }
160

161
        if config.Hook.PasswordVerificationAttempt.Enabled {
10✔
162
                input := hooks.PasswordVerificationAttemptInput{
2✔
163
                        UserID: user.ID,
2✔
164
                        Valid:  isValidPassword,
2✔
165
                }
2✔
166
                output := hooks.PasswordVerificationAttemptOutput{}
2✔
167
                err := a.invokeHook(ctx, nil, &input, &output)
2✔
168
                if err != nil {
2✔
169
                        return err
×
170
                }
×
171

172
                if output.Decision == hooks.HookRejection {
3✔
173
                        if output.Message == "" {
1✔
174
                                output.Message = hooks.DefaultPasswordHookRejectionMessage
×
175
                        }
×
176
                        if output.ShouldLogoutUser {
1✔
177
                                if err := models.Logout(a.db, user.ID); err != nil {
×
178
                                        return err
×
179
                                }
×
180
                        }
181
                        return forbiddenError(output.Message)
1✔
182
                }
183
        }
184
        if !isValidPassword {
7✔
185
                return oauthError("invalid_grant", InvalidLoginMessage)
×
186
        }
×
187

188
        if params.Email != "" && !user.IsConfirmed() {
7✔
189
                return oauthError("invalid_grant", "Email not confirmed")
×
190
        } else if params.Phone != "" && !user.IsPhoneConfirmed() {
7✔
191
                return oauthError("invalid_grant", "Phone not confirmed")
×
192
        }
×
193

194
        var token *AccessTokenResponse
7✔
195
        err = db.Transaction(func(tx *storage.Connection) error {
14✔
196
                var terr error
7✔
197
                if terr = models.NewAuditLogEntry(r, tx, user, models.LoginAction, "", map[string]interface{}{
7✔
198
                        "provider": provider,
7✔
199
                }); terr != nil {
7✔
200
                        return terr
×
201
                }
×
202
                token, terr = a.issueRefreshToken(ctx, tx, user, models.PasswordGrant, grantParams)
7✔
203
                if terr != nil {
8✔
204
                        return terr
1✔
205
                }
1✔
206

207
                if terr = a.setCookieTokens(config, token, false, w); terr != nil {
6✔
208
                        return internalServerError("Failed to set JWT cookie. %s", terr)
×
209
                }
×
210
                return nil
6✔
211
        })
212
        if err != nil {
8✔
213
                return err
1✔
214
        }
1✔
215

216
        token.WeakPassword = weakPasswordError
6✔
217

6✔
218
        metering.RecordLogin("password", user.ID)
6✔
219
        return sendJSON(w, http.StatusOK, token)
6✔
220
}
221

222
func (a *API) PKCE(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
8✔
223
        db := a.db.WithContext(ctx)
8✔
224
        var grantParams models.GrantParams
8✔
225

8✔
226
        // There is a slight problem with this as it will pick-up the
8✔
227
        // User-Agent and IP addresses from the server if used on the server
8✔
228
        // side. Currently there's no mechanism to distinguish, but the server
8✔
229
        // can be told to at least propagate the User-Agent header.
8✔
230
        grantParams.FillGrantParams(r)
8✔
231

8✔
232
        params := &PKCEGrantParams{}
8✔
233

8✔
234
        if err := retrieveRequestParams(r, params); err != nil {
8✔
NEW
235
                return err
×
UNCOV
236
        }
×
237

238
        if params.AuthCode == "" || params.CodeVerifier == "" {
8✔
239
                return badRequestError("invalid request: both auth code and code verifier should be non-empty")
×
240
        }
×
241

242
        flowState, err := models.FindFlowStateByAuthCode(db, params.AuthCode)
8✔
243
        // Sanity check in case user ID was not set properly
8✔
244
        if models.IsNotFoundError(err) || flowState.UserID == nil {
11✔
245
                return forbiddenError("invalid flow state, no valid flow state found")
3✔
246
        } else if err != nil {
8✔
247
                return err
×
248
        }
×
249
        if flowState.IsExpired(a.config.External.FlowStateExpiryDuration) {
5✔
250
                return forbiddenError("invalid flow state, flow state has expired")
×
251
        }
×
252

253
        user, err := models.FindUserByID(db, *flowState.UserID)
5✔
254
        if err != nil {
5✔
255
                return err
×
256
        }
×
257
        if err := flowState.VerifyPKCE(params.CodeVerifier); err != nil {
5✔
258
                return forbiddenError(err.Error())
×
259
        }
×
260

261
        var token *AccessTokenResponse
5✔
262
        err = db.Transaction(func(tx *storage.Connection) error {
10✔
263
                var terr error
5✔
264
                authMethod, err := models.ParseAuthenticationMethod(flowState.AuthenticationMethod)
5✔
265
                if err != nil {
5✔
266
                        return err
×
267
                }
×
268
                if terr := models.NewAuditLogEntry(r, tx, user, models.LoginAction, "", map[string]interface{}{
5✔
269
                        "provider_type": flowState.ProviderType,
5✔
270
                }); terr != nil {
5✔
271
                        return terr
×
272
                }
×
273
                token, terr = a.issueRefreshToken(ctx, tx, user, authMethod, grantParams)
5✔
274
                if terr != nil {
5✔
275
                        return oauthError("server_error", terr.Error())
×
276
                }
×
277
                token.ProviderAccessToken = flowState.ProviderAccessToken
5✔
278
                // Because not all providers give out a refresh token
5✔
279
                // See corresponding OAuth2 spec: <https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1>
5✔
280
                if flowState.ProviderRefreshToken != "" {
7✔
281
                        token.ProviderRefreshToken = flowState.ProviderRefreshToken
2✔
282
                }
2✔
283
                if terr = tx.Destroy(flowState); terr != nil {
5✔
284
                        return err
×
285
                }
×
286
                return nil
5✔
287
        })
288
        if err != nil {
5✔
289
                return err
×
290
        }
×
291

292
        return sendJSON(w, http.StatusOK, token)
5✔
293
}
294

295
func (a *API) generateAccessToken(ctx context.Context, tx *storage.Connection, user *models.User, sessionId *uuid.UUID, authenticationMethod models.AuthenticationMethod) (string, int64, error) {
168✔
296
        config := a.config
168✔
297
        aal, amr := models.AAL1.String(), []models.AMREntry{}
168✔
298
        sid := ""
168✔
299
        if sessionId != nil {
307✔
300
                sid = sessionId.String()
139✔
301
                session, terr := models.FindSessionByID(tx, *sessionId, false)
139✔
302
                if terr != nil {
139✔
303
                        return "", 0, terr
×
304
                }
×
305
                aal, amr, terr = session.CalculateAALAndAMR(user)
139✔
306
                if terr != nil {
139✔
307
                        return "", 0, terr
×
308
                }
×
309
        }
310

311
        issuedAt := time.Now().UTC()
168✔
312
        expiresAt := issuedAt.Add(time.Second * time.Duration(config.JWT.Exp)).Unix()
168✔
313

168✔
314
        claims := &hooks.AccessTokenClaims{
168✔
315
                StandardClaims: jwt.StandardClaims{
168✔
316
                        Subject:   user.ID.String(),
168✔
317
                        Audience:  user.Aud,
168✔
318
                        IssuedAt:  issuedAt.Unix(),
168✔
319
                        ExpiresAt: expiresAt,
168✔
320
                        Issuer:    config.JWT.Issuer,
168✔
321
                },
168✔
322
                Email:                         user.GetEmail(),
168✔
323
                Phone:                         user.GetPhone(),
168✔
324
                AppMetaData:                   user.AppMetaData,
168✔
325
                UserMetaData:                  user.UserMetaData,
168✔
326
                Role:                          user.Role,
168✔
327
                SessionId:                     sid,
168✔
328
                AuthenticatorAssuranceLevel:   aal,
168✔
329
                AuthenticationMethodReference: amr,
168✔
330
                IsAnonymous:                   user.IsAnonymous,
168✔
331
        }
168✔
332

168✔
333
        var token *jwt.Token
168✔
334
        if config.Hook.CustomAccessToken.Enabled {
173✔
335
                input := hooks.CustomAccessTokenInput{
5✔
336
                        UserID:               user.ID,
5✔
337
                        Claims:               claims,
5✔
338
                        AuthenticationMethod: authenticationMethod.String(),
5✔
339
                }
5✔
340

5✔
341
                output := hooks.CustomAccessTokenOutput{}
5✔
342

5✔
343
                err := a.invokeHook(ctx, tx, &input, &output)
5✔
344
                if err != nil {
7✔
345
                        return "", 0, err
2✔
346
                }
2✔
347
                goTrueClaims := jwt.MapClaims(output.Claims)
3✔
348

3✔
349
                token = jwt.NewWithClaims(jwt.SigningMethodHS256, goTrueClaims)
3✔
350

351
        } else {
163✔
352
                token = jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
163✔
353
        }
163✔
354

355
        if config.JWT.KeyID != "" {
166✔
356
                if token.Header == nil {
×
357
                        token.Header = make(map[string]interface{})
×
358
                }
×
359

360
                token.Header["kid"] = config.JWT.KeyID
×
361
        }
362

363
        signed, err := token.SignedString([]byte(config.JWT.Secret))
166✔
364
        if err != nil {
166✔
365
                return "", 0, err
×
366
        }
×
367

368
        return signed, expiresAt, nil
166✔
369
}
370

371
func (a *API) issueRefreshToken(ctx context.Context, conn *storage.Connection, user *models.User, authenticationMethod models.AuthenticationMethod, grantParams models.GrantParams) (*AccessTokenResponse, error) {
111✔
372
        config := a.config
111✔
373

111✔
374
        now := time.Now()
111✔
375
        user.LastSignInAt = &now
111✔
376

111✔
377
        var tokenString string
111✔
378
        var expiresAt int64
111✔
379
        var refreshToken *models.RefreshToken
111✔
380

111✔
381
        err := conn.Transaction(func(tx *storage.Connection) error {
222✔
382
                var terr error
111✔
383

111✔
384
                refreshToken, terr = models.GrantAuthenticatedUser(tx, user, grantParams)
111✔
385
                if terr != nil {
111✔
386
                        return internalServerError("Database error granting user").WithInternalError(terr)
×
387
                }
×
388

389
                terr = models.AddClaimToSession(tx, *refreshToken.SessionId, authenticationMethod)
111✔
390
                if terr != nil {
111✔
391
                        return terr
×
392
                }
×
393

394
                tokenString, expiresAt, terr = a.generateAccessToken(ctx, tx, user, refreshToken.SessionId, authenticationMethod)
111✔
395
                if terr != nil {
112✔
396
                        // Account for Hook Error
1✔
397
                        httpErr, ok := terr.(*HTTPError)
1✔
398
                        if ok {
2✔
399
                                return httpErr
1✔
400
                        }
1✔
401
                        return internalServerError("error generating jwt token").WithInternalError(terr)
×
402
                }
403
                return nil
110✔
404
        })
405
        if err != nil {
112✔
406
                return nil, err
1✔
407
        }
1✔
408

409
        return &AccessTokenResponse{
110✔
410
                Token:        tokenString,
110✔
411
                TokenType:    "bearer",
110✔
412
                ExpiresIn:    config.JWT.Exp,
110✔
413
                ExpiresAt:    expiresAt,
110✔
414
                RefreshToken: refreshToken.Token,
110✔
415
                User:         user,
110✔
416
        }, nil
110✔
417
}
418

419
func (a *API) updateMFASessionAndClaims(r *http.Request, tx *storage.Connection, user *models.User, authenticationMethod models.AuthenticationMethod, grantParams models.GrantParams) (*AccessTokenResponse, error) {
5✔
420
        ctx := r.Context()
5✔
421
        config := a.config
5✔
422
        var tokenString string
5✔
423
        var expiresAt int64
5✔
424
        var refreshToken *models.RefreshToken
5✔
425
        currentClaims := getClaims(ctx)
5✔
426
        sessionId, err := uuid.FromString(currentClaims.SessionId)
5✔
427
        if err != nil {
5✔
428
                return nil, internalServerError("Cannot read SessionId claim as UUID").WithInternalError(err)
×
429
        }
×
430
        err = tx.Transaction(func(tx *storage.Connection) error {
10✔
431
                if terr := models.AddClaimToSession(tx, sessionId, authenticationMethod); terr != nil {
5✔
432
                        return terr
×
433
                }
×
434
                session, terr := models.FindSessionByID(tx, sessionId, false)
5✔
435
                if terr != nil {
5✔
436
                        return terr
×
437
                }
×
438
                currentToken, terr := models.FindTokenBySessionID(tx, &session.ID)
5✔
439
                if terr != nil {
5✔
440
                        return terr
×
441
                }
×
442
                if err := tx.Load(user, "Identities"); err != nil {
5✔
443
                        return err
×
444
                }
×
445
                // Swap to ensure current token is the latest one
446
                refreshToken, terr = models.GrantRefreshTokenSwap(r, tx, user, currentToken)
5✔
447
                if terr != nil {
5✔
448
                        return terr
×
449
                }
×
450
                aal, _, terr := session.CalculateAALAndAMR(user)
5✔
451
                if terr != nil {
5✔
452
                        return terr
×
453
                }
×
454

455
                if err := session.UpdateAssociatedFactor(tx, grantParams.FactorID); err != nil {
5✔
456
                        return err
×
457
                }
×
458
                if err := session.UpdateAssociatedAAL(tx, aal); err != nil {
5✔
459
                        return err
×
460
                }
×
461

462
                tokenString, expiresAt, terr = a.generateAccessToken(ctx, tx, user, &sessionId, models.TOTPSignIn)
5✔
463
                if terr != nil {
5✔
464
                        httpErr, ok := terr.(*HTTPError)
×
465
                        if ok {
×
466
                                return httpErr
×
467
                        }
×
468
                        return internalServerError("error generating jwt token").WithInternalError(terr)
×
469
                }
470
                return nil
5✔
471
        })
472
        if err != nil {
5✔
473
                return nil, err
×
474
        }
×
475
        return &AccessTokenResponse{
5✔
476
                Token:        tokenString,
5✔
477
                TokenType:    "bearer",
5✔
478
                ExpiresIn:    config.JWT.Exp,
5✔
479
                ExpiresAt:    expiresAt,
5✔
480
                RefreshToken: refreshToken.Token,
5✔
481
                User:         user,
5✔
482
        }, nil
5✔
483
}
484

485
// setCookieTokens sets the access_token & refresh_token in the cookies
486
func (a *API) setCookieTokens(config *conf.GlobalConfiguration, token *AccessTokenResponse, session bool, w http.ResponseWriter) error {
121✔
487
        // don't need to catch error here since we always set the cookie name
121✔
488
        _ = a.setCookieToken(config, "access-token", token.Token, session, w)
121✔
489
        _ = a.setCookieToken(config, "refresh-token", token.RefreshToken, session, w)
121✔
490
        return nil
121✔
491
}
121✔
492

493
func (a *API) setCookieToken(config *conf.GlobalConfiguration, name string, tokenString string, session bool, w http.ResponseWriter) error {
242✔
494
        if name == "" {
242✔
495
                return errors.New("failed to set cookie, invalid name")
×
496
        }
×
497
        cookieName := config.Cookie.Key + "-" + name
242✔
498
        exp := time.Second * time.Duration(config.Cookie.Duration)
242✔
499
        cookie := &http.Cookie{
242✔
500
                Name:     cookieName,
242✔
501
                Value:    tokenString,
242✔
502
                Secure:   true,
242✔
503
                HttpOnly: true,
242✔
504
                Path:     "/",
242✔
505
                Domain:   config.Cookie.Domain,
242✔
506
        }
242✔
507
        if !session {
484✔
508
                cookie.Expires = time.Now().Add(exp)
242✔
509
                cookie.MaxAge = config.Cookie.Duration
242✔
510
        }
242✔
511

512
        http.SetCookie(w, cookie)
242✔
513
        return nil
242✔
514
}
515

516
func (a *API) clearCookieTokens(config *conf.GlobalConfiguration, w http.ResponseWriter) {
9✔
517
        a.clearCookieToken(config, "access-token", w)
9✔
518
        a.clearCookieToken(config, "refresh-token", w)
9✔
519
}
9✔
520

521
func (a *API) clearCookieToken(config *conf.GlobalConfiguration, name string, w http.ResponseWriter) {
18✔
522
        cookieName := config.Cookie.Key
18✔
523
        if name != "" {
36✔
524
                cookieName += "-" + name
18✔
525
        }
18✔
526
        http.SetCookie(w, &http.Cookie{
18✔
527
                Name:     cookieName,
18✔
528
                Value:    "",
18✔
529
                Expires:  time.Now().Add(-1 * time.Hour * 10),
18✔
530
                MaxAge:   -1,
18✔
531
                Secure:   true,
18✔
532
                HttpOnly: true,
18✔
533
                Path:     "/",
18✔
534
                Domain:   config.Cookie.Domain,
18✔
535
        })
18✔
536
}
537

538
func validateTokenClaims(outputClaims map[string]interface{}) error {
4✔
539
        schemaLoader := gojsonschema.NewStringLoader(hooks.MinimumViableTokenSchema)
4✔
540

4✔
541
        documentLoader := gojsonschema.NewGoLoader(outputClaims)
4✔
542

4✔
543
        result, err := gojsonschema.Validate(schemaLoader, documentLoader)
4✔
544
        if err != nil {
4✔
545
                return err
×
546
        }
×
547

548
        if !result.Valid() {
5✔
549
                var errorMessages string
1✔
550

1✔
551
                for _, desc := range result.Errors() {
2✔
552
                        errorMessages += fmt.Sprintf("- %s\n", desc)
1✔
553
                        fmt.Printf("- %s\n", desc)
1✔
554
                }
1✔
555
                return fmt.Errorf("output claims do not conform to the expected schema: \n%s", errorMessages)
1✔
556

557
        }
558

559
        return nil
3✔
560
}
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