First Commit
This commit is contained in:
@@ -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
|
||||
);
|
||||
@@ -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
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace ADS.Apis.Gps.Application.Common.Crypto;
|
||||
|
||||
public interface ILegacyPasswordEncryptor
|
||||
{
|
||||
string Encrypt(string plainText);
|
||||
}
|
||||
38
Apis/ADS.Apis.Gps/Application/Common/Crypto/Md5Hex32.cs
Normal file
38
Apis/ADS.Apis.Gps/Application/Common/Crypto/Md5Hex32.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
11
Apis/ADS.Apis.Gps/Application/Contracts/Common/PagedQuery.cs
Normal file
11
Apis/ADS.Apis.Gps/Application/Contracts/Common/PagedQuery.cs
Normal 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;
|
||||
}
|
||||
12
Apis/ADS.Apis.Gps/Application/Contracts/Common/RequestOf.cs
Normal file
12
Apis/ADS.Apis.Gps/Application/Contracts/Common/RequestOf.cs
Normal 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;
|
||||
}
|
||||
10
Apis/ADS.Apis.Gps/Application/Dtos/Auth/SicrepAuthRequest.cs
Normal file
10
Apis/ADS.Apis.Gps/Application/Dtos/Auth/SicrepAuthRequest.cs
Normal 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; }
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user