Merge pull request 'dev' (#12) from dev into registration
Reviewed-on: #12
This commit is contained in:
commit
1bb5f9fb9f
@ -43,6 +43,8 @@ namespace BusinessLogic.BusinessLogic
|
|||||||
}
|
}
|
||||||
// Хешируем пароль
|
// Хешируем пароль
|
||||||
model.PasswordHash = PasswordHasher.Hash(model.Password!);
|
model.PasswordHash = PasswordHasher.Hash(model.Password!);
|
||||||
|
model.Birthday = model.Birthday.ToUniversalTime();
|
||||||
|
|
||||||
var user = _userStorage.Insert(model);
|
var user = _userStorage.Insert(model);
|
||||||
if (user is null)
|
if (user is null)
|
||||||
{
|
{
|
||||||
@ -103,6 +105,7 @@ namespace BusinessLogic.BusinessLogic
|
|||||||
_validate(model);
|
_validate(model);
|
||||||
|
|
||||||
model.PasswordHash = PasswordHasher.Hash(model.Password!);
|
model.PasswordHash = PasswordHasher.Hash(model.Password!);
|
||||||
|
model.Birthday = model.Birthday.ToUniversalTime();
|
||||||
var user = _userStorage.Update(model);
|
var user = _userStorage.Update(model);
|
||||||
if (user is null)
|
if (user is null)
|
||||||
{
|
{
|
||||||
|
@ -9,5 +9,6 @@ namespace Contracts.SearchModels
|
|||||||
public class RoleSearchModel
|
public class RoleSearchModel
|
||||||
{
|
{
|
||||||
public Guid? Id { get; set; }
|
public Guid? Id { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -32,20 +32,21 @@ namespace DatabaseImplement.Implements
|
|||||||
|
|
||||||
public RoleBindingModel? GetElement(RoleSearchModel model)
|
public RoleBindingModel? GetElement(RoleSearchModel model)
|
||||||
{
|
{
|
||||||
if (model.Id is null)
|
if (model.Id is null && string.IsNullOrWhiteSpace(model.Name))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var context = new Database();
|
var context = new Database();
|
||||||
return context.Roles
|
return context.Roles
|
||||||
.FirstOrDefault(r => r.Id == model.Id)
|
.FirstOrDefault(r => (model.Id.HasValue && r.Id == model.Id)
|
||||||
|
|| (!string.IsNullOrWhiteSpace(model.Name) && r.Name.Contains(model.Name)))
|
||||||
?.GetBindingModel();
|
?.GetBindingModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<RoleBindingModel> GetList(RoleSearchModel? model)
|
public IEnumerable<RoleBindingModel> GetList(RoleSearchModel? model)
|
||||||
{
|
{
|
||||||
var context = new Database();
|
var context = new Database();
|
||||||
if (model is null)
|
if (model is null && string.IsNullOrWhiteSpace(model.Name))
|
||||||
{
|
{
|
||||||
return context.Roles.Select(r => r.GetBindingModel());
|
return context.Roles.Select(r => r.GetBindingModel());
|
||||||
}
|
}
|
||||||
@ -54,7 +55,8 @@ namespace DatabaseImplement.Implements
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return context.Roles
|
return context.Roles
|
||||||
.Where(r => r.Id == model.Id)
|
.Where(r => (model.Id.HasValue && r.Id == model.Id)
|
||||||
|
|| (!string.IsNullOrWhiteSpace(model.Name) && r.Name.Contains(model.Name)))
|
||||||
.Select(r => r.GetBindingModel());
|
.Select(r => r.GetBindingModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using BusinessLogic.BusinessLogic;
|
using BusinessLogic.BusinessLogic;
|
||||||
|
using BusinessLogic.Tools.Mail.MailTemplates;
|
||||||
using Contracts.BindingModels;
|
using Contracts.BindingModels;
|
||||||
using Contracts.BusinessLogicContracts;
|
using Contracts.BusinessLogicContracts;
|
||||||
using Contracts.Exceptions;
|
using Contracts.Exceptions;
|
||||||
@ -21,11 +22,11 @@ namespace RestAPI.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IResult Login(string email, string password)
|
public IResult Login([FromBody] UserData data)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var res = _userLogic.Login(email, password);
|
var res = _userLogic.Login(data.email, data.password);
|
||||||
return Results.Ok(res);
|
return Results.Ok(res);
|
||||||
}
|
}
|
||||||
catch (ElementNotFoundException ex)
|
catch (ElementNotFoundException ex)
|
||||||
@ -125,4 +126,6 @@ namespace RestAPI.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record class UserData(string email, string password);
|
||||||
}
|
}
|
70
WebApp/APIClient.cs
Normal file
70
WebApp/APIClient.cs
Normal file
@ -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<T>(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<T>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object? PostRequest<T>(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<T>(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;
|
||||||
|
}
|
||||||
|
}
|
10
WebApp/Helpers/Roles.cs
Normal file
10
WebApp/Helpers/Roles.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace WebApp.Helpers;
|
||||||
|
|
||||||
|
public static class Roles
|
||||||
|
{
|
||||||
|
public const string User = "Обычный пользователь";
|
||||||
|
public const string Admin = "Админ";
|
||||||
|
|
||||||
|
// TODO: Добавить нужные роли
|
||||||
|
public const string Worker = "Сотрудник";
|
||||||
|
}
|
57
WebApp/Pages/Login.cshtml
Normal file
57
WebApp/Pages/Login.cshtml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
@page
|
||||||
|
@model WebApp.Pages.LoginModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Log In";
|
||||||
|
}
|
||||||
|
<section class="vh-100">
|
||||||
|
<style>
|
||||||
|
.bg-image-vertical {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right center;
|
||||||
|
background-size: auto 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@media (min-width: 1025px) {
|
||||||
|
.h-custom-2 {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6 text-black">
|
||||||
|
<div class="d-flex align-items-center h-custom-2 px-5 ms-xl-4 mt-5 pt-5 pt-xl-0 mt-xl-n5">
|
||||||
|
|
||||||
|
<form style="width: 23rem;" method="post">
|
||||||
|
|
||||||
|
<h3 class="fw-normal mb-3 pb-3" style="letter-spacing: 1px;">Log in</h3>
|
||||||
|
|
||||||
|
<div data-mdb-input-init class="form-outline mb-4">
|
||||||
|
<input type="email" id="email" class="form-control form-control-lg" name="email" />
|
||||||
|
<label class="form-label" for="email">Email address</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-mdb-input-init class="form-outline mb-4">
|
||||||
|
<input type="password" id="password" class="form-control form-control-lg" name="password" />
|
||||||
|
<label class="form-label" for="password">Password</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-1 mb-4">
|
||||||
|
<button data-mdb-button-init data-mdb-ripple-init class="btn btn-info btn-lg btn-block" type="submit">Login</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Don't have an account? <a asp-page="/SignUp" class="link-info">Register here</a></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 px-0 d-none d-sm-block">
|
||||||
|
<img src="~/background-login.jpg"
|
||||||
|
alt="Login image" class="w-100 vh-100" style="object-fit: cover; object-position: left;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
28
WebApp/Pages/Login.cshtml.cs
Normal file
28
WebApp/Pages/Login.cshtml.cs
Normal file
@ -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 IActionResult OnPostAsync(string email, string password)
|
||||||
|
{
|
||||||
|
var response = APIClient.PostRequest("user/login", new { email, password });
|
||||||
|
|
||||||
|
if (response is null || response is not string)
|
||||||
|
{
|
||||||
|
throw new Exception("Something wrong LOL!");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.SetJWT((string)response);
|
||||||
|
|
||||||
|
return RedirectToPage("Index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
WebApp/Pages/PageModelExtension.cs
Normal file
30
WebApp/Pages/PageModelExtension.cs
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>@ViewData["Title"] - WebApp</title>
|
<title>@ViewData["Title"] - 21 GUNS</title>
|
||||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||||
<link rel="stylesheet" href="~/WebApp.styles.css" asp-append-version="true" />
|
<link rel="stylesheet" href="~/WebApp.styles.css" asp-append-version="true" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<header>
|
<header>
|
||||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="navbar-brand" asp-area="" asp-page="/Index">WebApp</a>
|
<a class="navbar-brand" asp-area="" asp-page="/Index">21 GUNS</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
||||||
aria-expanded="false" aria-label="Toggle navigation">
|
aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
@ -25,6 +25,18 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
|
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
|
||||||
</li>
|
</li>
|
||||||
|
@if (User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link text-dark" asp-area="" asp-page="/User/Index">Profile</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link text-dark" asp-area="" asp-page="/Login">Login</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -38,7 +50,7 @@
|
|||||||
|
|
||||||
<footer class="border-top footer text-muted">
|
<footer class="border-top footer text-muted">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
© 2024 - WebApp - <a asp-area="" asp-page="/Privacy">Privacy</a>
|
© 2024 - 21 GUNS - <a asp-area="" asp-page="/Privacy">Privacy</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
93
WebApp/Pages/SignUp.cshtml
Normal file
93
WebApp/Pages/SignUp.cshtml
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
@page
|
||||||
|
@model WebApp.Pages.SignUpModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Sign Up";
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Section: Design Block -->
|
||||||
|
<section class="overflow-hidden">
|
||||||
|
<style>
|
||||||
|
.bg-glass {
|
||||||
|
background-color: hsla(0, 0%, 100%, 0.9) !important;
|
||||||
|
backdrop-filter: saturate(200%) blur(25px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="container px-4 py-5 px-md-5 text-center text-lg-start my-5">
|
||||||
|
<div class="row gx-lg-5 align-items-center mb-5">
|
||||||
|
<div class="col-lg-6 mb-5 mb-lg-0" style="z-index: 10">
|
||||||
|
<h1 class="my-5 display-5 fw-bold ls-tight" style="color: #373A40">
|
||||||
|
Time to buy some... guns!*<br />
|
||||||
|
<span style="color: #DC5F00">In the store** of death*** and despair****</span>
|
||||||
|
</h1>
|
||||||
|
<div class="mb-4 opacity-70 text-body-emphasis" style="color: #686D76">
|
||||||
|
<p>
|
||||||
|
We would like to draw your attention to the fact that our company does not sell products to anyone under the age of 18. All of our products are intended for adult audiences only. We also do not ship to countries where our products are prohibited or restricted by law. Please make sure you meet all the necessary requirements before placing your order. We appreciate your understanding and co-operation in this matter.
|
||||||
|
<div style="font-size: 6px">
|
||||||
|
*toy guns,
|
||||||
|
**21 guns,
|
||||||
|
***a metaphor for death of the happiness of buying our merchandise,
|
||||||
|
****a metaphor for despair over the consumer's failure to find this shop previously
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-6 mb-5 mb-lg-0 position-relative">
|
||||||
|
|
||||||
|
<div class="card bg-glass">
|
||||||
|
<div class="card-body px-4 py-5 px-md-5">
|
||||||
|
<form method="post">
|
||||||
|
<!-- 2 column grid layout with text inputs for the first and last names -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div data-mdb-input-init class="form-outline">
|
||||||
|
<input asp-for="UserModel.FirstName" type="text" id="firstname" class="form-control" />
|
||||||
|
<label class="form-label" for="firstname">First name</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div data-mdb-input-init class="form-outline">
|
||||||
|
<input asp-for="UserModel.SecondName" type="text" id="lastname" class="form-control" />
|
||||||
|
<label class="form-label" for="lastname">Last name</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Email input -->
|
||||||
|
<div data-mdb-input-init class="form-outline mb-4">
|
||||||
|
<input asp-for="UserModel.Email" type="email" id="email" class="form-control" />
|
||||||
|
<label class="form-label" for="email">Email address</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Password input -->
|
||||||
|
<div data-mdb-input-init class="form-outline mb-4">
|
||||||
|
<input asp-for="UserModel.Password" type="password" id="password" class="form-control" />
|
||||||
|
<label class="form-label" for="password">Password</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Checkbox -->
|
||||||
|
<div class="form-check d-flex justify-content-center mb-4">
|
||||||
|
<input class="form-check-input me-2" type="checkbox" value="" id="confirming" />
|
||||||
|
<label class="form-check-label" for="confirming">
|
||||||
|
I confirm that I am 18 years of age and have read the Privacy Policy and Terms of Agreement
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Submit button -->
|
||||||
|
<button type="submit" data-mdb-button-init data-mdb-ripple-init class="btn btn-primary btn-block mb-4">
|
||||||
|
Sign up
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<p class="mb-0">
|
||||||
|
Already have an account? <a class="fw-bold" asp-area="" asp-page="/Login">Login</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- Section: Design Block -->
|
38
WebApp/Pages/SignUp.cshtml.cs
Normal file
38
WebApp/Pages/SignUp.cshtml.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using Contracts.BindingModels;
|
||||||
|
using Contracts.ViewModels;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using WebApp.Helpers;
|
||||||
|
|
||||||
|
namespace WebApp.Pages
|
||||||
|
{
|
||||||
|
public class SignUpModel : PageModel
|
||||||
|
{
|
||||||
|
[BindProperty]
|
||||||
|
public UserBindingModel UserModel { get; set; }
|
||||||
|
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult OnPostAsync()
|
||||||
|
{
|
||||||
|
var userRole = APIClient.GetRequest<RoleViewModel>($"role/get?name={Roles.User}");
|
||||||
|
if (userRole is null)
|
||||||
|
{
|
||||||
|
throw new Exception("User role is not found");
|
||||||
|
}
|
||||||
|
UserModel.Role = new() { Id = userRole.Id };
|
||||||
|
var response = APIClient.PostRequest("user/registration", UserModel);
|
||||||
|
|
||||||
|
if (response is null || response is not string)
|
||||||
|
{
|
||||||
|
throw new Exception("Something wrong LOL!");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.SetJWT((string)response);
|
||||||
|
|
||||||
|
return RedirectToPage("Index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
WebApp/Pages/User/Index.cshtml
Normal file
23
WebApp/Pages/User/Index.cshtml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
@page
|
||||||
|
@model WebApp.Pages.User.IndexModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "User page";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 offset-md-3">
|
||||||
|
<h1 class="text-center">@Model.UserModel.FirstName @Model.UserModel.SecondName</h1>
|
||||||
|
<p class="text-center">@Model.UserModel.Email</p>
|
||||||
|
<hr>
|
||||||
|
<div class="mt-3">
|
||||||
|
<a class="btn btn-primary" asp-page="/User/Settings">Settings</a>
|
||||||
|
</div>
|
||||||
|
<form asp-page-handler="SignOut" method="post">
|
||||||
|
<div class="mt-3">
|
||||||
|
<button class="btn btn-danger">Sign out</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
32
WebApp/Pages/User/Index.cshtml.cs
Normal file
32
WebApp/Pages/User/Index.cshtml.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using Contracts.BindingModels;
|
||||||
|
using Contracts.ViewModels;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using WebApp.Helpers;
|
||||||
|
|
||||||
|
namespace WebApp.Pages.User
|
||||||
|
{
|
||||||
|
[Authorize(Roles = Roles.User)]
|
||||||
|
public class IndexModel : PageModel
|
||||||
|
{
|
||||||
|
public UserViewModel UserModel { get; set; }
|
||||||
|
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
var id = this.GetUserId();
|
||||||
|
if (id is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserModel = APIClient.GetRequest<UserViewModel>($"user/get?id={id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult OnPostSignOut()
|
||||||
|
{
|
||||||
|
this.DeleteJWT();
|
||||||
|
return RedirectToPage("../Index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
WebApp/Pages/User/Settings.cshtml
Normal file
54
WebApp/Pages/User/Settings.cshtml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
@page
|
||||||
|
@model WebApp.Pages.User.SettingsModel
|
||||||
|
|
||||||
|
<div class="card bg-glass">
|
||||||
|
<div class="card-body px-4 py-5 px-md-5">
|
||||||
|
<form method="post">
|
||||||
|
<!-- 2 column grid layout with text inputs for the first and last names -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div data-mdb-input-init class="form-outline">
|
||||||
|
<input asp-for="UserModel.FirstName" type="text" id="firstname" class="form-control" />
|
||||||
|
<label class="form-label" for="firstname">First name</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div data-mdb-input-init class="form-outline">
|
||||||
|
<input asp-for="UserModel.SecondName" type="text" id="lastname" class="form-control" />
|
||||||
|
<label class="form-label" for="lastname">Last name</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Email input -->
|
||||||
|
<div data-mdb-input-init class="form-outline mb-4">
|
||||||
|
<input asp-for="UserModel.Email" type="email" id="email" class="form-control" />
|
||||||
|
<label class="form-label" for="email">Email address</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Password input -->
|
||||||
|
<div data-mdb-input-init class="form-outline mb-4">
|
||||||
|
<input asp-for="UserModel.Password" type="password" id="password" class="form-control" />
|
||||||
|
<label class="form-label" for="password">Password</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Birthday input -->
|
||||||
|
<div data-mdb-input-init class="form-outline mb-4">
|
||||||
|
<input asp-for="UserModel.Birthday" type="date" id="birthday" class="form-control" />
|
||||||
|
<label class="form-label" for="birthday">Birthday</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hidden inputs -->
|
||||||
|
<input type="hidden" asp-for="UserModel.Id" />
|
||||||
|
<input type="hidden" asp-for="UserModel.Role.Id" />
|
||||||
|
|
||||||
|
<!-- Submit button -->
|
||||||
|
<button type="submit" data-mdb-button-init data-mdb-ripple-init class="btn btn-primary btn-block mb-4">
|
||||||
|
Save settings
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<form asp-page-handler="DeleteProfile" method="post">
|
||||||
|
<button class="btn btn-danger btn-block mb-4">Delete this account permanently</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
65
WebApp/Pages/User/Settings.cshtml.cs
Normal file
65
WebApp/Pages/User/Settings.cshtml.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
using Contracts.BindingModels;
|
||||||
|
using Contracts.Converters;
|
||||||
|
using Contracts.ViewModels;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using WebApp.Helpers;
|
||||||
|
|
||||||
|
namespace WebApp.Pages.User
|
||||||
|
{
|
||||||
|
[Authorize(Roles = Roles.User)]
|
||||||
|
public class SettingsModel : PageModel
|
||||||
|
{
|
||||||
|
[BindProperty]
|
||||||
|
public UserBindingModel UserModel { get; set; }
|
||||||
|
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
var id = this.GetUserId();
|
||||||
|
if (id is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userView = APIClient.GetRequest<UserViewModel>($"user/get?id={id}");
|
||||||
|
if (userView is null)
|
||||||
|
{
|
||||||
|
throw new Exception("User is not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
UserModel = UserConverter.ToBinding(userView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult OnPostDeleteProfile()
|
||||||
|
{
|
||||||
|
var id = this.GetUserId();
|
||||||
|
if (id is null)
|
||||||
|
{
|
||||||
|
throw new Exception("User not found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = APIClient.DeleteRequest($"user/delete?id={id}");
|
||||||
|
if (response is null)
|
||||||
|
{
|
||||||
|
throw new Exception("Something wrong LOL!");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.DeleteJWT();
|
||||||
|
|
||||||
|
return RedirectToPage("../Index");
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult OnPostAsync()
|
||||||
|
{
|
||||||
|
var response = APIClient.PatchRequest("user/update", UserModel);
|
||||||
|
|
||||||
|
if (response is null)
|
||||||
|
{
|
||||||
|
throw new Exception("Something wrong LOL!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToPage("Index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorPages();
|
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();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
@ -18,6 +50,7 @@ app.UseStaticFiles();
|
|||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.MapRazorPages();
|
app.MapRazorPages();
|
||||||
|
@ -6,4 +6,13 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Contracts\Contracts.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -3,19 +3,19 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.9.34728.123
|
VisualStudioVersion = 17.9.34728.123
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
@ -5,5 +5,7 @@
|
|||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"API": "https://localhost:7051/",
|
||||||
|
"SecretKey": "secretkey_secretkey_secretkey_secretkey",
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*"
|
||||||
}
|
}
|
BIN
WebApp/wwwroot/background-login.jpg
Normal file
BIN
WebApp/wwwroot/background-login.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 69 KiB |
Loading…
Reference in New Issue
Block a user