Files
Core_Be/Services/AuthService.cs
Dhananjaya99 bffc9b1686 first commit
2026-09-28 11:44:26 +05:30

690 lines
29 KiB
C#

using Fitness_core_BE.DTOs.Auth;
using Fitness_core_BE.Helpers;
using Fitness_core_BE.Infra.UoW;
using Fitness_core_BE.Interfaces;
using Fitness_core_BE.Interfaces.Services;
using Fitness_core_BE.Models;
using System.Net.Http;
using System.Text;
using System.Text.Json;
namespace Fitness_core_BE.Services.Auth
{
public class AuthService(IHttpClientFactory httpClientFactory, ICustomerRepository customerRepository, IUnitOfWork unitOfWork, IConfiguration configuration) : IAuthService
{
private readonly HttpClient _httpClient = httpClientFactory.CreateClient();
private readonly ICustomerRepository _cusRepo = customerRepository;
private readonly IUnitOfWork _UoW = unitOfWork;
private readonly string _baseUrl = configuration["AuthService:BaseUrl"] ?? "http://127.0.0.1:5602";
private string AuthAltTargetUrl => _baseUrl + "/api/alt";
private string AuthUserTargetUrl => _baseUrl + "/api/user";
private string AuthRecoveryTargetUrl => _baseUrl + "/api/recovery";
public async Task<AuthResponse> SendOtp(object payload, HttpContext httpContext)
{
var targetUrl = AuthAltTargetUrl;
var request = new
{
functionName = "sendOtp",
payload,
reference = "send-otp"
};
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, targetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return authResponse ?? new AuthResponse { StatusCode = 500, Success = false, Message = "Empty response from auth service", Data = null };
}
catch (Exception ex)
{
return new AuthResponse { StatusCode = 500, Success = false, Message = $"Error communicating with auth service: {ex.Message}", Data = null };
}
}
public async Task<AuthResponse> LoginUser(object payload, HttpContext httpContext)
{
// Web clients explicitly opt in to a persistent 30-day cookie. Without
// this, closing the browser ends the session even though Auth retains the
// server-side refresh session for security/audit purposes.
var loginData = DeserializePayload.Deserialize(payload);
var rememberMe = loginData?.TryGetValue("rememberMe", out var rememberValue) == true
&& rememberValue.ValueKind is System.Text.Json.JsonValueKind.True;
var targetUrl = AuthUserTargetUrl;
var request = new
{
functionName = "loginUser",
payload,
reference = "LoginUser"
};
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, targetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>( responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (authResponse == null)
return new AuthResponse { StatusCode = 500, Success = false, Message = "Empty response from auth service", Data = null };
if (authResponse.Success && authResponse.Data is JsonElement dataElement)
{
SetAuthCookiesFromData(httpContext, dataElement, rememberMe);
}
return authResponse;
}
catch (Exception ex)
{
throw new Exception($"Auth service error: {ex.Message}");
}
}
/// <summary>Sets the access_token (and refresh_token if present) cookies from an auth-service data payload.</summary>
private void SetAuthCookiesFromData(HttpContext httpContext, JsonElement dataElement, bool persistent = true)
{
string? accessToken = null;
if (dataElement.TryGetProperty("accessToken", out var tokenProp) ||
dataElement.TryGetProperty("AccessToken", out tokenProp))
accessToken = tokenProp.GetString();
if (!string.IsNullOrEmpty(accessToken))
SetAccessTokenCookieInternal(httpContext, accessToken, persistent);
string? refreshToken = null;
if (dataElement.TryGetProperty("refreshToken", out var refreshProp) ||
dataElement.TryGetProperty("RefreshToken", out refreshProp))
refreshToken = refreshProp.GetString();
if (!string.IsNullOrEmpty(refreshToken))
SetRefreshTokenCookie(httpContext, refreshToken, persistent);
}
public void SetAccessTokenCookie(HttpContext httpContext, string token)
=> SetAccessTokenCookieInternal(httpContext, token, persistent: true);
private void SetAccessTokenCookieInternal(HttpContext httpContext, string token, bool persistent)
{
var requestHost = httpContext.Request.Host.Host;
var forwardedProto = httpContext.Request.Headers["X-Forwarded-Proto"].FirstOrDefault();
var isHttpsRequest =
httpContext.Request.IsHttps ||
string.Equals(forwardedProto, "https", StringComparison.OrdinalIgnoreCase);
httpContext.Response.Cookies.Append("access_token", token, new CookieOptions
{
HttpOnly = true,
Secure = isHttpsRequest,
SameSite = isHttpsRequest ? SameSiteMode.None : SameSiteMode.Lax,
Expires = persistent ? DateTimeOffset.UtcNow.AddDays(30) : null,
Path = "/",
Domain = IsLocalHost(requestHost) ? null : "hexafit.hexdive.com"
});
}
public void SetRefreshTokenCookie(HttpContext httpContext, string refreshToken, bool persistent = true)
{
var requestHost = httpContext.Request.Host.Host;
var forwardedProto = httpContext.Request.Headers["X-Forwarded-Proto"].FirstOrDefault();
var isHttpsRequest =
httpContext.Request.IsHttps ||
string.Equals(forwardedProto, "https", StringComparison.OrdinalIgnoreCase);
httpContext.Response.Cookies.Append("refresh_token", refreshToken, new CookieOptions
{
HttpOnly = true,
Secure = isHttpsRequest,
SameSite = isHttpsRequest ? SameSiteMode.None : SameSiteMode.Lax,
Expires = persistent ? DateTimeOffset.UtcNow.AddDays(30) : null,
Path = "/",
Domain = IsLocalHost(requestHost) ? null : "hexafit.hexdive.com"
});
}
public void ClearAuthCookies(HttpContext httpContext)
{
var requestHost = httpContext.Request.Host.Host;
var domain = IsLocalHost(requestHost) ? null : "hexafit.hexdive.com";
var options = new CookieOptions { Path = "/", Domain = domain };
httpContext.Response.Cookies.Delete("access_token", options);
httpContext.Response.Cookies.Delete("refresh_token", options);
}
public async Task<AuthResponse> RefreshToken(object payload, HttpContext httpContext)
{
var targetUrl = AuthUserTargetUrl;
// The refresh token lives in an httpOnly cookie so the browser can trigger a
// silent refresh without ever holding it in JS. Fall back to the request payload
// (e.g. mobile clients that pass it explicitly).
string? refreshToken = null;
try
{
var data = DeserializePayload.Deserialize(payload);
if (data.TryGetValue("refreshToken", out var rt) && rt.ValueKind == JsonValueKind.String)
refreshToken = rt.GetString();
}
catch { /* payload may be empty on cookie-driven refresh */ }
if (string.IsNullOrEmpty(refreshToken))
refreshToken = httpContext.Request.Cookies["refresh_token"];
if (string.IsNullOrEmpty(refreshToken))
{
ClearAuthCookies(httpContext);
return new AuthResponse { StatusCode = 401, Success = false, Message = "Missing refresh token", Data = null };
}
var deviceName = httpContext.Request.Headers["User-Agent"].ToString();
var request = new
{
functionName = "refreshToken",
payload = new { refreshToken, deviceName },
reference = "RefreshToken"
};
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, targetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
if (!string.IsNullOrWhiteSpace(deviceName))
forwardRequest.Headers.TryAddWithoutValidation("User-Agent", deviceName);
var forwardedFor = httpContext.Connection.RemoteIpAddress?.ToString();
if (!string.IsNullOrWhiteSpace(forwardedFor))
forwardRequest.Headers.TryAddWithoutValidation("X-Forwarded-For", forwardedFor);
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (authResponse == null)
return new AuthResponse { StatusCode = 500, Success = false, Message = "Empty response from auth service", Data = null };
if (authResponse.Success && authResponse.Data is JsonElement dataElement)
{
// Core-Hex-Auth rotates the refresh token; persist both new cookies.
SetAuthCookiesFromData(httpContext, dataElement);
return authResponse;
}
// Refresh token invalid/expired — clear cookies and report 401 so the
// frontend redirects to login rather than surfacing a 500.
ClearAuthCookies(httpContext);
return new AuthResponse { StatusCode = 401, Success = false, Message = "Session expired. Please log in again.", Data = null };
}
catch (Exception ex)
{
return new AuthResponse { StatusCode = 500, Success = false, Message = $"Error communicating with auth service: {ex.Message}", Data = null };
}
}
//customer ta only use? why
public async Task<AuthResponse> VerifyOTPForCus(object payload, HttpContext httpContext)
{
var data = DeserializePayload.Deserialize(payload);
var targetUrl = AuthRecoveryTargetUrl;
var request = new
{
functionName = "verifyOTP",
payload,
reference = "verify-otp"
};
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, targetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (authResponse == null)
throw new Exception("Empty response from auth service");
string identifireType = DataExtractor.GetRequiredString(data, "identifireType");
var identifier = DataExtractor.GetRequiredString(data, "identifier");
Guid userId = DataExtractor.GetUserIdFromContext(httpContext);
await _UoW.BeginAsync();
try
{
bool result = await _cusRepo.VerifyCustomer(userId, identifireType, identifier);
if (!result)
throw new Exception("verfifying faled");
await _UoW.CommitAsync();
}
catch(Exception ex)
{
await _UoW.RollbackAsync();
throw new Exception($"{ex.Message}");
}
return authResponse;
}
catch (Exception ex)
{
return new AuthResponse { StatusCode = 500, Success = false, Message = $"Error communicating with auth service: {ex.Message}", Data = null };
}
}
public async Task<AuthResponse> VerifyOtpForLogin(object payload, HttpContext httpContext)
{
var targetUrl = AuthUserTargetUrl;
var request = new
{
functionName = "VerifyOtpForLogin",
payload,
reference = "verify-otp-for-login"
};
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, targetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return authResponse ?? new AuthResponse { StatusCode = 500, Success = false, Message = "Empty response from auth service", Data = null };
}
catch (Exception ex)
{
return new AuthResponse { StatusCode = 500, Success = false, Message = $"Error communicating with auth service: {ex.Message}", Data = null };
}
}
public async Task<AuthResponse> IsAvailable(object payload, HttpContext httpContext)
{
var targetUrl = AuthAltTargetUrl;
var request = new
{
functionName = "IsAvailable",
payload,
reference = "is-available"
};
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, targetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return authResponse ?? new AuthResponse { StatusCode = 500, Success = false, Message = "Empty response from auth service", Data = null };
}
catch (Exception ex)
{
return new AuthResponse { StatusCode = 500, Success = false, Message = $"Error communicating with auth service: {ex.Message}", Data = null };
}
}
public async Task<AuthResponse> RegisterUser(object payload, HttpContext httpContext)
{
var targetUrl = AuthUserTargetUrl;
var request = new
{
functionName = "registerUser",
payload,
reference = "register-user"
};
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, targetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return authResponse ?? new AuthResponse { StatusCode = 500, Success = false, Message = "Empty response from auth service", Data = null };
}
catch (Exception ex)
{
return new AuthResponse { StatusCode = 500, Success = false, Message = $"Error communicating with auth service: {ex.Message}", Data = null };
}
}
public async Task<AuthResponse> GetUserDetails(object payload, HttpContext httpContext)
{
var targetUrl = AuthUserTargetUrl;
var request = new
{
functionName = "getUserDetails",
payload,
reference = "user-details"
};
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, targetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
var authHeader = httpContext.Request.Headers["Authorization"].FirstOrDefault();
if (!string.IsNullOrEmpty(authHeader))
{
forwardRequest.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer",
authHeader.Replace("Bearer ", "")
);
}
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return authResponse ?? new AuthResponse { StatusCode = 500, Success = false, Message = "Empty response from auth service", Data = null };
}
catch (Exception ex)
{
return new AuthResponse { StatusCode = 500, Success = false, Message = $"Error communicating with auth service: {ex.Message}", Data = null };
}
}
public async Task<AuthResponse> UpdateUser(object payload, HttpContext httpContext)
{
var targetUrl = AuthUserTargetUrl;
var request = new
{
functionName = "UpdateUser",
payload,
reference = "user-details"
};
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, targetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
// Web clients authenticate via the access_token cookie, not an Authorization
// header — forward whichever is present so the auth service can read the userId.
ApplyAuthHeader(forwardRequest, httpContext);
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return authResponse ?? new AuthResponse { StatusCode = 500, Success = false, Message = "Empty response from auth service", Data = null };
}
catch (Exception ex)
{
return new AuthResponse { StatusCode = 500, Success = false, Message = $"Error communicating with auth service: {ex.Message}", Data = null };
}
}
public async Task<AuthResponse> AdminUpdateUser(object payload, HttpContext httpContext)
{
var targetUrl = AuthUserTargetUrl;
var request = new
{
functionName = "adminUpdateUser",
payload,
reference = "admin-update-user"
};
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, targetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return authResponse ?? new AuthResponse { StatusCode = 500, Success = false, Message = "Empty response from auth service", Data = null };
}
catch (Exception ex)
{
return new AuthResponse { StatusCode = 500, Success = false, Message = $"Error communicating with auth service: {ex.Message}", Data = null };
}
}
/// <summary>Forwards a recovery-manager function (forgotPassword / resetPassword) — no auth required.</summary>
private async Task<AuthResponse> ForwardToRecovery(string functionName, object payload)
{
var request = new { functionName, payload, reference = functionName };
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, AuthRecoveryTargetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return authResponse ?? new AuthResponse { StatusCode = 500, Success = false, Message = "Empty response from auth service", Data = null };
}
catch (Exception ex)
{
return new AuthResponse { StatusCode = 500, Success = false, Message = $"Error communicating with auth service: {ex.Message}", Data = null };
}
}
public Task<AuthResponse> ForgotPassword(object payload, HttpContext httpContext)
=> ForwardToRecovery("forgotPassword", payload);
public Task<AuthResponse> ResetPassword(object payload, HttpContext httpContext)
=> ForwardToRecovery("resetPassword", payload);
public Task<AuthResponse> ResetPasswordWithToken(object payload, HttpContext httpContext)
=> ForwardToRecovery("resetPasswordWithToken", payload);
// Confirms the caller's own password (used to unlock sensitive views). Returns { valid }.
public async Task<AuthResponse> VerifyPassword(object payload, HttpContext httpContext)
{
var request = new { functionName = "VerifyPassword", payload, reference = "verify-password" };
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, AuthUserTargetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
ApplyAuthHeader(forwardRequest, httpContext);
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return authResponse ?? new AuthResponse { StatusCode = 500, Success = false, Message = "Empty response from auth service", Data = null };
}
catch (Exception ex)
{
return new AuthResponse { StatusCode = 500, Success = false, Message = $"Error communicating with auth service: {ex.Message}", Data = null };
}
}
/// <summary>Sets the outgoing Bearer token from the Authorization header, the MobileAuthToken header, or the access_token cookie.</summary>
private static void ApplyAuthHeader(HttpRequestMessage req, HttpContext ctx)
{
string? token = null;
var authHeader = ctx.Request.Headers["Authorization"].FirstOrDefault();
if (!string.IsNullOrWhiteSpace(authHeader))
token = authHeader.Replace("Bearer ", "").Trim();
if (string.IsNullOrWhiteSpace(token))
{
var mobile = ctx.Request.Headers["MobileAuthToken"].FirstOrDefault();
if (!string.IsNullOrWhiteSpace(mobile) && mobile.StartsWith("Bearer "))
token = mobile.Substring("Bearer ".Length).Trim();
}
if (string.IsNullOrWhiteSpace(token))
token = ctx.Request.Cookies["access_token"];
if (!string.IsNullOrWhiteSpace(token))
req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
// new otp verfy for client
public async Task<AuthResponse> VerifyOTP(object payload, HttpContext httpContext)
{
var data = DeserializePayload.Deserialize(payload);
var targetUrl = AuthRecoveryTargetUrl;
var request = new
{
functionName = "verifyOTP",
payload,
reference = "verify-otp"
};
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, targetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (authResponse == null)
throw new Exception("Empty response from auth service");
return authResponse;
}
catch (Exception ex)
{
return new AuthResponse { StatusCode = 500, Success = false, Message = $"Error communicating with auth service: {ex.Message}", Data = null };
}
}
//Logout user by clearing cookie
public async Task<AuthResponse> LogOut(object payload, HttpContext httpContext)
{
var targetUrl = AuthUserTargetUrl;
var request = new
{
functionName = "LogoutUser",
payload,
reference = "log-out"
};
var json = JsonSerializer.Serialize(request);
var forwardRequest = new HttpRequestMessage(HttpMethod.Post, targetUrl)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
// Always clear cookies locally, even if the remote revoke fails — the user
// clicked logout and their browser session must end.
ClearAuthCookies(httpContext);
try
{
var response = await _httpClient.SendAsync(forwardRequest);
var responseContent = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return authResponse ?? new AuthResponse { StatusCode = 200, Success = true, Message = "Logged out", Data = null };
}
catch
{
return new AuthResponse { StatusCode = 200, Success = true, Message = "Logged out", Data = null };
}
}
private static bool IsLocalHost(string? host)
{
return string.Equals(host, "localhost", StringComparison.OrdinalIgnoreCase) ||
string.Equals(host, "127.0.0.1", StringComparison.OrdinalIgnoreCase);
}
}
}