From 8184df36ca5ebd578417918476d2892a1b8ee45c Mon Sep 17 00:00:00 2001 From: Lucca Santangelo <luccasant95@gmail.com> Date: Wed, 14 Oct 2020 17:35:56 -0300 Subject: [PATCH] agregado jwt --- .../Tsi1.Api/Controllers/UserController.cs | 61 +++++++++ .../Infrastructure/IJwtAuthManager.cs | 18 +++ .../Tsi1.Api/Infrastructure/JwtAuthManager.cs | 126 ++++++++++++++++++ .../Infrastructure/JwtRefreshTokenCache.cs | 41 ++++++ .../Tsi1.Api/Infrastructure/JwtTokenConfig.cs | 22 +++ Tsi1.Api/Tsi1.Api/Models/JwtAuthResult.cs | 13 ++ Tsi1.Api/Tsi1.Api/Models/LoginRequest.cs | 20 +++ Tsi1.Api/Tsi1.Api/Models/LoginResult.cs | 26 ++++ Tsi1.Api/Tsi1.Api/Models/RefreshToken.cs | 18 +++ .../Tsi1.Api/Models/RefreshTokenRequest.cs | 14 ++ .../Tsi1.Api/Properties/launchSettings.json | 4 +- Tsi1.Api/Tsi1.Api/Startup.cs | 69 ++++++++++ Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj | 4 +- Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj.user | 8 ++ Tsi1.Api/Tsi1.Api/appsettings.json | 7 + .../Interfaces/IUserService.cs | 13 ++ .../Services/UserService.cs | 49 +++++++ 17 files changed, 510 insertions(+), 3 deletions(-) create mode 100644 Tsi1.Api/Tsi1.Api/Controllers/UserController.cs create mode 100644 Tsi1.Api/Tsi1.Api/Infrastructure/IJwtAuthManager.cs create mode 100644 Tsi1.Api/Tsi1.Api/Infrastructure/JwtAuthManager.cs create mode 100644 Tsi1.Api/Tsi1.Api/Infrastructure/JwtRefreshTokenCache.cs create mode 100644 Tsi1.Api/Tsi1.Api/Infrastructure/JwtTokenConfig.cs create mode 100644 Tsi1.Api/Tsi1.Api/Models/JwtAuthResult.cs create mode 100644 Tsi1.Api/Tsi1.Api/Models/LoginRequest.cs create mode 100644 Tsi1.Api/Tsi1.Api/Models/LoginResult.cs create mode 100644 Tsi1.Api/Tsi1.Api/Models/RefreshToken.cs create mode 100644 Tsi1.Api/Tsi1.Api/Models/RefreshTokenRequest.cs create mode 100644 Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj.user create mode 100644 Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IUserService.cs create mode 100644 Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs diff --git a/Tsi1.Api/Tsi1.Api/Controllers/UserController.cs b/Tsi1.Api/Tsi1.Api/Controllers/UserController.cs new file mode 100644 index 0000000..c952152 --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Controllers/UserController.cs @@ -0,0 +1,61 @@ +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Tsi1.Api.Infrastructure; +using Tsi1.Api.Models; +using Tsi1.BusinessLayer.Interfaces; + +namespace Tsi1.Api.Controllers +{ + [Authorize] + [Route("api/[controller]")] + [ApiController] + public class UserController : ControllerBase + { + private readonly IUserService _userService; + private readonly IJwtAuthManager _jwtAuthManager; + + public UserController(IUserService userService, IJwtAuthManager jwtAuthManager) + { + _userService = userService; + _jwtAuthManager = jwtAuthManager; + } + + [AllowAnonymous] + [HttpPost("Login")] + public async Task<IActionResult> Login(LoginRequest request) + { + var user = await _userService.Authenticate(request.UserName, request.Password); + + if (user == null) + { + return BadRequest(); + } + + var claims = new[] + { + new Claim(ClaimTypes.Name,user.Username), + new Claim(ClaimTypes.Role, user.UserType.Name) + }; + + var jwtResult = _jwtAuthManager.GenerateTokens(user.Username, claims, DateTime.Now); + + return Ok(new LoginResult + { + UserName = user.Username, + Role = user.UserType.Name, + AccessToken = jwtResult.AccessToken, + RefreshToken = jwtResult.RefreshToken.TokenString + }); + } + + [HttpGet("Register")] + public async Task<IActionResult> Register() + { + return Ok(); + } + + } +} diff --git a/Tsi1.Api/Tsi1.Api/Infrastructure/IJwtAuthManager.cs b/Tsi1.Api/Tsi1.Api/Infrastructure/IJwtAuthManager.cs new file mode 100644 index 0000000..e9342c6 --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Infrastructure/IJwtAuthManager.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Immutable; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using Tsi1.Api.Models; + +namespace Tsi1.Api.Infrastructure +{ + public interface IJwtAuthManager + { + IImmutableDictionary<string, RefreshToken> UsersRefreshTokensReadOnlyDictionary { get; } + JwtAuthResult GenerateTokens(string username, Claim[] claims, DateTime now); + JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime now); + void RemoveExpiredRefreshTokens(DateTime now); + void RemoveRefreshTokenByUserName(string userName); + (ClaimsPrincipal, JwtSecurityToken) DecodeJwtToken(string token); + } +} diff --git a/Tsi1.Api/Tsi1.Api/Infrastructure/JwtAuthManager.cs b/Tsi1.Api/Tsi1.Api/Infrastructure/JwtAuthManager.cs new file mode 100644 index 0000000..b8c4a7b --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Infrastructure/JwtAuthManager.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; +using Microsoft.IdentityModel.Tokens; +using Tsi1.Api.Models; + +namespace Tsi1.Api.Infrastructure +{ + public class JwtAuthManager : IJwtAuthManager + { + public IImmutableDictionary<string, RefreshToken> UsersRefreshTokensReadOnlyDictionary => _usersRefreshTokens.ToImmutableDictionary(); + private readonly ConcurrentDictionary<string, RefreshToken> _usersRefreshTokens; // can store in a database or a distributed cache + private readonly JwtTokenConfig _jwtTokenConfig; + private readonly byte[] _secret; + + public JwtAuthManager(JwtTokenConfig jwtTokenConfig) + { + _jwtTokenConfig = jwtTokenConfig; + _usersRefreshTokens = new ConcurrentDictionary<string, RefreshToken>(); + _secret = Encoding.ASCII.GetBytes(jwtTokenConfig.Secret); + } + + // optional: clean up expired refresh tokens + public void RemoveExpiredRefreshTokens(DateTime now) + { + var expiredTokens = _usersRefreshTokens.Where(x => x.Value.ExpireAt < now).ToList(); + foreach (var expiredToken in expiredTokens) + { + _usersRefreshTokens.TryRemove(expiredToken.Key, out _); + } + } + + // can be more specific to ip, user agent, device name, etc. + public void RemoveRefreshTokenByUserName(string userName) + { + var refreshTokens = _usersRefreshTokens.Where(x => x.Value.UserName == userName).ToList(); + foreach (var refreshToken in refreshTokens) + { + _usersRefreshTokens.TryRemove(refreshToken.Key, out _); + } + } + + public JwtAuthResult GenerateTokens(string username, Claim[] claims, DateTime now) + { + var shouldAddAudienceClaim = string.IsNullOrWhiteSpace(claims?.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Aud)?.Value); + var jwtToken = new JwtSecurityToken( + _jwtTokenConfig.Issuer, + shouldAddAudienceClaim ? _jwtTokenConfig.Audience : string.Empty, + claims, + expires: now.AddMinutes(_jwtTokenConfig.AccessTokenExpiration), + signingCredentials: new SigningCredentials(new SymmetricSecurityKey(_secret), SecurityAlgorithms.HmacSha256Signature)); + var accessToken = new JwtSecurityTokenHandler().WriteToken(jwtToken); + + var refreshToken = new RefreshToken + { + UserName = username, + TokenString = GenerateRefreshTokenString(), + ExpireAt = now.AddMinutes(_jwtTokenConfig.RefreshTokenExpiration) + }; + _usersRefreshTokens.AddOrUpdate(refreshToken.TokenString, refreshToken, (s, t) => refreshToken); + + return new JwtAuthResult + { + AccessToken = accessToken, + RefreshToken = refreshToken + }; + } + + public JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime now) + { + var (principal, jwtToken) = DecodeJwtToken(accessToken); + if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256Signature)) + { + throw new SecurityTokenException("Invalid token"); + } + + var userName = principal.Identity.Name; + if (!_usersRefreshTokens.TryGetValue(refreshToken, out var existingRefreshToken)) + { + throw new SecurityTokenException("Invalid token"); + } + if (existingRefreshToken.UserName != userName || existingRefreshToken.ExpireAt < now) + { + throw new SecurityTokenException("Invalid token"); + } + + return GenerateTokens(userName, principal.Claims.ToArray(), now); // need to recover the original claims + } + + public (ClaimsPrincipal, JwtSecurityToken) DecodeJwtToken(string token) + { + if (string.IsNullOrWhiteSpace(token)) + { + throw new SecurityTokenException("Invalid token"); + } + var principal = new JwtSecurityTokenHandler() + .ValidateToken(token, + new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = _jwtTokenConfig.Issuer, + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(_secret), + ValidAudience = _jwtTokenConfig.Audience, + ValidateAudience = true, + ValidateLifetime = true, + ClockSkew = TimeSpan.FromMinutes(1) + }, + out var validatedToken); + return (principal, validatedToken as JwtSecurityToken); + } + + private static string GenerateRefreshTokenString() + { + var randomNumber = new byte[32]; + using var randomNumberGenerator = RandomNumberGenerator.Create(); + randomNumberGenerator.GetBytes(randomNumber); + return Convert.ToBase64String(randomNumber); + } + } +} diff --git a/Tsi1.Api/Tsi1.Api/Infrastructure/JwtRefreshTokenCache.cs b/Tsi1.Api/Tsi1.Api/Infrastructure/JwtRefreshTokenCache.cs new file mode 100644 index 0000000..fce4ada --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Infrastructure/JwtRefreshTokenCache.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; + +namespace Tsi1.Api.Infrastructure +{ + public class JwtRefreshTokenCache : IHostedService, IDisposable + { + private Timer _timer; + private readonly IJwtAuthManager _jwtAuthManager; + + public JwtRefreshTokenCache(IJwtAuthManager jwtAuthManager) + { + _jwtAuthManager = jwtAuthManager; + } + + public Task StartAsync(CancellationToken stoppingToken) + { + // remove expired refresh tokens from cache every minute + _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); + return Task.CompletedTask; + } + + private void DoWork(object state) + { + _jwtAuthManager.RemoveExpiredRefreshTokens(DateTime.Now); + } + + public Task StopAsync(CancellationToken stoppingToken) + { + _timer?.Change(Timeout.Infinite, 0); + return Task.CompletedTask; + } + + public void Dispose() + { + _timer?.Dispose(); + } + } +} diff --git a/Tsi1.Api/Tsi1.Api/Infrastructure/JwtTokenConfig.cs b/Tsi1.Api/Tsi1.Api/Infrastructure/JwtTokenConfig.cs new file mode 100644 index 0000000..854d596 --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Infrastructure/JwtTokenConfig.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace Tsi1.Api.Infrastructure +{ + public class JwtTokenConfig + { + [JsonPropertyName("secret")] + public string Secret { get; set; } + + [JsonPropertyName("issuer")] + public string Issuer { get; set; } + + [JsonPropertyName("audience")] + public string Audience { get; set; } + + [JsonPropertyName("accessTokenExpiration")] + public int AccessTokenExpiration { get; set; } + + [JsonPropertyName("refreshTokenExpiration")] + public int RefreshTokenExpiration { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.Api/Models/JwtAuthResult.cs b/Tsi1.Api/Tsi1.Api/Models/JwtAuthResult.cs new file mode 100644 index 0000000..25cf137 --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Models/JwtAuthResult.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace Tsi1.Api.Models +{ + public class JwtAuthResult + { + [JsonPropertyName("accessToken")] + public string AccessToken { get; set; } + + [JsonPropertyName("refreshToken")] + public RefreshToken RefreshToken { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.Api/Models/LoginRequest.cs b/Tsi1.Api/Tsi1.Api/Models/LoginRequest.cs new file mode 100644 index 0000000..96e87b1 --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Models/LoginRequest.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Tsi1.Api.Models +{ + public class LoginRequest + { + [Required] + [JsonPropertyName("username")] + public string UserName { get; set; } + + [Required] + [JsonPropertyName("password")] + public string Password { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.Api/Models/LoginResult.cs b/Tsi1.Api/Tsi1.Api/Models/LoginResult.cs new file mode 100644 index 0000000..3fd35dd --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Models/LoginResult.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Tsi1.Api.Models +{ + public class LoginResult + { + [JsonPropertyName("username")] + public string UserName { get; set; } + + [JsonPropertyName("role")] + public string Role { get; set; } + + [JsonPropertyName("originalUserName")] + public string OriginalUserName { get; set; } + + [JsonPropertyName("accessToken")] + public string AccessToken { get; set; } + + [JsonPropertyName("refreshToken")] + public string RefreshToken { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.Api/Models/RefreshToken.cs b/Tsi1.Api/Tsi1.Api/Models/RefreshToken.cs new file mode 100644 index 0000000..fec06fc --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Models/RefreshToken.cs @@ -0,0 +1,18 @@ +using System; +using System.Text.Json.Serialization; + +namespace Tsi1.Api.Models +{ + public class RefreshToken + { + [JsonPropertyName("username")] + public string UserName { get; set; } // can be used for usage tracking + // can optionally include other metadata, such as user agent, ip address, device name, and so on + + [JsonPropertyName("tokenString")] + public string TokenString { get; set; } + + [JsonPropertyName("expireAt")] + public DateTime ExpireAt { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.Api/Models/RefreshTokenRequest.cs b/Tsi1.Api/Tsi1.Api/Models/RefreshTokenRequest.cs new file mode 100644 index 0000000..9535cd5 --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Models/RefreshTokenRequest.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Tsi1.Api.Models +{ + public class RefreshTokenRequest + { + [JsonPropertyName("refreshToken")] + public string RefreshToken { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.Api/Properties/launchSettings.json b/Tsi1.Api/Tsi1.Api/Properties/launchSettings.json index 24cce69..c733fd6 100644 --- a/Tsi1.Api/Tsi1.Api/Properties/launchSettings.json +++ b/Tsi1.Api/Tsi1.Api/Properties/launchSettings.json @@ -12,7 +12,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "weatherforecast", + "launchUrl": "swagger/index.html", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -20,7 +20,7 @@ "Tsi1.Api": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "weatherforecast", + "launchUrl": "swagger/index.html", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/Tsi1.Api/Tsi1.Api/Startup.cs b/Tsi1.Api/Tsi1.Api/Startup.cs index 6d36058..766b76c 100644 --- a/Tsi1.Api/Tsi1.Api/Startup.cs +++ b/Tsi1.Api/Tsi1.Api/Startup.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpsPolicy; @@ -11,6 +13,11 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using Tsi1.Api.Infrastructure; +using Tsi1.BusinessLayer.Interfaces; +using Tsi1.BusinessLayer.Services; using Tsi1.DataLayer; namespace Tsi1.Api @@ -30,6 +37,57 @@ namespace Tsi1.Api services.AddControllers(); services.AddDbContext<Tsi1Context>(x => x.UseNpgsql(Configuration.GetConnectionString("PostgreSql"))); + services.AddScoped<IUserService, UserService>(); + + var jwtTokenConfig = Configuration.GetSection("jwtTokenConfig").Get<JwtTokenConfig>(); + services.AddSingleton(jwtTokenConfig); + services.AddAuthentication(x => + { + x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(x => + { + x.RequireHttpsMetadata = true; + x.SaveToken = true; + x.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = jwtTokenConfig.Issuer, + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtTokenConfig.Secret)), + ValidAudience = jwtTokenConfig.Audience, + ValidateAudience = true, + ValidateLifetime = true, + ClockSkew = TimeSpan.FromMinutes(1) + }; + }); + services.AddSingleton<IJwtAuthManager, JwtAuthManager>(); + services.AddHostedService<JwtRefreshTokenCache>(); + + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Tsi1 api", Version = "v1" }); + + var securityScheme = new OpenApiSecurityScheme + { + Name = "JWT Authentication", + Description = "Enter JWT Bearer token **_only_**", + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Scheme = "bearer", // must be lower case + BearerFormat = "JWT", + Reference = new OpenApiReference + { + Id = JwtBearerDefaults.AuthenticationScheme, + Type = ReferenceType.SecurityScheme + } + }; + c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme); + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + {securityScheme, new string[] { }} + }); + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -42,8 +100,19 @@ namespace Tsi1.Api app.UseHttpsRedirection(); + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Tsi1 api V1"); + c.DocumentTitle = "Tsi1 api"; + //c.RoutePrefix = string.Empty; + }); + app.UseRouting(); + app.UseCors(x => x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); + + app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => diff --git a/Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj b/Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj index cf86097..1ed4b44 100644 --- a/Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj +++ b/Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj @@ -2,10 +2,12 @@ <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> + <UserSecretsId>3e0d10d1-b8c2-4975-b04b-a2b796a71d30</UserSecretsId> </PropertyGroup> <ItemGroup> - <Folder Include="Controllers\" /> + <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.9" /> + <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" /> </ItemGroup> <ItemGroup> diff --git a/Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj.user b/Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj.user new file mode 100644 index 0000000..85e159c --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj.user @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Controller_SelectedScaffolderID>ApiControllerWithActionsScaffolder</Controller_SelectedScaffolderID> + <Controller_SelectedScaffolderCategoryPath>root/Controller</Controller_SelectedScaffolderCategoryPath> + <WebStackScaffolding_ControllerDialogWidth>600</WebStackScaffolding_ControllerDialogWidth> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/Tsi1.Api/Tsi1.Api/appsettings.json b/Tsi1.Api/Tsi1.Api/appsettings.json index c796235..e957e7e 100644 --- a/Tsi1.Api/Tsi1.Api/appsettings.json +++ b/Tsi1.Api/Tsi1.Api/appsettings.json @@ -2,6 +2,13 @@ "ConnectionStrings": { "PostgreSql": "Host=localhost;Database=tsi1;Username=postgres;Password=111111" }, + "jwtTokenConfig": { + "secret": "1234567890123456789", + "issuer": "https://localhost:44363", + "audience": "https://localhost:44363", + "accessTokenExpiration": 20, + "refreshTokenExpiration": 60 + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IUserService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IUserService.cs new file mode 100644 index 0000000..2c715b9 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IUserService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Tsi1.DataLayer.Entities; + +namespace Tsi1.BusinessLayer.Interfaces +{ + public interface IUserService + { + Task<User> Authenticate(string username, string password); + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs new file mode 100644 index 0000000..935bddd --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Tsi1.BusinessLayer.Interfaces; +using Tsi1.DataLayer; +using Tsi1.DataLayer.Entities; + +namespace Tsi1.BusinessLayer.Services +{ + public class UserService : IUserService + { + private readonly Tsi1Context _context; + + public UserService(Tsi1Context context) + { + _context = context; + } + + public async Task<User> Authenticate(string username, string password) + { + var user = await _context.Users.FirstOrDefaultAsync(x => x.Username == username); + + user = new User() + { + Id = 1, + Username = "lucca", + UserType = new UserType() + { + Id = 1, + Name = "admin" + } + }; + + if (user == null) + { + // no existe el usuario + } + + if (user.Password != password) + { + // contraseña incorrecta + } + + return user; + } + } +} -- GitLab