mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-14 06:57:35 +08:00
support setting token min refresh interval
This commit is contained in:
+36
-3
@@ -2,12 +2,14 @@ package api
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -44,8 +46,7 @@ func (a *TokensApi) TokenListHandler(c *core.Context) (any, *errs.Error) {
|
||||
TokenId: a.tokens.GenerateTokenId(token),
|
||||
TokenType: token.TokenType,
|
||||
UserAgent: token.UserAgent,
|
||||
CreatedAt: token.CreatedUnixTime,
|
||||
ExpiredAt: token.ExpiredUnixTime,
|
||||
LastSeen: token.LastSeenUnixTime,
|
||||
}
|
||||
|
||||
if token.Uid == claims.Uid && utils.Int64ToString(token.UserTokenId) == claims.UserTokenId && token.CreatedUnixTime == claims.IssuedAt {
|
||||
@@ -176,6 +177,39 @@ func (a *TokensApi) TokenRefreshHandler(c *core.Context) (any, *errs.Error) {
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
oldTokenClaims := c.GetTokenClaims()
|
||||
|
||||
if now-oldTokenClaims.IssuedAt < int64(settings.Container.Current.TokenMinRefreshInterval) {
|
||||
log.InfofWithRequestId(c, "[token.TokenRefreshHandler] token of user \"uid:%d\" does not need to be refreshed", uid)
|
||||
|
||||
userTokenId, err := utils.StringToInt64(oldTokenClaims.UserTokenId)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[tokens.TokenRefreshHandler] parse user token id failed, because %s", err.Error())
|
||||
} else {
|
||||
tokenRecord := &models.TokenRecord{
|
||||
Uid: oldTokenClaims.Uid,
|
||||
UserTokenId: userTokenId,
|
||||
CreatedUnixTime: oldTokenClaims.IssuedAt,
|
||||
}
|
||||
|
||||
tokenId := a.tokens.GenerateTokenId(tokenRecord)
|
||||
|
||||
err = a.tokens.UpdateTokenLastSeen(c, tokenRecord)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[token.TokenRefreshHandler] failed to update last seen of token \"id:%s\" for user \"uid:%d\", because %s", tokenId, uid, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
refreshResp := &models.TokenRefreshResponse{
|
||||
User: user.ToUserBasicInfo(),
|
||||
}
|
||||
|
||||
return refreshResp, nil
|
||||
}
|
||||
|
||||
token, claims, err := a.tokens.CreateToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
@@ -183,7 +217,6 @@ func (a *TokensApi) TokenRefreshHandler(c *core.Context) (any, *errs.Error) {
|
||||
return nil, errs.Or(err, errs.ErrTokenGenerating)
|
||||
}
|
||||
|
||||
oldTokenClaims := c.GetTokenClaims()
|
||||
oldUserTokenId, _ := utils.StringToInt64(oldTokenClaims.UserTokenId)
|
||||
oldTokenRecord := &models.TokenRecord{
|
||||
Uid: uid,
|
||||
|
||||
+8
-7
@@ -13,11 +13,12 @@ var (
|
||||
ErrInvalidDuplicateCheckerType = NewSystemError(SystemSubcategorySetting, 6, http.StatusInternalServerError, "invalid duplicate checker type")
|
||||
ErrInvalidInMemoryDuplicateCheckerCleanupInterval = NewSystemError(SystemSubcategorySetting, 7, http.StatusInternalServerError, "invalid in-memory duplicate checker cleanup interval")
|
||||
ErrInvalidTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 8, http.StatusInternalServerError, "invalid token expired time")
|
||||
ErrInvalidTemporaryTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 9, http.StatusInternalServerError, "invalid temporary token expired time")
|
||||
ErrInvalidEmailVerifyTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 10, http.StatusInternalServerError, "invalid email verify token expired time")
|
||||
ErrInvalidAvatarProvider = NewSystemError(SystemSubcategorySetting, 11, http.StatusInternalServerError, "invalid avatar provider")
|
||||
ErrInvalidMapProvider = NewSystemError(SystemSubcategorySetting, 12, http.StatusInternalServerError, "invalid map provider")
|
||||
ErrInvalidAmapSecurityVerificationMethod = NewSystemError(SystemSubcategorySetting, 13, http.StatusInternalServerError, "invalid amap security verification method")
|
||||
ErrInvalidPasswordResetTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 14, http.StatusInternalServerError, "invalid password reset token expired time")
|
||||
ErrInvalidExchangeRatesDataSource = NewSystemError(SystemSubcategorySetting, 15, http.StatusInternalServerError, "invalid exchange rates data source")
|
||||
ErrInvalidTokenMinRefreshInterval = NewSystemError(SystemSubcategorySetting, 9, http.StatusInternalServerError, "invalid token min refresh interval")
|
||||
ErrInvalidTemporaryTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 10, http.StatusInternalServerError, "invalid temporary token expired time")
|
||||
ErrInvalidEmailVerifyTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 11, http.StatusInternalServerError, "invalid email verify token expired time")
|
||||
ErrInvalidAvatarProvider = NewSystemError(SystemSubcategorySetting, 12, http.StatusInternalServerError, "invalid avatar provider")
|
||||
ErrInvalidMapProvider = NewSystemError(SystemSubcategorySetting, 13, http.StatusInternalServerError, "invalid map provider")
|
||||
ErrInvalidAmapSecurityVerificationMethod = NewSystemError(SystemSubcategorySetting, 14, http.StatusInternalServerError, "invalid amap security verification method")
|
||||
ErrInvalidPasswordResetTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 15, http.StatusInternalServerError, "invalid password reset token expired time")
|
||||
ErrInvalidExchangeRatesDataSource = NewSystemError(SystemSubcategorySetting, 16, http.StatusInternalServerError, "invalid exchange rates data source")
|
||||
)
|
||||
|
||||
+12
-12
@@ -7,13 +7,14 @@ const TokenMaxUserAgentLength = 255
|
||||
|
||||
// TokenRecord represents token data stored in database
|
||||
type TokenRecord struct {
|
||||
Uid int64 `xorm:"PK INDEX(IDX_token_record_uid_type_expired_time)"`
|
||||
UserTokenId int64 `xorm:"PK"`
|
||||
TokenType core.TokenType `xorm:"INDEX(IDX_token_record_uid_type_expired_time) TINYINT NOT NULL"`
|
||||
Secret string `xorm:"VARCHAR(10) NOT NULL"`
|
||||
UserAgent string `xorm:"VARCHAR(255)"`
|
||||
CreatedUnixTime int64 `xorm:"PK"`
|
||||
ExpiredUnixTime int64 `xorm:"INDEX(IDX_token_record_uid_type_expired_time)"`
|
||||
Uid int64 `xorm:"PK INDEX(IDX_token_record_uid_type_expired_time)"`
|
||||
UserTokenId int64 `xorm:"PK"`
|
||||
TokenType core.TokenType `xorm:"INDEX(IDX_token_record_uid_type_expired_time) TINYINT NOT NULL"`
|
||||
Secret string `xorm:"VARCHAR(10) NOT NULL"`
|
||||
UserAgent string `xorm:"VARCHAR(255)"`
|
||||
CreatedUnixTime int64 `xorm:"PK"`
|
||||
ExpiredUnixTime int64 `xorm:"INDEX(IDX_token_record_uid_type_expired_time)"`
|
||||
LastSeenUnixTime int64
|
||||
}
|
||||
|
||||
// TokenRevokeRequest represents all parameters of token revoking request
|
||||
@@ -23,8 +24,8 @@ type TokenRevokeRequest struct {
|
||||
|
||||
// TokenRefreshResponse represents all parameters of token refreshing request
|
||||
type TokenRefreshResponse struct {
|
||||
NewToken string `json:"newToken"`
|
||||
OldTokenId string `json:"oldTokenId"`
|
||||
NewToken string `json:"newToken,omitempty"`
|
||||
OldTokenId string `json:"oldTokenId,omitempty"`
|
||||
User *UserBasicInfo `json:"user"`
|
||||
}
|
||||
|
||||
@@ -33,8 +34,7 @@ type TokenInfoResponse struct {
|
||||
TokenId string `json:"tokenId"`
|
||||
TokenType core.TokenType `json:"tokenType"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
ExpiredAt int64 `json:"expiredAt"`
|
||||
LastSeen int64 `json:"lastSeen"`
|
||||
IsCurrent bool `json:"isCurrent"`
|
||||
}
|
||||
|
||||
@@ -53,5 +53,5 @@ func (a TokenInfoResponseSlice) Swap(i, j int) {
|
||||
|
||||
// Less reports whether the first item is less than the second one
|
||||
func (a TokenInfoResponseSlice) Less(i, j int) bool {
|
||||
return a[i].ExpiredAt > a[j].ExpiredAt
|
||||
return a[i].LastSeen > a[j].LastSeen
|
||||
}
|
||||
|
||||
+33
-7
@@ -58,7 +58,7 @@ func (s *TokenService) GetAllUnexpiredNormalTokensByUid(c *core.Context, uid int
|
||||
now := time.Now().Unix()
|
||||
|
||||
var tokenRecords []*models.TokenRecord
|
||||
err := s.TokenDB(uid).NewSession(c).Cols("uid", "user_token_id", "token_type", "user_agent", "created_unix_time", "expired_unix_time").Where("uid=? AND token_type=? AND expired_unix_time>?", uid, core.USER_TOKEN_TYPE_NORMAL, now).Find(&tokenRecords)
|
||||
err := s.TokenDB(uid).NewSession(c).Cols("uid", "user_token_id", "token_type", "user_agent", "created_unix_time", "expired_unix_time", "last_seen_unix_time").Where("uid=? AND token_type=? AND expired_unix_time>?", uid, core.USER_TOKEN_TYPE_NORMAL, now).Find(&tokenRecords)
|
||||
|
||||
return tokenRecords, err
|
||||
}
|
||||
@@ -98,6 +98,31 @@ func (s *TokenService) CreatePasswordResetToken(c *core.Context, user *models.Us
|
||||
return s.createToken(c, user, core.USER_TOKEN_TYPE_PASSWORD_RESET, s.getUserAgent(c), s.CurrentConfig().PasswordResetTokenExpiredTimeDuration)
|
||||
}
|
||||
|
||||
// UpdateTokenLastSeen updates the last seen time of specified token
|
||||
func (s *TokenService) UpdateTokenLastSeen(c *core.Context, tokenRecord *models.TokenRecord) error {
|
||||
if tokenRecord.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
if tokenRecord.UserTokenId <= 0 {
|
||||
return errs.ErrInvalidUserTokenId
|
||||
}
|
||||
|
||||
tokenRecord.LastSeenUnixTime = time.Now().Unix()
|
||||
|
||||
return s.TokenDB(tokenRecord.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
updatedRows, err := sess.Cols("last_seen_unix_time").Where("uid=? AND user_token_id=? AND created_unix_time=?", tokenRecord.Uid, tokenRecord.UserTokenId, tokenRecord.CreatedUnixTime).Update(tokenRecord)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if updatedRows < 1 {
|
||||
return errs.ErrTokenRecordNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteToken deletes given token from database
|
||||
func (s *TokenService) DeleteToken(c *core.Context, tokenRecord *models.TokenRecord) error {
|
||||
if tokenRecord.Uid <= 0 {
|
||||
@@ -294,12 +319,13 @@ func (s *TokenService) createToken(c *core.Context, user *models.User, tokenType
|
||||
now := time.Now()
|
||||
|
||||
tokenRecord := &models.TokenRecord{
|
||||
Uid: user.Uid,
|
||||
UserTokenId: s.getUserTokenId(),
|
||||
TokenType: tokenType,
|
||||
UserAgent: userAgent,
|
||||
CreatedUnixTime: now.Unix(),
|
||||
ExpiredUnixTime: now.Add(expiryDate).Unix(),
|
||||
Uid: user.Uid,
|
||||
UserTokenId: s.getUserTokenId(),
|
||||
TokenType: tokenType,
|
||||
UserAgent: userAgent,
|
||||
CreatedUnixTime: now.Unix(),
|
||||
ExpiredUnixTime: now.Add(expiryDate).Unix(),
|
||||
LastSeenUnixTime: now.Unix(),
|
||||
}
|
||||
|
||||
if tokenRecord.Secret, err = utils.GetRandomString(10); err != nil {
|
||||
|
||||
@@ -124,6 +124,7 @@ const (
|
||||
|
||||
defaultSecretKey string = "ezbookkeeping"
|
||||
defaultTokenExpiredTime uint32 = 2592000 // 30 days
|
||||
defaultTokenMinRefreshInterval uint32 = 86400 // 1 day
|
||||
defaultTemporaryTokenExpiredTime uint32 = 300 // 5 minutes
|
||||
defaultEmailVerifyTokenExpiredTime uint32 = 3600 // 60 minutes
|
||||
defaultPasswordResetTokenExpiredTime uint32 = 3600 // 60 minutes
|
||||
@@ -217,6 +218,7 @@ type Config struct {
|
||||
EnableTwoFactor bool
|
||||
TokenExpiredTime uint32
|
||||
TokenExpiredTimeDuration time.Duration
|
||||
TokenMinRefreshInterval uint32
|
||||
TemporaryTokenExpiredTime uint32
|
||||
TemporaryTokenExpiredTimeDuration time.Duration
|
||||
EmailVerifyTokenExpiredTime uint32
|
||||
@@ -572,6 +574,12 @@ func loadSecurityConfiguration(config *Config, configFile *ini.File, sectionName
|
||||
|
||||
config.TokenExpiredTimeDuration = time.Duration(config.TokenExpiredTime) * time.Second
|
||||
|
||||
config.TokenMinRefreshInterval = getConfigItemUint32Value(configFile, sectionName, "token_min_refresh_interval", defaultTokenMinRefreshInterval)
|
||||
|
||||
if config.TokenMinRefreshInterval >= config.TokenExpiredTime {
|
||||
return errs.ErrInvalidTokenMinRefreshInterval
|
||||
}
|
||||
|
||||
config.TemporaryTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "temporary_token_expired_time", defaultTemporaryTokenExpiredTime)
|
||||
|
||||
if config.TemporaryTokenExpiredTime < 60 {
|
||||
|
||||
Reference in New Issue
Block a user