mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-23 18:54:39 +02:00
fix(auth): avoid "compromised account" false-alarm when login with remember-me token (#39686)
1. Remember the last token that has been rotated and skip its validation 2. Don't alarm when the token mismatch occurs in a short window
This commit is contained in:
1 parent
8c7b8d085c
commit
2f1761e3fe
7 files changed
+118
-36
No files matched your search
+30
-22
@@ -78,49 +78,54 @@ func prepareCommonAuthPageData(ctx *context.Context, opt CommonAuthOptions) {
|
||||
|
||||
// autoSignIn reads cookie and try to auto-login.
|
||||
func autoSignIn(ctx *context.Context) (bool, error) {
|
||||
isSucceed := false
|
||||
defer func() {
|
||||
if !isSucceed {
|
||||
ctx.DeleteSiteCookie(setting.CookieRememberName)
|
||||
}
|
||||
}()
|
||||
cookieTokenValue := ctx.GetSiteCookie(setting.CookieRememberName)
|
||||
if cookieTokenValue == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := auth.DeleteExpiredAuthTokens(ctx); err != nil {
|
||||
log.Error("Failed to delete expired auth tokens: %v", err)
|
||||
}
|
||||
|
||||
t, err := auth_service.CheckAuthToken(ctx, ctx.GetSiteCookie(setting.CookieRememberName))
|
||||
if err != nil {
|
||||
switch err {
|
||||
case auth_service.ErrAuthTokenInvalidFormat, auth_service.ErrAuthTokenExpired:
|
||||
return false, nil
|
||||
deleteCookie := true
|
||||
defer func() {
|
||||
if deleteCookie {
|
||||
ctx.DeleteSiteCookie(setting.CookieRememberName)
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if t == nil {
|
||||
}()
|
||||
|
||||
t, err := auth_service.CheckAuthToken(ctx, cookieTokenValue)
|
||||
if errors.Is(err, auth_service.ErrAuthTokenStale) {
|
||||
deleteCookie = false
|
||||
return false, nil
|
||||
} else if errors.Is(err, auth_service.ErrAuthTokenInvalidFormat) || errors.Is(err, auth_service.ErrAuthTokenExpired) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
u, err := user_model.GetUserByID(ctx, t.UserID)
|
||||
if err != nil {
|
||||
if !user_model.IsErrUserNotExist(err) {
|
||||
return false, fmt.Errorf("GetUserByID: %w", err)
|
||||
}
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, fmt.Errorf("GetUserByID: %w", err)
|
||||
}
|
||||
|
||||
userHasTwoFactorAuth, err := auth.HasTwoFactorOrWebAuthn(ctx, u.ID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("HasTwoFactorOrWebAuthn: %w", err)
|
||||
}
|
||||
|
||||
isSucceed = true
|
||||
|
||||
nt, token, err := auth_service.RegenerateAuthToken(ctx, t)
|
||||
if err != nil {
|
||||
if errors.Is(err, auth_service.ErrAuthTokenStale) {
|
||||
deleteCookie = false
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
ctx.SetSiteCookie(setting.CookieRememberName, nt.ID+":"+token, setting.LogInRememberDays*timeutil.Day)
|
||||
deleteCookie = false
|
||||
|
||||
if err := regenerateSession(ctx, map[string]any{
|
||||
session.KeyUID: u.ID,
|
||||
@@ -130,7 +135,7 @@ func autoSignIn(ctx *context.Context) (bool, error) {
|
||||
}
|
||||
|
||||
if err := resetLocale(ctx, u); err != nil {
|
||||
return false, err
|
||||
log.Error("Failed to reset locale for user %d: %v", u.ID, err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
@@ -374,6 +379,9 @@ func handleSignIn(ctx *context.Context, u *user_model.User, remember bool) {
|
||||
|
||||
func handleSignInFull(ctx *context.Context, u *user_model.User, remember bool) {
|
||||
if remember {
|
||||
if err := auth.DeleteExpiredAuthTokens(ctx); err != nil {
|
||||
log.Error("Failed to delete expired auth tokens: %v", err)
|
||||
}
|
||||
nt, token, err := auth_service.CreateAuthTokenForUserID(ctx, u.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("CreateAuthTokenForUserID", err)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"gitea.dev/modules/test"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
auth_service "gitea.dev/services/auth"
|
||||
"gitea.dev/services/auth/source/oauth2"
|
||||
"gitea.dev/services/contexttest"
|
||||
"gitea.dev/services/forms"
|
||||
@@ -216,3 +217,22 @@ func TestRegisterOpenIDPostRejectsWrongCaptcha(t *testing.T) {
|
||||
assert.Equal(t, true, ctx.Data["Err_Captcha"])
|
||||
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "openid-captcha-user"})
|
||||
}
|
||||
|
||||
func TestAutoSignInStaleCookie(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
at, token, err := auth_service.CreateAuthTokenForUserID(t.Context(), 2)
|
||||
require.NoError(t, err)
|
||||
current, currentToken, err := auth_service.RegenerateAuthToken(t.Context(), at)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, resp := contexttest.MockContext(t, "/user/login")
|
||||
ctx.Req.AddCookie(&http.Cookie{Name: setting.CookieRememberName, Value: at.ID + ":" + token})
|
||||
succeeded, err := autoSignIn(ctx)
|
||||
require.NoError(t, err)
|
||||
require.False(t, succeeded)
|
||||
for _, cookie := range resp.Result().Cookies() {
|
||||
assert.NotEqual(t, setting.CookieRememberName, cookie.Name)
|
||||
}
|
||||
_, err = auth_service.CheckAuthToken(t.Context(), current.ID+":"+currentToken)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
Reference in new issue
Block a user