code.gitea.io/gitea@v1.22.3/services/auth/auth_token.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package auth 5 6 import ( 7 "context" 8 "crypto/sha256" 9 "crypto/subtle" 10 "encoding/hex" 11 "errors" 12 "strings" 13 "time" 14 15 auth_model "code.gitea.io/gitea/models/auth" 16 "code.gitea.io/gitea/modules/setting" 17 "code.gitea.io/gitea/modules/timeutil" 18 "code.gitea.io/gitea/modules/util" 19 ) 20 21 // Based on https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence#secure-remember-me-cookies 22 23 // The auth token consists of two parts: ID and token hash 24 // Every device login creates a new auth token with an individual id and hash. 25 // If a device uses the token to login into the instance, a fresh token gets generated which has the same id but a new hash. 26 27 var ( 28 ErrAuthTokenInvalidFormat = util.NewInvalidArgumentErrorf("auth token has an invalid format") 29 ErrAuthTokenExpired = util.NewInvalidArgumentErrorf("auth token has expired") 30 ErrAuthTokenInvalidHash = util.NewInvalidArgumentErrorf("auth token is invalid") 31 ) 32 33 func CheckAuthToken(ctx context.Context, value string) (*auth_model.AuthToken, error) { 34 if len(value) == 0 { 35 return nil, nil 36 } 37 38 parts := strings.SplitN(value, ":", 2) 39 if len(parts) != 2 { 40 return nil, ErrAuthTokenInvalidFormat 41 } 42 43 t, err := auth_model.GetAuthTokenByID(ctx, parts[0]) 44 if err != nil { 45 if errors.Is(err, util.ErrNotExist) { 46 return nil, ErrAuthTokenExpired 47 } 48 return nil, err 49 } 50 51 if t.ExpiresUnix < timeutil.TimeStampNow() { 52 return nil, ErrAuthTokenExpired 53 } 54 55 hashedToken := sha256.Sum256([]byte(parts[1])) 56 57 if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(hex.EncodeToString(hashedToken[:]))) == 0 { 58 // If an attacker steals a token and uses the token to create a new session the hash gets updated. 59 // When the victim uses the old token the hashes don't match anymore and the victim should be notified about the compromised token. 60 return nil, ErrAuthTokenInvalidHash 61 } 62 63 return t, nil 64 } 65 66 func RegenerateAuthToken(ctx context.Context, t *auth_model.AuthToken) (*auth_model.AuthToken, string, error) { 67 token, hash, err := generateTokenAndHash() 68 if err != nil { 69 return nil, "", err 70 } 71 72 newToken := &auth_model.AuthToken{ 73 ID: t.ID, 74 TokenHash: hash, 75 UserID: t.UserID, 76 ExpiresUnix: timeutil.TimeStampNow().AddDuration(time.Duration(setting.LogInRememberDays*24) * time.Hour), 77 } 78 79 if err := auth_model.UpdateAuthTokenByID(ctx, newToken); err != nil { 80 return nil, "", err 81 } 82 83 return newToken, token, nil 84 } 85 86 func CreateAuthTokenForUserID(ctx context.Context, userID int64) (*auth_model.AuthToken, string, error) { 87 t := &auth_model.AuthToken{ 88 UserID: userID, 89 ExpiresUnix: timeutil.TimeStampNow().AddDuration(time.Duration(setting.LogInRememberDays*24) * time.Hour), 90 } 91 92 var err error 93 t.ID, err = util.CryptoRandomString(10) 94 if err != nil { 95 return nil, "", err 96 } 97 98 token, hash, err := generateTokenAndHash() 99 if err != nil { 100 return nil, "", err 101 } 102 103 t.TokenHash = hash 104 105 if err := auth_model.InsertAuthToken(ctx, t); err != nil { 106 return nil, "", err 107 } 108 109 return t, token, nil 110 } 111 112 func generateTokenAndHash() (string, string, error) { 113 buf, err := util.CryptoRandomBytes(32) 114 if err != nil { 115 return "", "", err 116 } 117 118 token := hex.EncodeToString(buf) 119 120 hashedToken := sha256.Sum256([]byte(token)) 121 122 return token, hex.EncodeToString(hashedToken[:]), nil 123 }