Skip to content

Commit 279f922

Browse files
authored
Merge pull request #10 from DiyorMarket/integration
weekly-master-update
2 parents 1e11395 + e8bb5e6 commit 279f922

20 files changed

+1476
-14
lines changed

CheckDrive.Api/CheckDrive.Api/Controllers/AccountsController.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using CheckDrive.Application.DTOs.Account;
1+
using CheckDrive.Application.Constants;
2+
using CheckDrive.Application.DTOs.Account;
23
using CheckDrive.Application.Interfaces;
4+
using Microsoft.AspNetCore.Authorization;
35
using Microsoft.AspNetCore.Mvc;
46

57
namespace CheckDrive.Api.Controllers;
@@ -32,6 +34,7 @@ public async Task<ActionResult<AccountDto>> GetAccountByIdAsync(string id)
3234
}
3335

3436
[HttpPost]
37+
[Authorize(Roles = $"{Roles.Manager},{Roles.Administrator}")]
3538
public async Task<ActionResult<AccountDto>> CreateAsync([FromBody] CreateAccountDto account)
3639
{
3740
var createdAccount = await _service.CreateAsync(account);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using CheckDrive.Application.Constants;
2+
using CheckDrive.Application.DTOs.Identity;
3+
using CheckDrive.Application.Interfaces;
4+
using CheckDrive.Application.Interfaces.Authorization;
5+
using Microsoft.AspNetCore.Authorization;
6+
using Microsoft.AspNetCore.Mvc;
7+
8+
namespace CheckDrive.Api.Controllers.Authorization;
9+
10+
[Route("api/auth")]
11+
[ApiController]
12+
public class AuthController : ControllerBase
13+
{
14+
private readonly IAuthService _authService;
15+
16+
public AuthController(IAuthService authService)
17+
{
18+
_authService = authService ?? throw new ArgumentNullException(nameof(authService));
19+
}
20+
21+
[HttpPost("login")]
22+
public async Task<IActionResult> LoginAsync([FromBody] LoginDto request)
23+
{
24+
var token = await _authService.LoginAsync(request);
25+
return Ok(token);
26+
}
27+
}

CheckDrive.Api/CheckDrive.Api/Extensions/DependencyInjection.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using CheckDrive.Api.Filters;
2-
using CheckDrive.Application.Extensions;
1+
using CheckDrive.Infrastructure.Persistence;
2+
using FluentEmail.MailKitSmtp;
3+
using CheckDrive.Application.Extensions;
34
using CheckDrive.Infrastructure.Configurations;
45
using CheckDrive.Infrastructure.Extensions;
56
using CheckDrive.TestDataCreator.Configurations;
@@ -10,6 +11,8 @@
1011
using Newtonsoft.Json.Converters;
1112
using Newtonsoft.Json.Serialization;
1213
using System.Text;
14+
using Microsoft.AspNetCore.Identity;
15+
using CheckDrive.Api.Filters;
1316

1417
namespace CheckDrive.Api.Extensions;
1518

@@ -158,3 +161,4 @@ private static void AddSyncfusion(IConfiguration configuration)
158161
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense(key);
159162
}
160163
}
164+

CheckDrive.Api/CheckDrive.Api/Extensions/StartupExtensions.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-

2-
3-
using CheckDrive.Api.Helpers;
1+
using CheckDrive.Api.Helpers;
42
using CheckDrive.Api.Middlewares;
3+
using CheckDrive.Application.Constants;
54
using CheckDrive.Domain.Interfaces;
65
using CheckDrive.TestDataCreator.Configurations;
76
using Microsoft.AspNetCore.Identity;

CheckDrive.Api/CheckDrive.Api/Middlewares/ErrorHandlerMiddleware.cs

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.AspNetCore.Http.HttpResults;
1+
using CheckDrive.Domain.Exceptions;
2+
using Microsoft.AspNetCore.Http.HttpResults;
23
using System.Net;
34

45
namespace CheckDrive.Api.Middlewares;
@@ -37,6 +38,18 @@ private async Task HandleAsync(HttpContext context, Exception exception)
3738
message = exception.Message;
3839
}
3940

