package logic import ( "context" "fmt" "net/http" "net/url" "time" "git.cialloo.com/CiallooWeb/Authenticator/app/internal/svc" "git.cialloo.com/CiallooWeb/Authenticator/app/internal/types" "git.cialloo.com/CiallooWeb/Authenticator/app/internal/utils/steamauth" "github.com/golang-jwt/jwt/v4" "github.com/zeromicro/go-zero/core/logx" ) type SteamLoginCallbackLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } // JWT Claims structure type SteamJWTClaims struct { SteamID string `json:"steamId"` jwt.RegisteredClaims } // Steam login callback func NewSteamLoginCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SteamLoginCallbackLogic { return &SteamLoginCallbackLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *SteamLoginCallbackLogic) SteamLoginCallback(req *types.SteamLoginCallbackReq, w http.ResponseWriter, r *http.Request) error { // Get the frontend callback URL from config frontendCallbackURL := l.svcCtx.Config.Steam.FrontendCallbackURL // Extract nonce from query parameter nonce := r.URL.Query().Get("nonce") if nonce == "" { l.Logger.Errorf("Missing nonce in callback request") redirectURL := fmt.Sprintf("%s?status=error&message=%s", frontendCallbackURL, url.QueryEscape("Invalid login session")) http.Redirect(w, r, redirectURL, http.StatusFound) return nil } // Validate nonce exists in Redis nonceKey := nonceKeyPrefix + nonce exists, err := l.svcCtx.Redis.Exists(nonceKey) if err != nil { l.Logger.Errorf("Failed to check nonce in Redis: %v", err) redirectURL := fmt.Sprintf("%s?status=error&message=%s", frontendCallbackURL, url.QueryEscape("Session validation failed")) http.Redirect(w, r, redirectURL, http.StatusFound) return nil } if !exists { l.Logger.Errorf("Invalid or expired nonce: %s", nonce) redirectURL := fmt.Sprintf("%s?status=error&message=%s", frontendCallbackURL, url.QueryEscape("Login session expired or invalid")) http.Redirect(w, r, redirectURL, http.StatusFound) return nil } // Delete nonce from Redis to prevent replay attacks _, err = l.svcCtx.Redis.Del(nonceKey) if err != nil { l.Logger.Errorf("Failed to delete nonce from Redis: %v", err) // Continue anyway - nonce will expire naturally } // Convert the request to a map for validation openidParams := map[string]string{ "openid.mode": req.OpenidMode, "openid.ns": req.OpenidNs, "openid.op_endpoint": req.OpenidOpEndpoint, "openid.claimed_id": req.OpenidClaimedId, "openid.identity": req.OpenidIdentity, "openid.return_to": req.OpenidReturnTo, "openid.response_nonce": req.OpenidResponseNonce, "openid.assoc_handle": req.OpenidAssocHandle, "openid.signed": req.OpenidSigned, "openid.sig": req.OpenidSig, } // Validate the Steam OpenID response steamID, isValid, err := steamauth.ValidateResponse(openidParams) if err != nil { l.Logger.Errorf("Failed to validate Steam OpenID response: %v", err) // Redirect to frontend with error status redirectURL := fmt.Sprintf("%s?status=error&message=%s", frontendCallbackURL, url.QueryEscape("Failed to validate Steam response")) http.Redirect(w, r, redirectURL, http.StatusFound) return nil } if !isValid { l.Logger.Errorf("Invalid Steam OpenID response") // Redirect to frontend with failure status redirectURL := fmt.Sprintf("%s?status=failed&message=%s", frontendCallbackURL, url.QueryEscape("Steam authentication failed")) http.Redirect(w, r, redirectURL, http.StatusFound) return nil } // Successful validation l.Logger.Infof("Steam login successful for Steam ID: %s (nonce: %s)", steamID, nonce) // Generate JWT token token, err := l.generateJWT(steamID) if err != nil { l.Logger.Errorf("Failed to generate JWT token: %v", err) redirectURL := fmt.Sprintf("%s?status=error&message=%s", frontendCallbackURL, url.QueryEscape("Failed to generate authentication token")) http.Redirect(w, r, redirectURL, http.StatusFound) return nil } // Redirect to frontend with success status, Steam ID, and JWT token redirectURL := fmt.Sprintf("%s?status=success&steamId=%s&token=%s", frontendCallbackURL, steamID, token) http.Redirect(w, r, redirectURL, http.StatusFound) return nil } // generateJWT creates a JWT token for the authenticated user func (l *SteamLoginCallbackLogic) generateJWT(steamID string) (string, error) { // Create claims claims := SteamJWTClaims{ SteamID: steamID, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(l.svcCtx.Config.JWT.ExpiresIn) * time.Second)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), Issuer: l.svcCtx.Config.JWT.Issuer, Subject: steamID, }, } // Create token token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // Sign token with secret tokenString, err := token.SignedString([]byte(l.svcCtx.Config.JWT.Secret)) if err != nil { return "", err } return tokenString, nil }