Files
tiku-backend.net/Tiku.Application/Security/JwtOptions.cs

82 lines
2.5 KiB
C#

namespace Tiku.Application.Security;
public sealed class JwtOptions
{
[System.ComponentModel.DataAnnotations.Required]
public string Issuer { get; set; } = "tiku-backend";
[System.ComponentModel.DataAnnotations.Required]
public string Audience { get; set; } = "tiku-api";
[System.ComponentModel.DataAnnotations.Required]
public string KeyId { get; set; } = "development-ephemeral";
public string PrivateKeyPem { get; set; } = string.Empty;
public Dictionary<string, string> PublicKeys { get; set; } = new(StringComparer.Ordinal);
[System.ComponentModel.DataAnnotations.Range(1, 1440)]
public int AccessTokenMinutes { get; set; } = 15;
[System.ComponentModel.DataAnnotations.Range(1, 365)]
public int RefreshTokenDays { get; set; } = 30;
public static bool BeValid(JwtOptions options, bool isProduction)
{
if (string.IsNullOrWhiteSpace(options.Issuer) ||
string.IsNullOrWhiteSpace(options.Audience) ||
string.IsNullOrWhiteSpace(options.KeyId) ||
options.AccessTokenMinutes != 15 ||
(isProduction && string.Equals(
options.KeyId,
"development-ephemeral",
StringComparison.Ordinal)))
{
return false;
}
if (string.IsNullOrWhiteSpace(options.PrivateKeyPem))
{
if (isProduction)
{
return false;
}
}
else if (!IsValidRsaPem(options.PrivateKeyPem, requirePrivateKey: true))
{
return false;
}
return options.PublicKeys.All(pair =>
!string.IsNullOrWhiteSpace(pair.Key) &&
!string.Equals(pair.Key, options.KeyId, StringComparison.Ordinal) &&
IsValidRsaPem(pair.Value, requirePrivateKey: false));
}
private static bool IsValidRsaPem(string pem, bool requirePrivateKey)
{
try
{
using var rsa = System.Security.Cryptography.RSA.Create();
rsa.ImportFromPem(pem);
if (rsa.KeySize < 2048)
{
return false;
}
if (requirePrivateKey)
{
_ = rsa.ExportParameters(includePrivateParameters: true);
}
return true;
}
catch (Exception exception) when (
exception is ArgumentException or
System.Security.Cryptography.CryptographicException)
{
return false;
}
}
}