41+
if (exception is InvalidLoginAttemptException)
42+
{
43+
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
44+
message = exception.Message;
45+
}
46+
47+
if (exception is RegistrationFailedException)
48+
{
49+
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
50+
message = exception.Message;
51+
}
52+
4053
await context.Response.WriteAsync(message);
4154
_logger.LogError($"{message}. {exception.Message}");
4255
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace CheckDrive.Application.Constants;
2+
3+
public class Roles
4+
{
5+
public const string Administrator = nameof(Administrator);
6+
public const string Driver = nameof(Driver);
7+
public const string Doctor = nameof(Doctor);
8+
public const string Manager = nameof(Manager);
9+
public const string Operator = nameof(Operator);
10+
public const string Mechanic = nameof(Mechanic);
11+
public const string Dispatcher = nameof(Dispatcher);
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace CheckDrive.Application.DTOs.Identity;
4+
5+
public sealed record LoginDto(
6+
string UserName,
7+
string Password);

CheckDrive.Api/CheckDrive.Application/Extensions/DependencyInjection.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
using CheckDrive.Application.Interfaces;
1+
using CheckDrive.Application.Interfaces.Authorization;
22
using CheckDrive.Application.Interfaces.Review;
3+
using CheckDrive.Application.Services.Authorization;
4+
using CheckDrive.Application.Interfaces;
35
using CheckDrive.Application.Services;
46
using CheckDrive.Application.Services.Review;
7+
using Microsoft.Extensions.Configuration;
58
using Microsoft.Extensions.DependencyInjection;
69

710
namespace CheckDrive.Application.Extensions;
@@ -11,14 +14,14 @@ public static class DependencyInjection
1114
public static IServiceCollection RegisterApplication(this IServiceCollection services)
1215
{
1316
AddServices(services);
14-
1517
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
1618

1719
return services;
1820
}
1921

2022
private static void AddServices(IServiceCollection services)
2123
{
24+
services.AddScoped<IAuthService, AuthService>();
2225
services.AddScoped<IDoctorReviewService, DoctorReviewService>();
2326
services.AddScoped<IMechanicHandoverService, MechanicHandoverService>();
2427
services.AddScoped<IOperatorReviewService, OperatorReviewService>();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using CheckDrive.Application.DTOs.Account;
2+
using CheckDrive.Application.DTOs.Identity;
3+
4+
namespace CheckDrive.Application.Interfaces.Authorization;
5+
6+
public interface IAuthService
7+
{
8+
Task<string> LoginAsync(LoginDto login);
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using Microsoft.AspNetCore.Identity;
2+
3+
namespace CheckDrive.Application.Interfaces.Authorization;
4+
5+
public interface IJwtTokenGenerator
6+
{
7+
string GenerateToken(IdentityUser user, IList<string> roles);
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using CheckDrive.Application.DTOs.Identity;
2+
using CheckDrive.Application.Interfaces.Authorization;
3+
using CheckDrive.Domain.Exceptions;
4+
using Microsoft.AspNetCore.Identity;
5+
6+
namespace CheckDrive.Application.Services.Authorization;
7+
8+
internal sealed class AuthService : IAuthService
9+
{
10+
private readonly IJwtTokenGenerator _jwtTokenGenerator;
11+
private readonly UserManager<IdentityUser> _userManager;
12+
13+
public AuthService(IJwtTokenGenerator jwtTokenGenerator, UserManager<IdentityUser> userManager)
14+
{
15+
_jwtTokenGenerator = jwtTokenGenerator ?? throw new ArgumentNullException(nameof(jwtTokenGenerator));
16+
_userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
17+
}
18+
19+
public async Task<string> LoginAsync(LoginDto loginDto)
20+
{
21+
if (loginDto is null)
22+
{
23+
throw new ArgumentNullException(nameof(loginDto));
24+
}
25+
26+
var user = await _userManager.FindByNameAsync(loginDto.UserName);
27+
28+
if (user is null)
29+
{
30+
throw new InvalidLoginAttemptException("Invalid email or password");
31+
}
32+
33+
if (!await _userManager.CheckPasswordAsync(user, loginDto.Password))
34+
{
35+
throw new InvalidLoginAttemptException("Invalid email or password");
36+
}
37+
38+
var roles = await _userManager.GetRolesAsync(user);
39+
40+
var token = _jwtTokenGenerator.GenerateToken(user, roles);
41+
42+
return token;
43+
}
44+
}

CheckDrive.Api/CheckDrive.Application/Services/Review/MechanicHandoverService.cs

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ private async Task<Mechanic> GetAndValidateMechanicAsync(int mechanicId)
5757
private async Task<CheckPoint> GetAndValidateCheckPointAsync(int checkPointId)
5858
{
5959
var checkPoint = await _context.CheckPoints
60+
.Include(x => x.DoctorReview)
6061
.FirstOrDefaultAsync(x => x.Id == checkPointId);
6162

6263
if (checkPoint == null)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace CheckDrive.Domain.Exceptions;
2+
3+
public class InvalidLoginAttemptException : Exception
4+
{
5+
public InvalidLoginAttemptException() : base() { }
6+
public InvalidLoginAttemptException(string message) : base(message) { }
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace CheckDrive.Domain.Exceptions;
2+
3+
public class RegistrationFailedException : Exception
4+
{
5+
public RegistrationFailedException() { }
6+
public RegistrationFailedException(string message) : base(message) { }
7+
}

CheckDrive.Api/CheckDrive.Infrastructure/Extensions/DependencyInjection.cs

+29-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
using CheckDrive.Domain.Interfaces;
12
using CheckDrive.Application.Interfaces;
2-
using CheckDrive.Domain.Interfaces;
33
using CheckDrive.Infrastructure.Configurations;
44
using CheckDrive.Infrastructure.Email;
55
using CheckDrive.Infrastructure.Persistence;
@@ -9,6 +9,8 @@
99
using Microsoft.EntityFrameworkCore;
1010
using Microsoft.Extensions.Configuration;
1111
using Microsoft.Extensions.DependencyInjection;
12+
using CheckDrive.Application.Interfaces.Authorization;
13+
using CheckDrive.Infrastructure.Helpers;
1214

1315
namespace CheckDrive.Infrastructure.Extensions;
1416

@@ -20,6 +22,7 @@ public static IServiceCollection RegisterInfrastructure(this IServiceCollection
2022
AddConfigurations(services, configuration);
2123
AddFluentEmail(services, configuration);
2224
AddServices(services);
25+
AddIdentity(services);
2326

2427
return services;
2528
}
@@ -28,10 +31,6 @@ private static void AddPersistence(IServiceCollection services, IConfiguration c
2831
{
2932
services.AddDbContext<ICheckDriveDbContext, CheckDriveDbContext>(options =>
3033
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
31-
32-
services.AddIdentity<IdentityUser, IdentityRole>()
33-
.AddEntityFrameworkStores<CheckDriveDbContext>()
34-
.AddDefaultTokenProviders();
3534
}
3635

3736
private static void AddConfigurations(IServiceCollection services, IConfiguration configuration)
@@ -50,6 +49,11 @@ private static void AddConfigurations(IServiceCollection services, IConfiguratio
5049
.Bind(configuration.GetSection(SmsConfigurations.SectionName))
5150
.ValidateDataAnnotations()
5251
.ValidateOnStart();
52+
53+
services.AddOptions<JwtOptions>()
54+
.Bind(configuration.GetSection(JwtOptions.SectionName))
55+
.ValidateDataAnnotations()
56+
.ValidateOnStart();
5357
}
5458

5559
private static void AddFluentEmail(IServiceCollection services, IConfiguration configuration)
@@ -82,5 +86,25 @@ private static void AddServices(IServiceCollection services)
8286
{
8387
services.AddScoped<IEmailService, EmailService>();
8488
services.AddScoped<ISmsService, SmsService>();
89+
services.AddSingleton<IJwtTokenGenerator, JwtTokenGenerator>();
90+
}
91+
92+
private static void AddIdentity(IServiceCollection services)
93+
{
94+
services.AddIdentity<IdentityUser, IdentityRole>(options =>
95+
{
96+
options.Password.RequiredLength = 7;
97+
options.Password.RequireUppercase = false;
98+
options.Password.RequireLowercase = false;
99+
options.Password.RequireNonAlphanumeric = false;
100+
options.Password.RequireDigit = false;
101+
})
102+
.AddEntityFrameworkStores<CheckDriveDbContext>()
103+
.AddDefaultTokenProviders();
104+
105+
services.Configure<DataProtectionTokenProviderOptions>(options =>
106+
{
107+
options.TokenLifespan = TimeSpan.FromHours(12);
108+
});
85109
}
86110
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using CheckDrive.Application.Interfaces.Authorization;
2+
using CheckDrive.Infrastructure.Configurations;
3+
using Microsoft.AspNetCore.Identity;
4+
using Microsoft.Extensions.Options;
5+
using Microsoft.IdentityModel.Tokens;
6+
using System.IdentityModel.Tokens.Jwt;
7+
using System.Security.Claims;
8+
using System.Text;
9+
10+
namespace CheckDrive.Infrastructure.Helpers;
11+
12+
internal sealed class JwtTokenGenerator : IJwtTokenGenerator
13+
{
14+
private readonly JwtOptions _options;
15+
16+
public JwtTokenGenerator(IOptions<JwtOptions> options )
17+
{
18+
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
19+
}
20+
21+
public string GenerateToken(IdentityUser user,
22+
IList<string> roles)
23+
{
24+
var claims = GetClaims(user, roles);
25+
26+
var signingKey = GetSigningKey();
27+
var securityToken = new JwtSecurityToken(
28+
claims: claims,
29+
expires: DateTime.UtcNow.AddHours(_options.ExpiresHours),
30+
signingCredentials: signingKey);
31+
32+
var token = new JwtSecurityTokenHandler().WriteToken(securityToken);
33+
34+
return token;
35+
}
36+
37+
private SigningCredentials GetSigningKey()
38+
{
39+
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.SecretKey));
40+
var signingKey = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
41+
42+
return signingKey;
43+
}
44+
45+
private static List<Claim> GetClaims(IdentityUser user, IList<string> roles)
46+
{
47+
var claims = new List<Claim>()
48+
{
49+
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
50+
};
51+
52+
foreach (var role in roles)
53+
{
54+
claims.Add(new Claim(ClaimTypes.Role, role));
55+
}
56+
57+
return claims;
58+
}
59+
}

0 commit comments

Comments
 (0)