package services import ( "fmt" "math" "strings" "time" "github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go/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 } // ParseToken returns the token model according to request data func (s *TokenService) ParseToken(c *core.Context) (*jwt.Token, *core.UserTokenClaims, error) { claims := &core.UserTokenClaims{} token, err := request.ParseFromRequest(c.Request, request.AuthorizationHeaderExtractor, func(token *jwt.Token) (interface{}, error) { uid, err := utils.StringToInt64(claims.Id) now := time.Now().Unix() if err != nil { log.WarnfWithRequestId(c, "[tokens.ParseToken] user \"uid:%s\" in token is invalid, because %s", claims.Id, err.Error()) return nil, errs.ErrInvalidToken } userTokenId, err := utils.StringToInt64(claims.UserTokenId) if err != nil { log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" in token of user \"uid:%s\" is invalid, because %s", claims.UserTokenId, claims.Id, err.Error()) return nil, errs.ErrInvalidUserTokenId } tokenRecord, err := s.getTokenRecord(uid, userTokenId, claims.IssuedAt) if err != nil { log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" of user \"uid:%s\" record not found, because %s", claims.UserTokenId, claims.Id, err.Error()) return nil, errs.ErrTokenRecordNotFound } if tokenRecord.ExpiredUnixTime < now { log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" of user \"uid:%s\" record is expired", claims.UserTokenId, claims.Id) return nil, errs.ErrTokenExpired } return []byte(tokenRecord.Secret), nil }, request.WithClaims(claims)) if err != nil { return nil, nil, err } return token, claims, err } // 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) } // 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 { uid, err := utils.StringToInt64(claims.Id) if err != nil { return errs.ErrUserIdInvalid } userTokenId, err := utils.StringToInt64(claims.UserTokenId) if err != nil { return errs.ErrInvalidUserTokenId } return s.DeleteToken(&models.TokenRecord{Uid: uid, UserTokenId: userTokenId, CreatedUnixTime: claims.IssuedAt}) } // DeleteTokensBeforeTime deletes tokens that is created before specific tim 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 }