mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-31 06:21:11 +00:00 
			
		
		
		
	[v1.21] [GITEA] rework long-term authentication
- The current architecture is inherently insecure, because you can construct the 'secret' cookie value with values that are available in the database. Thus provides zero protection when a database is dumped/leaked. - This patch implements a new architecture that's inspired from: [Paragonie Initiative](https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence#secure-remember-me-cookies). - Integration testing is added to ensure the new mechanism works. - Removes a setting, because it's not used anymore. (cherry picked from commiteff097448b) [GITEA] rework long-term authentication (squash) add migration Reminder: the migration is run via integration tests as explained in the commit "[DB] run all Forgejo migrations in integration tests" (cherry picked from commit4accf7443c) (cherry picked from commit 99d06e344ebc3b50bafb2ac4473dd95f057d1ddc) (cherry picked from commitd8bc98a8f0) (cherry picked from commit6404845df9)
This commit is contained in:
		
					parent
					
						
							
								0c409950f1
							
						
					
				
			
			
				commit
				
					
						72bdd4f3b9
					
				
			
		
					 17 changed files with 365 additions and 154 deletions
				
			
		|  | @ -5,6 +5,8 @@ | |||
| package auth | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto/subtle" | ||||
| 	"encoding/hex" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
|  | @ -50,21 +52,47 @@ func AutoSignIn(ctx *context.Context) (bool, error) { | |||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	uname := ctx.GetSiteCookie(setting.CookieUserName) | ||||
| 	if len(uname) == 0 { | ||||
| 	authCookie := ctx.GetSiteCookie(setting.CookieRememberName) | ||||
| 	if len(authCookie) == 0 { | ||||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	isSucceed := false | ||||
| 	defer func() { | ||||
| 		if !isSucceed { | ||||
| 			log.Trace("auto-login cookie cleared: %s", uname) | ||||
| 			ctx.DeleteSiteCookie(setting.CookieUserName) | ||||
| 			log.Trace("Auto login cookie is cleared: %s", authCookie) | ||||
| 			ctx.DeleteSiteCookie(setting.CookieRememberName) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	u, err := user_model.GetUserByName(ctx, uname) | ||||
| 	lookupKey, validator, found := strings.Cut(authCookie, ":") | ||||
| 	if !found { | ||||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	authToken, err := auth.FindAuthToken(ctx, lookupKey) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, util.ErrNotExist) { | ||||
| 			return false, nil | ||||
| 		} | ||||
| 		return false, err | ||||
| 	} | ||||
| 
 | ||||
| 	if authToken.IsExpired() { | ||||
| 		err = auth.DeleteAuthToken(ctx, authToken) | ||||
| 		return false, err | ||||
| 	} | ||||
| 
 | ||||
| 	rawValidator, err := hex.DecodeString(validator) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 
 | ||||
| 	if subtle.ConstantTimeCompare([]byte(authToken.HashedValidator), []byte(auth.HashValidator(rawValidator))) == 0 { | ||||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	u, err := user_model.GetUserByID(ctx, authToken.UID) | ||||
| 	if err != nil { | ||||
| 		if !user_model.IsErrUserNotExist(err) { | ||||
| 			return false, fmt.Errorf("GetUserByName: %w", err) | ||||
|  | @ -72,17 +100,11 @@ func AutoSignIn(ctx *context.Context) (bool, error) { | |||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	if val, ok := ctx.GetSuperSecureCookie( | ||||
| 		base.EncodeMD5(u.Rands+u.Passwd), setting.CookieRememberName); !ok || val != u.Name { | ||||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	isSucceed = true | ||||
| 
 | ||||
| 	if err := updateSession(ctx, nil, map[string]any{ | ||||
| 		// Set session IDs | ||||
| 		"uid":   u.ID, | ||||
| 		"uname": u.Name, | ||||
| 		"uid": authToken.UID, | ||||
| 	}); err != nil { | ||||
| 		return false, fmt.Errorf("unable to updateSession: %w", err) | ||||
| 	} | ||||
|  | @ -291,10 +313,10 @@ func handleSignIn(ctx *context.Context, u *user_model.User, remember bool) { | |||
| 
 | ||||
| func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRedirect bool) string { | ||||
| 	if remember { | ||||
| 		days := 86400 * setting.LogInRememberDays | ||||
| 		ctx.SetSiteCookie(setting.CookieUserName, u.Name, days) | ||||
| 		ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd), | ||||
| 			setting.CookieRememberName, u.Name, days) | ||||
| 		if err := ctx.SetLTACookie(u); err != nil { | ||||
| 			ctx.ServerError("GenerateAuthToken", err) | ||||
| 			return setting.AppSubURL + "/" | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if err := updateSession(ctx, []string{ | ||||
|  | @ -307,8 +329,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe | |||
| 		"twofaRemember", | ||||
| 		"linkAccount", | ||||
| 	}, map[string]any{ | ||||
| 		"uid":   u.ID, | ||||
| 		"uname": u.Name, | ||||
| 		"uid": u.ID, | ||||
| 	}); err != nil { | ||||
| 		ctx.ServerError("RegenerateSession", err) | ||||
| 		return setting.AppSubURL + "/" | ||||
|  | @ -369,7 +390,6 @@ func getUserName(gothUser *goth.User) string { | |||
| func HandleSignOut(ctx *context.Context) { | ||||
| 	_ = ctx.Session.Flush() | ||||
| 	_ = ctx.Session.Destroy(ctx.Resp, ctx.Req) | ||||
| 	ctx.DeleteSiteCookie(setting.CookieUserName) | ||||
| 	ctx.DeleteSiteCookie(setting.CookieRememberName) | ||||
| 	ctx.Csrf.DeleteCookie(ctx) | ||||
| 	middleware.DeleteRedirectToCookie(ctx.Resp) | ||||
|  | @ -732,8 +752,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) { | |||
| 	log.Trace("User activated: %s", user.Name) | ||||
| 
 | ||||
| 	if err := updateSession(ctx, nil, map[string]any{ | ||||
| 		"uid":   user.ID, | ||||
| 		"uname": user.Name, | ||||
| 		"uid": user.ID, | ||||
| 	}); err != nil { | ||||
| 		log.Error("Unable to regenerate session for user: %-v with email: %s: %v", user, user.Email, err) | ||||
| 		ctx.ServerError("ActivateUserEmail", err) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue