Updated UserModel, UpdatePassword made

This commit is contained in:
LilleBRG 2024-08-16 12:53:39 +02:00
parent bb44d2bc56
commit f1327571e6
11 changed files with 190 additions and 28 deletions

View File

@ -77,9 +77,6 @@ namespace API.Application.Users.Commands
CreatedAt = DateTime.UtcNow.AddHours(2), CreatedAt = DateTime.UtcNow.AddHours(2),
UpdatedAt = DateTime.UtcNow.AddHours(2), UpdatedAt = DateTime.UtcNow.AddHours(2),
HashedPassword = hashedPassword, HashedPassword = hashedPassword,
Salt = salt,
PasswordBackdoor = signUpDTO.Password,
// Only for educational purposes, not in the final product!
}; };
} }
} }

View File

@ -1,6 +1,57 @@
namespace API.Application.Users.Commands using API.Models;
using API.Persistence.Repositories;
using Microsoft.AspNetCore.Mvc;
using System.Text.RegularExpressions;
namespace API.Application.Users.Commands
{ {
public class UpdateUserPassword public class UpdateUserPassword
{ {
private readonly IUserRepository _repository;
public UpdateUserPassword(IUserRepository repository)
{
_repository = repository;
}
public async Task<IActionResult> Handle(ChangePasswordDTO changePasswordDTO)
{
if (!IsPasswordSecure(changePasswordDTO.NewPassword))
{
return new ConflictObjectResult(new { message = "New Password is not secure." });
}
User currentUser = await _repository.QueryUserByIdAsync(changePasswordDTO.Id);
if (currentUser == null || !BCrypt.Net.BCrypt.Verify(changePasswordDTO.OldPassword, currentUser.HashedPassword))
{
return new UnauthorizedObjectResult(new { message = "Old Password is incorrect" });
}
string hashedPassword = BCrypt.Net.BCrypt.HashPassword(changePasswordDTO.NewPassword);
currentUser.HashedPassword = hashedPassword;
bool success = await _repository.UpdateUserPasswordAsync(currentUser);
if (success)
return new OkResult();
else
return new StatusCodeResult(StatusCodes.Status500InternalServerError);
}
private bool IsPasswordSecure(string password)
{
var hasUpperCase = new Regex(@"[A-Z]+");
var hasLowerCase = new Regex(@"[a-z]+");
var hasDigits = new Regex(@"[0-9]+");
var hasSpecialChar = new Regex(@"[\W_]+");
var hasMinimum8Chars = new Regex(@".{8,}");
return hasUpperCase.IsMatch(password)
&& hasLowerCase.IsMatch(password)
&& hasDigits.IsMatch(password)
&& hasSpecialChar.IsMatch(password)
&& hasMinimum8Chars.IsMatch(password);
}
} }
} }

View File

@ -1,5 +1,6 @@
using API.Models; using API.Models;
using API.Persistence.Repositories; using API.Persistence.Repositories;
using Microsoft.AspNetCore.Mvc;
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage; using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage;
namespace API.Application.Users.Queries namespace API.Application.Users.Queries
@ -13,10 +14,15 @@ namespace API.Application.Users.Queries
_repository = repository; _repository = repository;
} }
public async Task<UserDTO> Handle(string id) public async Task<ActionResult<UserDTO>> Handle(string id)
{ {
User user = await _repository.QueryUserByIdAsync(id); User user = await _repository.QueryUserByIdAsync(id);
if (user == null)
{
return new ConflictObjectResult(new { message = "No user on given Id" });
}
UserDTO userDTO = new UserDTO UserDTO userDTO = new UserDTO
{ {
Id = user.Id, Id = user.Id,

View File

@ -20,6 +20,7 @@ namespace API.Controllers
private readonly QueryUserById _queryUserById; private readonly QueryUserById _queryUserById;
private readonly CreateUser _createUser; private readonly CreateUser _createUser;
private readonly UpdateUser _updateUser; private readonly UpdateUser _updateUser;
private readonly UpdateUserPassword _updateUserPassword;
private readonly DeleteUser _deleteUser; private readonly DeleteUser _deleteUser;
private readonly LoginUser _loginUser; private readonly LoginUser _loginUser;
@ -28,6 +29,7 @@ namespace API.Controllers
QueryUserById queryUserById, QueryUserById queryUserById,
CreateUser createUser, CreateUser createUser,
UpdateUser updateUser, UpdateUser updateUser,
UpdateUserPassword updateUserPassword,
DeleteUser deleteUser, DeleteUser deleteUser,
LoginUser loginUser) LoginUser loginUser)
{ {
@ -35,18 +37,17 @@ namespace API.Controllers
_queryUserById = queryUserById; _queryUserById = queryUserById;
_createUser = createUser; _createUser = createUser;
_updateUser = updateUser; _updateUser = updateUser;
_updateUserPassword = updateUserPassword;
_deleteUser = deleteUser; _deleteUser = deleteUser;
_loginUser = loginUser; _loginUser = loginUser;
} }
// POST: api/Users/login
[HttpPost("login")] [HttpPost("login")]
public async Task<IActionResult> Login(LoginDTO login) public async Task<IActionResult> Login(LoginDTO login)
{ {
return await _loginUser.Handle(login); return await _loginUser.Handle(login);
} }
// GET: api/Users
[Authorize] [Authorize]
[HttpGet] [HttpGet]
public async Task<ActionResult<List<UserDTO>>> GetUsers() public async Task<ActionResult<List<UserDTO>>> GetUsers()
@ -54,8 +55,7 @@ namespace API.Controllers
return await _queryAllUsers.Handle(); return await _queryAllUsers.Handle();
} }
// GET: api/Users/5
[Authorize]
[HttpGet("{id}")] [HttpGet("{id}")]
public async Task<ActionResult<UserDTO>> GetUser(string id) public async Task<ActionResult<UserDTO>> GetUser(string id)
{ {
@ -63,25 +63,26 @@ namespace API.Controllers
} }
// PUT: api/Users/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[Authorize] [Authorize]
[HttpPut("{id}")] [HttpPut]
public async Task<IActionResult> PutUser(UserDTO userDTO) public async Task<IActionResult> PutUser(UserDTO userDTO)
{ {
return await _updateUser.Handle(userDTO); return await _updateUser.Handle(userDTO);
} }
// POST: api/Users [Authorize]
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754 [HttpPut("password")]
public async Task<IActionResult> PutUserPassword(ChangePasswordDTO changePasswordDTO)
{
return await _updateUserPassword.Handle(changePasswordDTO);
}
[HttpPost] [HttpPost]
public async Task<ActionResult<Guid>> PostUser(SignUpDTO signUpDTO) public async Task<ActionResult<Guid>> PostUser(SignUpDTO signUpDTO)
{ {
return await _createUser.Handle(signUpDTO); return await _createUser.Handle(signUpDTO);
} }
// DELETE: api/Users/5
[Authorize] [Authorize]
[HttpDelete("{id}")] [HttpDelete("{id}")]
public async Task<IActionResult> DeleteUser(string id) public async Task<IActionResult> DeleteUser(string id)

