summaryrefslogtreecommitdiffstats
path: root/models/auth/token.go
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2022-08-25 10:31:57 +0800
committerGitHub <noreply@github.com>2022-08-25 10:31:57 +0800
commit1d8543e7db58d7c4973758e47f005c4d8bd7d7a3 (patch)
treeb60c99e2dfd69ccb998f8a0829d98d7cadcf0d6b /models/auth/token.go
parent4a4bfafa238bf48851f8c11fa3701bd42b912475 (diff)
downloadgitea-1d8543e7db58d7c4973758e47f005c4d8bd7d7a3.tar.gz
gitea-1d8543e7db58d7c4973758e47f005c4d8bd7d7a3.zip
Move some files into models' sub packages (#20262)
* Move some files into models' sub packages * Move functions * merge main branch * Fix check * fix check * Fix some tests * Fix lint * Fix lint * Revert lint changes * Fix error comments * Fix lint Co-authored-by: 6543 <6543@obermui.de>
Diffstat (limited to 'models/auth/token.go')
-rw-r--r--models/auth/token.go230
1 files changed, 230 insertions, 0 deletions
diff --git a/models/auth/token.go b/models/auth/token.go
new file mode 100644
index 0000000000..01654f2901
--- /dev/null
+++ b/models/auth/token.go
@@ -0,0 +1,230 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package auth
+
+import (
+ "crypto/subtle"
+ "fmt"
+ "time"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/util"
+
+ gouuid "github.com/google/uuid"
+ lru "github.com/hashicorp/golang-lru"
+)
+
+// ErrAccessTokenNotExist represents a "AccessTokenNotExist" kind of error.
+type ErrAccessTokenNotExist struct {
+ Token string
+}
+
+// IsErrAccessTokenNotExist checks if an error is a ErrAccessTokenNotExist.
+func IsErrAccessTokenNotExist(err error) bool {
+ _, ok := err.(ErrAccessTokenNotExist)
+ return ok
+}
+
+func (err ErrAccessTokenNotExist) Error() string {
+ return fmt.Sprintf("access token does not exist [sha: %s]", err.Token)
+}
+
+// ErrAccessTokenEmpty represents a "AccessTokenEmpty" kind of error.
+type ErrAccessTokenEmpty struct{}
+
+// IsErrAccessTokenEmpty checks if an error is a ErrAccessTokenEmpty.
+func IsErrAccessTokenEmpty(err error) bool {
+ _, ok := err.(ErrAccessTokenEmpty)
+ return ok
+}
+
+func (err ErrAccessTokenEmpty) Error() string {
+ return "access token is empty"
+}
+
+var successfulAccessTokenCache *lru.Cache
+
+// AccessToken represents a personal access token.
+type AccessToken struct {
+ ID int64 `xorm:"pk autoincr"`
+ UID int64 `xorm:"INDEX"`
+ Name string
+ Token string `xorm:"-"`
+ TokenHash string `xorm:"UNIQUE"` // sha256 of token
+ TokenSalt string
+ TokenLastEight string `xorm:"token_last_eight"`
+
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ HasRecentActivity bool `xorm:"-"`
+ HasUsed bool `xorm:"-"`
+}
+
+// AfterLoad is invoked from XORM after setting the values of all fields of this object.
+func (t *AccessToken) AfterLoad() {
+ t.HasUsed = t.UpdatedUnix > t.CreatedUnix
+ t.HasRecentActivity = t.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
+}
+
+func init() {
+ db.RegisterModel(new(AccessToken), func() error {
+ if setting.SuccessfulTokensCacheSize > 0 {
+ var err error
+ successfulAccessTokenCache, err = lru.New(setting.SuccessfulTokensCacheSize)
+ if err != nil {
+ return fmt.Errorf("unable to allocate AccessToken cache: %v", err)
+ }
+ } else {
+ successfulAccessTokenCache = nil
+ }
+ return nil
+ })
+}
+
+// NewAccessToken creates new access token.
+func NewAccessToken(t *AccessToken) error {
+ salt, err := util.CryptoRandomString(10)
+ if err != nil {
+ return err
+ }
+ t.TokenSalt = salt
+ t.Token = base.EncodeSha1(gouuid.New().String())
+ t.TokenHash = HashToken(t.Token, t.TokenSalt)
+ t.TokenLastEight = t.Token[len(t.Token)-8:]
+ _, err = db.GetEngine(db.DefaultContext).Insert(t)
+ return err
+}
+
+func getAccessTokenIDFromCache(token string) int64 {
+ if successfulAccessTokenCache == nil {
+ return 0
+ }
+ tInterface, ok := successfulAccessTokenCache.Get(token)
+ if !ok {
+ return 0
+ }
+ t, ok := tInterface.(int64)
+ if !ok {
+ return 0
+ }
+ return t
+}
+
+// GetAccessTokenBySHA returns access token by given token value
+func GetAccessTokenBySHA(token string) (*AccessToken, error) {
+ if token == "" {
+ return nil, ErrAccessTokenEmpty{}
+ }
+ // A token is defined as being SHA1 sum these are 40 hexadecimal bytes long
+ if len(token) != 40 {
+ return nil, ErrAccessTokenNotExist{token}
+ }
+ for _, x := range []byte(token) {
+ if x < '0' || (x > '9' && x < 'a') || x > 'f' {
+ return nil, ErrAccessTokenNotExist{token}
+ }
+ }
+
+ lastEight := token[len(token)-8:]
+
+ if id := getAccessTokenIDFromCache(token); id > 0 {
+ token := &AccessToken{
+ TokenLastEight: lastEight,
+ }
+ // Re-get the token from the db in case it has been deleted in the intervening period
+ has, err := db.GetEngine(db.DefaultContext).ID(id).Get(token)
+ if err != nil {
+ return nil, err
+ }
+ if has {
+ return token, nil
+ }
+ successfulAccessTokenCache.Remove(token)
+ }
+
+ var tokens []AccessToken
+ err := db.GetEngine(db.DefaultContext).Table(&AccessToken{}).Where("token_last_eight = ?", lastEight).Find(&tokens)
+ if err != nil {
+ return nil, err
+ } else if len(tokens) == 0 {
+ return nil, ErrAccessTokenNotExist{token}
+ }
+
+ for _, t := range tokens {
+ tempHash := HashToken(token, t.TokenSalt)
+ if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 {
+ if successfulAccessTokenCache != nil {
+ successfulAccessTokenCache.Add(token, t.ID)
+ }
+ return &t, nil
+ }
+ }
+ return nil, ErrAccessTokenNotExist{token}
+}
+
+// AccessTokenByNameExists checks if a token name has been used already by a user.
+func AccessTokenByNameExists(token *AccessToken) (bool, error) {
+ return db.GetEngine(db.DefaultContext).Table("access_token").Where("name = ?", token.Name).And("uid = ?", token.UID).Exist()
+}
+
+// ListAccessTokensOptions contain filter options
+type ListAccessTokensOptions struct {
+ db.ListOptions
+ Name string
+ UserID int64
+}
+
+// ListAccessTokens returns a list of access tokens belongs to given user.
+func ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, error) {
+ sess := db.GetEngine(db.DefaultContext).Where("uid=?", opts.UserID)
+
+ if len(opts.Name) != 0 {
+ sess = sess.Where("name=?", opts.Name)
+ }
+
+ sess = sess.Desc("created_unix")
+
+ if opts.Page != 0 {
+ sess = db.SetSessionPagination(sess, &opts)
+
+ tokens := make([]*AccessToken, 0, opts.PageSize)
+ return tokens, sess.Find(&tokens)
+ }
+
+ tokens := make([]*AccessToken, 0, 5)
+ return tokens, sess.Find(&tokens)
+}
+
+// UpdateAccessToken updates information of access token.
+func UpdateAccessToken(t *AccessToken) error {
+ _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t)
+ return err
+}
+
+// CountAccessTokens count access tokens belongs to given user by options
+func CountAccessTokens(opts ListAccessTokensOptions) (int64, error) {
+ sess := db.GetEngine(db.DefaultContext).Where("uid=?", opts.UserID)
+ if len(opts.Name) != 0 {
+ sess = sess.Where("name=?", opts.Name)
+ }
+ return sess.Count(&AccessToken{})
+}
+
+// DeleteAccessTokenByID deletes access token by given ID.
+func DeleteAccessTokenByID(id, userID int64) error {
+ cnt, err := db.GetEngine(db.DefaultContext).ID(id).Delete(&AccessToken{
+ UID: userID,
+ })
+ if err != nil {
+ return err
+ } else if cnt != 1 {
+ return ErrAccessTokenNotExist{}
+ }
+ return nil
+}