You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

token.go 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package auth
  5. import (
  6. "crypto/subtle"
  7. "fmt"
  8. "time"
  9. "code.gitea.io/gitea/models/db"
  10. "code.gitea.io/gitea/modules/base"
  11. "code.gitea.io/gitea/modules/setting"
  12. "code.gitea.io/gitea/modules/timeutil"
  13. "code.gitea.io/gitea/modules/util"
  14. gouuid "github.com/google/uuid"
  15. lru "github.com/hashicorp/golang-lru"
  16. )
  17. // ErrAccessTokenNotExist represents a "AccessTokenNotExist" kind of error.
  18. type ErrAccessTokenNotExist struct {
  19. Token string
  20. }
  21. // IsErrAccessTokenNotExist checks if an error is a ErrAccessTokenNotExist.
  22. func IsErrAccessTokenNotExist(err error) bool {
  23. _, ok := err.(ErrAccessTokenNotExist)
  24. return ok
  25. }
  26. func (err ErrAccessTokenNotExist) Error() string {
  27. return fmt.Sprintf("access token does not exist [sha: %s]", err.Token)
  28. }
  29. func (err ErrAccessTokenNotExist) Unwrap() error {
  30. return util.ErrNotExist
  31. }
  32. // ErrAccessTokenEmpty represents a "AccessTokenEmpty" kind of error.
  33. type ErrAccessTokenEmpty struct{}
  34. // IsErrAccessTokenEmpty checks if an error is a ErrAccessTokenEmpty.
  35. func IsErrAccessTokenEmpty(err error) bool {
  36. _, ok := err.(ErrAccessTokenEmpty)
  37. return ok
  38. }
  39. func (err ErrAccessTokenEmpty) Error() string {
  40. return "access token is empty"
  41. }
  42. func (err ErrAccessTokenEmpty) Unwrap() error {
  43. return util.ErrInvalidArgument
  44. }
  45. var successfulAccessTokenCache *lru.Cache
  46. // AccessToken represents a personal access token.
  47. type AccessToken struct {
  48. ID int64 `xorm:"pk autoincr"`
  49. UID int64 `xorm:"INDEX"`
  50. Name string
  51. Token string `xorm:"-"`
  52. TokenHash string `xorm:"UNIQUE"` // sha256 of token
  53. TokenSalt string
  54. TokenLastEight string `xorm:"INDEX token_last_eight"`
  55. CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
  56. UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
  57. HasRecentActivity bool `xorm:"-"`
  58. HasUsed bool `xorm:"-"`
  59. }
  60. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
  61. func (t *AccessToken) AfterLoad() {
  62. t.HasUsed = t.UpdatedUnix > t.CreatedUnix
  63. t.HasRecentActivity = t.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
  64. }
  65. func init() {
  66. db.RegisterModel(new(AccessToken), func() error {
  67. if setting.SuccessfulTokensCacheSize > 0 {
  68. var err error
  69. successfulAccessTokenCache, err = lru.New(setting.SuccessfulTokensCacheSize)
  70. if err != nil {
  71. return fmt.Errorf("unable to allocate AccessToken cache: %w", err)
  72. }
  73. } else {
  74. successfulAccessTokenCache = nil
  75. }
  76. return nil
  77. })
  78. }
  79. // NewAccessToken creates new access token.
  80. func NewAccessToken(t *AccessToken) error {
  81. salt, err := util.CryptoRandomString(10)
  82. if err != nil {
  83. return err
  84. }
  85. t.TokenSalt = salt
  86. t.Token = base.EncodeSha1(gouuid.New().String())
  87. t.TokenHash = HashToken(t.Token, t.TokenSalt)
  88. t.TokenLastEight = t.Token[len(t.Token)-8:]
  89. _, err = db.GetEngine(db.DefaultContext).Insert(t)
  90. return err
  91. }
  92. func getAccessTokenIDFromCache(token string) int64 {
  93. if successfulAccessTokenCache == nil {
  94. return 0
  95. }
  96. tInterface, ok := successfulAccessTokenCache.Get(token)
  97. if !ok {
  98. return 0
  99. }
  100. t, ok := tInterface.(int64)
  101. if !ok {
  102. return 0
  103. }
  104. return t
  105. }
  106. // GetAccessTokenBySHA returns access token by given token value
  107. func GetAccessTokenBySHA(token string) (*AccessToken, error) {
  108. if token == "" {
  109. return nil, ErrAccessTokenEmpty{}
  110. }
  111. // A token is defined as being SHA1 sum these are 40 hexadecimal bytes long
  112. if len(token) != 40 {
  113. return nil, ErrAccessTokenNotExist{token}
  114. }
  115. for _, x := range []byte(token) {
  116. if x < '0' || (x > '9' && x < 'a') || x > 'f' {
  117. return nil, ErrAccessTokenNotExist{token}
  118. }
  119. }
  120. lastEight := token[len(token)-8:]
  121. if id := getAccessTokenIDFromCache(token); id > 0 {
  122. token := &AccessToken{
  123. TokenLastEight: lastEight,
  124. }
  125. // Re-get the token from the db in case it has been deleted in the intervening period
  126. has, err := db.GetEngine(db.DefaultContext).ID(id).Get(token)
  127. if err != nil {
  128. return nil, err
  129. }
  130. if has {
  131. return token, nil
  132. }
  133. successfulAccessTokenCache.Remove(token)
  134. }
  135. var tokens []AccessToken
  136. err := db.GetEngine(db.DefaultContext).Table(&AccessToken{}).Where("token_last_eight = ?", lastEight).Find(&tokens)
  137. if err != nil {
  138. return nil, err
  139. } else if len(tokens) == 0 {
  140. return nil, ErrAccessTokenNotExist{token}
  141. }
  142. for _, t := range tokens {
  143. tempHash := HashToken(token, t.TokenSalt)
  144. if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 {
  145. if successfulAccessTokenCache != nil {
  146. successfulAccessTokenCache.Add(token, t.ID)
  147. }
  148. return &t, nil
  149. }
  150. }
  151. return nil, ErrAccessTokenNotExist{token}
  152. }
  153. // AccessTokenByNameExists checks if a token name has been used already by a user.
  154. func AccessTokenByNameExists(token *AccessToken) (bool, error) {
  155. return db.GetEngine(db.DefaultContext).Table("access_token").Where("name = ?", token.Name).And("uid = ?", token.UID).Exist()
  156. }
  157. // ListAccessTokensOptions contain filter options
  158. type ListAccessTokensOptions struct {
  159. db.ListOptions
  160. Name string
  161. UserID int64
  162. }
  163. // ListAccessTokens returns a list of access tokens belongs to given user.
  164. func ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, error) {
  165. sess := db.GetEngine(db.DefaultContext).Where("uid=?", opts.UserID)
  166. if len(opts.Name) != 0 {
  167. sess = sess.Where("name=?", opts.Name)
  168. }
  169. sess = sess.Desc("created_unix")
  170. if opts.Page != 0 {
  171. sess = db.SetSessionPagination(sess, &opts)
  172. tokens := make([]*AccessToken, 0, opts.PageSize)
  173. return tokens, sess.Find(&tokens)
  174. }
  175. tokens := make([]*AccessToken, 0, 5)
  176. return tokens, sess.Find(&tokens)
  177. }
  178. // UpdateAccessToken updates information of access token.
  179. func UpdateAccessToken(t *AccessToken) error {
  180. _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t)
  181. return err
  182. }
  183. // CountAccessTokens count access tokens belongs to given user by options
  184. func CountAccessTokens(opts ListAccessTokensOptions) (int64, error) {
  185. sess := db.GetEngine(db.DefaultContext).Where("uid=?", opts.UserID)
  186. if len(opts.Name) != 0 {
  187. sess = sess.Where("name=?", opts.Name)
  188. }
  189. return sess.Count(&AccessToken{})
  190. }
  191. // DeleteAccessTokenByID deletes access token by given ID.
  192. func DeleteAccessTokenByID(id, userID int64) error {
  193. cnt, err := db.GetEngine(db.DefaultContext).ID(id).Delete(&AccessToken{
  194. UID: userID,
  195. })
  196. if err != nil {
  197. return err
  198. } else if cnt != 1 {
  199. return ErrAccessTokenNotExist{}
  200. }
  201. return nil
  202. }