diff --git a/Cloud/Cloud.csproj b/Cloud/Cloud.csproj index d9f0064..93e4190 100644 --- a/Cloud/Cloud.csproj +++ b/Cloud/Cloud.csproj @@ -7,6 +7,8 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -14,6 +16,7 @@ + diff --git a/Cloud/Controllers/AuthController.cs b/Cloud/Controllers/AuthController.cs new file mode 100644 index 0000000..c63a89c --- /dev/null +++ b/Cloud/Controllers/AuthController.cs @@ -0,0 +1,104 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Identity; +using Cloud.Models; +using Cloud.Requests; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Text; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authorization; +using System.Security.Claims; + +namespace Cloud.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class AuthController : ControllerBase +{ + private PasswordHasher _passwordHasher; + private IConfiguration _config; + private ApplicationContext _context; + + public AuthController(IConfiguration config, ApplicationContext context) + { + _passwordHasher = new PasswordHasher(); + _config = config; + _context = context; + } + + [HttpPost("register")] + public async Task Register([FromBody] RegisterRequest request) + { + var existUser = await _context.Users.SingleOrDefaultAsync(u => u.Email == request.Email); + + if (existUser != null) { + return BadRequest("Пользователь с такой эл. почтой уже существует"); + } + + var user = new User + { + Name = request.Name, + Email = request.Email, + Password = _passwordHasher.HashPassword(null, request.Password) + }; + + _context.Users.Add(user); + await _context.SaveChangesAsync(); + + return Ok("Пользователь успешно зарегистрирован"); + } + + [HttpPost("login")] + public async Task Login([FromBody] LoginRequest request) + { + var user = await _context.Users.SingleOrDefaultAsync(u => u.Email == request.Email); + + if (user == null) { + return Unauthorized("Пользователя с такой эл. почтой не существует"); + } + + var verificationResult = _passwordHasher.VerifyHashedPassword(null, user.Password, request.Password); + + if (verificationResult == PasswordVerificationResult.Failed) { + return Unauthorized("Неверный пароль"); + } + + var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"])); + var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); + + var claims = new[] + { + new Claim(ClaimTypes.Name, user.Email), + }; + + var Sectoken = new JwtSecurityToken(_config["Jwt:Issuer"], + _config["Jwt:Issuer"], + claims: claims, + expires: DateTime.Now.AddMinutes(120), + signingCredentials: credentials); + + var token = new JwtSecurityTokenHandler().WriteToken(Sectoken); + + return Ok(token); + } + + [Authorize] + [HttpGet("user")] + public async Task GetAuthUser() + { + var userEmail = User.Identity.Name; + + var user = await _context.Users.SingleOrDefaultAsync(u => u.Email == userEmail); + + if (user == null) { + return NotFound("Пользователь не найден"); + } + + return Ok(new + { + user.Id, + user.Name, + user.Email + }); + } +} \ No newline at end of file diff --git a/Cloud/Program.cs b/Cloud/Program.cs index 2b9379b..1c10d82 100644 --- a/Cloud/Program.cs +++ b/Cloud/Program.cs @@ -1,17 +1,73 @@ using Cloud; using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using System.Text; +using FluentValidation; +using FluentValidation.AspNetCore; +using Cloud.Validation; var builder = WebApplication.CreateBuilder(args); // Add services to the container. +//Jwt configuration starts here +var jwtIssuer = builder.Configuration.GetSection("Jwt:Issuer").Get(); +var jwtKey = builder.Configuration.GetSection("Jwt:Key").Get(); + +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = jwtIssuer, + ValidAudience = jwtIssuer, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)) + }; + }); + builder.Services.AddDbContext(options => options.UseNpgsql("Host=localhost;Port=5438;Database=main_database;Username=postgres;Password=12345")); builder.Services.AddControllers(); +builder.Services.AddFluentValidationAutoValidation(); +builder.Services.AddFluentValidationClientsideAdapters(); +builder.Services.AddValidatorsFromAssemblyContaining(); +builder.Services.AddValidatorsFromAssemblyContaining(); + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "Cloud API", Version = "v1" }); + + c.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme + { + Description = "Введите ваш Bearer токен", + Name = "Authorization", + In = Microsoft.OpenApi.Models.ParameterLocation.Header, + Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey + }); + + c.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement + { + { + new Microsoft.OpenApi.Models.OpenApiSecurityScheme + { + Reference = new Microsoft.OpenApi.Models.OpenApiReference + { + Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] {} + } + }); +}); var app = builder.Build(); @@ -19,11 +75,17 @@ var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Cloud API V1"); + c.RoutePrefix = string.Empty; + }); } app.UseHttpsRedirection(); +app.UseAuthentication(); + app.UseAuthorization(); app.MapControllers(); diff --git a/Cloud/Requests/LoginRequest.cs b/Cloud/Requests/LoginRequest.cs new file mode 100644 index 0000000..472da63 --- /dev/null +++ b/Cloud/Requests/LoginRequest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Cloud.Requests; + +public class LoginRequest +{ + public string Email { get; set; } + + public string Password { get; set; } +} \ No newline at end of file diff --git a/Cloud/Requests/RegisterRequest.cs b/Cloud/Requests/RegisterRequest.cs new file mode 100644 index 0000000..a92e7a8 --- /dev/null +++ b/Cloud/Requests/RegisterRequest.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Cloud.Requests; + +public class RegisterRequest +{ + public string Name { get; set; } + + public string Email { get; set; } + + public string Password { get; set; } +} \ No newline at end of file diff --git a/Cloud/Validation/LoginValidator.cs b/Cloud/Validation/LoginValidator.cs new file mode 100644 index 0000000..19f6ec0 --- /dev/null +++ b/Cloud/Validation/LoginValidator.cs @@ -0,0 +1,18 @@ +using Cloud.Requests; +using FluentValidation; + +namespace Cloud.Validation; + +public class LoginValidator : AbstractValidator +{ + public LoginValidator() + { + RuleFor(request => request.Email) + .NotEmpty().WithMessage("Email обязателен для заполнения") + .EmailAddress().WithMessage("Некорректный формат Email"); + + RuleFor(request => request.Password) + .NotEmpty().WithMessage("Пароль обязателен для заполнения") + .MinimumLength(8).WithMessage("Пароль должен быть не менее 8 символов"); + } +} diff --git a/Cloud/Validation/RegisterValidator.cs b/Cloud/Validation/RegisterValidator.cs new file mode 100644 index 0000000..1cfcb8d --- /dev/null +++ b/Cloud/Validation/RegisterValidator.cs @@ -0,0 +1,22 @@ +using Cloud.Requests; +using FluentValidation; + +namespace Cloud.Validation; + +public class RegisterValidator : AbstractValidator +{ + public RegisterValidator() + { + RuleFor(user => user.Name) + .NotEmpty().WithMessage("Имя обязательно для заполнения") + .MaximumLength(50).WithMessage("Имя должно быть не более 50 символов"); + + RuleFor(user => user.Email) + .NotEmpty().WithMessage("Email обязателен для заполнения") + .EmailAddress().WithMessage("Некорректный формат Email"); + + RuleFor(user => user.Password) + .NotEmpty().WithMessage("Пароль обязателен для заполнения") + .MinimumLength(8).WithMessage("Пароль должен быть не менее 8 символов"); + } +} \ No newline at end of file diff --git a/Cloud/appsettings.json b/Cloud/appsettings.json index 10f68b8..b272a9c 100644 --- a/Cloud/appsettings.json +++ b/Cloud/appsettings.json @@ -5,5 +5,9 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" -} + "AllowedHosts": "*", + "Jwt": { + "Key": "m7TyhE20s0dVtUDAr9EnFdPZnAG8maxgBTaiW5j6kO6RQhWDAGxYmXyu0suDnE0o", + "Issuer": "localhost" + } +} \ No newline at end of file