Last active
December 28, 2018 01:54
-
-
Save christoffersch/04444c1baf2d335c72754cc3895b80f5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Godot; | |
using Newtonsoft.Json; | |
using Newtonsoft.Json.Linq; | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Net; | |
using System.Net.Sockets; | |
using System.Text; | |
using System.Windows; | |
using System.Security.Cryptography; | |
using System.Threading; | |
using System.Threading.Tasks; | |
public class googleAuth : Node | |
{ | |
const string clientID = "xxx"; | |
const string clientSecret = "xxx"; | |
const string authorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth"; | |
const string tokenEndpoint = "https://www.googleapis.com/oauth2/v4/token"; | |
const string userInfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo"; | |
// Called when the node enters the scene tree for the first time. | |
public override void _Ready() | |
{ | |
} | |
private async void _on_googleSignInButton_pressed() | |
{ | |
// Set loading | |
GetNode("../googleAuthVisualBehavior").Call("startLoadingIcon"); | |
// Generates state and PKCE values. | |
string state = randomDataBase64url(32); | |
string code_verifier = randomDataBase64url(32); | |
string code_challenge = base64urlencodeNoPadding(sha256(code_verifier)); | |
const string code_challenge_method = "S256"; | |
// Creates a redirect URI using an available port on the loopback address. | |
string redirectURI = string.Format("http://{0}:{1}/", IPAddress.Loopback, GetRandomUnusedPort()); | |
output("redirect URI: " + redirectURI); | |
// Creates an HttpListener to listen for requests on that redirect URI. | |
var http = new HttpListener(); | |
http.Prefixes.Add(redirectURI); | |
output("Listening..."); | |
http.Start(); | |
// Creates the OAuth 2.0 authorization request. | |
string authorizationRequest = string.Format("{0}?response_type=code&scope=openid%20profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}", | |
authorizationEndpoint, | |
System.Uri.EscapeDataString(redirectURI), | |
clientID, | |
state, | |
code_challenge, | |
code_challenge_method); | |
// Opens request in the browser. | |
Godot.OS.ShellOpen(authorizationRequest); | |
// Waits for the OAuth authorization response. | |
var context = await http.GetContextAsync(); | |
// Brings this app back to the foreground. | |
Godot.OS.MoveWindowToForeground(); | |
// Sends an HTTP response to the browser. | |
var response = context.Response; | |
string responseString = string.Format("<html><head><meta http-equiv='refresh' content='10;url=https://google.com'></head><body>Please return to the game.</body></html>"); | |
var buffer = System.Text.Encoding.UTF8.GetBytes(responseString); | |
response.ContentLength64 = buffer.Length; | |
var responseOutput = response.OutputStream; | |
Task responseTask = responseOutput.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) => | |
{ | |
responseOutput.Close(); | |
http.Stop(); | |
Console.WriteLine("HTTP server stopped."); | |
}); | |
// Checks for errors. | |
if (context.Request.QueryString.Get("error") != null) | |
{ | |
output(String.Format("OAuth authorization error: {0}.", context.Request.QueryString.Get("error"))); | |
return; | |
} | |
if (context.Request.QueryString.Get("code") == null | |
|| context.Request.QueryString.Get("state") == null) | |
{ | |
output("Malformed authorization response. " + context.Request.QueryString); | |
return; | |
} | |
// extracts the code | |
var code = context.Request.QueryString.Get("code"); | |
var incoming_state = context.Request.QueryString.Get("state"); | |
// Compares the receieved state to the expected value, to ensure that | |
// this app made the request which resulted in authorization. | |
if (incoming_state != state) | |
{ | |
output(String.Format("Received request with invalid state ({0})", incoming_state)); | |
return; | |
} | |
output("Authorization code: " + code); | |
// Starts the code exchange at the Token Endpoint. | |
performCodeExchange(code, code_verifier, redirectURI); | |
} | |
public async void performCodeExchange(string code, string code_verifier, string redirectURI) | |
{ | |
output("Exchanging code for tokens..."); | |
// builds the request | |
string tokenRequestURI = "https://www.googleapis.com/oauth2/v4/token"; | |
string tokenRequestBody = string.Format("code={0}&redirect_uri={1}&client_id={2}&code_verifier={3}&client_secret={4}&scope=&grant_type=authorization_code", | |
code, | |
System.Uri.EscapeDataString(redirectURI), | |
clientID, | |
code_verifier, | |
clientSecret | |
); | |
// sends the request | |
HttpWebRequest tokenRequest = (HttpWebRequest)WebRequest.Create(tokenRequestURI); | |
tokenRequest.Method = "POST"; | |
tokenRequest.ContentType = "application/x-www-form-urlencoded"; | |
tokenRequest.Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; | |
byte[] _byteVersion = Encoding.ASCII.GetBytes(tokenRequestBody); | |
tokenRequest.ContentLength = _byteVersion.Length; | |
Stream stream = tokenRequest.GetRequestStream(); | |
await stream.WriteAsync(_byteVersion, 0, _byteVersion.Length); | |
stream.Close(); | |
try | |
{ | |
// gets the response | |
WebResponse tokenResponse = await tokenRequest.GetResponseAsync(); | |
using (StreamReader reader = new StreamReader(tokenResponse.GetResponseStream())) | |
{ | |
// reads response body | |
string responseText = await reader.ReadToEndAsync(); | |
output(responseText); | |
// converts to dictionary | |
Dictionary<string, string> tokenEndpointDecoded = JsonConvert.DeserializeObject<Dictionary<string, string>>(responseText); | |
string access_token = tokenEndpointDecoded["access_token"]; | |
userinfoCall(access_token); | |
} | |
} | |
catch (WebException ex) | |
{ | |
if (ex.Status == WebExceptionStatus.ProtocolError) | |
{ | |
var response = ex.Response as HttpWebResponse; | |
if (response != null) | |
{ | |
output("HTTP: " + response.StatusCode); | |
using (StreamReader reader = new StreamReader(response.GetResponseStream())) | |
{ | |
// reads response body | |
string responseText = await reader.ReadToEndAsync(); | |
output(responseText); | |
} | |
} | |
} | |
} | |
} | |
public async void userinfoCall(string access_token) | |
{ | |
output("Making API Call to Userinfo..."); | |
// builds the request | |
string userinfoRequestURI = "https://www.googleapis.com/oauth2/v3/userinfo"; | |
// sends the request | |
HttpWebRequest userinfoRequest = (HttpWebRequest)WebRequest.Create(userinfoRequestURI); | |
userinfoRequest.Method = "GET"; | |
userinfoRequest.Headers.Add(string.Format("Authorization: Bearer {0}", access_token)); | |
userinfoRequest.ContentType = "application/x-www-form-urlencoded"; | |
userinfoRequest.Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; | |
// gets the response | |
WebResponse userinfoResponse = await userinfoRequest.GetResponseAsync(); | |
using (StreamReader userinfoResponseReader = new StreamReader(userinfoResponse.GetResponseStream())) | |
{ | |
// reads response body | |
var userinfoResponseText = await userinfoResponseReader.ReadToEndAsync(); | |
outputData(userinfoResponseText); | |
} | |
} | |
// GETTING A RANDOM PORT | |
public static int GetRandomUnusedPort() | |
{ | |
var listener = new TcpListener(IPAddress.Loopback, 0); | |
listener.Start(); | |
var port = ((IPEndPoint)listener.LocalEndpoint).Port; | |
listener.Stop(); | |
return port; | |
} | |
// ------------ SECURITY ------------------ | |
public static byte[] sha256(string inputStirng) | |
{ | |
byte[] bytes = Encoding.ASCII.GetBytes(inputStirng); | |
SHA256Managed sha256 = new SHA256Managed(); | |
return sha256.ComputeHash(bytes); | |
} | |
public static string randomDataBase64url(uint length) | |
{ | |
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); | |
byte[] bytes = new byte[length]; | |
rng.GetBytes(bytes); | |
return base64urlencodeNoPadding(bytes); | |
} | |
public static string base64urlencodeNoPadding(byte[] buffer) | |
{ | |
string base64 = Convert.ToBase64String(buffer); | |
// Converts base64 to base64url. | |
base64 = base64.Replace("+", "-"); | |
base64 = base64.Replace("/", "_"); | |
// Strips padding. | |
base64 = base64.Replace("=", ""); | |
return base64; | |
} | |
// ----------- OUTPUT ------------------ | |
public void output(string output) | |
{ | |
GD.Print(output); | |
} | |
public void outputData(string output) | |
{ | |
JObject data = JObject.Parse(output); | |
string name = (string)data["given_name"]; | |
string sub = (string)data["sub"]; | |
string profile_picture = (string)data["picture"]; | |
GetNode("../googleAuthVisualBehavior").Call("updateInformation", sub, name, profile_picture); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment