mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
Use native game auth client only
This commit is contained in:
@@ -3,7 +3,6 @@ using System.Security.Claims;
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
|
||||||
using Duende.IdentityServer;
|
using Duende.IdentityServer;
|
||||||
using Duende.IdentityServer.Models;
|
using Duende.IdentityServer.Models;
|
||||||
using Duende.IdentityServer.Services;
|
using Duende.IdentityServer.Services;
|
||||||
@@ -130,9 +129,8 @@ public static class RecRoomIdentityServerRegistration
|
|||||||
|
|
||||||
internal static class RecRoomIdentityServerConfig
|
internal static class RecRoomIdentityServerConfig
|
||||||
{
|
{
|
||||||
public const string ClientId = "recroom.client";
|
public const string ClientId = "recroom";
|
||||||
public const string NativeClientId = "recroom";
|
public const string ClientSecret = "VxZ53kgbbEaRoZAeMe00MagtgD12GLL2";
|
||||||
public const string NativeClientSecret = "VxZ53kgbbEaRoZAeMe00MagtgD12GLL2";
|
|
||||||
public const string GameClientScope = "gameClient";
|
public const string GameClientScope = "gameClient";
|
||||||
public const string CachedLoginGrantType = "cached_login";
|
public const string CachedLoginGrantType = "cached_login";
|
||||||
|
|
||||||
@@ -159,25 +157,9 @@ internal static class RecRoomIdentityServerConfig
|
|||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
ClientId = ClientId,
|
ClientId = ClientId,
|
||||||
ClientName = "Rec Room compatibility client",
|
ClientName = "Rec Room game client",
|
||||||
RequireClientSecret = false,
|
|
||||||
AllowedGrantTypes = { GrantType.ResourceOwnerPassword, GrantType.RefreshToken, CachedLoginGrantType },
|
|
||||||
AllowedScopes = RecRoomScopes(),
|
|
||||||
AllowOfflineAccess = true,
|
|
||||||
AccessTokenLifetime = 60 * 60 * 12,
|
|
||||||
AbsoluteRefreshTokenLifetime = 60 * 60 * 24 * 30,
|
|
||||||
SlidingRefreshTokenLifetime = 60 * 60 * 24 * 30,
|
|
||||||
RefreshTokenUsage = TokenUsage.ReUse,
|
|
||||||
RefreshTokenExpiration = TokenExpiration.Absolute,
|
|
||||||
UpdateAccessTokenClaimsOnRefresh = true,
|
|
||||||
AlwaysIncludeUserClaimsInIdToken = true,
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
ClientId = NativeClientId,
|
|
||||||
ClientName = "Rec Room native client",
|
|
||||||
RequireClientSecret = true,
|
RequireClientSecret = true,
|
||||||
ClientSecrets = { new Secret(NativeClientSecret.Sha256()) },
|
ClientSecrets = { new Secret(ClientSecret.Sha256()) },
|
||||||
AllowedGrantTypes = { GrantType.ResourceOwnerPassword, GrantType.RefreshToken, CachedLoginGrantType },
|
AllowedGrantTypes = { GrantType.ResourceOwnerPassword, GrantType.RefreshToken, CachedLoginGrantType },
|
||||||
AllowedScopes = RecRoomScopes(),
|
AllowedScopes = RecRoomScopes(),
|
||||||
AllowOfflineAccess = true,
|
AllowOfflineAccess = true,
|
||||||
@@ -201,7 +183,7 @@ internal static class RecRoomIdentityServerConfig
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class IdentityServerLegacyTokenRequestMiddleware(RequestDelegate next)
|
public sealed class IdentityServerGameTokenRequestMiddleware(RequestDelegate next)
|
||||||
{
|
{
|
||||||
public async Task InvokeAsync(HttpContext context)
|
public async Task InvokeAsync(HttpContext context)
|
||||||
{
|
{
|
||||||
@@ -218,12 +200,6 @@ public sealed class IdentityServerLegacyTokenRequestMiddleware(RequestDelegate n
|
|||||||
StringComparer.OrdinalIgnoreCase);
|
StringComparer.OrdinalIgnoreCase);
|
||||||
var changed = false;
|
var changed = false;
|
||||||
|
|
||||||
if (!values.ContainsKey("client_id"))
|
|
||||||
{
|
|
||||||
values["client_id"] = RecRoomIdentityServerConfig.ClientId;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsDevicePasswordGrant(values))
|
if (IsDevicePasswordGrant(values))
|
||||||
{
|
{
|
||||||
if (!values.ContainsKey("username") || string.IsNullOrWhiteSpace(values["username"].ToString()))
|
if (!values.ContainsKey("username") || string.IsNullOrWhiteSpace(values["username"].ToString()))
|
||||||
@@ -291,114 +267,6 @@ public sealed class IdentityServerLegacyTokenRequestMiddleware(RequestDelegate n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class IdentityServerTokenResponseMiddleware(RequestDelegate next, DomainConfig domain)
|
|
||||||
{
|
|
||||||
public async Task InvokeAsync(HttpContext context)
|
|
||||||
{
|
|
||||||
if (!IsTokenRequest(context))
|
|
||||||
{
|
|
||||||
await next(context);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var originalBody = context.Response.Body;
|
|
||||||
await using var buffer = new MemoryStream();
|
|
||||||
context.Response.Body = buffer;
|
|
||||||
|
|
||||||
await next(context);
|
|
||||||
|
|
||||||
context.Response.Body = originalBody;
|
|
||||||
buffer.Position = 0;
|
|
||||||
|
|
||||||
if (context.Response.StatusCode != StatusCodes.Status200OK ||
|
|
||||||
!IsJson(context.Response.ContentType))
|
|
||||||
{
|
|
||||||
await buffer.CopyToAsync(originalBody);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var document = await JsonDocument.ParseAsync(buffer);
|
|
||||||
var response = new Dictionary<string, object?>(StringComparer.Ordinal);
|
|
||||||
foreach (var property in document.RootElement.EnumerateObject())
|
|
||||||
response[property.Name] = ConvertJsonValue(property.Value);
|
|
||||||
|
|
||||||
if (response.TryGetValue("access_token", out var accessValue) &&
|
|
||||||
accessValue is string accessToken &&
|
|
||||||
!string.IsNullOrWhiteSpace(accessToken))
|
|
||||||
{
|
|
||||||
AddLegacyTokenAliases(response, accessToken);
|
|
||||||
SetAccessCookie(context, accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes = JsonSerializer.SerializeToUtf8Bytes(response);
|
|
||||||
context.Response.ContentLength = bytes.Length;
|
|
||||||
context.Response.ContentType = "application/json; charset=utf-8";
|
|
||||||
await originalBody.WriteAsync(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsTokenRequest(HttpContext context) =>
|
|
||||||
HttpMethods.IsPost(context.Request.Method)
|
|
||||||
&& context.Request.Path.Equals("/connect/token", StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
private static bool IsJson(string? contentType) =>
|
|
||||||
contentType?.StartsWith("application/json", StringComparison.OrdinalIgnoreCase) == true;
|
|
||||||
|
|
||||||
private static object? ConvertJsonValue(JsonElement value) =>
|
|
||||||
value.ValueKind switch
|
|
||||||
{
|
|
||||||
JsonValueKind.String => value.GetString(),
|
|
||||||
JsonValueKind.Number when value.TryGetInt64(out var l) => l,
|
|
||||||
JsonValueKind.Number => value.GetDouble(),
|
|
||||||
JsonValueKind.True => true,
|
|
||||||
JsonValueKind.False => false,
|
|
||||||
JsonValueKind.Null => null,
|
|
||||||
_ => JsonSerializer.Deserialize<object>(value.GetRawText()),
|
|
||||||
};
|
|
||||||
|
|
||||||
private static void AddLegacyTokenAliases(Dictionary<string, object?> response, string accessToken)
|
|
||||||
{
|
|
||||||
response["Access_token"] = accessToken;
|
|
||||||
response["accessToken"] = accessToken;
|
|
||||||
response["AccessToken"] = accessToken;
|
|
||||||
response["token"] = accessToken;
|
|
||||||
response["Token"] = accessToken;
|
|
||||||
response["tokenType"] = "Bearer";
|
|
||||||
response["TokenType"] = "Bearer";
|
|
||||||
|
|
||||||
if (response.TryGetValue("expires_in", out var expiresIn))
|
|
||||||
{
|
|
||||||
response["expiresIn"] = expiresIn;
|
|
||||||
response["ExpiresIn"] = expiresIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.TryGetValue("refresh_token", out var refreshToken))
|
|
||||||
{
|
|
||||||
response["Refresh_token"] = refreshToken;
|
|
||||||
response["refreshToken"] = refreshToken;
|
|
||||||
response["RefreshToken"] = refreshToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
var signingKey = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
|
|
||||||
response["key"] = signingKey;
|
|
||||||
response["Key"] = signingKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetAccessCookie(HttpContext context, string accessToken)
|
|
||||||
{
|
|
||||||
var cookie = new CookieOptions
|
|
||||||
{
|
|
||||||
HttpOnly = true,
|
|
||||||
Secure = string.Equals(domain.Scheme, "https", StringComparison.OrdinalIgnoreCase),
|
|
||||||
SameSite = SameSiteMode.None,
|
|
||||||
Expires = DateTimeOffset.UtcNow.AddHours(12),
|
|
||||||
IsEssential = true,
|
|
||||||
};
|
|
||||||
if (domain.Apex.Contains('.'))
|
|
||||||
cookie.Domain = "." + domain.Apex;
|
|
||||||
context.Response.Cookies.Append(AuthService.AccessCookieName, accessToken, cookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class RecRoomPasswordValidator(
|
public sealed class RecRoomPasswordValidator(
|
||||||
PlayerService playerService,
|
PlayerService playerService,
|
||||||
LevelService level,
|
LevelService level,
|
||||||
|
|||||||
@@ -1213,13 +1213,6 @@ public class AdminController(
|
|||||||
Store = "in-memory",
|
Store = "in-memory",
|
||||||
Warning = "Refresh tokens are process-local until an operational store is configured.",
|
Warning = "Refresh tokens are process-local until an operational store is configured.",
|
||||||
},
|
},
|
||||||
LegacyCompatibility = new
|
|
||||||
{
|
|
||||||
InjectsClientId = true,
|
|
||||||
InjectsDefaultScopes = true,
|
|
||||||
AddsLegacyTokenAliases = true,
|
|
||||||
AcceptsLegacyJwtSigningKey = true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/// <summary>GET <c>api/admin/v1/settings</c> — current server-wide
|
/// <summary>GET <c>api/admin/v1/settings</c> — current server-wide
|
||||||
|
|||||||
@@ -62,8 +62,7 @@ public static class MiddlewarePipelineExtensions
|
|||||||
|
|
||||||
app.UseMiddleware<IpBanCheckMiddleware>();
|
app.UseMiddleware<IpBanCheckMiddleware>();
|
||||||
|
|
||||||
app.UseMiddleware<IdentityServerTokenResponseMiddleware>();
|
app.UseMiddleware<IdentityServerGameTokenRequestMiddleware>();
|
||||||
app.UseMiddleware<IdentityServerLegacyTokenRequestMiddleware>();
|
|
||||||
app.UseIdentityServer();
|
app.UseIdentityServer();
|
||||||
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
|
|||||||
@@ -36,12 +36,6 @@ interface IdentityServerSettings {
|
|||||||
store: string;
|
store: string;
|
||||||
warning: string;
|
warning: string;
|
||||||
};
|
};
|
||||||
legacyCompatibility: {
|
|
||||||
injectsClientId: boolean;
|
|
||||||
injectsDefaultScopes: boolean;
|
|
||||||
addsLegacyTokenAliases: boolean;
|
|
||||||
acceptsLegacyJwtSigningKey: boolean;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IdentityServer() {
|
export function IdentityServer() {
|
||||||
@@ -126,15 +120,6 @@ export function IdentityServer() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card !p-5">
|
|
||||||
<h2 className="text-sm font-semibold text-ink-50">Legacy compatibility</h2>
|
|
||||||
<div className="mt-4 grid gap-3 md:grid-cols-2">
|
|
||||||
<Compatibility label="Inject client_id for old clients" ok={settings.legacyCompatibility.injectsClientId} />
|
|
||||||
<Compatibility label="Merge default scopes" ok={settings.legacyCompatibility.injectsDefaultScopes} />
|
|
||||||
<Compatibility label="Add legacy token aliases" ok={settings.legacyCompatibility.addsLegacyTokenAliases} />
|
|
||||||
<Compatibility label="Accept legacy JWT signing key" ok={settings.legacyCompatibility.acceptsLegacyJwtSigningKey} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
@@ -198,15 +183,6 @@ function Metric({ label, value }: { label: string; value: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Compatibility({ label, ok }: { label: string; ok: boolean }) {
|
|
||||||
return (
|
|
||||||
<div className="flex min-h-10 items-center justify-between gap-3 rounded-md border border-ink-800 bg-ink-950/30 px-3 py-2">
|
|
||||||
<span className="text-xs text-ink-100">{label}</span>
|
|
||||||
<StatusBadge ok={ok} on="On" off="Off" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Detail({ label, value, mono }: { label: string; value: string; mono?: boolean }) {
|
function Detail({ label, value, mono }: { label: string; value: string; mono?: boolean }) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user