package services import ( "fmt" "math" "strings" "time" "github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5/request" "xorm.io/xorm" "github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/datastore" "github.com/mayswind/ezbookkeeping/pkg/errs" "github.com/mayswind/ezbookkeeping/pkg/log" "github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/settings" "github.com/mayswind/ezbookkeeping/pkg/utils" ) // TokenService represents user token service type TokenService struct { ServiceUsingDB ServiceUsingConfig } // Initialize a user token service singleton instance var ( Tokens = &TokenService{ ServiceUsingDB: ServiceUsingDB{ container: datastore.Container, }, ServiceUsingConfig: ServiceUsingConfig{ container: settings.Container, }, } ) // GetAllTokensByUid returns all token models of given user func (s *TokenService) GetAllTokensByUid(uid int64) ([]*models.TokenRecord, error) { if uid <= 0 { return nil, errs.ErrUserIdInvalid } var tokenRecords []*models.TokenRecord err := s.TokenDB(uid).Cols("uid", "user_token_id", "token_type", "user_agent", "created_unix_time", "expired_unix_time").Where("uid=?", uid).Find(&tokenRecords) return tokenRecords, err } // GetAllUnexpiredNormalTokensByUid returns all available token models of given user func (s *TokenService) GetAllUnexpiredNormalTokensByUid(uid int64) ([]*models.TokenRecord, error) { if uid <= 0 { return nil, errs.ErrUserIdInvalid } now := time.Now().Unix() var tokenRecords []*models.TokenRecord err := s.TokenDB(uid).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) return tokenRecords, err } // ParseTokenByHeader returns the token model according to request data func (s *TokenService) ParseTokenByHeader(c *core.Context) (*jwt.Token, *core.UserTokenClaims, error) { return s.parseToken(c, request.BearerExtractor{}) } // ParseTokenByArgument returns the token model according to request data func (s *TokenService) ParseTokenByArgument(c *core.Context, tokenParameterName string) (*jwt.Token, *core.UserTokenClaims, error) { return s.parseToken(c, request.ArgumentExtractor{tokenParameterName}) } // ParseTokenByCookie returns the token model according to request data func (s *TokenService) ParseTokenByCookie(c *core.Context, tokenCookieName string) (*jwt.Token, *core.UserTokenClaims, error) { return s.parseToken(c, utils.CookieExtractor{tokenCookieName}) } // CreateToken generates a new normal token and saves to database func (s *TokenService) CreateToken(user *models.User, ctx *core.Context) (string, *core.UserTokenClaims, error) { return s.createToken(user, core.USER_TOKEN_TYPE_NORMAL, s.getUserAgent(ctx), s.CurrentConfig().TokenExpiredTimeDuration) } // CreateRequire2FAToken generates a new token requiring user to verify 2fa passcode and saves to database func (s *TokenService) CreateRequire2FAToken(user *models.User, ctx *core.Context) (string, *core.UserTokenClaims, error) { return s.createToken(user, core.USER_TOKEN_TYPE_REQUIRE_2FA, s.getUserAgent(ctx), s.CurrentConfig().TemporaryTokenExpiredTimeDuration) } // CreatePasswordResetToken generates a new password reset token and saves to database func (s *TokenService) CreatePasswordResetToken(user *models.User, ctx *core.Context) (string, *core.UserTokenClaims, error) { return s.createToken(user, core.USER_TOKEN_TYPE_RESET_PASSWORD, s.getUserAgent(ctx), s.CurrentConfig().ForgetPasswordTokenExpiredTimeDuration) } // DeleteToken deletes given token from database func (s *TokenService) DeleteToken(tokenRecord *models.TokenRecord) error { if tokenRecord.Uid <= 0 { return errs.ErrUserIdInvalid } if tokenRecord.UserTokenId <= 0 { return errs.ErrInvalidUserTokenId } return s.TokenDB(tokenRecord.Uid).DoTransaction(func(sess *xorm.Session) error { deletedRows, err := sess.Where("uid=? AND user_token_id=? AND created_unix_time=?", tokenRecord.Uid, tokenRecord.UserTokenId, tokenRecord.CreatedUnixTime).Delete(&models.TokenRecord{}) if err != nil { return err } else if deletedRows < 1 { return errs.ErrTokenRecordNotFound } return nil }) } // DeleteTokens deletes given tokens from database func (s *TokenService) DeleteTokens(uid int64, tokenRecords []*models.TokenRecord) error { if uid <= 0 { return errs.ErrUserIdInvalid } return s.TokenDB(uid).DoTransaction(func(sess *xorm.Session) error { for i := 0; i < len(tokenRecords); i++ { tokenRecord := tokenRecords[i] deletedRows, err := sess.Where("uid=? AND user_token_id=? AND created_unix_time=?", uid, tokenRecord.UserTokenId, tokenRecord.CreatedUnixTime).Delete(&models.TokenRecord{}) if err != nil { return err } else if deletedRows < 1 { return errs.ErrTokenRecordNotFound } } return nil }) } // DeleteTokenByClaims deletes given token from database func (s *TokenService) DeleteTokenByClaims(claims *core.UserTokenClaims) error { userTokenId, err := utils.StringToInt64(claims.UserTokenId) if err != nil { return errs.ErrInvalidUserTokenId } return s.DeleteToken(&models.TokenRecord{ Uid: claims.Uid, UserTokenId: userTokenId, CreatedUnixTime: claims.IssuedAt, }) } // DeleteTokensBeforeTime deletes tokens that is created before specific time func (s *TokenService) DeleteTokensBeforeTime(uid int64, expireTime int64) error { if uid <= 0 { return errs.ErrUserIdInvalid } return s.TokenDB(uid).DoTransaction(func(sess *xorm.Session) error { _, err := sess.Where("uid=? AND created_unix_time models.TokenMaxUserAgentLength { userAgent = utils.SubString(userAgent, 0, models.TokenMaxUserAgentLength) } return userAgent }