First Commit

This commit is contained in:
Jorge Luis caudillo esparza
2026-05-09 16:36:57 -07:00
parent 96ad4c0d3e
commit d0c284d9c0
131 changed files with 4009 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
namespace ADS.Apis.Gps.Application.Abstractions.Security;
public interface IAdsClientCredentialValidator
{
Task<AdsClientCredentialResult> ValidateAsync(string clientId, string clientSecret, CancellationToken ct = default);
}
public sealed record AdsClientCredentialResult(
bool IsValid,
int? ClientId,
string? ClientKey,
string? ClientName
);

View File

@@ -0,0 +1,13 @@
namespace ADS.Apis.Gps.Application.Abstractions.Security;
public interface IAdsUserCredentialValidator
{
Task<AdsUserCredentialResult> ValidateAsync(string username, string password, CancellationToken ct = default);
}
public sealed record AdsUserCredentialResult(
bool IsValid,
int? UserId,
string? Username,
string? DisplayName
);

View File

@@ -0,0 +1,6 @@
namespace ADS.Apis.Gps.Application.Common.Crypto;
public interface ILegacyPasswordEncryptor
{
string Encrypt(string plainText);
}

View File

@@ -0,0 +1,38 @@
using System.Security.Cryptography;
using System.Text;
namespace ADS.Apis.Gps.Application.Common.Crypto;
public static class Md5Hex32
{
public static bool IsMd5Hex32(string value)
{
if (string.IsNullOrWhiteSpace(value) || value.Length != 32) return false;
for (var i = 0; i < value.Length; i++)
{
var c = value[i];
var isHex =
(c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F');
if (!isHex) return false;
}
return true;
}
public static string ToMd5Hex32(string plainText)
{
using var md5 = MD5.Create();
var bytes = Encoding.UTF8.GetBytes(plainText ?? string.Empty);
var hash = md5.ComputeHash(bytes);
var sb = new StringBuilder(32);
foreach (var b in hash)
sb.Append(b.ToString("x2"));
return sb.ToString();
}
}

View File

@@ -0,0 +1,10 @@
using System.Threading;
using System.Threading.Tasks;
using ADS.Apis.Gps.Application.Dtos.Auth;
namespace ADS.Apis.Gps.Application.Contracts.Auth;
public interface ISicrepAuthService
{
Task<SicrepAuthResponse> CreateTokenAsync(SicrepAuthRequest request, string? ip, string? mac, string? grupoEmpresa, CancellationToken ct = default);
}

View File

@@ -0,0 +1,20 @@
namespace ADS.Apis.Gps.Contracts.Common;
public sealed class ApiResponse<T>
{
public int CountRecords { get; init; }
public int StatusCode { get; init; }
public string StatusCodeText { get; init; } = string.Empty;
public string? ConsoleMessage { get; init; } // mensaje consola
public UiMessage? ScreenMessage { get; init; } // mensaje pantalla
public T? Value { get; init; }
}
public sealed class UiMessage
{
public int MessageType { get; init; } // tipoMensaje
public string Message { get; init; } = ""; // mensaje
public int MessageCode { get; init; } // codigoMsg
}

View File

@@ -0,0 +1,11 @@
namespace ADS.Apis.Gps.Contracts.Common;
public sealed class PagedQuery
{
public string? Search { get; init; }
public int Page { get; init; } = 1;
public int PageSize { get; init; } = 50;
// Si Pagination=false, tu servicio puede ignorar Page/PageSize y devolver todo (o un límite fijo).
public bool Pagination { get; init; } = true;
}

View File

@@ -0,0 +1,12 @@
namespace ADS.Apis.Gps.Contracts.Common;
public sealed class RequestOf<T>
{
public T? Value { get; init; }
// Si también quieres paginación en algunos body requests:
public string? Search { get; init; }
public int Page { get; init; } = 1;
public int PageSize { get; init; } = 50;
public bool Pagination { get; init; } = true;
}

View File

@@ -0,0 +1,10 @@
namespace ADS.Apis.Gps.Application.Dtos.Auth;
public sealed class SicrepAuthRequest
{
public string Usuario { get; set; } = string.Empty;
public string Contrasena { get; set; } = string.Empty;
// Opcional: si quieres forzar si viene en MD5 o plaintext
// public bool IsMd5Hex32 { get; set; }
}

View File

@@ -0,0 +1,7 @@
namespace ADS.Apis.Gps.Application.Dtos.Auth;
public sealed class SicrepAuthResponse
{
public string AccessToken { get; init; } = string.Empty;
public string TokenType { get; init; } = "Bearer";
}

View File

@@ -0,0 +1,13 @@
namespace ADS.Apis.Gps.Application.Units;
public interface IUnitLocationQueryService
{
Task<UnitLastLocationResponse?> GetLastLocationByNoEconomicoAsync(
int cveUsuario,
string noEconomico,
CancellationToken ct = default);
Task<IReadOnlyList<UnitClientLocationResponse>> GetLocationsByClientAsync(
int cveUsuario,
int cveCliente,
CancellationToken ct = default);
}

View File

@@ -0,0 +1,12 @@
namespace ADS.Apis.Gps.Application.Units;
public sealed class UnitClientLocationResponse
{
public string? NoEconomico { get; set; }
public decimal? Velocidad { get; set; }
public bool? Encendido { get; set; }
public DateTime? FechaTrama { get; set; }
public string? Punto { get; set; }
public double? Longitud { get; set; }
public double? Latitud { get; set; }
}

View File

@@ -0,0 +1,12 @@
namespace ADS.Apis.Gps.Application.Units;
public sealed class UnitLastLocationResponse
{
public string? NoEconomico { get; set; }
public decimal? Velocidad { get; set; }
public bool? Encendido { get; set; }
public DateTime? FechaTrama { get; set; }
public string? Punto { get; set; }
public double? Longitud { get; set; }
public double? Latitud { get; set; }
}

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using ADS.Apis.Gps.Application.Common.Crypto;
using ADS.Apis.Gps.Application.Contracts.Auth;
using ADS.Apis.Gps.Application.Dtos.Auth;
using ADS.Apis.Gps.Domain.Ports.Sicrep;
using ADS.Helpers.MultiTenant.Abstractions;
// AJUSTA el namespace real de IAdsTokenService
using ADS.Helpers.Auth.Abstractions;
using ADS.Helpers.MultiTenant.Context;
namespace ADS.Apis.Gps.Application.UseCases.Auth;
public sealed class SicrepAuthService : ISicrepAuthService
{
private readonly ISicrepUserValidationRepository _repo;
private readonly ITenantContextAccessor _tenantAccessor;
private readonly IAdsTokenService _tokenService;
private readonly ILegacyPasswordEncryptor _passwordEncryptor;
public SicrepAuthService(
ISicrepUserValidationRepository repo,
ITenantContextAccessor tenantAccessor,
IAdsTokenService tokenService,
ILegacyPasswordEncryptor passwordEncryptor)
{
_repo = repo;
_tenantAccessor = tenantAccessor;
_tokenService = tokenService;
_passwordEncryptor = passwordEncryptor;
}
public async Task<SicrepAuthResponse> CreateTokenAsync(SicrepAuthRequest request,
string? ip,
string? mac,
string? grupoEmpresa, CancellationToken ct = default)
{
if (request is null) throw new ArgumentNullException(nameof(request));
var tenant = _tenantAccessor.Current;
if (tenant is null)
throw new UnauthorizedAccessException("TenantContext not available. Ensure UseInternalTenantDb() is configured.");
var cveEmpresa = tenant.EmpresaId;
var cveCliente = tenant.ClienteId;
var tenantId = tenant.TenantId;
if (string.IsNullOrWhiteSpace(request.Usuario))
throw new ArgumentException("Usuario is required.", nameof(request.Usuario));
if (string.IsNullOrWhiteSpace(request.Contrasena))
throw new ArgumentException("Contrasena is required.", nameof(request.Contrasena));
// NOTA MD5:
// No puedo asegurar si el SP espera MD5 hex 32 o plaintext.
// Estrategia segura:
// - Si ya viene con 32 hex -> se manda tal cual.
// - Si no -> se manda MD5.
// Si tu sistema espera plaintext, cambia esta línea.
var contrasena32 = _passwordEncryptor.Encrypt(request.Contrasena);/*Md5Hex32.IsMd5Hex32(request.Contrasena)
? request.Contrasena
: Md5Hex32.ToMd5Hex32(request.Contrasena);*/
var result = await _repo.ValidateUserAsync(request.Usuario, contrasena32, cveEmpresa,ip,mac,grupoEmpresa, ct);
if (result is null || result.CveUsuario <= 0 || result.Acceso != 1)
throw new UnauthorizedAccessException("Invalid credentials or access denied.");
var claims = new List<Claim>
{
new("CveUsuario", result.CveUsuario.ToString()),
new("CveEmpresa", cveEmpresa.ToString()),
new("CveCliente", cveCliente.ToString())
};
if (result.CveRol is not null)
claims.Add(new("CveRol", result.CveRol.Value.ToString()));
var token = await _tokenService.IssueFromClaimsAsync(claims, scope: null, ct);
return new SicrepAuthResponse
{
AccessToken = token.AccessToken
};
}
}