Features: - Complete .NET client SDK for LittleShop API - JWT authentication with automatic token management - Catalog service for products and categories - Order service with payment creation - Retry policies using Polly for resilience - Error handling middleware - Dependency injection support - Comprehensive documentation and examples SDK Components: - Authentication service with token refresh - Strongly-typed models for all API responses - HTTP handlers for retry and error handling - Extension methods for easy DI registration - Example console application demonstrating usage Test Updates: - Fixed test compilation errors - Updated test data builders for new models - Corrected service constructor dependencies - Fixed enum value changes (PaymentStatus, OrderStatus) Documentation: - Complete project README with features and usage - Client SDK README with detailed examples - API endpoint documentation - Security considerations - Deployment guidelines Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
126 lines
4.1 KiB
C#
126 lines
4.1 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Net.Http.Json;
|
|
using LittleShop.Client.Models;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace LittleShop.Client.Services;
|
|
|
|
public class AuthenticationService : IAuthenticationService
|
|
{
|
|
private readonly HttpClient _httpClient;
|
|
private readonly ILogger<AuthenticationService> _logger;
|
|
private string? _currentToken;
|
|
private DateTime? _tokenExpiry;
|
|
|
|
public AuthenticationService(HttpClient httpClient, ILogger<AuthenticationService> logger)
|
|
{
|
|
_httpClient = httpClient;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<ApiResponse<LoginResponse>> LoginAsync(string username, string password)
|
|
{
|
|
try
|
|
{
|
|
var request = new LoginRequest { Username = username, Password = password };
|
|
var response = await _httpClient.PostAsJsonAsync("api/auth/login", request);
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var loginResponse = await response.Content.ReadFromJsonAsync<LoginResponse>();
|
|
if (loginResponse != null)
|
|
{
|
|
SetToken(loginResponse.Token);
|
|
return ApiResponse<LoginResponse>.Success(loginResponse);
|
|
}
|
|
}
|
|
|
|
var error = await response.Content.ReadAsStringAsync();
|
|
return ApiResponse<LoginResponse>.Failure(
|
|
error ?? "Login failed",
|
|
response.StatusCode);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Login failed");
|
|
return ApiResponse<LoginResponse>.Failure(
|
|
ex.Message,
|
|
System.Net.HttpStatusCode.InternalServerError);
|
|
}
|
|
}
|
|
|
|
public async Task<ApiResponse<LoginResponse>> RefreshTokenAsync()
|
|
{
|
|
if (string.IsNullOrEmpty(_currentToken))
|
|
{
|
|
return ApiResponse<LoginResponse>.Failure(
|
|
"No token to refresh",
|
|
System.Net.HttpStatusCode.Unauthorized);
|
|
}
|
|
|
|
try
|
|
{
|
|
_httpClient.DefaultRequestHeaders.Authorization =
|
|
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _currentToken);
|
|
|
|
var response = await _httpClient.PostAsync("api/auth/refresh", null);
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var loginResponse = await response.Content.ReadFromJsonAsync<LoginResponse>();
|
|
if (loginResponse != null)
|
|
{
|
|
SetToken(loginResponse.Token);
|
|
return ApiResponse<LoginResponse>.Success(loginResponse);
|
|
}
|
|
}
|
|
|
|
return ApiResponse<LoginResponse>.Failure(
|
|
"Token refresh failed",
|
|
response.StatusCode);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Token refresh failed");
|
|
return ApiResponse<LoginResponse>.Failure(
|
|
ex.Message,
|
|
System.Net.HttpStatusCode.InternalServerError);
|
|
}
|
|
}
|
|
|
|
public void SetToken(string token)
|
|
{
|
|
_currentToken = token;
|
|
_httpClient.DefaultRequestHeaders.Authorization =
|
|
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
|
|
|
// Parse token to get expiry
|
|
try
|
|
{
|
|
var handler = new JwtSecurityTokenHandler();
|
|
var jsonToken = handler.ReadJwtToken(token);
|
|
_tokenExpiry = jsonToken.ValidTo;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to parse token expiry");
|
|
_tokenExpiry = DateTime.UtcNow.AddHours(1); // Default to 1 hour
|
|
}
|
|
}
|
|
|
|
public string? GetToken() => _currentToken;
|
|
|
|
public bool IsAuthenticated()
|
|
{
|
|
return !string.IsNullOrEmpty(_currentToken) &&
|
|
_tokenExpiry.HasValue &&
|
|
_tokenExpiry.Value > DateTime.UtcNow;
|
|
}
|
|
|
|
public void Logout()
|
|
{
|
|
_currentToken = null;
|
|
_tokenExpiry = null;
|
|
_httpClient.DefaultRequestHeaders.Authorization = null;
|
|
}
|
|
} |