gitea源码

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