chat/internal/usecase/auth.go

154 lines
4.4 KiB
Go

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
}