using System.Globalization; using System.Security.Cryptography; using System.Text; namespace SeaHaven.Services.Helpers { /// /// Generation and hashing for invite tokens and email confirmation codes. Raw /// values exist only in memory and in the email sent to the invitee. /// public static class TeamMemberInviteSecrets { /// 256 bits from the OS CSPRNG. public const int TokenBytes = 32; /// Upper bound on an accepted token string; a 32-byte base64url token is 43 characters. 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(); } } }