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:
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