package usecase import ( "chat/internal/domain/repository" "chat/internal/domain/usecase" "chat/internal/utils" "context" "fmt" "time" "github.com/golang-jwt/jwt/v4" "github.com/zeromicro/go-zero/core/logx" ) type authUseCase struct { jwtSecret string jwtExpire int64 centrifugoSecret string matchmakingRepo repository.MatchmakingRepository } // NewAuthUseCase 創建新的認證 UseCase func NewAuthUseCase(jwtSecret string, jwtExpire int64, centrifugoSecret string, matchmakingRepo repository.MatchmakingRepository) usecase.AuthUseCase { if centrifugoSecret == "" { centrifugoSecret = jwtSecret } return &authUseCase{ jwtSecret: jwtSecret, jwtExpire: jwtExpire, centrifugoSecret: centrifugoSecret, matchmakingRepo: matchmakingRepo, } } // AnonLogin 匿名登入 func (u *authUseCase) AnonLogin(ctx context.Context, name string) (uid string, token string, centrifugoToken string, expireAt int64, err error) { // 生成匿名 UID uid = utils.GenerateUID() // 生成 API JWT token now := time.Now() expireAt = now.Add(time.Duration(u.jwtExpire) * time.Second).Unix() claims := jwt.MapClaims{ "uid": uid, "exp": expireAt, "iat": now.Unix(), "name": name, } jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) token, err = jwtToken.SignedString([]byte(u.jwtSecret)) if err != nil { return "", "", "", 0, fmt.Errorf("failed to sign JWT: %w", err) } // 匿名登入時還未加入房間,只給予個人頻道的權限 centrifugoExpireAt := expireAt centrifugoClaims := jwt.MapClaims{ "sub": uid, "exp": centrifugoExpireAt, "iat": now.Unix(), "channels": []string{fmt.Sprintf("user:%s", uid)}, "name": name, } centrifugoJwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, centrifugoClaims) centrifugoToken, err = centrifugoJwtToken.SignedString([]byte(u.centrifugoSecret)) if err != nil { return "", "", "", 0, fmt.Errorf("failed to sign Centrifugo JWT: %w", err) } logx.Infof("User %s logged in anonymously", uid) return uid, token, centrifugoToken, expireAt, nil } // RefreshToken 刷新 token func (u *authUseCase) RefreshToken(ctx context.Context, oldToken string) (uid string, token string, centrifugoToken string, expireAt int64, err error) { // 解析舊 token parser := jwt.NewParser(jwt.WithoutClaimsValidation()) tokenObj, err := parser.Parse(oldToken, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrSignatureInvalid } return []byte(u.jwtSecret), nil }) if err != nil { return "", "", "", 0, fmt.Errorf("failed to parse old token: %w", err) } claims, ok := tokenObj.Claims.(jwt.MapClaims) if !ok { return "", "", "", 0, fmt.Errorf("invalid token claims") } uid, ok = claims["uid"].(string) if !ok || uid == "" { return "", "", "", 0, fmt.Errorf("UID not found in token") } name, _ := claims["name"].(string) // 檢查使用者當前的房間狀態 _, roomID, err := u.matchmakingRepo.GetMatchStatus(ctx, uid) if err != nil { logx.Errorf("Failed to get match status for refresh token: %v", err) // 不中斷流程,只是不給房間權限 } // 生成新的 API JWT token now := time.Now() expireAt = now.Add(time.Duration(u.jwtExpire) * time.Second).Unix() newClaims := jwt.MapClaims{ "uid": uid, "exp": expireAt, "iat": now.Unix(), "name": name, } jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, newClaims) token, err = jwtToken.SignedString([]byte(u.jwtSecret)) if err != nil { return "", "", "", 0, fmt.Errorf("failed to sign new JWT: %w", err) } // 生成新的 Centrifugo JWT token channels := []string{fmt.Sprintf("user:%s", uid)} // 如果已經在房間中,添加房間頻道的權限 if roomID != "" { channels = append(channels, fmt.Sprintf("room:%s", roomID)) } centrifugoExpireAt := expireAt centrifugoClaims := jwt.MapClaims{ "sub": uid, "exp": centrifugoExpireAt, "iat": now.Unix(), "channels": channels, "name": name, } centrifugoJwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, centrifugoClaims) centrifugoToken, err = centrifugoJwtToken.SignedString([]byte(u.centrifugoSecret)) if err != nil { return "", "", "", 0, fmt.Errorf("failed to sign new Centrifugo JWT: %w", err) } logx.Infof("User %s refreshed token, room: %s", uid, roomID) return uid, token, centrifugoToken, expireAt, nil }