62 lines
2.3 KiB
C#
62 lines
2.3 KiB
C#
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]+)</text>").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;
|
|
}
|
|
}
|