View File

@ -0,0 +1,54 @@
// <auto-generated />
using System;
using API;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace API.Migrations
{
[DbContext(typeof(AppDBContext))]
[Migration("20240816102314_removedSaltAndBackdoor")]
partial class removedSaltAndBackdoor
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
modelBuilder.Entity("API.Models.User", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasColumnType("TEXT");
b.Property<string>("HashedPassword")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Password")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("Username")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace API.Migrations
{
/// <inheritdoc />
public partial class removedSaltAndBackdoor : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PasswordBackdoor",
table: "Users");
migrationBuilder.DropColumn(
name: "Salt",
table: "Users");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "PasswordBackdoor",
table: "Users",
type: "TEXT",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "Salt",
table: "Users",
type: "TEXT",
nullable: false,
defaultValue: "");
}
}
}

View File

@ -35,14 +35,6 @@ namespace API.Migrations
b.Property<string>("Password") b.Property<string>("Password")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("PasswordBackdoor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Salt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt") b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT"); .HasColumnType("TEXT");

View File

@ -8,8 +8,6 @@ public class User : BaseModel
public string? Username { get; set; } public string? Username { get; set; }
public string? Password { get; set; } public string? Password { get; set; }
public string HashedPassword { get; set; } public string HashedPassword { get; set; }
public string PasswordBackdoor { get; set; }
public string Salt { get; set; }
} }
public class UserDTO public class UserDTO
@ -30,5 +28,12 @@ public class SignUpDTO
public string Email { get; set; } public string Email { get; set; }
public string Username { get; set; } public string Username { get; set; }
public string Password { get; set; } public string Password { get; set; }
} }
public class ChangePasswordDTO
{
public string Id { get; set; }
public string OldPassword { get; set; }
public string NewPassword { get; set; }
}

View File

@ -10,5 +10,6 @@ namespace API.Persistence.Repositories
Task<User> QueryUserByIdAsync(string id); Task<User> QueryUserByIdAsync(string id);
Task<User> QueryUserByEmailAsync(string email); Task<User> QueryUserByEmailAsync(string email);
Task<bool> UpdateUserAsync(User user); Task<bool> UpdateUserAsync(User user);
Task<bool> UpdateUserPasswordAsync(User user);
} }
} }

View File

@ -21,7 +21,6 @@ namespace API.Persistence.Repositories
} }
catch (Exception) catch (Exception)
{ {
return new User(); return new User();
} }
@ -58,6 +57,21 @@ namespace API.Persistence.Repositories
return true; return true;
} }
public async Task<bool> UpdateUserPasswordAsync(User user)
{
try
{
_context.Entry(user).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
catch (Exception)
{
return false;
}
return true;
}
public async Task<bool> DeleteUserAsync(string id) public async Task<bool> DeleteUserAsync(string id)
{ {
var user = await _context.Users.FindAsync(id); var user = await _context.Users.FindAsync(id);

View File

@ -36,6 +36,7 @@ namespace API
builder.Services.AddScoped<QueryUserById>(); builder.Services.AddScoped<QueryUserById>();
builder.Services.AddScoped<CreateUser>(); builder.Services.AddScoped<CreateUser>();
builder.Services.AddScoped<UpdateUser>(); builder.Services.AddScoped<UpdateUser>();
builder.Services.AddScoped<UpdateUserPassword>();
builder.Services.AddScoped<DeleteUser>(); builder.Services.AddScoped<DeleteUser>();
builder.Services.AddScoped<LoginUser>(); builder.Services.AddScoped<LoginUser>();
builder.Services.AddScoped<IUserRepository, UserRepository>(); builder.Services.AddScoped<IUserRepository, UserRepository>();