Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add change password endpoint #18

Merged
merged 3 commits into from
Jan 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/Vulder.Admin.Api/bin/Debug/net6.0/Vulder.Admin.Api.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Vulder.Admin.Api",
"stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}
42 changes: 42 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/src/Vulder.Admin.Api/Vulder.Admin.Api.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/src/Vulder.Admin.Api/Vulder.Admin.Api.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"${workspaceFolder}/src/Vulder.Admin.Api/Vulder.Admin.Api.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}
39 changes: 39 additions & 0 deletions src/Vulder.Admin.Api/Controllers/Admin/ChangePasswordController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Security.Claims;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Vulder.Admin.Core.Models;
using Vulder.Admin.Infrastructure.Database.Interfaces;

namespace Vulder.Admin.Api.Controllers.Admin;

[Authorize]
[ApiController]
[Route("/admin/[controller]")]
public class ChangePasswordController : ControllerBase
{
private readonly IMediator _mediator;
private readonly IUnitOfWork _unitOfWork;

public ChangePasswordController(IMediator mediator, IUnitOfWork unitOfWork)
{
_mediator = mediator;
_unitOfWork = unitOfWork;
}

[HttpPut]
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordModel changePasswordModel)
{
var result = await _mediator.Send(new ChangePasswordRequestModel
{
CurrentPassword = changePasswordModel.CurrentPassword,
NewPassword = changePasswordModel.NewPassword,
Id = Guid.Parse(User.FindFirst(ClaimTypes.PrimarySid)?.Value!),
Email = User.FindFirst(ClaimTypes.Email)?.Value!
});

await _unitOfWork.CompleteAsync();

return Ok(result);
}
}
5 changes: 3 additions & 2 deletions src/Vulder.Admin.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Vulder.Admin.Application;
using Vulder.Admin.Infrastructure;
using Vulder.Admin.Infrastructure.Middlewares;
using Vulder.SharedKernel;

AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);

Expand Down Expand Up @@ -33,10 +34,10 @@

app.UseHttpsRedirection();

app.UseAuthorization();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

app.UseCors("CORS");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using MediatR;
using Vulder.Admin.Core.Exceptions;
using Vulder.Admin.Core.Models;
using Vulder.Admin.Core.ProjectAggregate.User.Dtos;
using Vulder.Admin.Core.Utils;
using Vulder.Admin.Infrastructure.Database.Interfaces;

namespace Vulder.Admin.Application.Admin.Password;

public class ChangePasswordRequestHandler : IRequestHandler<ChangePasswordRequestModel, ResultDto>
{
private readonly IUserRepository _userRepository;

public ChangePasswordRequestHandler(IUserRepository userRepository)
{
_userRepository = userRepository;
}

public async Task<ResultDto> Handle(ChangePasswordRequestModel request, CancellationToken cancellationToken)
{
var user = await _userRepository.GetUser(request.Email!);

if (!PasswordUtil.VerifyPassword(request.CurrentPassword!, user!.Password!))
throw new AuthException("Password is incorrect");

user.Password = PasswordUtil.GetEncryptedPassword(request.NewPassword!);
await _userRepository.UpdateUser(user);

return new ResultDto
{
Result = true
};
}
}
7 changes: 7 additions & 0 deletions src/Vulder.Admin.Core/Models/ChangePasswordModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Vulder.Admin.Core.Models;

public class ChangePasswordModel
{
public string? CurrentPassword { get; set; }
public string? NewPassword { get; set; }
}
10 changes: 10 additions & 0 deletions src/Vulder.Admin.Core/Models/ChangePasswordRequestModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using MediatR;
using Vulder.Admin.Core.ProjectAggregate.User.Dtos;

namespace Vulder.Admin.Core.Models;

public class ChangePasswordRequestModel : ChangePasswordModel, IRequest<ResultDto>
{
public Guid Id { get; set; }
public string? Email { get; set; }
}
6 changes: 6 additions & 0 deletions src/Vulder.Admin.Core/ProjectAggregate/User/Dtos/ResultDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Vulder.Admin.Core.ProjectAggregate.User.Dtos;

public class ResultDto
{
public bool Result { get; set; }
}
2 changes: 1 addition & 1 deletion src/Vulder.Admin.Core/Utils/PasswordUtil.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Vulder.Admin.Core.Utils;

