Files
cneicy 8eaaa3040a
Publish UPM package / publish (push) Failing after 1s
chore: initialize standalone UPM package
2026-08-26 02:50:34 +08:00

153 lines
4.8 KiB
Plaintext

using System.Collections.Concurrent;
using System.Security.Cryptography;
using ShrinkNetwork;
namespace ShrinkNetwork.ServerHost.Framework;
public sealed class ServerAuthStore
{
public sealed class AuthState
{
public string AuthName { get; set; } = string.Empty;
public bool IsAuthenticated { get; set; }
public DateTimeOffset AuthenticatedAtUtc { get; set; }
public DateTimeOffset LastSeenAtUtc { get; set; }
public string SessionToken { get; set; } = string.Empty;
public DateTimeOffset SessionTokenExpiresAtUtc { get; set; }
}
private readonly ConcurrentDictionary<(ShrinkNetworkService service, long sessionId), AuthState> _states = new();
public AuthState MarkAuthenticated(ShrinkNetworkService service, long sessionId, string authName, TimeSpan tokenTtl)
{
var now = DateTimeOffset.UtcNow;
var state = new AuthState
{
AuthName = authName,
IsAuthenticated = true,
AuthenticatedAtUtc = now,
LastSeenAtUtc = now,
SessionToken = CreateSessionToken(),
SessionTokenExpiresAtUtc = now.Add(tokenTtl)
};
_states[(service, sessionId)] = state;
return CloneState(state);
}
public void Remove(ShrinkNetworkService service, long sessionId)
{
_states.TryRemove((service, sessionId), out _);
}
public bool IsAuthenticated(ShrinkNetworkService service, long sessionId)
{
return _states.TryGetValue((service, sessionId), out var state) && state.IsAuthenticated;
}
public bool TryGetState(ShrinkNetworkService service, long sessionId, out AuthState state)
{
if (_states.TryGetValue((service, sessionId), out var current))
{
state = CloneState(current);
return true;
}
state = null!;
return false;
}
public bool TryValidateSessionToken(ShrinkNetworkService service, long sessionId, string? sessionToken, out AuthState state,
out string failureReason)
{
if (!_states.TryGetValue((service, sessionId), out var current) || !current.IsAuthenticated)
{
state = null!;
failureReason = "会话未认证。";
return false;
}
if (string.IsNullOrWhiteSpace(sessionToken))
{
state = null!;
failureReason = "缺少会话令牌。";
return false;
}
if (!string.Equals(current.SessionToken, sessionToken.Trim(), StringComparison.Ordinal))
{
state = null!;
failureReason = "会话令牌不匹配。";
return false;
}
var now = DateTimeOffset.UtcNow;
if (current.SessionTokenExpiresAtUtc <= now)
{
state = null!;
failureReason = "会话令牌已过期。";
return false;
}
current.LastSeenAtUtc = now;
state = CloneState(current);
failureReason = string.Empty;
return true;
}
public bool TryRefreshSessionToken(ShrinkNetworkService service, long sessionId, string? sessionToken, TimeSpan tokenTtl,
TimeSpan refreshWindow, out AuthState state, out string failureReason)
{
if (!TryValidateSessionToken(service, sessionId, sessionToken, out _, out failureReason))
{
state = null!;
return false;
}
var key = (service, sessionId);
if (!_states.TryGetValue(key, out var current))
{
state = null!;
failureReason = "认证状态不存在。";
return false;
}
var now = DateTimeOffset.UtcNow;
if (refreshWindow > TimeSpan.Zero && current.SessionTokenExpiresAtUtc - now > refreshWindow)
{
state = null!;
failureReason = "当前还不在会话令牌续期窗口内。";
return false;
}
current.SessionToken = CreateSessionToken();
current.SessionTokenExpiresAtUtc = now.Add(tokenTtl);
current.LastSeenAtUtc = now;
state = CloneState(current);
failureReason = string.Empty;
return true;
}
private static AuthState CloneState(AuthState state)
{
return new AuthState
{
AuthName = state.AuthName,
IsAuthenticated = state.IsAuthenticated,
AuthenticatedAtUtc = state.AuthenticatedAtUtc,
LastSeenAtUtc = state.LastSeenAtUtc,
SessionToken = state.SessionToken,
SessionTokenExpiresAtUtc = state.SessionTokenExpiresAtUtc
};
}
private static string CreateSessionToken()
{
var buffer = new byte[32];
RandomNumberGenerator.Fill(buffer);
return Convert.ToBase64String(buffer)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
}
}