Merge branch 'dev' into production
This commit is contained in:
@@ -2,14 +2,27 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Proxies all authentication traffic from the Next.js frontend to the
|
||||
/// external Auth Service. The frontend must never call the Auth Service
|
||||
/// directly — all auth goes through this controller.
|
||||
///
|
||||
/// TODO: Implement endpoints once Auth Service contract is agreed.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/auth")]
|
||||
public class AuthController(AuthService service) : ControllerBase
|
||||
public class AuthController(IAuthServiceClient authServiceClient) : ControllerBase
|
||||
{
|
||||
// TODO: replace stub endpoints below with real proxy calls once
|
||||
// the Auth Service API contract is finalised.
|
||||
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login(LoginRequest request)
|
||||
public Task<IActionResult> Login()
|
||||
{
|
||||
var result = await service.LoginAsync(request);
|
||||
return result is null ? Unauthorized() : Ok(result);
|
||||
// TODO: accept login request shape from Auth Service contract,
|
||||
// forward to authServiceClient, relay response (token / session) back.
|
||||
return Task.FromResult<IActionResult>(StatusCode(501, new { error = "Auth integration not yet implemented." }));
|
||||
}
|
||||
|
||||
// TODO: add further endpoints as needed (refresh, logout, etc.)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
public record LoginRequest(string Username, string Password);
|
||||
|
||||
public record LoginResponse(string Token, Guid UserId, string Username, string RoleName);
|
||||
// TODO: Define request/response shapes once the Auth Service contract is agreed.
|
||||
// These will be the DTOs passed between the Next.js frontend and this proxy controller,
|
||||
// which should mirror (or adapt) whatever the Auth Service expects/returns.
|
||||
|
||||
@@ -1,24 +1,6 @@
|
||||
using Distribution.Features.User;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
public class AuthService(IUserRepository userRepo, IJwtTokenService tokenService)
|
||||
{
|
||||
private readonly PasswordHasher<Models.User> _hasher = new();
|
||||
|
||||
public async Task<LoginResponse?> LoginAsync(LoginRequest request)
|
||||
{
|
||||
var user = await userRepo.GetByUsernameAsync(request.Username);
|
||||
if (user is null || !user.IsActive)
|
||||
return null;
|
||||
|
||||
var result = _hasher.VerifyHashedPassword(user, user.PasswordHash, request.Password);
|
||||
if (result == PasswordVerificationResult.Failed)
|
||||
return null;
|
||||
|
||||
var token = tokenService.GenerateToken(user);
|
||||
return new LoginResponse(token, user.Id, user.Username, user.Role?.Name ?? string.Empty);
|
||||
}
|
||||
}
|
||||
// TODO: This service previously contained local password validation logic.
|
||||
// It has been replaced by the IAuthServiceClient connector which proxies
|
||||
// to the external Auth Service. Remove this file or repurpose it for any
|
||||
// local orchestration logic once the Auth Service contract is defined.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Typed HttpClient that proxies requests to the external Auth Service.
|
||||
/// Registered via AddHttpClient<AuthServiceClient> in Program.cs.
|
||||
/// BaseAddress is configured from AuthService:BaseUrl in appsettings.json.
|
||||
///
|
||||
/// TODO: Implement methods once Auth Service contract is agreed.
|
||||
/// </summary>
|
||||
public class AuthServiceClient(HttpClient http) : IAuthServiceClient
|
||||
{
|
||||
// TODO: implement proxy methods, e.g.:
|
||||
//
|
||||
// public async Task<AuthServiceResponse?> LoginAsync(LoginRequest request, CancellationToken ct)
|
||||
// {
|
||||
// var response = await http.PostAsJsonAsync("/TODO_AUTH_SERVICE_LOGIN_PATH", request, ct);
|
||||
// if (!response.IsSuccessStatusCode) return null;
|
||||
// return await response.Content.ReadFromJsonAsync<AuthServiceResponse>(ct);
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
public class AuthServiceOptions
|
||||
{
|
||||
public const string SectionName = "AuthService";
|
||||
|
||||
/// <summary>Base URL of the external Auth Service, e.g. "https://auth.internal".</summary>
|
||||
public string BaseUrl { get; set; } = string.Empty;
|
||||
|
||||
// TODO: add other config (API keys, timeout, etc.) once Auth Service is defined.
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Connector to the external Auth Service. This backend proxies all auth
|
||||
/// traffic from the Next.js frontend through to the Auth Service — the
|
||||
/// frontend never calls the Auth Service directly.
|
||||
///
|
||||
/// TODO: Fill in the method signatures once the Auth Service contract is agreed.
|
||||
/// </summary>
|
||||
public interface IAuthServiceClient
|
||||
{
|
||||
// TODO: define methods based on Auth Service contract, e.g.:
|
||||
// Task<AuthServiceResponse?> LoginAsync(LoginRequest request, CancellationToken ct);
|
||||
// Task<AuthServiceResponse?> RefreshAsync(string refreshToken, CancellationToken ct);
|
||||
// Task LogoutAsync(string token, CancellationToken ct);
|
||||
}
|
||||
+31
-4
@@ -91,7 +91,6 @@ builder.Services.AddScoped<OrderService>();
|
||||
builder.Services.AddScoped<SaleService>();
|
||||
builder.Services.AddScoped<UserService>();
|
||||
builder.Services.AddScoped<RoleService>();
|
||||
builder.Services.AddScoped<AuthService>();
|
||||
builder.Services.AddScoped<CategoryService>();
|
||||
builder.Services.AddScoped<BrandService>();
|
||||
builder.Services.AddScoped<UomService>();
|
||||
@@ -117,7 +116,6 @@ builder.Services.AddScoped<PaymentService>();
|
||||
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
|
||||
builder.Services.AddScoped<IJwtTokenService, JwtTokenService>();
|
||||
builder.Services.AddScoped<IPermissionRepository, PermissionRepository>();
|
||||
builder.Services.AddScoped<IScopeRepository, ScopeRepository>();
|
||||
builder.Services.AddScoped<IPermissionService, PermissionService>();
|
||||
@@ -125,9 +123,31 @@ builder.Services.AddScoped<IScopeAccessService, ScopeAccessService>();
|
||||
builder.Services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
|
||||
builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
|
||||
|
||||
var jwtSection = builder.Configuration.GetSection("Jwt");
|
||||
var jwtKey = jwtSection["Key"] ?? throw new InvalidOperationException("Jwt:Key is not configured.");
|
||||
// ── Auth Service connector ───────────────────────────────────────────────────
|
||||
// TODO: replace placeholder BaseAddress with real Auth Service URL once decided.
|
||||
// The AuthServiceOptions:BaseUrl value in appsettings.json drives this.
|
||||
builder.Services.Configure<AuthServiceOptions>(
|
||||
builder.Configuration.GetSection(AuthServiceOptions.SectionName));
|
||||
|
||||
var authServiceBaseUrl = builder.Configuration[$"{AuthServiceOptions.SectionName}:BaseUrl"];
|
||||
|
||||
builder.Services.AddHttpClient<AuthServiceClient>(client =>
|
||||
{
|
||||
// TODO: set BaseAddress once Auth Service URL is known.
|
||||
if (!string.IsNullOrWhiteSpace(authServiceBaseUrl))
|
||||
client.BaseAddress = new Uri(authServiceBaseUrl);
|
||||
});
|
||||
|
||||
builder.Services.AddScoped<IAuthServiceClient, AuthServiceClient>();
|
||||
|
||||
// ── Authentication ───────────────────────────────────────────────────────────
|
||||
// TODO: replace this JWT configuration with the token validation strategy
|
||||
// dictated by the Auth Service (symmetric key, JWKS/OIDC, etc.) once decided.
|
||||
var jwtSection = builder.Configuration.GetSection("Jwt");
|
||||
var jwtKey = jwtSection["Key"];
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(jwtKey))
|
||||
{
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
@@ -147,6 +167,13 @@ builder.Services.AddAuthentication(options =>
|
||||
ClockSkew = TimeSpan.FromMinutes(1)
|
||||
};
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Auth Service integration not yet configured — all [Authorize] checks will
|
||||
// fail until the token validation strategy is wired up.
|
||||
builder.Services.AddAuthentication();
|
||||
}
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
|
||||
@@ -17,5 +17,8 @@
|
||||
},
|
||||
"StoreManagement": {
|
||||
"UnverifiedKycOrderThreshold": 0
|
||||
},
|
||||
"AuthService": {
|
||||
"BaseUrl": ""
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user