feat: add session expiration tracking and validation

feat: implement Redis caching for terminated sessions
feat: add new session GUID queries for validation
refactor: extend Session model with last_refresh_exp_time
refactor: update token generation to include role and session
refactor: modify auth middleware to validate session status
refactor: replace GetUserSessions with GetValidUserSessions
chore: add uuid/v5 dependency
fix: update router to pass dependencies to auth middleware
chore: update SQL schema and queries for new expiration field
This commit is contained in:
2025-07-14 20:44:30 +03:00
parent 24cb8ecb6e
commit d8ea9f79c6
10 changed files with 248 additions and 74 deletions

View File

@@ -53,7 +53,27 @@ type authServiceImpl struct {
}
func NewAuthService(_log *zap.Logger, _dbctx database.DbContext, _redis *redis.Client, _smtp SmtpService) AuthService {
return &authServiceImpl{log: _log, dbctx: _dbctx, redis: _redis, smtp: _smtp}
authService := &authServiceImpl{log: _log, dbctx: _dbctx, redis: _redis, smtp: _smtp}
// Cache terminated sessions
// FIXME: review possible RAM overflow
db := database.NewDbHelper(_dbctx)
guids, err := db.Queries.GetUnexpiredTerminatedSessionsGuids(db.CTX)
if err != nil {
panic("Failed to load terminated sessions' GUIDs")
}
ctx := context.TODO()
// FIXME: review possible problems due to a large pipeline request
pipe := _redis.Pipeline()
for _, guid := range guids {
if err := pipe.Set(ctx, fmt.Sprint("session:%s:is_terminated", guid), true, 0); err != nil {
panic("Failed to cache terminated session: " + err.Err().Error())
}
}
_log.Info("Cached terminated sessions' GUIDs in Redis", zap.Int("amount", len(guids)))
return authService
}
func (a *authServiceImpl) RegistrationBegin(request models.RegistrationBeginRequest) (bool, error) {
@@ -324,7 +344,8 @@ func (a *authServiceImpl) RegistrationComplete(request models.RegistrationComple
return nil, errs.ErrServerError
}
accessToken, refreshToken, err = utils.GenerateTokens(user.Username, session.Guid.String())
// TODO: get user role
accessToken, refreshToken, err = utils.GenerateTokens(user.Username, session.Guid.String(), enums.UserRole)
if err != nil {
a.log.Error(
@@ -415,7 +436,8 @@ func (a *authServiceImpl) Login(request models.LoginRequest) (*models.LoginRespo
return nil, errs.ErrServerError
}
accessToken, refreshToken, err := utils.GenerateTokens(userRow.Username, session.Guid.String())
// TODO: get user role
accessToken, refreshToken, err := utils.GenerateTokens(userRow.Username, session.Guid.String(), enums.UserRole)
if err != nil {
a.log.Error(
"Failed to generate tokens for a new login",
@@ -654,7 +676,8 @@ func (a *authServiceImpl) PasswordResetComplete(request models.PasswordResetComp
zap.Error(err))
}
if accessToken, refreshToken, err = utils.GenerateTokens(user.Username, session.Guid.String()); err != nil {
// TODO: get user role
if accessToken, refreshToken, err = utils.GenerateTokens(user.Username, session.Guid.String(), enums.UserRole); err != nil {
a.log.Error(
"Failed to generate tokens as part of user password reset",
zap.String("email", request.Email),