using Microsoft.Extensions.Configuration; using QuestionsApi.Models; using QuestionsApi.Services; using System.IdentityModel.Tokens.Jwt; using Xunit; namespace QuestionsApi.Tests; public sealed class AuthTests { [Theory] [InlineData("valid_user", "person@example.com", "password1", null)] [InlineData("ab", "person@example.com", "password1", "Username")] [InlineData("invalid-user", "person@example.com", "password1", "Username")] [InlineData("valid_user", "not-an-email", "password1", "email")] [InlineData("valid_user", "person@example.com", "short", "Password")] public void NewAccountValidationRejectsInvalidInput( string username, string email, string password, string? expectedError) { var result = AuthValidation.ValidateNewAccount(username, email, password); if (expectedError is null) Assert.Null(result); else Assert.Contains(expectedError, result!, StringComparison.OrdinalIgnoreCase); } [Fact] public void PasswordResetCodesAreSixDigitsAndBoundToTheirRequest() { var service = new PasswordResetCodeService(Configuration(new Dictionary { ["PasswordReset:HmacKey"] = "A-test-only-key-that-is-longer-than-thirty-two-bytes" })); var code = service.GenerateCode(); var hash = service.Hash("request-one", code); Assert.Matches("^[0-9]{6}$", code); Assert.True(service.Verify("request-one", code, hash)); Assert.False(service.Verify("request-two", code, hash)); Assert.False(service.Verify("request-one", "000000", hash)); } [Fact] public void PasswordResetCanRemainUnconfiguredWithoutBreakingAuthentication() { var service = new PasswordResetCodeService(Configuration([])); Assert.False(service.IsConfigured); } [Fact] public void JwtContainsCompatibleIdentityRoleAndAuthVersionClaims() { var service = new JwtTokenService(Configuration(new Dictionary { ["Jwt:Key"] = "A-test-only-jwt-key-that-is-longer-than-thirty-two-bytes", ["Jwt:Issuer"] = "promiscuity", ["Jwt:Audience"] = "promiscuity-auth-api" })); var user = new AuthUser { Id = "507f1f77bcf86cd799439011", Username = "doctor", Role = "SUPER", AuthVersion = 4 }; var generated = service.Generate(user); var token = new JwtSecurityTokenHandler().ReadJwtToken(generated.Token); Assert.Equal("promiscuity", token.Issuer); Assert.Contains("promiscuity-auth-api", token.Audiences); Assert.Equal("4", token.Claims.Single(claim => claim.Type == "auth_ver").Value); Assert.Equal(generated.Jti, token.Id); } [Fact] public void LegacyUsersDefaultToAuthVersionZero() { Assert.Equal(0, new AuthUser().AuthVersion); } [Fact] public void SteamClaimedIdExtractionRejectsUnexpectedHostsAndLengths() { Assert.Equal("76561198000000000", SteamOpenIdService.ExtractSteamId("https://steamcommunity.com/openid/id/76561198000000000")); Assert.Null(SteamOpenIdService.ExtractSteamId("https://example.com/openid/id/76561198000000000")); Assert.Null(SteamOpenIdService.ExtractSteamId("https://steamcommunity.com/openid/id/123")); } private static IConfiguration Configuration(Dictionary values) => new ConfigurationBuilder().AddInMemoryCollection(values).Build(); }