feat: Implement Steam login initialization logic
Some checks failed
CI - Build and Push / Build and Push Docker Image (push) Failing after 32s
Some checks failed
CI - Build and Push / Build and Push Docker Image (push) Failing after 32s
- Added SteamLoginInitLogic to handle the initiation of Steam login. - Created service context for managing configuration and Redis connection. - Defined types for Steam login requests and callbacks. - Implemented utility functions for building OpenID query strings and validating responses from Steam. - Updated go.mod and go.sum to include necessary dependencies. - Modified GenApi.ps1 script to generate code in the correct directory.
This commit is contained in:
151
app/internal/logic/steamlogincallbacklogic.go
Normal file
151
app/internal/logic/steamlogincallbacklogic.go
Normal file
@@ -0,0 +1,151 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user