Files
Fishing2Server/Hotfix/Api/Controllers/AuthController.cs
2026-04-01 16:40:34 +08:00

139 lines
4.1 KiB
C#

using System.Collections.Concurrent;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using Fantasy;
using Fantasy.Async;
using Fantasy.Network.HTTP;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
namespace NBF;
[ApiController]
[Route("api/[controller]")]
[ServiceFilter(typeof(SceneContextFilter))]
public class AuthController : NBControllerBase
{
private static readonly ConcurrentDictionary<string, CaptchaInfo> CaptchaCache = new();
private static readonly TimeSpan CaptchaLifetime = TimeSpan.FromMinutes(10);
private readonly Scene _scene;
public AuthController(Scene scene)
{
_scene = scene;
}
[HttpGet("login")]
[AllowAnonymous]
public async FTask<IActionResult> Login([FromQuery] string account, [FromQuery] string code)
{
if (string.IsNullOrWhiteSpace(account) || string.IsNullOrWhiteSpace(code))
{
await FTask.CompletedTask;
return Error(ErrorCode.ArgsError, "account or code is empty");
}
var key = account.Trim();
if (!CaptchaCache.TryGetValue(key, out var captchaInfo))
{
await FTask.CompletedTask;
return Error(ErrorCode.ArgsError, "captcha not found");
}
if (captchaInfo.ExpireAtUtc <= DateTime.UtcNow)
{
CaptchaCache.TryRemove(key, out _);
await FTask.CompletedTask;
return Error(ErrorCode.ArgsError, "captcha expired");
}
if (!string.Equals(captchaInfo.Code, code.Trim(), StringComparison.Ordinal))
{
await FTask.CompletedTask;
return Error(ErrorCode.ArgsError, "captcha invalid");
}
CaptchaCache.TryRemove(key, out _);
var (token, expireAtUtc) = GenerateJwtToken(key);
await FTask.CompletedTask;
return Success(new
{
token,
expireAtUtc
});
}
[HttpGet("code")]
[AllowAnonymous]
public async FTask<IActionResult> SendCode([FromQuery] string account)
{
if (string.IsNullOrWhiteSpace(account))
{
await FTask.CompletedTask;
return Error(ErrorCode.ArgsError, "account is empty");
}
var key = account.Trim();
var captchaCode = RandomNumberGenerator.GetInt32(0, 1_000_000).ToString("D6");
var expireAtUtc = DateTime.UtcNow.Add(CaptchaLifetime);
CaptchaCache[key] = new CaptchaInfo
{
Code = captchaCode,
ExpireAtUtc = expireAtUtc
};
await DingTalkHelper.SendCAPTCHA(account, captchaCode);
return Success();
}
[HttpGet("me")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Me()
{
var account = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty;
return Success(new
{
account
});
}
private static (string Token, DateTime ExpireAtUtc) GenerateJwtToken(string account)
{
var now = DateTime.UtcNow;
var expireAtUtc = now.Add(ApiJwtHelper.TokenLifetime);
var tokenHandler = new JwtSecurityTokenHandler();
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, account),
new Claim(ClaimTypes.Name, account),
new Claim(ClaimTypes.Role, "Player")
};
var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = expireAtUtc,
NotBefore = now,
Issuer = ApiJwtHelper.Issuer,
Audience = ApiJwtHelper.Audience,
SigningCredentials = new SigningCredentials(
ApiJwtHelper.CreateSigningKey(),
SecurityAlgorithms.HmacSha256)
});
return (tokenHandler.WriteToken(token), expireAtUtc);
}
private sealed class CaptchaInfo
{
public string Code { get; init; } = string.Empty;
public DateTime ExpireAtUtc { get; init; }
}
}