TapLogin-Unity/Standalone/Runtime/Internal/WebLoginRequestManager.cs

189 lines
5.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using Random = System.Random;
namespace TapTap.Login.Internal
{
public class WebLoginRequestManager
{
private static volatile WebLoginRequestManager _instance;
private static readonly object ObjLock = new object();
public static WebLoginRequestManager Instance
{
get
{
if (_instance != null) return _instance;
lock (ObjLock)
{
if (_instance == null)
{
_instance = new WebLoginRequestManager();
}
}
return _instance;
}
}
private string _codeVerifier;
private WebLoginRequest _currentRequest;
public void CreateNewLoginRequest(string[] permissions)
{
_codeVerifier = CodeUtil.GenerateCodeVerifier();
_currentRequest = new WebLoginRequest(permissions, _codeVerifier);
}
public string GetCodeVerifier()
{
return _codeVerifier;
}
public WebLoginRequest GetCurrentRequest()
{
return _currentRequest;
}
}
public class WebLoginRequest
{
private readonly string[] _permissions;
private readonly string _state;
private readonly string _codeChallenge;
private string _redirectHost;
public WebLoginRequest(string[] permissions, string codeVerifier)
{
this._permissions = permissions;
_codeChallenge = CodeUtil.GetCodeChallenge(codeVerifier);
_state = Guid.NewGuid().ToString("N");
}
public string GetWebLoginUrl()
{
var data = new Dictionary<string, string>
{
{ "client_id", TapTapSdk.ClientId },
{ "response_type", "code" },
{ "redirect_uri", GetRedirectUri() },
{ "state", _state },
{ "code_challenge", _codeChallenge },
{ "code_challenge_method", "S256" },
{ "scope", string.Join(",", _permissions) },
{ "flow", "pc_localhost" }
};
var paramStr = MapToParameters(data);
return TapTapSdk.CurrentRegion.AccountUrl() + paramStr;
}
private static string MapToParameters(Dictionary<string, string> data)
{
var builder = new StringBuilder();
foreach (var pair in data)
{
builder.Append(System.Web.HttpUtility.UrlEncode(pair.Key));
builder.Append("=");
builder.Append(System.Web.HttpUtility.UrlEncode(pair.Value));
builder.Append("&");
}
return builder.Remove(builder.Length - 1, 1).ToString();
}
public string GetState()
{
return _state;
}
public string GetRedirectHost()
{
if (string.IsNullOrEmpty(_redirectHost))
{
_redirectHost = CodeUtil.GetRedirectUri();
}
return _redirectHost;
}
public string GetRedirectUri()
{
return GetRedirectHost() + "authorize";
}
}
public static class CodeUtil
{
public static string GetRedirectUri()
{
var redirectUri = $"http://{IPAddress.Loopback}:{GetRandomUnusedPort()}/";
Debug.Log($"redirect uri:{redirectUri}");
return redirectUri;
}
public static string GetTapTapOAuthRedirectUri()
{
return $"open-taptap-{TapTapSdk.ClientId}://{GetTapTapOAuthPrefix()}";
}
public static string GetTapTapOAuthPrefix()
{
return "authorize";
}
private static int GetRandomUnusedPort()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return port;
}
public static string GenerateCodeVerifier()
{
const string baseStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
var random = new Random();
var builder = new StringBuilder();
for (var i = 0; i < 128; i++)
{
var number = random.Next(baseStr.Length);
builder.Append(baseStr[number]);
}
return builder.ToString();
}
public static string GetCodeChallenge(string codeVerifier)
{
return Base64UrlEncodeNoPadding(SHA256(codeVerifier));
}
private static string Base64UrlEncodeNoPadding(byte[] buffer)
{
var base64 = Convert.ToBase64String(buffer);
// Converts base64 to base64url.
base64 = base64.Replace("+", "-");
base64 = base64.Replace("/", "_");
// Strips padding.
base64 = base64.Replace("=", "");
return base64;
}
private static byte[] SHA256(string inputStirng)
{
var bytes = Encoding.ASCII.GetBytes(inputStirng);
var sha256 = new SHA256Managed();
return sha256.ComputeHash(bytes);
}
}
}