refactor: introduce DTOs for claims, session, and request handling
feat: add token validation service method refactor: update middleware to use structured DTOs feat: implement session info propagation through context refactor: replace ad-hoc structs with DTOs in middleware chore: organize auth-related data structures
This commit is contained in:
@@ -21,6 +21,7 @@ import (
|
||||
"context"
|
||||
"easywish/config"
|
||||
"easywish/internal/database"
|
||||
"easywish/internal/dto"
|
||||
errs "easywish/internal/errors"
|
||||
"easywish/internal/models"
|
||||
"easywish/internal/utils"
|
||||
@@ -30,6 +31,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgerrcode"
|
||||
"github.com/jackc/pgx/v5"
|
||||
@@ -43,6 +45,7 @@ type AuthService interface {
|
||||
Refresh(request models.RefreshRequest) (*models.RefreshResponse, error)
|
||||
PasswordResetBegin(request models.PasswordResetBeginRequest) (bool, error)
|
||||
PasswordResetComplete(request models.PasswordResetCompleteRequest) (*models.PasswordResetCompleteResponse, error)
|
||||
ValidateToken(token string, tokenType enums.JwtTokenType) (*dto.SessionInfo, error)
|
||||
}
|
||||
|
||||
type authServiceImpl struct {
|
||||
@@ -505,6 +508,100 @@ func (a *authServiceImpl) Login(request models.LoginRequest) (*models.LoginRespo
|
||||
}
|
||||
|
||||
func (a *authServiceImpl) Refresh(request models.RefreshRequest) (*models.RefreshResponse, error) {
|
||||
|
||||
var err error
|
||||
|
||||
token, err := jwt.ParseWithClaims(
|
||||
request.RefreshToken,
|
||||
&dto.UserClaims{},
|
||||
func(token *jwt.Token) (any, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(config.GetConfig().JwtSecret), nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||
// AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Token expired"})
|
||||
} else {
|
||||
// c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
|
||||
}
|
||||
return nil, errs.ErrUnauthorized
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(*dto.UserClaims); ok && token.Valid {
|
||||
|
||||
if claims.Type != enums.JwtAccessTokenType {
|
||||
// c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Not an access token"})
|
||||
return nil, errs.ErrUnauthorized
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
isTerminated, redisErr := a.redis.Get(ctx, fmt.Sprintf("session::%s::is_terminated", claims.Session)).Bool()
|
||||
if redisErr != nil && redisErr != redis.Nil {
|
||||
a.log.Error(
|
||||
"Failed to lookup cache to check whether session is not terminated",
|
||||
zap.Error(redisErr))
|
||||
// c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return nil, errs.ErrServerError
|
||||
}
|
||||
|
||||
// Cache if nil
|
||||
if redisErr == redis.Nil {
|
||||
db := database.NewDbHelper(a.dbctx)
|
||||
|
||||
session, err := db.Queries.GetSessionByGuid(db.CTX, claims.Session)
|
||||
if err != nil {
|
||||
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
a.log.Warn(
|
||||
"Session does not exist or was deleted",
|
||||
zap.String("session", claims.Session))
|
||||
// c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return nil, errs.ErrUnauthorized
|
||||
}
|
||||
|
||||
a.log.Error(
|
||||
"Failed to lookup session in database",
|
||||
zap.String("session", claims.Session),
|
||||
zap.Error(err))
|
||||
// c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return nil, errs.ErrServerError
|
||||
}
|
||||
|
||||
if err := a.redis.Set(
|
||||
ctx,
|
||||
fmt.Sprintf("session::%s::is_terminated", claims.Session),
|
||||
session.Terminated,
|
||||
time.Duration(8 * time.Hour), // XXX: magic number
|
||||
).Err(); err != nil {
|
||||
a.log.Error(
|
||||
"Failed to cache session's is_terminated state",
|
||||
zap.String("session", claims.Session),
|
||||
zap.Error(err))
|
||||
// c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return nil, errs.ErrServerError
|
||||
}
|
||||
|
||||
isTerminated = *session.Terminated
|
||||
}
|
||||
|
||||
if isTerminated {
|
||||
a.log.Warn(
|
||||
"Attempt to access resource from a terminated session",
|
||||
zap.String("session", claims.Session))
|
||||
// c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return nil, errs.ErrUnauthorized
|
||||
}
|
||||
}
|
||||
// TODO: generate some tokens
|
||||
|
||||
return nil, errs.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (a *authServiceImpl) ValidateToken(token string, tokenType enums.JwtTokenType) (*dto.SessionInfo, error) {
|
||||
return nil, errs.ErrNotImplemented
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user