shoc-backend/SeaHaven.Services/Helpers/TeamMemberInviteSecrets.cs

63 lines
2.1 KiB
C#
Raw Permalink Normal View History

using System.Globalization;
using System.Security.Cryptography;
using System.Text;
namespace SeaHaven.Services.Helpers
{
/// <summary>
/// Generation and hashing for invite tokens and email confirmation codes. Raw
/// values exist only in memory and in the email sent to the invitee.
/// </summary>
public static class TeamMemberInviteSecrets
{
/// <summary>256 bits from the OS CSPRNG.</summary>
public const int TokenBytes = 32;
/// <summary>Upper bound on an accepted token string; a 32-byte base64url token is 43 characters.</summary>
public const int MaxTokenLength = 128;
public static string NewToken()
{
return Convert.ToBase64String(RandomNumberGenerator.GetBytes(TokenBytes))
.Replace('+', '-')
.Replace('/', '_')
.TrimEnd('=');
}
public static string HashToken(string token)
{
ArgumentNullException.ThrowIfNull(token);
return Sha256Hex(token);
}
public static string NewCode()
{
return RandomNumberGenerator.GetInt32(0, 1_000_000).ToString("D6", CultureInfo.InvariantCulture);
}
public static string NewCodeSalt()
{
return Convert.ToHexString(RandomNumberGenerator.GetBytes(16)).ToLowerInvariant();
}
public static string HashCode(string salt, string code)
{
ArgumentNullException.ThrowIfNull(salt);
ArgumentNullException.ThrowIfNull(code);
return Sha256Hex(salt + ":" + code);
}
public static bool CodeMatches(string salt, string candidate, string expectedHash)
{
var actual = Encoding.ASCII.GetBytes(HashCode(salt, candidate));
var expected = Encoding.ASCII.GetBytes(expectedHash);
return CryptographicOperations.FixedTimeEquals(actual, expected);
}
private static string Sha256Hex(string value)
{
return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(value))).ToLowerInvariant();
}
}
}