From 4dec3251a064c017daf6f49fcffd74a33d7a3b76 Mon Sep 17 00:00:00 2001 From: kittyegg Date: Mon, 15 Jun 2026 03:32:39 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8E=20=D0=B8=20=D0=B2=D1=85=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- URL-Shortener/Controllers/UserController.cs | 23 +++++++------ URL-Shortener/DTO/LoginUserDTO.cs | 12 +++++++ URL-Shortener/DTO/RegisterUserDTO.cs | 14 ++++++++ URL-Shortener/Program.cs | 7 +++- URL-Shortener/Services/JwtService.cs | 32 +++++++++++++++++ URL-Shortener/Services/UserService.cs | 38 ++++++++++++++++++--- URL-Shortener/Settings/AuthSettings.cs | 8 +++++ URL-Shortener/URL-Shortener.csproj | 14 ++++---- URL-Shortener/appsettings.json | 4 +++ 9 files changed, 131 insertions(+), 21 deletions(-) create mode 100644 URL-Shortener/DTO/LoginUserDTO.cs create mode 100644 URL-Shortener/DTO/RegisterUserDTO.cs create mode 100644 URL-Shortener/Services/JwtService.cs create mode 100644 URL-Shortener/Settings/AuthSettings.cs diff --git a/URL-Shortener/Controllers/UserController.cs b/URL-Shortener/Controllers/UserController.cs index 4e349db..30206bb 100644 --- a/URL-Shortener/Controllers/UserController.cs +++ b/URL-Shortener/Controllers/UserController.cs @@ -1,24 +1,27 @@ using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using URL_Shortener.Models; using URL_Shortener.DTO; +using URL_Shortener.Services; namespace URL_Shortener.Controllers { [ApiController] [Route("user")] - public class UserController(AppDbContext db) : ControllerBase + public class UserController(UserService userService) : ControllerBase { - private readonly AppDbContext _db = db; + private readonly UserService _userService = userService; - [HttpGet("email")] - public async Task GetUserByEmailAsync(string email) + [HttpPost("register")] + public async Task RegisterAsync(RegisterUserDTO dto) { - var user = await _db.Users.FirstOrDefaultAsync(u => u.Email == email); - if (user == null) - return NotFound("User not found"); + await _userService.RegisterAsync(dto.Name, dto.Email, dto.Password); + return Ok(); + } - return Ok(user); + [HttpPost("login")] + public async Task LoginAsync(LoginUserDTO dto) + { + var jwt = await _userService.LoginAsync(dto.Email, dto.Password); + return Ok(jwt); } } } diff --git a/URL-Shortener/DTO/LoginUserDTO.cs b/URL-Shortener/DTO/LoginUserDTO.cs new file mode 100644 index 0000000..6f8346a --- /dev/null +++ b/URL-Shortener/DTO/LoginUserDTO.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace URL_Shortener.DTO +{ + public class LoginUserDTO + { + [Required, EmailAddress] + public string Email { get; set; } = string.Empty; + [Required] + public string Password { get; set; } = string.Empty; + } +} diff --git a/URL-Shortener/DTO/RegisterUserDTO.cs b/URL-Shortener/DTO/RegisterUserDTO.cs new file mode 100644 index 0000000..8cc1d51 --- /dev/null +++ b/URL-Shortener/DTO/RegisterUserDTO.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace URL_Shortener.DTO +{ + public class RegisterUserDTO + { + [Required] + public string Name { get; set; } = string.Empty; + [Required, EmailAddress] + public string Email { get; set; } = string.Empty; + [Required] + public string Password { get; set; } = string.Empty; + } +} diff --git a/URL-Shortener/Program.cs b/URL-Shortener/Program.cs index 08ca003..1caf70d 100644 --- a/URL-Shortener/Program.cs +++ b/URL-Shortener/Program.cs @@ -1,5 +1,7 @@ using Microsoft.EntityFrameworkCore; using URL_Shortener; +using URL_Shortener.Services; +using URL_Shortener.Settings; var builder = WebApplication.CreateBuilder(args); @@ -12,9 +14,12 @@ builder.Services.AddOpenApi(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -// Чтобы короткие коды генерировать builder.Services.AddSingleton(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.Configure(builder.Configuration.GetSection("AuthSettings")); + builder.Services.AddDbContext(options => { var connString = builder.Configuration.GetConnectionString("DefaultConnection"); diff --git a/URL-Shortener/Services/JwtService.cs b/URL-Shortener/Services/JwtService.cs new file mode 100644 index 0000000..e7b7f57 --- /dev/null +++ b/URL-Shortener/Services/JwtService.cs @@ -0,0 +1,32 @@ +using URL_Shortener.Models; +using System.IdentityModel.Tokens.Jwt; +using URL_Shortener.Settings; +using Microsoft.Extensions.Options; +using System.Security.Claims; +using Microsoft.IdentityModel.Tokens; +using System.Text; + + +namespace URL_Shortener.Services +{ + public class JwtService(IOptions options) + { + public string GenerateToken(User user) + { + var claims = new List() { + new("userName", user.Name), + new("userEmail", user.Email), + new("id", user.Id.ToString()) + }; + var jwt = new JwtSecurityToken( + expires: DateTime.UtcNow.Add(options.Value.Expires), + claims: claims, + signingCredentials: new SigningCredentials( + new SymmetricSecurityKey(Encoding.UTF8.GetBytes(options.Value.SecretKey)), + SecurityAlgorithms.HmacSha256) + ); + + return new JwtSecurityTokenHandler().WriteToken(jwt); + } + } +} diff --git a/URL-Shortener/Services/UserService.cs b/URL-Shortener/Services/UserService.cs index da27fe2..9db36d0 100644 --- a/URL-Shortener/Services/UserService.cs +++ b/URL-Shortener/Services/UserService.cs @@ -1,10 +1,40 @@ -namespace URL_Shortener.Services +using URL_Shortener.Models; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; + +namespace URL_Shortener.Services { - public class UserService + public class UserService(AppDbContext db, JwtService jwtService) { - public async Task Register(string userName, string email, string hashedPassword) + private readonly AppDbContext _db = db; + private readonly JwtService _jwtService = jwtService; + + public async Task RegisterAsync(string userName, string email, string password, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (await _db.Users.FirstOrDefaultAsync(u => u.Email == email, cancellationToken) != null) + throw new Exception("This user already exists."); + + var user = new User() + { + Name = userName, + Email = email + }; + user.HashedPassword = new PasswordHasher().HashPassword(user, password); + + await _db.Users.AddAsync(user, cancellationToken); + await _db.SaveChangesAsync(cancellationToken); + } + + public async Task LoginAsync(string email, string password, CancellationToken cancellationToken = default) + { + var user = await _db.Users.FirstOrDefaultAsync(u => u.Email == email, cancellationToken) ?? + throw new Exception("User not found."); + + var res = new PasswordHasher().VerifyHashedPassword(user, user.HashedPassword, password); + if (res == PasswordVerificationResult.Failed) + throw new Exception("Incorrect password."); + + return _jwtService.GenerateToken(user); } } } diff --git a/URL-Shortener/Settings/AuthSettings.cs b/URL-Shortener/Settings/AuthSettings.cs new file mode 100644 index 0000000..fd2da3f --- /dev/null +++ b/URL-Shortener/Settings/AuthSettings.cs @@ -0,0 +1,8 @@ +namespace URL_Shortener.Settings +{ + public class AuthSettings + { + public string SecretKey { get; set; } = string.Empty; + public TimeSpan Expires { get; set; } + } +} diff --git a/URL-Shortener/URL-Shortener.csproj b/URL-Shortener/URL-Shortener.csproj index 7699adf..a1cbb21 100644 --- a/URL-Shortener/URL-Shortener.csproj +++ b/URL-Shortener/URL-Shortener.csproj @@ -11,19 +11,21 @@ - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + diff --git a/URL-Shortener/appsettings.json b/URL-Shortener/appsettings.json index 5ae06ee..dd31d1e 100644 --- a/URL-Shortener/appsettings.json +++ b/URL-Shortener/appsettings.json @@ -8,5 +8,9 @@ "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Host=localhost;Database=appdb;Username=postgres;Password=app" + }, + "AuthSettings": { + "SecretKey": "mysecretkeyqwertyuiopasdfghjklzxcvbnm", + "Expires": "00:01:00" } }