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:
24
app/internal/config/config.go
Normal file
24
app/internal/config/config.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
"github.com/zeromicro/go-zero/rest"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
rest.RestConf
|
||||
Steam SteamConfig
|
||||
JWT JWTConfig
|
||||
Redis redis.RedisConf
|
||||
}
|
||||
|
||||
type SteamConfig struct {
|
||||
CallbackURL string
|
||||
FrontendCallbackURL string
|
||||
}
|
||||
|
||||
type JWTConfig struct {
|
||||
Secret string
|
||||
Issuer string
|
||||
ExpiresIn int64
|
||||
}
|
||||
29
app/internal/handler/pinghandler.go
Normal file
29
app/internal/handler/pinghandler.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.cialloo.com/CiallooWeb/Authenticator/app/internal/logic"
|
||||
"git.cialloo.com/CiallooWeb/Authenticator/app/internal/svc"
|
||||
"git.cialloo.com/CiallooWeb/Authenticator/app/internal/types"
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
// Ping the server to check if it's alive
|
||||
func pingHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.PingReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
httpx.ErrorCtx(r.Context(), w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := logic.NewPingLogic(r.Context(), svcCtx)
|
||||
resp, err := l.Ping(&req)
|
||||
if err != nil {
|
||||
httpx.ErrorCtx(r.Context(), w, err)
|
||||
} else {
|
||||
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
38
app/internal/handler/routes.go
Normal file
38
app/internal/handler/routes.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Code generated by goctl. DO NOT EDIT.
|
||||
// goctl 1.8.4
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.cialloo.com/CiallooWeb/Authenticator/app/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest"
|
||||
)
|
||||
|
||||
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{
|
||||
// Ping the server to check if it's alive
|
||||
Method: http.MethodGet,
|
||||
Path: "/ping",
|
||||
Handler: pingHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// Steam login callback
|
||||
Method: http.MethodGet,
|
||||
Path: "/steam/callback",
|
||||
Handler: steamLoginCallbackHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// Initiate Steam login
|
||||
Method: http.MethodGet,
|
||||
Path: "/steam/login",
|
||||
Handler: steamLoginInitHandler(serverCtx),
|
||||
},
|
||||
},
|
||||
rest.WithPrefix("/api/authenticator"),
|
||||
)
|
||||
}
|
||||
29
app/internal/handler/steamlogincallbackhandler.go
Normal file
29
app/internal/handler/steamlogincallbackhandler.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.cialloo.com/CiallooWeb/Authenticator/app/internal/logic"
|
||||
"git.cialloo.com/CiallooWeb/Authenticator/app/internal/svc"
|
||||
"git.cialloo.com/CiallooWeb/Authenticator/app/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
// Steam login callback
|
||||
func steamLoginCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.SteamLoginCallbackReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
httpx.ErrorCtx(r.Context(), w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := logic.NewSteamLoginCallbackLogic(r.Context(), svcCtx)
|
||||
err := l.SteamLoginCallback(&req, w, r)
|
||||
if err != nil {
|
||||
httpx.ErrorCtx(r.Context(), w, err)
|
||||
}
|
||||
// No response needed - redirect is handled in logic
|
||||
}
|
||||
}
|
||||
29
app/internal/handler/steamlogininithandler.go
Normal file
29
app/internal/handler/steamlogininithandler.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.cialloo.com/CiallooWeb/Authenticator/app/internal/logic"
|
||||
"git.cialloo.com/CiallooWeb/Authenticator/app/internal/svc"
|
||||
"git.cialloo.com/CiallooWeb/Authenticator/app/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
// Initiate Steam login
|
||||
func steamLoginInitHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.SteamLoginInitReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
httpx.ErrorCtx(r.Context(), w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := logic.NewSteamLoginInitLogic(r.Context(), svcCtx)
|
||||
err := l.SteamLoginInit(&req, w, r)
|
||||
if err != nil {
|
||||
httpx.ErrorCtx(r.Context(), w, err)
|
||||
}
|
||||
// No response needed - redirect is handled in logic
|
||||
}
|
||||
}
|
||||
31
app/internal/logic/pinglogic.go
Normal file
31
app/internal/logic/pinglogic.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.cialloo.com/CiallooWeb/Authenticator/app/internal/svc"
|
||||
"git.cialloo.com/CiallooWeb/Authenticator/app/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type PingLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Ping the server to check if it's alive
|
||||
func NewPingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PingLogic {
|
||||
return &PingLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *PingLogic) Ping(req *types.PingReq) (resp *types.PingResp, err error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
66
app/internal/logic/steamlogininitlogic.go
Normal file
66
app/internal/logic/steamlogininitlogic.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"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/google/uuid"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type SteamLoginInitLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
const (
|
||||
// Redis key prefix for storing nonces
|
||||
nonceKeyPrefix = "steam:nonce:"
|
||||
// Nonce expiration time (5 minutes)
|
||||
nonceExpiration = 5 * time.Minute
|
||||
)
|
||||
|
||||
// Initiate Steam login
|
||||
func NewSteamLoginInitLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SteamLoginInitLogic {
|
||||
return &SteamLoginInitLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *SteamLoginInitLogic) SteamLoginInit(req *types.SteamLoginInitReq, w http.ResponseWriter, r *http.Request) error {
|
||||
// Generate a unique nonce (UUID) for this login attempt
|
||||
nonce := uuid.New().String()
|
||||
|
||||
// Store the nonce in Redis with expiration
|
||||
nonceKey := nonceKeyPrefix + nonce
|
||||
err := l.svcCtx.Redis.Setex(nonceKey, "1", int(nonceExpiration.Seconds()))
|
||||
if err != nil {
|
||||
l.Logger.Errorf("Failed to store nonce in Redis: %v", err)
|
||||
return fmt.Errorf("failed to initialize login session")
|
||||
}
|
||||
|
||||
// Get the callback URL from config
|
||||
callbackURL := l.svcCtx.Config.Steam.CallbackURL
|
||||
|
||||
// Append nonce to callback URL for validation later
|
||||
callbackURLWithNonce := fmt.Sprintf("%s?nonce=%s", callbackURL, nonce)
|
||||
|
||||
// Build the Steam OpenID redirect URL
|
||||
redirectURL := steamauth.GetRedirectURL(callbackURLWithNonce)
|
||||
|
||||
l.Logger.Infof("Initiating Steam login with nonce: %s, callback URL: %s", nonce, callbackURL)
|
||||
|
||||
// Redirect user directly to Steam
|
||||
http.Redirect(w, r, redirectURL, http.StatusFound)
|
||||
|
||||
return nil
|
||||
}
|
||||
19
app/internal/svc/servicecontext.go
Normal file
19
app/internal/svc/servicecontext.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"git.cialloo.com/CiallooWeb/Authenticator/app/internal/config"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
)
|
||||
|
||||
type ServiceContext struct {
|
||||
Config config.Config
|
||||
Redis *redis.Redis
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
Redis: redis.MustNewRedis(c.Redis),
|
||||
}
|
||||
}
|
||||
27
app/internal/types/types.go
Normal file
27
app/internal/types/types.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Code generated by goctl. DO NOT EDIT.
|
||||
// goctl 1.8.4
|
||||
|
||||
package types
|
||||
|
||||
type PingReq struct {
|
||||
}
|
||||
|
||||
type PingResp struct {
|
||||
Ok bool `json:"ok"`
|
||||
}
|
||||
|
||||
type SteamLoginCallbackReq struct {
|
||||
OpenidMode string `form:"openid.mode"`
|
||||
OpenidNs string `form:"openid.ns"`
|
||||
OpenidOpEndpoint string `form:"openid.op_endpoint"`
|
||||
OpenidClaimedId string `form:"openid.claimed_id"`
|
||||
OpenidIdentity string `form:"openid.identity"`
|
||||
OpenidReturnTo string `form:"openid.return_to"`
|
||||
OpenidResponseNonce string `form:"openid.response_nonce"`
|
||||
OpenidAssocHandle string `form:"openid.assoc_handle"`
|
||||
OpenidSigned string `form:"openid.signed"`
|
||||
OpenidSig string `form:"openid.sig"`
|
||||
}
|
||||
|
||||
type SteamLoginInitReq struct {
|
||||
}
|
||||
148
app/internal/utils/steamauth/steamopenid.go
Normal file
148
app/internal/utils/steamauth/steamopenid.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package steamauth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var validCredRx *regexp.Regexp
|
||||
var steamRx *regexp.Regexp
|
||||
var provider string = "https://steamcommunity.com/openid/login"
|
||||
|
||||
func init() {
|
||||
validCredRx = regexp.MustCompile("is_valid:true")
|
||||
steamRx = regexp.MustCompile(`https://steamcommunity\.com/openid/id/(\d+)`)
|
||||
}
|
||||
|
||||
// StringMapToString is a utility function that aims to efficiently build a query string
|
||||
// with a tiny footprint. theMap is expected to be a map of key strings with a value type of a string.
|
||||
func StringMapToString(theMap map[string]string) string {
|
||||
|
||||
mapLength := len(theMap)
|
||||
strSeparator := "&"
|
||||
i := 1
|
||||
|
||||
var builder strings.Builder
|
||||
builder.Grow(66) // We already roughly know our base size.
|
||||
|
||||
for k, v := range theMap {
|
||||
|
||||
if i == mapLength {
|
||||
strSeparator = ""
|
||||
}
|
||||
|
||||
i++
|
||||
|
||||
fmt.Fprintf(&builder, "%s=%s%s", k, url.QueryEscape(v), strSeparator)
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// BuildQueryString is more or less building up a query string to be passed when reaching
|
||||
// Steam's openid 2.0 provider (or technically any openid 2.0 provider). We only care
|
||||
// that the Scheme is either http or https. Any other validation should really be done
|
||||
// before using this function.
|
||||
func BuildQueryString(responsePath string) string {
|
||||
|
||||
if responsePath[0:4] != "http" {
|
||||
log.Fatal("http was not found in the responsePath!")
|
||||
}
|
||||
|
||||
if responsePath[4:5] != "s" {
|
||||
log.Println("https isn't being used! Is this intentional?")
|
||||
}
|
||||
|
||||
// Even though the below URLs no longer function, the oauth 2.0 process formally calls
|
||||
// for them and Valve actively checks for their presence.
|
||||
openIdParameters := map[string]string{
|
||||
"openid.mode": "checkid_setup",
|
||||
"openid.return_to": responsePath,
|
||||
"openid.realm": responsePath,
|
||||
"openid.ns": "http://specs.openid.net/auth/2.0",
|
||||
"openid.identity": "http://specs.openid.net/auth/2.0/identifier_select",
|
||||
"openid.claimed_id": "http://specs.openid.net/auth/2.0/identifier_select",
|
||||
}
|
||||
|
||||
return StringMapToString(openIdParameters)
|
||||
}
|
||||
|
||||
// ValidateResponse is the real chunk of work that goes on. When the client comes back to our site
|
||||
// we need to take what they give us in the query string and hit up the openid 2.0 provider directly
|
||||
// to verify what we're being provided with is well, valid.
|
||||
// If we end up with "is_valid:true" response from the Steam then isValid will always return true.
|
||||
// In any other situation (credential failure, error etc) isValid will always return false.
|
||||
// Takes a map[string]string to be agnostic among various http clients that exist out there
|
||||
func ValidateResponse(results map[string]string) (steamID64 string, isValid bool, err error) {
|
||||
|
||||
openIdValidation := map[string]string{
|
||||
"openid.assoc_handle": results["openid.assoc_handle"],
|
||||
"openid.signed": results["openid.signed"],
|
||||
"openid.sig": results["openid.sig"],
|
||||
"openid.ns": results["openid.ns"],
|
||||
"openid.mode": "check_authentication",
|
||||
}
|
||||
|
||||
signedParams := strings.Split(results["openid.signed"], ",")
|
||||
|
||||
for _, value := range signedParams {
|
||||
item := fmt.Sprintf("openid.%s", value)
|
||||
if _, exists := openIdValidation[item]; !exists {
|
||||
openIdValidation[item] = results[item]
|
||||
}
|
||||
}
|
||||
|
||||
urlObj, err := url.Parse(provider)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
urlObj.RawQuery = StringMapToString(openIdValidation)
|
||||
|
||||
httpClient := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
validationResp, err := httpClient.Get(urlObj.String())
|
||||
if err != nil {
|
||||
log.Printf("Failed to validate %s. Error: %s ", results["openid.claimed_id"], err)
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
defer validationResp.Body.Close()
|
||||
returnedBytes, err := ioutil.ReadAll(validationResp.Body)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
if validCredRx.MatchString(string(returnedBytes)) == true {
|
||||
return steamRx.FindStringSubmatch(results["openid.claimed_id"])[1], true, nil
|
||||
}
|
||||
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
// GetRedirectURL builds the Steam OpenID redirect URL
|
||||
func GetRedirectURL(callbackURL string) string {
|
||||
urlObj, _ := url.Parse(provider)
|
||||
urlObj.RawQuery = BuildQueryString(callbackURL)
|
||||
return urlObj.String()
|
||||
}
|
||||
|
||||
// ValuesToMap is a boilerplate function designed to convert the results of a url.Values
|
||||
// in to a readable map[string]string for ValidateResponse.
|
||||
// We don't get duplicate query keys supplied normally - but we'll always take the first one anyways
|
||||
func ValuesToMap(fakeMap url.Values) map[string]string {
|
||||
returnMap := map[string]string{}
|
||||
for k, v := range fakeMap {
|
||||
returnMap[k] = v[0]
|
||||
}
|
||||
|
||||
return returnMap
|
||||
}
|
||||
Reference in New Issue
Block a user