Ume & Relay Api Add

This commit is contained in:
linmeideli committed 2026-05-28 20:20:44 +08:00
1 parent d763ecd8c5
commit 6527e2e292
8 files changed
+271 -9

No files matched your search

@@ -150,6 +150,7 @@
"System.Runtime.CompilerServices.Unsafe": "4.5.3" "System.Runtime.CompilerServices.Unsafe": "4.5.3"
} }
} }
} },
".NETStandard,Version=v2.0/win-x64": {}
} }
} }
+10
View File
@@ -5,6 +5,8 @@ public enum AuthApiMode
Innersloth, Innersloth,
Niko, Niko,
Both, Both,
Relay,
Ume,
} }
public class AuthApiConfig public class AuthApiConfig
@@ -16,4 +18,12 @@ public class AuthApiConfig
public string NikoApiKey { get; set; } = ""; public string NikoApiKey { get; set; } = "";
public string NikoApiBaseUrl { get; set; } = "https://au-verify.niko233.top"; public string NikoApiBaseUrl { get; set; } = "https://au-verify.niko233.top";
public string RelayApiBaseUrl { get; set; } = "http://localhost:5100";
public string RelayApiKey { get; set; } = "";
public string UmeApiBaseUrl { get; set; } = "https://auverify.hayashiume.top";
public string UmeApiKey { get; set; } = "sk-empostor-globalapikey";
} }
+8 -1
View File
@@ -34,6 +34,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
} }
}, },
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[8.0.27, )",
"resolved": "8.0.27",
"contentHash": "rQi9TxifHRnXP7lVRZH05DxD2/XGbJp12q0ozcbrlBlBnyyzssFTH/2vLhtKWUp2CT1qVscTrcYTFiwTyKPKRg=="
},
"StyleCop.Analyzers": { "StyleCop.Analyzers": {
"type": "Direct", "type": "Direct",
"requested": "[1.2.0-beta.556, )", "requested": "[1.2.0-beta.556, )",
@@ -139,6 +145,7 @@
"NextFast.Hazel.Abstractions": { "NextFast.Hazel.Abstractions": {
"type": "Project" "type": "Project"
} }
} },
"net8.0/win-x64": {}
} }
} }
+230 -4
View File
@@ -155,12 +155,52 @@ public sealed class TokenController : ControllerBase
string? friendCode = null; string? friendCode = null;
if (mode == AuthApiMode.Niko || mode == AuthApiMode.Both) if (mode == AuthApiMode.Relay)
{
friendCode = await FetchFromRelayAsync(eosToken, productUserId);
}
if (string.IsNullOrEmpty(friendCode) && mode == AuthApiMode.Ume)
{
friendCode = await FetchFromUmeAsync(eosToken, productUserId);
}
if (string.IsNullOrEmpty(friendCode) && mode == AuthApiMode.Niko)
{ {
friendCode = await FetchFromNikoAsync(eosToken, productUserId); friendCode = await FetchFromNikoAsync(eosToken, productUserId);
} }
if (string.IsNullOrEmpty(friendCode) && (mode == AuthApiMode.Innersloth || mode == AuthApiMode.Both)) if (string.IsNullOrEmpty(friendCode) && mode == AuthApiMode.Both)
{
var nikoKeyIsCustom = !string.IsNullOrEmpty(_authApiConfig.NikoApiKey)
&& _authApiConfig.NikoApiKey != "niko-request-api-key";
if (nikoKeyIsCustom)
{
_logger.LogDebug("[TokenController] Both mode: trying Niko first (custom key)");
friendCode = await FetchFromNikoAsync(eosToken, productUserId);
if (string.IsNullOrEmpty(friendCode))
{
friendCode = await FetchFromUmeAsync(eosToken, productUserId);
}
}
else
{
_logger.LogDebug("[TokenController] Both mode: trying Ume first (default Niko key)");
friendCode = await FetchFromUmeAsync(eosToken, productUserId);
if (string.IsNullOrEmpty(friendCode))
{
friendCode = await FetchFromNikoAsync(eosToken, productUserId);
}
}
if (string.IsNullOrEmpty(friendCode))
{
friendCode = await FetchFromInnerslothAsync(eosToken, productUserId);
}
}
if (string.IsNullOrEmpty(friendCode) && mode == AuthApiMode.Innersloth)
{ {
friendCode = await FetchFromInnerslothAsync(eosToken, productUserId); friendCode = await FetchFromInnerslothAsync(eosToken, productUserId);
} }
@@ -172,8 +212,10 @@ public sealed class TokenController : ControllerBase
"[TokenController] FriendCode fetch failed for PUID={Puid}, using fallback: {FC}", "[TokenController] FriendCode fetch failed for PUID={Puid}, using fallback: {FC}",
productUserId, friendCode); productUserId, friendCode);
} }
else
SaveFriendCodeToCache(productUserId, friendCode); {
SaveFriendCodeToCache(productUserId, friendCode);
}
return friendCode; return friendCode;
} }
@@ -357,6 +399,15 @@ public sealed class TokenController : ControllerBase
if (!string.IsNullOrEmpty(result.FriendCode) if (!string.IsNullOrEmpty(result.FriendCode)
&& (result.VerifyStatus == "HttpPending" || result.VerifyStatus == "Verified")) && (result.VerifyStatus == "HttpPending" || result.VerifyStatus == "Verified"))
{ {
if (!string.IsNullOrEmpty(result.Puid)
&& !string.Equals(result.Puid, productUserId, StringComparison.OrdinalIgnoreCase))
{
_logger.LogWarning(
"[TokenController] Niko PUID mismatch: expected={Expected} got={Got}",
productUserId, result.Puid);
return null;
}
_nikoFriendCodeConfirmed = true; _nikoFriendCodeConfirmed = true;
_logger.LogInformation( _logger.LogInformation(
"[TokenController] FriendCode fetched from Niko: PUID={Puid} FC={FC} Status={Status}", "[TokenController] FriendCode fetched from Niko: PUID={Puid} FC={FC} Status={Status}",
@@ -430,6 +481,154 @@ public sealed class TokenController : ControllerBase
} }
} }
private async Task<string?> FetchFromRelayAsync(string eosToken, string productUserId)
{
if (string.IsNullOrEmpty(_authApiConfig.RelayApiBaseUrl)
|| string.IsNullOrEmpty(_authApiConfig.RelayApiKey))
{
_logger.LogWarning("[TokenController] RelayApi config incomplete, skipping relay");
return null;
}
try
{
using var client = _httpClientFactory.CreateClient("relay");
var body = JsonSerializer.SerializeToUtf8Bytes(new RelayVerifyRequest
{
EosToken = eosToken,
ProductUserId = productUserId,
});
var req = new HttpRequestMessage(HttpMethod.Post,
$"{_authApiConfig.RelayApiBaseUrl.TrimEnd('/')}/api/verify")
{
Content = new ByteArrayContent(body),
};
req.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _authApiConfig.RelayApiKey);
req.Content.Headers.TryAddWithoutValidation("Content-Type", "application/json");
var resp = await client.SendAsync(req);
if (!resp.IsSuccessStatusCode)
{
_logger.LogWarning(
"[TokenController] Relay returned {Status} for PUID={Puid}",
resp.StatusCode, productUserId);
return null;
}
var json = await resp.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<RelayVerifyResult>(json);
if (result == null || string.IsNullOrEmpty(result.FriendCode))
{
_logger.LogWarning(
"[TokenController] Relay response missing FriendCode for PUID={Puid}",
productUserId);
return null;
}
if (string.IsNullOrEmpty(result.ProductUserId)
|| !string.Equals(result.ProductUserId, productUserId, StringComparison.OrdinalIgnoreCase))
{
_logger.LogWarning(
"[TokenController] Relay PUID mismatch: expected={Expected} got={Got}",
productUserId, result.ProductUserId);
return null;
}
_logger.LogInformation(
"[TokenController] FriendCode from Relay: PUID={Puid} FC={FC}",
productUserId, result.FriendCode);
return result.FriendCode;
}
catch (Exception ex)
{
_logger.LogError(ex, "[TokenController] Exception calling Relay for PUID={Puid}", productUserId);
}
return null;
}
private async Task<string?> FetchFromUmeAsync(string eosToken, string productUserId)
{
if (string.IsNullOrEmpty(_authApiConfig.UmeApiBaseUrl)
|| string.IsNullOrEmpty(_authApiConfig.UmeApiKey))
{
_logger.LogWarning("[TokenController] UmeApi config incomplete, skipping Ume");
return null;
}
try
{
using var client = _httpClientFactory.CreateClient("ume");
var body = JsonSerializer.SerializeToUtf8Bytes(new UmeVerifyRequest
{
ApiKey = _authApiConfig.UmeApiKey,
EosToken = eosToken,
});
var req = new HttpRequestMessage(HttpMethod.Post,
$"{_authApiConfig.UmeApiBaseUrl.TrimEnd('/')}/api/verify")
{
Content = new ByteArrayContent(body),
};
req.Content.Headers.TryAddWithoutValidation("Content-Type", "application/json");
var resp = await client.SendAsync(req);
if (!resp.IsSuccessStatusCode)
{
_logger.LogWarning(
"[TokenController] Ume returned {Status} for PUID={Puid}",
resp.StatusCode, productUserId);
return null;
}
var json = await resp.Content.ReadAsStringAsync();
_logger.LogInformation("[TokenController] Ume raw response: {Json}", json);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
var status = root.TryGetProperty("VerifyStatus", out var s) ? s.GetString() : null;
if (status != "Verified")
{
_logger.LogWarning(
"[TokenController] Ume VerifyStatus={Status} for PUID={Puid}",
status, productUserId);
return null;
}
var friendCode = root.TryGetProperty("FriendCode", out var fc) ? fc.GetString() : null;
if (string.IsNullOrEmpty(friendCode))
{
_logger.LogWarning(
"[TokenController] Ume response missing FriendCode for PUID={Puid}",
productUserId);
return null;
}
var returnedPuid = root.TryGetProperty("ProductUserId", out var rp) ? rp.GetString() : null;
if (string.IsNullOrEmpty(returnedPuid)
|| !string.Equals(returnedPuid, productUserId, StringComparison.OrdinalIgnoreCase))
{
_logger.LogWarning(
"[TokenController] Ume PUID mismatch: expected={Expected} got={Got}",
productUserId, returnedPuid);
return null;
}
_logger.LogInformation(
"[TokenController] FriendCode from Ume: PUID={Puid} FC={FC}",
productUserId, friendCode);
return friendCode;
}
catch (Exception ex)
{
_logger.LogError(ex, "[TokenController] Exception calling Ume for PUID={Puid}", productUserId);
}
return null;
}
private static string GenerateMatchmakerToken(string productUserId) private static string GenerateMatchmakerToken(string productUserId)
{ {
var salt = RandomNumberGenerator.GetBytes(16); var salt = RandomNumberGenerator.GetBytes(16);
@@ -523,5 +722,32 @@ public sealed class TokenController : ControllerBase
[JsonPropertyName("verifycode")] [JsonPropertyName("verifycode")]
public required string VerifyCode { get; init; } public required string VerifyCode { get; init; }
} }
private sealed class RelayVerifyRequest
{
[JsonPropertyName("EosToken")]
public required string EosToken { get; init; }
[JsonPropertyName("ProductUserId")]
public required string ProductUserId { get; init; }
}
private sealed class RelayVerifyResult
{
[JsonPropertyName("FriendCode")]
public string? FriendCode { get; init; }
[JsonPropertyName("ProductUserId")]
public string? ProductUserId { get; init; }
}
private sealed class UmeVerifyRequest
{
[JsonPropertyName("ApiKey")]
public required string ApiKey { get; init; }
[JsonPropertyName("EosToken")]
public required string EosToken { get; init; }
}
#endregion #endregion
} }
+2 -2
View File
@@ -47,12 +47,12 @@ namespace Empostor.Server.Net
// NOTE: If this warning annoys you, set your PublicIp to "localhost" // NOTE: If this warning annoys you, set your PublicIp to "localhost"
_logger.LogError("Your PublicIp is set to the default value of 127.0.0.1."); _logger.LogError("Your PublicIp is set to the default value of 127.0.0.1.");
_logger.LogError("To allow people on other devices to connect to your server, change this value to your Public IP address"); _logger.LogError("To allow people on other devices to connect to your server, change this value to your Public IP address");
_logger.LogError("For more info on how to do this see https://github.com/Empostor/Empostor/blob/master/docs/Server-configuration.md"); _logger.LogError("For more info on how to do this see https://empostor.github.io/Server-configuration");
} }
else if (_httpServerConfig.ListenIp == "0.0.0.0" && runningOutsideContainer) else if (_httpServerConfig.ListenIp == "0.0.0.0" && runningOutsideContainer)
{ {
_logger.LogWarning("Since Among Us 16.0.5 it is required to support HTTPS for players to connect, we recommend setting up a reverse proxy:"); _logger.LogWarning("Since Among Us 16.0.5 it is required to support HTTPS for players to connect, we recommend setting up a reverse proxy:");
_logger.LogWarning("See https://github.com/Empostor/Empostor/blob/master/docs/Http-server.md for instructions"); _logger.LogWarning("See https://empostor.github.io/Http-server for instructions");
} }
} }
+8
View File
@@ -128,6 +128,14 @@ namespace Empostor.Server
"UnityPlayer/2022.3.44f1 (UnityWebRequest/1.0, libcurl/7.84.0-DEV)"); "UnityPlayer/2022.3.44f1 (UnityWebRequest/1.0, libcurl/7.84.0-DEV)");
client.DefaultRequestHeaders.Add("X-Unity-Version", "2022.3.44f1"); client.DefaultRequestHeaders.Add("X-Unity-Version", "2022.3.44f1");
}); });
services.AddHttpClient("relay", client =>
{
client.Timeout = TimeSpan.FromSeconds(15);
});
services.AddHttpClient("ume", client =>
{
client.Timeout = TimeSpan.FromSeconds(15);
});
services.AddSingleton<ICompatibilityManager, CompatibilityManager>(); services.AddSingleton<ICompatibilityManager, CompatibilityManager>();
services.AddSingleton<ClientManager>(); services.AddSingleton<ClientManager>();
+5 -1
View File
@@ -39,7 +39,11 @@
"AuthApi": { "AuthApi": {
"Mode": "Both", "Mode": "Both",
"NikoApiKey": "niko-request-api-key", "NikoApiKey": "niko-request-api-key",
"NikoApiBaseUrl": "https://au-verify.niko233.top" "NikoApiBaseUrl": "https://au-verify.niko233.top",
"RelayApiBaseUrl": "http://localhost:5100",
"RelayApiKey": "empostor-relay-api-key-change-me",
"UmeApiBaseUrl": "https://auverify.hayashiume.top",
"UmeApiKey": "sk-empostor-globalapikey"
}, },
"Admin": { "Admin": {
"Password": "CHANGE-ME", "Password": "CHANGE-ME",
+6
View File
@@ -58,6 +58,12 @@
"resolved": "8.0.1", "resolved": "8.0.1",
"contentHash": "X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA==" "contentHash": "X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA=="
}, },
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[8.0.27, )",
"resolved": "8.0.27",
"contentHash": "rQi9TxifHRnXP7lVRZH05DxD2/XGbJp12q0ozcbrlBlBnyyzssFTH/2vLhtKWUp2CT1qVscTrcYTFiwTyKPKRg=="
},
"Serilog.Extensions.Hosting": { "Serilog.Extensions.Hosting": {
"type": "Direct", "type": "Direct",
"requested": "[8.0.0, )", "requested": "[8.0.0, )",