From 4d8c2c8a15d85cb75d85dfd0b0410fc44cb4cf48 Mon Sep 17 00:00:00 2001 From: mfnefd Date: Wed, 19 Jun 2024 22:30:34 +0400 Subject: [PATCH 1/5] fix login --- RestAPI/Controllers/UserController.cs | 227 +++++++++++++------------- 1 file changed, 115 insertions(+), 112 deletions(-) diff --git a/RestAPI/Controllers/UserController.cs b/RestAPI/Controllers/UserController.cs index 57d6eee..adae152 100644 --- a/RestAPI/Controllers/UserController.cs +++ b/RestAPI/Controllers/UserController.cs @@ -1,4 +1,5 @@ using BusinessLogic.BusinessLogic; +using BusinessLogic.Tools.Mail.MailTemplates; using Contracts.BindingModels; using Contracts.BusinessLogicContracts; using Contracts.Exceptions; @@ -7,122 +8,124 @@ using Microsoft.AspNetCore.Mvc; namespace RestAPI.Controllers { - [Route("[controller]/[action]")] - [ApiController] - public class UserController - { - private readonly ILogger _logger; - private readonly IUserLogic _userLogic; + [Route("[controller]/[action]")] + [ApiController] + public class UserController + { + private readonly ILogger _logger; + private readonly IUserLogic _userLogic; - public UserController(ILogger logger, IUserLogic userLogic) - { - _userLogic = userLogic; - _logger = logger; - } + public UserController(ILogger logger, IUserLogic userLogic) + { + _userLogic = userLogic; + _logger = logger; + } - [HttpPost] - public IResult Login(string email, string password) - { - try - { - var res = _userLogic.Login(email, password); - return Results.Ok(res); - } - catch (ElementNotFoundException ex) - { - _logger.LogInformation(ex, "User not found"); - return Results.NoContent(); - } - catch (AccountException ex) - { - _logger.LogWarning(ex, "Wrong login data"); - return Results.BadRequest(ex.Message); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error get user"); - return Results.Problem(ex.Message); - } - } + [HttpPost] + public IResult Login([FromBody] UserData data) + { + try + { + var res = _userLogic.Login(data.email, data.password); + return Results.Ok(res); + } + catch (ElementNotFoundException ex) + { + _logger.LogInformation(ex, "User not found"); + return Results.NoContent(); + } + catch (AccountException ex) + { + _logger.LogWarning(ex, "Wrong login data"); + return Results.BadRequest(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error get user"); + return Results.Problem(ex.Message); + } + } - [HttpPost] - public IResult Registration([FromBody] UserBindingModel model) - { - try - { - var res = _userLogic.Create(model); - return Results.Ok(res); - } - catch (AccountException ex) - { - _logger.LogWarning(ex, "Wrong registration data"); - return Results.BadRequest(ex.Message); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error create user"); - return Results.Problem(ex.Message); - } - } + [HttpPost] + public IResult Registration([FromBody] UserBindingModel model) + { + try + { + var res = _userLogic.Create(model); + return Results.Ok(res); + } + catch (AccountException ex) + { + _logger.LogWarning(ex, "Wrong registration data"); + return Results.BadRequest(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error create user"); + return Results.Problem(ex.Message); + } + } - [HttpGet] - public IResult Get([FromQuery] UserSearchModel model) - { - try - { - var res = _userLogic.ReadElement(model); - return Results.Ok(res); - } - catch (ElementNotFoundException ex) - { - _logger.LogInformation(ex, "User not found"); - return Results.NoContent(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error get user"); - return Results.Problem(ex.Message); - } - } + [HttpGet] + public IResult Get([FromQuery] UserSearchModel model) + { + try + { + var res = _userLogic.ReadElement(model); + return Results.Ok(res); + } + catch (ElementNotFoundException ex) + { + _logger.LogInformation(ex, "User not found"); + return Results.NoContent(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error get user"); + return Results.Problem(ex.Message); + } + } - [HttpPatch] - public IResult Update([FromBody] UserBindingModel model) - { - try - { - var res = _userLogic.Update(model); - return Results.Ok(res); - } - catch (AccountException ex) - { - _logger.LogWarning(ex, "Wrong update data"); - return Results.BadRequest(ex.Message); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error update user"); - return Results.Problem(ex.Message); - } - } + [HttpPatch] + public IResult Update([FromBody] UserBindingModel model) + { + try + { + var res = _userLogic.Update(model); + return Results.Ok(res); + } + catch (AccountException ex) + { + _logger.LogWarning(ex, "Wrong update data"); + return Results.BadRequest(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error update user"); + return Results.Problem(ex.Message); + } + } - [HttpDelete] - public IResult Delete(Guid id) - { - try - { - var res = _userLogic.Delete(new() { Id = id }); - return Results.Ok(res); - } - catch (ElementNotFoundException ex) - { - _logger.LogInformation(ex, "User not found"); - return Results.NoContent(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error delete user"); - return Results.Problem(ex.Message); - } - } - } + [HttpDelete] + public IResult Delete(Guid id) + { + try + { + var res = _userLogic.Delete(new() { Id = id }); + return Results.Ok(res); + } + catch (ElementNotFoundException ex) + { + _logger.LogInformation(ex, "User not found"); + return Results.NoContent(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error delete user"); + return Results.Problem(ex.Message); + } + } + } + + public record class UserData(string email, string password); } \ No newline at end of file From ff3d4b3824f9fdf404610441b661dc16eb0eb129 Mon Sep 17 00:00:00 2001 From: mfnefd Date: Wed, 19 Jun 2024 22:32:09 +0400 Subject: [PATCH 2/5] add jwt parser and auth roles --- WebApp/APIClient.cs | 70 ++++++++++++++++++++++++++++ WebApp/Helpers/Roles.cs | 10 ++++ WebApp/Pages/Login.cshtml | 9 ++++ WebApp/Pages/Login.cshtml.cs | 28 +++++++++++ WebApp/Pages/User/Settings.cshtml | 2 + WebApp/Pages/User/Settings.cshtml.cs | 15 ++++++ WebApp/Program.cs | 35 +++++++++++++- WebApp/WebApp.csproj | 9 ++++ WebApp/WebApp.sln | 14 +++--- WebApp/appsettings.json | 4 +- 10 files changed, 187 insertions(+), 9 deletions(-) create mode 100644 WebApp/APIClient.cs create mode 100644 WebApp/Helpers/Roles.cs create mode 100644 WebApp/Pages/Login.cshtml create mode 100644 WebApp/Pages/Login.cshtml.cs create mode 100644 WebApp/Pages/User/Settings.cshtml create mode 100644 WebApp/Pages/User/Settings.cshtml.cs diff --git a/WebApp/APIClient.cs b/WebApp/APIClient.cs new file mode 100644 index 0000000..3257bd9 --- /dev/null +++ b/WebApp/APIClient.cs @@ -0,0 +1,70 @@ +using Newtonsoft.Json; +using System.Net.Http.Headers; +using System.Text; + +namespace WebApp; + +public class APIClient +{ + private static readonly HttpClient _client = new(); + + public static void Connect(IConfiguration configuration) + { + _client.BaseAddress = new Uri(configuration["API"]); + _client.DefaultRequestHeaders.Accept.Clear(); + _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + } + + public static T? GetRequest(string requestUrl) + { + var response = _client.GetAsync(requestUrl); + var result = response.Result.Content.ReadAsStringAsync().Result; + if (!response.Result.IsSuccessStatusCode) + { + throw new Exception(response.Result.ReasonPhrase); + } + return JsonConvert.DeserializeObject(result); + } + + public static object? PostRequest(string requestUrl, T model) + { + var json = JsonConvert.SerializeObject(model); + var data = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = _client.PostAsync(requestUrl, data); + + var result = response.Result.Content.ReadAsStringAsync().Result; + if (!response.Result.IsSuccessStatusCode) + { + throw new Exception(response.Result.ReasonPhrase); + } + return result; + } + + public static object? DeleteRequest(string requestUrl) + { + var response = _client.DeleteAsync(requestUrl); + + var result = response.Result.Content.ReadAsStringAsync().Result; + if (!response.Result.IsSuccessStatusCode) + { + throw new Exception(response.Result.ReasonPhrase); + } + return result; + } + + public static object? PatchRequest(string requestUrl, T model) + { + var json = JsonConvert.SerializeObject(model); + var data = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = _client.PatchAsync(requestUrl, data); + + var result = response.Result.Content.ReadAsStringAsync().Result; + if (!response.Result.IsSuccessStatusCode) + { + throw new Exception(response.Result.ReasonPhrase); + } + return result; + } +} \ No newline at end of file diff --git a/WebApp/Helpers/Roles.cs b/WebApp/Helpers/Roles.cs new file mode 100644 index 0000000..084cfe6 --- /dev/null +++ b/WebApp/Helpers/Roles.cs @@ -0,0 +1,10 @@ +namespace WebApp.Helpers; + +public static class Roles +{ + public const string User = "Обычный пользователь"; + public const string Admin = "Админ"; + + // TODO: Добавить нужные роли + public const string Worker = "Сотрудник"; +} \ No newline at end of file diff --git a/WebApp/Pages/Login.cshtml b/WebApp/Pages/Login.cshtml new file mode 100644 index 0000000..597f8be --- /dev/null +++ b/WebApp/Pages/Login.cshtml @@ -0,0 +1,9 @@ +@page +@model WebApp.Pages.LoginModel +
+ + + + + +
diff --git a/WebApp/Pages/Login.cshtml.cs b/WebApp/Pages/Login.cshtml.cs new file mode 100644 index 0000000..ce8c5b7 --- /dev/null +++ b/WebApp/Pages/Login.cshtml.cs @@ -0,0 +1,28 @@ +using Contracts.BindingModels; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Newtonsoft.Json; + +namespace WebApp.Pages +{ + public class LoginModel : PageModel + { + public void OnGet() + { + } + + public void OnPostAsync(string email, string password) + { + var response = APIClient.PostRequest("user/login", new { email, password }); + + if (response is null || response is not string) + { + return; + } + string token = (string)JsonConvert.DeserializeObject((string)response); + // + Response.Cookies.Append("21gunsthebest", token); + Redirect("/"); + } + } +} \ No newline at end of file diff --git a/WebApp/Pages/User/Settings.cshtml b/WebApp/Pages/User/Settings.cshtml new file mode 100644 index 0000000..256edcd --- /dev/null +++ b/WebApp/Pages/User/Settings.cshtml @@ -0,0 +1,2 @@ +@page +@model WebApp.Pages.User.SettingsModel diff --git a/WebApp/Pages/User/Settings.cshtml.cs b/WebApp/Pages/User/Settings.cshtml.cs new file mode 100644 index 0000000..519daf6 --- /dev/null +++ b/WebApp/Pages/User/Settings.cshtml.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using WebApp.Helpers; + +namespace WebApp.Pages.User +{ + [Authorize(Roles = Roles.Admin + Roles.Worker)] + public class SettingsModel : PageModel + { + public void OnGet() + { + } + } +} \ No newline at end of file diff --git a/WebApp/Program.cs b/WebApp/Program.cs index bc275e4..5b659a3 100644 --- a/WebApp/Program.cs +++ b/WebApp/Program.cs @@ -1,8 +1,40 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using WebApp; + var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorPages(); +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["SecretKey"])); + options.TokenValidationParameters = new() + { + ValidateLifetime = true, + IssuerSigningKey = secretKey, + ValidateIssuer = false, + ValidateAudience = false, + }; + options.Events = new JwtBearerEvents() + { + OnMessageReceived = context => + { + // JWT + context.Token = context.Request.Cookies["21gunsthebest"]; + return Task.CompletedTask; + } + }; + }); +builder.Services.AddAuthorization(); +// API +APIClient.Connect(builder.Configuration); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -18,8 +50,9 @@ app.UseStaticFiles(); app.UseRouting(); +app.UseAuthentication(); app.UseAuthorization(); app.MapRazorPages(); -app.Run(); +app.Run(); \ No newline at end of file diff --git a/WebApp/WebApp.csproj b/WebApp/WebApp.csproj index 1b28a01..33e3dc7 100644 --- a/WebApp/WebApp.csproj +++ b/WebApp/WebApp.csproj @@ -6,4 +6,13 @@ enable + + + + + + + + + diff --git a/WebApp/WebApp.sln b/WebApp/WebApp.sln index cb02cc3..622b5e3 100644 --- a/WebApp/WebApp.sln +++ b/WebApp/WebApp.sln @@ -3,19 +3,19 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.9.34728.123 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApp", "WebApp.csproj", "{494318C5-209C-42B9-B15F-BF0D5A8ECF18}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApp", "WebApp.csproj", "{494318C5-209C-42B9-B15F-BF0D5A8ECF18}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinFormsApp", "..\WinFormsApp\WinFormsApp.csproj", "{11F917BB-0ABC-41A0-91B9-B3FD9CEC5277}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinFormsApp", "..\WinFormsApp\WinFormsApp.csproj", "{11F917BB-0ABC-41A0-91B9-B3FD9CEC5277}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestAPI", "..\RestAPI\RestAPI.csproj", "{D3211E26-438E-48B6-9396-2FFC28271DE1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RestAPI", "..\RestAPI\RestAPI.csproj", "{D3211E26-438E-48B6-9396-2FFC28271DE1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BusinessLogic", "..\BusinessLogic\BusinessLogic.csproj", "{919726B5-89B3-43B3-AA9A-25C1348D86B1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BusinessLogic", "..\BusinessLogic\BusinessLogic.csproj", "{919726B5-89B3-43B3-AA9A-25C1348D86B1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataModels", "..\DataModels\DataModels.csproj", "{645ED499-8C00-4F04-91FB-A9EF6F1A438E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataModels", "..\DataModels\DataModels.csproj", "{645ED499-8C00-4F04-91FB-A9EF6F1A438E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "..\Contracts\Contracts.csproj", "{D7BD8791-F687-460F-8BF7-8F9CD2301EB5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Contracts", "..\Contracts\Contracts.csproj", "{D7BD8791-F687-460F-8BF7-8F9CD2301EB5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DatabaseImplement", "..\DatabaseImplement\DatabaseImplement.csproj", "{527EAA88-4EAF-42D7-93E9-494221351F9C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DatabaseImplement", "..\DatabaseImplement\DatabaseImplement.csproj", "{527EAA88-4EAF-42D7-93E9-494221351F9C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/WebApp/appsettings.json b/WebApp/appsettings.json index 10f68b8..e4d66e6 100644 --- a/WebApp/appsettings.json +++ b/WebApp/appsettings.json @@ -5,5 +5,7 @@ "Microsoft.AspNetCore": "Warning" } }, + "API": "https://localhost:7051/", + "SecretKey": "secretkey_secretkey_secretkey_secretkey", "AllowedHosts": "*" -} +} \ No newline at end of file From b1d13a591dd7c657e1629916deba78f3aa224f77 Mon Sep 17 00:00:00 2001 From: mfnefd Date: Thu, 20 Jun 2024 04:17:21 +0400 Subject: [PATCH 3/5] fix datetime error --- BusinessLogic/BusinessLogic/UserLogic.cs | 247 ++++++++++++----------- 1 file changed, 125 insertions(+), 122 deletions(-) diff --git a/BusinessLogic/BusinessLogic/UserLogic.cs b/BusinessLogic/BusinessLogic/UserLogic.cs index 7e22df0..ee6b806 100644 --- a/BusinessLogic/BusinessLogic/UserLogic.cs +++ b/BusinessLogic/BusinessLogic/UserLogic.cs @@ -21,147 +21,150 @@ using System.Threading.Tasks; namespace BusinessLogic.BusinessLogic { - public class UserLogic : IUserLogic - { - private readonly ILogger _logger; - private readonly IUserStorage _userStorage; + public class UserLogic : IUserLogic + { + private readonly ILogger _logger; + private readonly IUserStorage _userStorage; - public UserLogic(ILogger logger, IUserStorage userStorage) - { - _logger = logger; - _userStorage = userStorage; - } + public UserLogic(ILogger logger, IUserStorage userStorage) + { + _logger = logger; + _userStorage = userStorage; + } - public string Create(UserBindingModel model) - { - // Проверяем модель - _validate(model); - var usr = _userStorage.GetElement(new() { Email = model.Email }); - if (usr is not null) - { - throw new AccountException("An account with that email already exists."); - } - // Хешируем пароль - model.PasswordHash = PasswordHasher.Hash(model.Password!); - var user = _userStorage.Insert(model); - if (user is null) - { - throw new Exception("Insert operation failed."); - } + public string Create(UserBindingModel model) + { + // Проверяем модель + _validate(model); + var usr = _userStorage.GetElement(new() { Email = model.Email }); + if (usr is not null) + { + throw new AccountException("An account with that email already exists."); + } + // Хешируем пароль + model.PasswordHash = PasswordHasher.Hash(model.Password!); + model.Birthday = model.Birthday.ToUniversalTime(); - MailSender.Send(new MailRegistration(user)); + var user = _userStorage.Insert(model); + if (user is null) + { + throw new Exception("Insert operation failed."); + } - return JwtProvider.Generate(user); - } + MailSender.Send(new MailRegistration(user)); - public UserViewModel Delete(UserSearchModel model) - { - ArgumentNullException.ThrowIfNull(model); + return JwtProvider.Generate(user); + } - _logger.LogInformation("Delete user. Id: {0}", model.Id); - var user = _userStorage.Delete(model); - if (user is null) - { - throw new ElementNotFoundException(); - } - MailSender.Send(new MailDeleteUser(user)); + public UserViewModel Delete(UserSearchModel model) + { + ArgumentNullException.ThrowIfNull(model); - return UserConverter.ToView(user); - } + _logger.LogInformation("Delete user. Id: {0}", model.Id); + var user = _userStorage.Delete(model); + if (user is null) + { + throw new ElementNotFoundException(); + } + MailSender.Send(new MailDeleteUser(user)); - public IEnumerable ReadElements(UserSearchModel? model) - { - _logger.LogInformation("ReadList. Id: {Id}", model?.Id); - var list = _userStorage.GetList(model); - if (list is null || list.Count() == 0) - { - _logger.LogWarning("ReadList return null list"); - return []; - } - _logger.LogInformation("ReadList. Count: {Count}", list.Count()); + return UserConverter.ToView(user); + } - return list.Select(UserConverter.ToView); - } + public IEnumerable ReadElements(UserSearchModel? model) + { + _logger.LogInformation("ReadList. Id: {Id}", model?.Id); + var list = _userStorage.GetList(model); + if (list is null || list.Count() == 0) + { + _logger.LogWarning("ReadList return null list"); + return []; + } + _logger.LogInformation("ReadList. Count: {Count}", list.Count()); - public UserViewModel ReadElement(UserSearchModel model) - { - ArgumentNullException.ThrowIfNull(model); + return list.Select(UserConverter.ToView); + } - _logger.LogInformation("ReadElement. Id: {0}", model.Id); - var user = _userStorage.GetElement(model); - if (user is null) - { - throw new ElementNotFoundException(); - } - _logger.LogInformation("ReadElement find. Id: {0}", user.Id); + public UserViewModel ReadElement(UserSearchModel model) + { + ArgumentNullException.ThrowIfNull(model); - return UserConverter.ToView(user); - } + _logger.LogInformation("ReadElement. Id: {0}", model.Id); + var user = _userStorage.GetElement(model); + if (user is null) + { + throw new ElementNotFoundException(); + } + _logger.LogInformation("ReadElement find. Id: {0}", user.Id); - public UserViewModel Update(UserBindingModel model) - { - _validate(model); + return UserConverter.ToView(user); + } - model.PasswordHash = PasswordHasher.Hash(model.Password!); - var user = _userStorage.Update(model); - if (user is null) - { - throw new Exception("Update operation failed."); - } + public UserViewModel Update(UserBindingModel model) + { + _validate(model); - MailSender.Send(new MailUpdateUserData(user)); + model.PasswordHash = PasswordHasher.Hash(model.Password!); + model.Birthday = model.Birthday.ToUniversalTime(); + var user = _userStorage.Update(model); + if (user is null) + { + throw new Exception("Update operation failed."); + } - return UserConverter.ToView(user); - } + MailSender.Send(new MailUpdateUserData(user)); - public string Login(string email, string password) - { - _isValidEmail(email); - var user = _userStorage.GetElement(new() { Email = email }); + return UserConverter.ToView(user); + } - if (user is null) - { - throw new ElementNotFoundException(); - } - // Проверяем пароль - _isValidPassword(password); - if (!PasswordHasher.Verify(password, user.PasswordHash)) - { - throw new AccountException("The passwords don't match."); - } - return JwtProvider.Generate(user); - } + public string Login(string email, string password) + { + _isValidEmail(email); + var user = _userStorage.GetElement(new() { Email = email }); - private void _validate(UserBindingModel model) - { - ArgumentNullException.ThrowIfNull(model); - _isValidPassword(model.Password); - _isValidEmail(model.Email); - } + if (user is null) + { + throw new ElementNotFoundException(); + } + // Проверяем пароль + _isValidPassword(password); + if (!PasswordHasher.Verify(password, user.PasswordHash)) + { + throw new AccountException("The passwords don't match."); + } + return JwtProvider.Generate(user); + } - private void _isValidPassword(string? password) - { - if (string.IsNullOrWhiteSpace(password)) - { - throw new AccountException("The password is null."); - } - var hasMin8Max15Chars = new Regex(@".{8,15}"); - if (!hasMin8Max15Chars.IsMatch(password)) - { - throw new AccountException("The password must not be less than 8 or more than 15 characters long."); - } - } + private void _validate(UserBindingModel model) + { + ArgumentNullException.ThrowIfNull(model); + _isValidPassword(model.Password); + _isValidEmail(model.Email); + } - private void _isValidEmail(string? email) - { - if (string.IsNullOrWhiteSpace(email)) - { - throw new AccountException("The email is null."); - } - if (!MailAddress.TryCreate(email, out _)) - { - throw new AccountException("The email is not valid."); - } - } - } + private void _isValidPassword(string? password) + { + if (string.IsNullOrWhiteSpace(password)) + { + throw new AccountException("The password is null."); + } + var hasMin8Max15Chars = new Regex(@".{8,15}"); + if (!hasMin8Max15Chars.IsMatch(password)) + { + throw new AccountException("The password must not be less than 8 or more than 15 characters long."); + } + } + + private void _isValidEmail(string? email) + { + if (string.IsNullOrWhiteSpace(email)) + { + throw new AccountException("The email is null."); + } + if (!MailAddress.TryCreate(email, out _)) + { + throw new AccountException("The email is not valid."); + } + } + } } \ No newline at end of file From 106ad563255e04cc25a9ab48de11a0b75137274e Mon Sep 17 00:00:00 2001 From: mfnefd Date: Thu, 20 Jun 2024 04:17:46 +0400 Subject: [PATCH 4/5] add role search by name --- Contracts/SearchModels/RoleSearchModel.cs | 9 +- DatabaseImplement/Implements/RoleStorage.cs | 132 ++++++++++---------- 2 files changed, 72 insertions(+), 69 deletions(-) diff --git a/Contracts/SearchModels/RoleSearchModel.cs b/Contracts/SearchModels/RoleSearchModel.cs index 058eda2..f0df1b8 100644 --- a/Contracts/SearchModels/RoleSearchModel.cs +++ b/Contracts/SearchModels/RoleSearchModel.cs @@ -6,8 +6,9 @@ using System.Threading.Tasks; namespace Contracts.SearchModels { - public class RoleSearchModel - { - public Guid? Id { get; set; } - } + public class RoleSearchModel + { + public Guid? Id { get; set; } + public string? Name { get; set; } + } } \ No newline at end of file diff --git a/DatabaseImplement/Implements/RoleStorage.cs b/DatabaseImplement/Implements/RoleStorage.cs index 4394a59..a1d8e63 100644 --- a/DatabaseImplement/Implements/RoleStorage.cs +++ b/DatabaseImplement/Implements/RoleStorage.cs @@ -9,80 +9,82 @@ using System.Threading.Tasks; namespace DatabaseImplement.Implements { - public class RoleStorage : IRoleStorage - { - public RoleBindingModel? Delete(RoleSearchModel model) - { - if (model.Id is null) - { - return null; - } + public class RoleStorage : IRoleStorage + { + public RoleBindingModel? Delete(RoleSearchModel model) + { + if (model.Id is null) + { + return null; + } - var context = new Database(); - var role = context.Roles.FirstOrDefault(r => r.Id == model.Id); - if (role is null) - { - return null; - } + var context = new Database(); + var role = context.Roles.FirstOrDefault(r => r.Id == model.Id); + if (role is null) + { + return null; + } - context.Remove(role); - context.SaveChanges(); - return role.GetBindingModel(); - } + context.Remove(role); + context.SaveChanges(); + return role.GetBindingModel(); + } - public RoleBindingModel? GetElement(RoleSearchModel model) - { - if (model.Id is null) - { - return null; - } - var context = new Database(); - return context.Roles - .FirstOrDefault(r => r.Id == model.Id) - ?.GetBindingModel(); - } + public RoleBindingModel? GetElement(RoleSearchModel model) + { + if (model.Id is null && string.IsNullOrWhiteSpace(model.Name)) + { + return null; + } + var context = new Database(); + return context.Roles + .FirstOrDefault(r => (model.Id.HasValue && r.Id == model.Id) + || (!string.IsNullOrWhiteSpace(model.Name) && r.Name.Contains(model.Name))) + ?.GetBindingModel(); + } - public IEnumerable GetList(RoleSearchModel? model) - { - var context = new Database(); - if (model is null) - { - return context.Roles.Select(r => r.GetBindingModel()); - } - if (model.Id is null) - { - return []; - } - return context.Roles - .Where(r => r.Id == model.Id) - .Select(r => r.GetBindingModel()); - } + public IEnumerable GetList(RoleSearchModel? model) + { + var context = new Database(); + if (model is null && string.IsNullOrWhiteSpace(model.Name)) + { + return context.Roles.Select(r => r.GetBindingModel()); + } + if (model.Id is null) + { + return []; + } + return context.Roles + .Where(r => (model.Id.HasValue && r.Id == model.Id) + || (!string.IsNullOrWhiteSpace(model.Name) && r.Name.Contains(model.Name))) + .Select(r => r.GetBindingModel()); + } - public RoleBindingModel? Insert(RoleBindingModel model) - { - var context = new Database(); - var newRole = Models.Role.ToRoleFromBinding(model); + public RoleBindingModel? Insert(RoleBindingModel model) + { + var context = new Database(); + var newRole = Models.Role.ToRoleFromBinding(model); - context.Roles.Add(newRole); - context.SaveChanges(); + context.Roles.Add(newRole); + context.SaveChanges(); - return newRole.GetBindingModel(); - } + return newRole.GetBindingModel(); + } - public RoleBindingModel? Update(RoleBindingModel model) - { - var context = new Database(); - var role = context.Roles.FirstOrDefault(r => r.Id == model.Id); + public RoleBindingModel? Update(RoleBindingModel model) + { + var context = new Database(); + var role = context.Roles.FirstOrDefault(r => r.Id == model.Id); - if (role is null) - { - return null; - } + if (role is null) + { + return null; + } - role.Update(model); + role.Update(model); - context.SaveChanges(); - return role.GetBindingModel(); - } - } + context.SaveChanges(); + return role.GetBindingModel(); + } + } } \ No newline at end of file From 7d4c9dad87ab4e47c916d904b6ba14d725dfd4be Mon Sep 17 00:00:00 2001 From: mfnefd Date: Thu, 20 Jun 2024 04:18:28 +0400 Subject: [PATCH 5/5] add login, sign up, user page and settings --- WebApp/Pages/Login.cshtml | 58 +++++++++++++++-- WebApp/Pages/Login.cshtml.cs | 12 ++-- WebApp/Pages/PageModelExtension.cs | 30 +++++++++ WebApp/Pages/Shared/_Layout.cshtml | 18 +++++- WebApp/Pages/SignUp.cshtml | 93 +++++++++++++++++++++++++++ WebApp/Pages/SignUp.cshtml.cs | 38 +++++++++++ WebApp/Pages/User/Index.cshtml | 23 +++++++ WebApp/Pages/User/Index.cshtml.cs | 32 +++++++++ WebApp/Pages/User/Settings.cshtml | 52 +++++++++++++++ WebApp/Pages/User/Settings.cshtml.cs | 52 ++++++++++++++- WebApp/wwwroot/background-login.jpg | Bin 0 -> 70394 bytes 11 files changed, 393 insertions(+), 15 deletions(-) create mode 100644 WebApp/Pages/PageModelExtension.cs create mode 100644 WebApp/Pages/SignUp.cshtml create mode 100644 WebApp/Pages/SignUp.cshtml.cs create mode 100644 WebApp/Pages/User/Index.cshtml create mode 100644 WebApp/Pages/User/Index.cshtml.cs create mode 100644 WebApp/wwwroot/background-login.jpg diff --git a/WebApp/Pages/Login.cshtml b/WebApp/Pages/Login.cshtml index 597f8be..d914310 100644 --- a/WebApp/Pages/Login.cshtml +++ b/WebApp/Pages/Login.cshtml @@ -1,9 +1,57 @@ @page @model WebApp.Pages.LoginModel -
- +@{ + ViewData["Title"] = "Log In"; +} +
+ +
+
+
+
- - +
+ +

Log in

+ +
+ + +
+ +
+ + +
+ +
+ +
+ +

Don't have an account? Register here

+
+ +
+ +
+
+ Login image +
+
+
+
\ No newline at end of file diff --git a/WebApp/Pages/Login.cshtml.cs b/WebApp/Pages/Login.cshtml.cs index ce8c5b7..4a53a79 100644 --- a/WebApp/Pages/Login.cshtml.cs +++ b/WebApp/Pages/Login.cshtml.cs @@ -11,18 +11,18 @@ namespace WebApp.Pages { } - public void OnPostAsync(string email, string password) + public IActionResult OnPostAsync(string email, string password) { var response = APIClient.PostRequest("user/login", new { email, password }); if (response is null || response is not string) { - return; + throw new Exception("Something wrong LOL!"); } - string token = (string)JsonConvert.DeserializeObject((string)response); - // - Response.Cookies.Append("21gunsthebest", token); - Redirect("/"); + + this.SetJWT((string)response); + + return RedirectToPage("Index"); } } } \ No newline at end of file diff --git a/WebApp/Pages/PageModelExtension.cs b/WebApp/Pages/PageModelExtension.cs new file mode 100644 index 0000000..8d8969d --- /dev/null +++ b/WebApp/Pages/PageModelExtension.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; +using Newtonsoft.Json; + +namespace WebApp.Pages +{ + public static class PageModelExtension + { + public static string? GetUserId(this PageModel pageModel) + { + if (pageModel.User.Identity.IsAuthenticated) + { + var userIdClaim = pageModel.User.Claims.FirstOrDefault(c => c.Type == "userId"); + return userIdClaim?.Value; + } + return null; + } + + public static void SetJWT(this PageModel pageModel, string jwt) + { + string token = (string)JsonConvert.DeserializeObject(jwt); + // Сохраняем в кукис токен + pageModel.Response.Cookies.Append("21gunsthebest", token); + } + + public static void DeleteJWT(this PageModel pageModel) + { + pageModel.Response.Cookies.Delete("21gunsthebest"); + } + } +} \ No newline at end of file diff --git a/WebApp/Pages/Shared/_Layout.cshtml b/WebApp/Pages/Shared/_Layout.cshtml index b103339..e7560e9 100644 --- a/WebApp/Pages/Shared/_Layout.cshtml +++ b/WebApp/Pages/Shared/_Layout.cshtml @@ -3,7 +3,7 @@ - @ViewData["Title"] - WebApp + @ViewData["Title"] - 21 GUNS @@ -12,7 +12,7 @@