mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
90 lines
3.8 KiB
C#
90 lines
3.8 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
using DorkNet.Server.Auth;
|
|
using DorkNet.Server.Data;
|
|
|
|
namespace DorkNet.Server.Services;
|
|
|
|
public class AuthService(IConfiguration config, DorkNetDbContext db, DomainConfig domain, IdentityServerSigningKeyProvider signingKeyProvider)
|
|
{
|
|
public const string AccessCookieName = "dorknet_access_token";
|
|
public const string LegacyIssuer = "https://api.rec.net/";
|
|
|
|
// Resolution order MUST match Program.cs's JwtBearer validator setup —
|
|
// env var first, config key second — otherwise tokens are signed with
|
|
// one secret and validated against another and every authenticated
|
|
// request 401s. In production we set DORKNET_JWT_SECRET in Coolify
|
|
// (RECNET_JWT_SECRET kept as fallback for backward compat with older
|
|
// configs) and leave Jwt:Secret as the appsettings.json placeholder;
|
|
// the mismatched-secret bug shows up only there.
|
|
private string SecretKey =>
|
|
Environment.GetEnvironmentVariable("DORKNET_JWT_SECRET")
|
|
?? Environment.GetEnvironmentVariable("RECNET_JWT_SECRET")
|
|
?? config["Jwt:Secret"]
|
|
?? throw new InvalidOperationException(
|
|
"JWT secret not configured. Set DORKNET_JWT_SECRET env var or " +
|
|
"Jwt:Secret in appsettings.Local.json.");
|
|
|
|
public string GenerateToken(long playerId) => CreateJwt(playerId, TimeSpan.FromHours(12), "access");
|
|
|
|
/// <summary>
|
|
/// Issues an (access, refresh) pair for the OAuth-style password-grant
|
|
/// response shape (Login.LoginResponse). Access tokens are short-lived;
|
|
/// the refresh token is what api/platformlogin/refresh accepts later.
|
|
/// </summary>
|
|
public (string AccessToken, string RefreshToken) GenerateTokenPair(long playerId) =>
|
|
(CreateJwt(playerId, TimeSpan.FromHours(12), "access"),
|
|
CreateJwt(playerId, TimeSpan.FromDays(30), "refresh"));
|
|
|
|
private string CreateJwt(long playerId, TimeSpan lifetime, string tokenType)
|
|
{
|
|
var handler = new JwtSecurityTokenHandler();
|
|
var claims = RecRoomIdentityClaims.CreateAsync(db, playerId)
|
|
.GetAwaiter()
|
|
.GetResult();
|
|
claims.Add(new Claim("token_type", tokenType));
|
|
|
|
var descriptor = new SecurityTokenDescriptor
|
|
{
|
|
Subject = new ClaimsIdentity(claims),
|
|
Expires = DateTime.UtcNow.Add(lifetime),
|
|
Issuer = domain.AuthIssuer,
|
|
SigningCredentials = new SigningCredentials(signingKeyProvider.SigningKey, SecurityAlgorithms.RsaSha256),
|
|
};
|
|
|
|
return handler.WriteToken(handler.CreateJwtSecurityToken(descriptor));
|
|
}
|
|
|
|
public long? ValidateToken(string token)
|
|
{
|
|
try
|
|
{
|
|
var handler = new JwtSecurityTokenHandler();
|
|
var principal = handler.ValidateToken(token, new TokenValidationParameters
|
|
{
|
|
ValidateIssuerSigningKey = true,
|
|
IssuerSigningKeys = signingKeyProvider.ValidationKeys,
|
|
ValidateIssuer = true,
|
|
ValidIssuers = new[] { domain.AuthIssuer, LegacyIssuer },
|
|
ValidateAudience = false,
|
|
ClockSkew = TimeSpan.Zero,
|
|
}, out _);
|
|
|
|
var idClaim = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value
|
|
?? principal.FindFirst(JwtRegisteredClaimNames.Sub)?.Value
|
|
?? principal.FindFirst("sub")?.Value
|
|
?? principal.FindFirst("accountId")?.Value
|
|
?? principal.FindFirst("account_id")?.Value
|
|
?? principal.FindFirst("accountid")?.Value;
|
|
return idClaim is not null ? long.Parse(idClaim) : null;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|