diff --git a/DysonNetwork.Sphere/Account/AccountController.cs b/DysonNetwork.Sphere/Account/AccountController.cs index 1277dad..9fd1cbe 100644 --- a/DysonNetwork.Sphere/Account/AccountController.cs +++ b/DysonNetwork.Sphere/Account/AccountController.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -6,7 +7,7 @@ namespace DysonNetwork.Sphere.Account; [ApiController] [Route("/accounts")] -public class AccountController(AppDatabase db) +public class AccountController(AppDatabase db, IHttpContextAccessor httpContext) { [HttpGet("{name}")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -22,13 +23,22 @@ public class AccountController(AppDatabase db) [Required] [MaxLength(256)] public string Name { get; set; } = string.Empty; [Required] [MaxLength(256)] public string Nick { get; set; } = string.Empty; [Required] [MaxLength(1024)] public string Email { get; set; } = string.Empty; - [Required] [MinLength(4)] [MaxLength(128)] public string Password { get; set; } = string.Empty; + + [Required] + [MinLength(4)] + [MaxLength(128)] + public string Password { get; set; } = string.Empty; } - + [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> CreateAccount([FromBody] AccountCreateRequest request) { + var dupeNameCount = await db.Accounts.Where(a => a.Name == request.Name).CountAsync(); + if (dupeNameCount > 0) + return new BadRequestObjectResult("The name is already taken."); + var account = new Account { Name = request.Name, @@ -50,9 +60,23 @@ public class AccountController(AppDatabase db) }.HashSecret() } }; - + await db.Accounts.AddAsync(account); await db.SaveChangesAsync(); return account; } + + [Authorize] + [HttpGet("me")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> GetMe() + { + var userIdClaim = httpContext.HttpContext?.User.FindFirst("user_id")?.Value; + long? userId = long.TryParse(userIdClaim, out var id) ? id : null; + if (userId is null) return new BadRequestObjectResult("Invalid or missing user_id claim."); + + var account = await db.Accounts.FindAsync(userId); + + return new OkObjectResult(account); + } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/AuthController.cs b/DysonNetwork.Sphere/Auth/AuthController.cs index 553c588..416235f 100644 --- a/DysonNetwork.Sphere/Auth/AuthController.cs +++ b/DysonNetwork.Sphere/Auth/AuthController.cs @@ -24,11 +24,23 @@ public class AuthController(AppDatabase db, AccountService accounts, AuthService public async Task> StartChallenge([FromBody] ChallengeRequest request) { var account = await accounts.LookupAccount(request.Account); - if (account is null) return new NotFoundResult(); + if (account is null) return new NotFoundObjectResult("Account was not found."); var ipAddress = httpContext.HttpContext?.Connection.RemoteIpAddress?.ToString(); var userAgent = httpContext.HttpContext?.Request.Headers.UserAgent.ToString(); + var now = Instant.FromDateTimeUtc(DateTime.UtcNow); + + // Trying to pick up challenges from the same IP address and user agent + var existingChallenge = await db.AuthChallenges + .Where(e => e.Account == account) + .Where(e => e.IpAddress == ipAddress) + .Where(e => e.UserAgent == userAgent) + .Where(e => e.StepRemain > 0) + .Where(e => e.ExpiredAt != null && now < e.ExpiredAt) + .FirstOrDefaultAsync(); + if (existingChallenge is not null) return existingChallenge; + var challenge = new Challenge { Account = account, @@ -149,7 +161,10 @@ public class AuthController(AppDatabase db, AccountService accounts, AuthService if (!Guid.TryParse(sessionIdClaim, out var sessionId)) return new UnauthorizedObjectResult("Invalid or missing session_id claim in refresh token."); - session = await db.AuthSessions.FirstOrDefaultAsync(s => s.Id == sessionId); + session = await db.AuthSessions + .Include(e => e.Account) + .Include(e => e.Challenge) + .FirstOrDefaultAsync(s => s.Id == sessionId); if (session is null) return new NotFoundObjectResult("Session not found or expired."); diff --git a/DysonNetwork.Sphere/Program.cs b/DysonNetwork.Sphere/Program.cs index f5ebea5..639819f 100644 --- a/DysonNetwork.Sphere/Program.cs +++ b/DysonNetwork.Sphere/Program.cs @@ -45,6 +45,7 @@ builder.Services.AddSingleton // Other pipelines +builder.Services.AddCors(); builder.Services.AddAuthorization(); builder.Services.AddAuthentication("Bearer").AddJwtBearer(options => { @@ -108,6 +109,12 @@ builder.Services.AddScoped(); var app = builder.Build(); +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.Migrate(); +} + if (app.Environment.IsDevelopment()) app.MapOpenApi(); app.UseSwagger(); @@ -118,11 +125,11 @@ app.UseForwardedHeaders(new ForwardedHeadersOptions ForwardedHeaders = ForwardedHeaders.All }); -using (var scope = app.Services.CreateScope()) -{ - var db = scope.ServiceProvider.GetRequiredService(); - db.Database.Migrate(); -} +app.UseCors(opts => + opts.SetIsOriginAllowed(_ => true) + .AllowCredentials() + .AllowAnyHeader() + .AllowAnyMethod()); app.UseHttpsRedirection(); app.UseAuthorization(); diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index d0cfeca..837c008 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -1,5 +1,6 @@  ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded