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.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  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. "context"
  7. "crypto/subtle"
  8. "encoding/hex"
  9. "fmt"
  10. "time"
  11. "code.gitea.io/gitea/models/db"
  12. "code.gitea.io/gitea/modules/setting"
  13. "code.gitea.io/gitea/modules/timeutil"
  14. "code.gitea.io/gitea/modules/util"
  15. lru "github.com/hashicorp/golang-lru/v2"
  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[string, any]
  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. Scope AccessTokenScope
  56. CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
  57. UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
  58. HasRecentActivity bool `xorm:"-"`
  59. HasUsed bool `xorm:"-"`
  60. }
  61. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
  62. func (t *AccessToken) AfterLoad() {
  63. t.HasUsed = t.UpdatedUnix > t.CreatedUnix
  64. t.HasRecentActivity = t.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
  65. }
  66. func init() {
  67. db.RegisterModel(new(AccessToken), func() error {
  68. if setting.SuccessfulTokensCacheSize > 0 {
  69. var err error
  70. successfulAccessTokenCache, err = lru.New[string, any](setting.SuccessfulTokensCacheSize)
  71. if err != nil {
  72. return fmt.Errorf("unable to allocate AccessToken cache: %w", err)
  73. }
  74. } else {
  75. successfulAccessTokenCache = nil
  76. }
  77. return nil
  78. })
  79. }
  80. // NewAccessToken creates new access token.
  81. func NewAccessToken(ctx context.Context, t *AccessToken) error {
  82. salt, err := util.CryptoRandomString(10)
  83. if err != nil {
  84. return err
  85. }
  86. token, err := util.CryptoRandomBytes(20)
  87. if err != nil {
  88. return err
  89. }
  90. t.TokenSalt = salt
  91. t.Token = hex.EncodeToString(token)
  92. t.TokenHash = HashToken(t.Token, t.TokenSalt)
  93. t.TokenLastEight = t.Token[len(t.Token)-8:]
  94. _, err = db.GetEngine(ctx).Insert(t)
  95. return err
  96. }
  97. // DisplayPublicOnly whether to display this as a public-only token.
  98. func (t *AccessToken) DisplayPublicOnly() bool {
  99. publicOnly, err := t.Scope.PublicOnly()
  100. if err != nil {
  101. return false
  102. }
  103. return publicOnly
  104. }
  105. func getAccessTokenIDFromCache(token string) int64 {
  106. if successfulAccessTokenCache == nil {
  107. return 0
  108. }
  109. tInterface, ok := successfulAccessTokenCache.Get(token)
  110. if !ok {
  111. return 0
  112. }
  113. t, ok := tInterface.(int64)
  114. if !ok {
  115. return 0
  116. }
  117. return t
  118. }
  119. // GetAccessTokenBySHA returns access token by given token value
  120. func GetAccessTokenBySHA(ctx context.Context, token string) (*AccessToken, error) {
  121. if token == "" {
  122. return nil, ErrAccessTokenEmpty{}
  123. }
  124. // A token is defined as being SHA1 sum these are 40 hexadecimal bytes long
  125. if len(token) != 40 {
  126. return nil, ErrAccessTokenNotExist{token}
  127. }
  128. for _, x := range []byte(token) {
  129. if x < '0' || (x > '9' && x < 'a') || x > 'f' {
  130. return nil, ErrAccessTokenNotExist{token}
  131. }
  132. }
  133. lastEight := token[len(token)-8:]
  134. if id := getAccessTokenIDFromCache(token); id > 0 {
  135. accessToken := &AccessToken{
  136. TokenLastEight: lastEight,
  137. }
  138. // Re-get the token from the db in case it has been deleted in the intervening period
  139. has, err := db.GetEngine(ctx).ID(id).Get(accessToken)
  140. if err != nil {
  141. return nil, err
  142. }
  143. if has {
  144. return accessToken, nil
  145. }
  146. successfulAccessTokenCache.Remove(token)
  147. }
  148. var tokens []AccessToken
  149. err := db.GetEngine(ctx).Table(&AccessToken{}).Where("token_last_eight = ?", lastEight).Find(&tokens)
  150. if err != nil {
  151. return nil, err
  152. } else if len(tokens) == 0 {
  153. return nil, ErrAccessTokenNotExist{token}
  154. }
  155. for _, t := range tokens {
  156. tempHash := HashToken(token, t.TokenSalt)
  157. if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 {
  158. if successfulAccessTokenCache != nil {
  159. successfulAccessTokenCache.Add(token, t.ID)
  160. }
  161. return &t, nil
  162. }
  163. }
  164. return nil, ErrAccessTokenNotExist{token}
  165. }
  166. // AccessTokenByNameExists checks if a token name has been used already by a user.
  167. func AccessTokenByNameExists(ctx context.Context, token *AccessToken) (bool, error) {
  168. return db.GetEngine(ctx).Table("access_token").Where("name = ?", token.Name).And("uid = ?", token.UID).Exist()
  169. }
  170. // ListAccessTokensOptions contain filter options
  171. type ListAccessTokensOptions struct {
  172. db.ListOptions
  173. Name string
  174. UserID int64
  175. }
  176. // ListAccessTokens returns a list of access tokens belongs to given user.
  177. func ListAccessTokens(ctx context.Context, opts ListAccessTokensOptions) ([]*AccessToken, error) {
  178. sess := db.GetEngine(ctx).Where("uid=?", opts.UserID)
  179. if len(opts.Name) != 0 {
  180. sess = sess.Where("name=?", opts.Name)
  181. }
  182. sess = sess.Desc("created_unix")
  183. if opts.Page != 0 {
  184. sess = db.SetSessionPagination(sess, &opts)
  185. tokens := make([]*AccessToken, 0, opts.PageSize)
  186. return tokens, sess.Find(&tokens)
  187. }
  188. tokens := make([]*AccessToken, 0, 5)
  189. return tokens, sess.Find(&tokens)
  190. }
  191. // UpdateAccessToken updates information of access token.
  192. func UpdateAccessToken(ctx context.Context, t *AccessToken) error {
  193. _, err := db.GetEngine(ctx).ID(t.ID).AllCols().Update(t)
  194. return err
  195. }
  196. // CountAccessTokens count access tokens belongs to given user by options
  197. func CountAccessTokens(ctx context.Context, opts ListAccessTokensOptions) (int64, error) {
  198. sess := db.GetEngine(ctx).Where("uid=?", opts.UserID)
  199. if len(opts.Name) != 0 {
  200. sess = sess.Where("name=?", opts.Name)
  201. }
  202. return sess.Count(&AccessToken{})
  203. }
  204. // DeleteAccessTokenByID deletes access token by given ID.
  205. func DeleteAccessTokenByID(ctx context.Context, id, userID int64) error {
  206. cnt, err := db.GetEngine(ctx).ID(id).Delete(&AccessToken{
  207. UID: userID,
  208. })
  209. if err != nil {
  210. return err
  211. } else if cnt != 1 {
  212. return ErrAccessTokenNotExist{}
  213. }
  214. return nil
  215. }