using System.Text; using System.Text.RegularExpressions; using Jiaowu.Api.Infrastructure.Auth; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; namespace Jiaowu.Api.Tests; public sealed class LoginCaptchaServiceTests { [Fact] public async Task Text_challenge_creates_a_one_time_proof_bound_to_the_same_device() { var cache = new MemoryDistributedCache( Options.Create(new MemoryDistributedCacheOptions())); var service = new LoginCaptchaService(cache); var context = CreateContext("127.0.0.1"); LoginCaptchaChallenge challenge; do { challenge = await service.CreateAsync("device-hash", context, CancellationToken.None); } while (challenge.Kind != nameof(LoginCaptchaKind.Text)); var svg = Encoding.UTF8.GetString(Convert.FromBase64String(challenge.ImageSvg.Split(',')[1])); var text = Regex.Match(svg, "fill='#1f456c'>([A-Z0-9]+)").Groups[1].Value; var proof = await service.VerifyAsync( new LoginCaptchaVerification(challenge.Id, "device-hash", text, null, null), context, CancellationToken.None); Assert.NotNull(proof); Assert.True(await service.ConsumeProofAsync(proof!, "device-hash", context, CancellationToken.None)); Assert.False(await service.ConsumeProofAsync(proof!, "device-hash", context, CancellationToken.None)); } [Fact] public async Task Challenge_cannot_be_verified_from_a_different_device_binding() { var cache = new MemoryDistributedCache( Options.Create(new MemoryDistributedCacheOptions())); var service = new LoginCaptchaService(cache); var context = CreateContext("127.0.0.1"); var challenge = await service.CreateAsync("device-a", context, CancellationToken.None); var proof = await service.VerifyAsync( new LoginCaptchaVerification(challenge.Id, "device-b", null, 0, []), context, CancellationToken.None); Assert.Null(proof); } private static DefaultHttpContext CreateContext(string ip) { var context = new DefaultHttpContext(); context.Connection.RemoteIpAddress = System.Net.IPAddress.Parse(ip); return context; } }