public class PasswordUtil
public static class PasswordUtil
{
public static string GetEncryptedPassword(string password)
{
Expand Down
24 changes: 24 additions & 0 deletions src/Vulder.Admin.Core/Validators/ChangePasswordModelValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using FluentValidation;
using Vulder.Admin.Core.Models;

namespace Vulder.Admin.Core.Validators;

public class ChangePasswordModelValidator : AbstractValidator<ChangePasswordModel>
{
public ChangePasswordModelValidator()
{
const int passwordLenght = 6;

RuleFor(x => x.CurrentPassword)
.NotNull()
.NotEmpty()
.MinimumLength(passwordLenght)
.NotEqual(x => x.NewPassword);

RuleFor(x => x.NewPassword)
.NotNull()
.NotEmpty()
.MinimumLength(passwordLenght)
.NotEqual(x => x.CurrentPassword);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ public interface IUserRepository
{
Task<User> CreateUser(User user);
Task<User?> GetUser(string email);
Task UpdateUser(User user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@ public async Task<User> CreateUser(User user)

public async Task<User?> GetUser(string email)
=> await Users!.Where(e => e.Email == email).FirstOrDefaultAsync();

public Task UpdateUser(User user)
{
Users!.Update(user);
return Task.CompletedTask;
}
}
36 changes: 1 addition & 35 deletions src/Vulder.Admin.Infrastructure/StartupExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,6 @@ public static void AddValidators(this IServiceCollection services)

services.AddTransient<IValidator<RegisterUserModel>, AuthModelValidator>();
services.AddTransient<IValidator<LoginUserModel>, AuthModelValidator>();
}

public static void AddDefaultJwtConfiguration(this IServiceCollection services, IConfiguration configuration)
{
var authSection = configuration.GetSection("Jwt");
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSection.GetValue<string>("Key"))),
ValidateIssuer = true,
ValidIssuer = authSection.GetValue<string>("Issuer"),
ValidateAudience = true,
ValidAudience = authSection.GetValue<string>("Audience"),
RequireExpirationTime = true,
ValidateLifetime = true
};
});
}

public static void AddDefaultCorsPolicy(this IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CORS", corsPolicyBuilder =>
{
corsPolicyBuilder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddTransient<IValidator<ChangePasswordModel>, ChangePasswordModelValidator>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Net;
using System.Net.Http;
using System.Text;
using Newtonsoft.Json;
using Vulder.Admin.Core.Models;
using Vulder.Admin.Core.ProjectAggregate.User.Dtos;
using Xunit;

namespace Vulder.Admin.IntegrationTests.Controllers.Admin;

public class ChangePasswordControllerTest
{
[Fact]
public async void POST_RegisterController_200_StatusCode()
{
var registerModel = new RegisterUserModel
{
Email = "change@example.com",
Password = "XSfsT1bvbPHLwCcaPYq/Waum5I8EUSZ1aTLy1bo/Gz0="
};

await using var application = new WebServerFactory();
using var client = application.CreateClient();
var httpContent = new StringContent(JsonConvert.SerializeObject(registerModel), Encoding.UTF8, "application/json");
using var registerResponse = await client.PostAsync("/auth/Register", httpContent);
var token = JsonConvert.DeserializeObject<AuthUserDto>(await registerResponse.Content.ReadAsStringAsync())!.Token;

var changePasswordModel = new ChangePasswordModel
{
CurrentPassword = "XSfsT1bvbPHLwCcaPYq/Waum5I8EUSZ1aTLy1bo/Gz0=",
NewPassword = "NzjDED/7tqsSFI62KRwHyBe8eHOFjGbyiDw/M1BEOEw="
};

httpContent = new StringContent(JsonConvert.SerializeObject(changePasswordModel), Encoding.UTF8, "application/json");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
using var changePasswordResponse = await client.PutAsync("/admin/ChangePassword", httpContent);

Assert.Equal(HttpStatusCode.OK, changePasswordResponse.StatusCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using FluentValidation.TestHelper;
using Vulder.Admin.Core.Models;
using Vulder.Admin.Core.Validators;
using Xunit;

namespace Vulder.Admin.UnitTests.Core.Validators;

public class ChangePasswordModelValidatorTest
{

[Fact]
public void ValidateChangePasswordModel_Correct()
{
var model = new ChangePasswordModel
{
CurrentPassword = "XSfsT1bvbPHLwCcaPYq/Waum5I8EUSZ1aTLy1bo/Gz0=",
NewPassword = "NzjDED/7tqsSFI62KRwHyBe8eHOFjGbyiDw/M1BEOEw="
};

var result = new ChangePasswordModelValidator().TestValidate(model).IsValid;

Assert.True(result);
}

[Fact]
public void ValidateChangePasswordModel_NotValid()
{
var model = new ChangePasswordModel
{
CurrentPassword = "example",
NewPassword = "example"
};

var result = new ChangePasswordModelValidator().TestValidate(model).IsValid;

Assert.False(result);
}
}