1
1
//
2
- // Copyright 2024 The Chainloop Authors.
2
+ // Copyright 2024-2025 The Chainloop Authors.
3
3
//
4
4
// Licensed under the Apache License, Version 2.0 (the "License");
5
5
// you may not use this file except in compliance with the License.
@@ -59,8 +59,34 @@ const (
59
59
devUserDuration = 30 * longLivedDuration
60
60
)
61
61
62
+ type oauthResp struct {
63
+ code int
64
+ err error
65
+ showErrToUser bool
66
+ }
67
+
68
+ // This is used to provide by default a generic error message to the user
69
+ // unless showErrToUser is true
70
+ func (e * oauthResp ) ErrorMessage (l * log.Helper ) string {
71
+ if e .err != nil {
72
+ // If the error is an internal server error, log it and raise it masked
73
+ if e .code == http .StatusInternalServerError {
74
+ return sl .LogAndMaskErr (e .err , l ).Error ()
75
+ }
76
+ // otherwise return the error message to the user
77
+ // or the default status text
78
+ if e .showErrToUser {
79
+ return e .err .Error ()
80
+ }
81
+
82
+ return http .StatusText (e .code )
83
+ }
84
+
85
+ return ""
86
+ }
87
+
62
88
type oauthHandler struct {
63
- H func (* AuthService , http.ResponseWriter , * http.Request ) ( int , error )
89
+ H func (* AuthService , http.ResponseWriter , * http.Request ) * oauthResp
64
90
svc * AuthService
65
91
}
66
92
@@ -164,17 +190,16 @@ func (svc *AuthService) RegisterLoginHandler() http.Handler {
164
190
165
191
// Implement http.Handler interface
166
192
func (h oauthHandler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
167
- status , err := h .H (h .svc , w , r )
168
- if err != nil {
169
- http .Error (w , http .StatusText (status ), status )
193
+ if err := h .H (h .svc , w , r ); err != nil {
194
+ http .Error (w , err .ErrorMessage (h .svc .log ), err .code )
170
195
}
171
196
}
172
197
173
- func loginHandler (svc * AuthService , w http.ResponseWriter , r * http.Request ) ( int , error ) {
198
+ func loginHandler (svc * AuthService , w http.ResponseWriter , r * http.Request ) * oauthResp {
174
199
b := make ([]byte , 16 )
175
200
_ , err := rand .Read (b )
176
201
if err != nil {
177
- return http .StatusInternalServerError , sl . LogAndMaskErr ( err , nil )
202
+ return & oauthResp { http .StatusInternalServerError , fmt . Errorf ( "failed to generate random string: %w" , err ), false }
178
203
}
179
204
180
205
// Store a random string to check it in the oauth callback
@@ -196,7 +221,7 @@ func loginHandler(svc *AuthService, w http.ResponseWriter, r *http.Request) (int
196
221
if connectionStr != "" {
197
222
uri , err := url .Parse (authorizationURI )
198
223
if err != nil {
199
- return http .StatusInternalServerError , sl . LogAndMaskErr ( err , svc . log )
224
+ return & oauthResp { http .StatusInternalServerError , fmt . Errorf ( "failed to parse authorization URI: %w" , err ), false }
200
225
}
201
226
q := uri .Query ()
202
227
q .Set ("connection" , connectionStr )
@@ -205,7 +230,7 @@ func loginHandler(svc *AuthService, w http.ResponseWriter, r *http.Request) (int
205
230
}
206
231
207
232
http .Redirect (w , r , authorizationURI , http .StatusFound )
208
- return http .StatusTemporaryRedirect , nil
233
+ return & oauthResp { http .StatusTemporaryRedirect , nil , false }
209
234
}
210
235
211
236
// Extract custom claims
@@ -231,35 +256,35 @@ func (c *upstreamOIDCclaims) preferredEmail() string {
231
256
return c .Email
232
257
}
233
258
234
- type errorWithCode struct {
235
- code int
236
- error
237
- }
238
-
239
- func callbackHandler (svc * AuthService , w http.ResponseWriter , r * http.Request ) (int , error ) {
259
+ func callbackHandler (svc * AuthService , w http.ResponseWriter , r * http.Request ) * oauthResp {
240
260
ctx := context .Background ()
261
+ // if OIDC provider returns an error, return it to and show it to the user
262
+ if desc := r .URL .Query ().Get ("error_description" ); desc != "" {
263
+ return & oauthResp {http .StatusUnauthorized , errors .New (desc ), true }
264
+ }
265
+
241
266
// Get information from google OIDC token
242
267
claims , errWithCode := extractUserInfoFromToken (ctx , svc , r )
243
268
if errWithCode != nil {
244
- return errWithCode .code , sl . LogAndMaskErr ( errWithCode .error , svc . log )
269
+ return & oauthResp { errWithCode .code , errWithCode .err , errWithCode . showErrToUser }
245
270
}
246
271
247
272
// Create user if needed
248
273
u , err := svc .userUseCase .FindOrCreateByEmail (ctx , claims .preferredEmail ())
249
274
if err != nil {
250
- return http .StatusInternalServerError , sl . LogAndMaskErr ( err , svc . log )
275
+ return & oauthResp { http .StatusInternalServerError , fmt . Errorf ( "failed to find or create user: %w" , err ), false }
251
276
}
252
277
253
278
// Accept any pending invites
254
279
if err := svc .orgInvitesUseCase .AcceptPendingInvitations (ctx , u .Email ); err != nil {
255
- return http .StatusInternalServerError , sl . LogAndMaskErr ( err , svc . log )
280
+ return & oauthResp { http .StatusInternalServerError , fmt . Errorf ( "failed to accept pending invitations: %w" , err ), false }
256
281
}
257
282
258
283
// Set the expiration
259
284
expiration := shortLivedDuration
260
285
longLived , err := r .Cookie (cookieLongLived )
261
286
if err != nil {
262
- return http .StatusInternalServerError , sl . LogAndMaskErr ( err , svc . log )
287
+ return & oauthResp { http .StatusInternalServerError , fmt . Errorf ( "failed to get long lived cookie: %w" , err ), false }
263
288
}
264
289
265
290
if longLived .Value == "true" {
@@ -269,32 +294,32 @@ func callbackHandler(svc *AuthService, w http.ResponseWriter, r *http.Request) (
269
294
// Generate user token
270
295
userToken , err := generateUserJWT (u .ID , svc .authConfig .GeneratedJwsHmacSecret , expiration )
271
296
if err != nil {
272
- return http .StatusInternalServerError , sl . LogAndMaskErr ( err , svc . log )
297
+ return & oauthResp { http .StatusInternalServerError , fmt . Errorf ( "failed to generate user token: %w" , err ), false }
273
298
}
274
299
275
300
// Either redirect or render the token if fallback is specified
276
301
// Callback URL from the cookie
277
302
callbackURLFromCookie , err := r .Cookie (cookieCallback )
278
303
if err != nil {
279
- return http .StatusInternalServerError , sl . LogAndMaskErr ( err , svc . log )
304
+ return & oauthResp { http .StatusInternalServerError , fmt . Errorf ( "failed to get callback URL from cookie: %w" , err ), false }
280
305
}
281
306
282
307
callbackValue := callbackURLFromCookie .Value
283
308
284
309
// There is no callback, just render the token
285
310
if callbackValue == "" {
286
311
fmt .Fprintf (w , "copy this token and paste it in your terminal window\n \n %s" , userToken )
287
- return http .StatusOK , nil
312
+ return & oauthResp { http .StatusOK , nil , false }
288
313
}
289
314
290
315
// Redirect to the callback URL
291
316
callbackURL , err := crafCallbackURL (callbackValue , userToken )
292
317
if err != nil {
293
- return http .StatusInternalServerError , sl . LogAndMaskErr ( err , svc . log )
318
+ return & oauthResp { http .StatusInternalServerError , fmt . Errorf ( "failed to craft callback URL: %w" , err ), false }
294
319
}
295
320
296
321
http .Redirect (w , r , callbackURL , http .StatusFound )
297
- return http .StatusTemporaryRedirect , nil
322
+ return & oauthResp { http .StatusTemporaryRedirect , nil , false }
298
323
}
299
324
300
325
func crafCallbackURL (callback , userToken string ) (string , error ) {
@@ -311,14 +336,15 @@ func crafCallbackURL(callback, userToken string) (string, error) {
311
336
}
312
337
313
338
// Returns the claims from the OIDC token received during the OIDC callback
314
- func extractUserInfoFromToken (ctx context.Context , svc * AuthService , r * http.Request ) (* upstreamOIDCclaims , * errorWithCode ) {
339
+ func extractUserInfoFromToken (ctx context.Context , svc * AuthService , r * http.Request ) (* upstreamOIDCclaims , * oauthResp ) {
315
340
cookieState , err := r .Cookie (cookieOauthStateName )
341
+ // if the cookie is not found, it likely means the authentication process has expired
316
342
if err != nil {
317
- return nil , & errorWithCode {http .StatusUnauthorized , fmt . Errorf ( "retrieving cookie %s: %w" , cookieOauthStateName , err ) }
343
+ return nil , & oauthResp {http .StatusUnauthorized , errors . New ( "the authentication process has expired, please try again" ), true }
318
344
}
319
345
320
346
if r .URL .Query ().Get ("state" ) != cookieState .Value {
321
- return nil , & errorWithCode {http .StatusUnauthorized , errors .New ("oauth state does not match" ) }
347
+ return nil , & oauthResp {http .StatusUnauthorized , errors .New ("the authentication was invalid, please try again" ), true }
322
348
}
323
349
324
350
code := r .URL .Query ().Get ("code" )
@@ -329,23 +355,23 @@ func extractUserInfoFromToken(ctx context.Context, svc *AuthService, r *http.Req
329
355
// Exchange the code for a token
330
356
oauth2Token , err := svc .authenticator .Exchange (ctx , code )
331
357
if err != nil {
332
- return nil , & errorWithCode {http .StatusUnauthorized , err }
358
+ return nil , & oauthResp {http .StatusUnauthorized , err , false }
333
359
}
334
360
335
361
// It's a valid Oauth2 token
336
362
if ! oauth2Token .Valid () {
337
- return nil , & errorWithCode {http .StatusUnauthorized , errors .New ("retrieved invalid Token" )}
363
+ return nil , & oauthResp {http .StatusUnauthorized , errors .New ("retrieved invalid Token" ), false }
338
364
}
339
365
340
366
// Parse and verify ID token content and signature
341
367
idToken , err := svc .authenticator .VerifyIDToken (ctx , oauth2Token )
342
368
if err != nil {
343
- return nil , & errorWithCode {http .StatusInternalServerError , err }
369
+ return nil , & oauthResp {http .StatusInternalServerError , err , false }
344
370
}
345
371
346
372
var claims * upstreamOIDCclaims
347
373
if err := idToken .Claims (& claims ); err != nil {
348
- return nil , & errorWithCode {http .StatusInternalServerError , err }
374
+ return nil , & oauthResp {http .StatusInternalServerError , err , false }
349
375
}
350
376
351
377
return claims , nil
@@ -367,7 +393,7 @@ func generateUserJWT(userID, passphrase string, expiration time.Duration) (strin
367
393
}
368
394
369
395
func setOauthCookie (w http.ResponseWriter , name , value string ) {
370
- http .SetCookie (w , & http.Cookie {Name : name , Value : value , Path : "/" , Expires : time .Now ().Add (5 * time .Minute )})
396
+ http .SetCookie (w , & http.Cookie {Name : name , Value : value , Path : "/" , Expires : time .Now ().Add (10 * time .Minute )})
371
397
}
372
398
373
399
func generateAndLogDevUser (userUC * biz.UserUseCase , log * log.Helper , authConfig * conf.Auth ) error {
0 commit comments