Skip to content
This repository has been archived by the owner on Feb 17, 2025. It is now read-only.

fix: authentication, authorization, role-wise access #33

Merged
merged 4 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using DotnetFoundation.Application.Models.Common;
using DotnetFoundation.Application.Models.DTOs.TaskDetailsDTO;
using DotnetFoundation.Application.Models.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace DotnetFoundation.Api.Controllers;
Expand All @@ -25,6 +26,7 @@ public TaskController(ITaskDetailsService TaskDetailsService, ITaskValidator tas
/// Get all tasks.
/// </summary>
[HttpGet("tasks")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<BaseResponse<PagedList<TaskDetailsResponse>>>> GetAllTasksAsync([FromQuery] PagingRequest request)
Expand All @@ -50,6 +52,7 @@ public async Task<ActionResult<BaseResponse<PagedList<TaskDetailsResponse>>>> Ge
/// Get all active tasks.
/// </summary>
[HttpGet("tasks/active")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<BaseResponse<PagedList<TaskDetailsResponse>>>> GetActiveTasksAsync([FromQuery] PagingRequest request)
Expand All @@ -76,6 +79,7 @@ public async Task<ActionResult<BaseResponse<PagedList<TaskDetailsResponse>>>> Ge
/// </summary>
/// <param name="taskId">Id of task record</param>
[HttpGet("tasks/{taskId}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
Expand Down Expand Up @@ -118,6 +122,7 @@ public async Task<ActionResult<BaseResponse<TaskDetailsResponse>>> GetTaskByIdAs
/// </summary>
/// <param name="request">Role request details</param>
[HttpPost("task")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
Expand Down Expand Up @@ -159,6 +164,7 @@ public async Task<ActionResult<BaseResponse<TaskDetailsResponse>>> InsertTaskAsy
/// <param name="taskId">Id of task record</param>
/// <param name="request">Modified details for task record</param>
[HttpPut("task/{taskId}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
Expand Down Expand Up @@ -209,6 +215,7 @@ public async Task<ActionResult<BaseResponse<TaskDetailsResponse>>> UpdateTaskAsy
/// </summary>
/// <param name="taskId">Id of task record</param>
[HttpDelete("tasks/{taskId}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ public UserController(IUserService userService, IUserValidator userValidator)
[Authorize(Roles = "LEAD")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<BaseResponse<List<UserResponse>>>> GetAllUsersAsync()
public async Task<ActionResult<BaseResponse<PagedList<UserResponse>>>> GetAllUsersAsync([FromQuery] PagingRequest pagingRequest)
{
BaseResponse<List<UserResponse>> response = new(ResponseStatus.Fail);
BaseResponse<PagedList<UserResponse>> response = new(ResponseStatus.Fail);
try
{
response.Data = await _userService.GetAllUsersAsync().ConfigureAwait(false);
response.Data = await _userService.GetAllUsersAsync(pagingRequest).ConfigureAwait(false);
response.Status = ResponseStatus.Success;

return Ok(response);
Expand All @@ -55,6 +55,7 @@ public async Task<ActionResult<BaseResponse<List<UserResponse>>>> GetAllUsersAsy
/// </summary>
/// <param name="userId">Id of user record</param>
[HttpGet("{userId}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
Expand Down Expand Up @@ -139,6 +140,7 @@ public async Task<ActionResult<BaseResponse<bool>>> AddUserRoleAsync(UserRoleReq
/// <param name="userId">Id of user record</param>
/// <param name="request">user details updation request</param>
[HttpPut("{userId}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
Expand Down Expand Up @@ -180,6 +182,7 @@ public async Task<ActionResult<BaseResponse<UserResponse>>> UpdateUserAsync(int
/// </summary>
/// <param name="userId">Id of user record</param>
[HttpDelete("{userId}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
Expand Down
2 changes: 1 addition & 1 deletion DotnetFoundation/DotnetFoundation.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Name = "JWT Authorization",
Name = "Authorization",
Description = "Standard Authorization header using the Bearer Scheme (\"bearer {token}\")",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using DotnetFoundation.Application.Models.Common;
using DotnetFoundation.Application.Models.DTOs.AuthenticationDTO;
using DotnetFoundation.Application.Models.DTOs.UserDTO;
using DotnetFoundation.Domain.Entities;
Expand All @@ -9,7 +10,7 @@ public interface IUserRepository
{
public Task<string> AddUserAsync(RegisterRequest request);
public Task<UserInfo> LoginUserAsync(LoginRequest request);
public Task<List<User>> GetAllUsersAsync();
public Task<PagedList<User>> GetAllUsersAsync(PagingRequest pagingRequest);
public Task<User?> GetUserByIdAsync(int userId);
public Task<string> ForgotPasswordAsync(string email);
public Task ResetPasswordAsync(string email, string token, string newPassword);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using DotnetFoundation.Application.Models.Common;
using DotnetFoundation.Application.Models.DTOs.UserDTO;

namespace DotnetFoundation.Application.Interfaces.Services;

public interface IUserService
{
public Task<UserResponse?> GetUserByIdAsync(int userId);
public Task<List<UserResponse>> GetAllUsersAsync();
public Task<PagedList<UserResponse>> GetAllUsersAsync(PagingRequest pagingRequest);
public Task<bool> AddUserRoleAsync(UserRoleRequest request);
public Task<UserResponse?> UpdateUserAsync(int userId, UpdateUserRequest request);
public Task<UserResponse?> DeleteUserAsync(int userId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public record LoginRequest
/// Gets the password of the user.
/// </summary>
[Required(ErrorMessage = "Password is required")]
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,10}$",
ErrorMessage = "8-10 character length with 1 uppercase, 1 lowercase, 1 number, 1 special character is required")]
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,25}$",
ErrorMessage = "8-25 character length with 1 uppercase, 1 lowercase, 1 number, 1 special character is required")]
public string Password { get; init; } = String.Empty;
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ public static IServiceCollection AddInfrastructure(this IServiceCollection servi
services.Configure<DataProtectionTokenProviderOptions>(options => options.TokenLifespan = TimeSpan.FromMinutes(Convert.ToDouble(configuration["Appsettings:IdentityTokenLifespanInMinutes"])));

// Authentication
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
string JWT_KEY = Environment.GetEnvironmentVariable("JWT_KEY") ?? throw new InvalidOperationException("No JWT key specified");
Expand All @@ -45,6 +49,7 @@ public static IServiceCollection AddInfrastructure(this IServiceCollection servi
ValidateAudience = true,
ValidIssuer = configuration["Jwt:Issuer"],
ValidAudience = configuration["Jwt:Audience"],
// ClockSkew will be used for Lifetime validators
ClockSkew = TimeSpan.Zero,
ValidateLifetime = true,
LifetimeValidator = (DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using DotnetFoundation.Application.Exceptions;
using DotnetFoundation.Application.Interfaces.Integrations;
using DotnetFoundation.Application.Interfaces.Persistence;
using DotnetFoundation.Application.Models.Common;
using DotnetFoundation.Application.Models.DTOs.AuthenticationDTO;
using DotnetFoundation.Application.Models.DTOs.UserDTO;
using DotnetFoundation.Domain.Entities;
Expand All @@ -18,12 +20,14 @@ public class UserRepository : IUserRepository
private readonly SignInManager<IdentityApplicationUser> _signInManager;
private readonly UserManager<IdentityApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public UserRepository(SqlDatabaseContext sqlDatabaseContext, SignInManager<IdentityApplicationUser> signinManager, RoleManager<IdentityRole> roleManager, UserManager<IdentityApplicationUser> userManager)
private readonly IPaginationService<User> _paginationService;
public UserRepository(SqlDatabaseContext sqlDatabaseContext, SignInManager<IdentityApplicationUser> signinManager, RoleManager<IdentityRole> roleManager, UserManager<IdentityApplicationUser> userManager, IPaginationService<User> paginationService)
{
_dbContext = sqlDatabaseContext;
_roleManager = roleManager;
_signInManager = signinManager;
_userManager = userManager;
_paginationService = paginationService;
}

public async Task<string> AddUserAsync(RegisterRequest request)
Expand Down Expand Up @@ -63,20 +67,12 @@ public async Task<List<string>> GetUserRoleAsync(string email)
return (await _userManager.GetRolesAsync(identityApplicationUser!).ConfigureAwait(false)).ToList();
}

public async Task<List<User>> GetAllUsersAsync()
public async Task<PagedList<User>> GetAllUsersAsync(PagingRequest pagingRequest)
{
return (await _dbContext.ApplicationUsers
.Where(u => u.Status == Status.ACTIVE)
.ToListAsync().ConfigureAwait(false))
.Select(user => new User
{
Id = user.Id,
Email = user.Email,
FirstName = user.FirstName,
LastName = user.LastName,
Country = user.Country,
PhoneNumber = user.PhoneNumber
}).ToList();
IQueryable<User> usersQueryable = _dbContext.ApplicationUsers.Where(u => u.Status == Status.ACTIVE).AsQueryable();
PagedList<User> usersPagination = await _paginationService.ToPagedListAsync(usersQueryable,
pagingRequest.PageNumber, pagingRequest.PageSize).ConfigureAwait(false);
return usersPagination;
}

public async Task<User?> GetUserByIdAsync(int userId)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using AutoMapper;
using DotnetFoundation.Application.Exceptions;
using DotnetFoundation.Application.Interfaces.Persistence;
using DotnetFoundation.Application.Interfaces.Services;
using DotnetFoundation.Application.Models.Common;
using DotnetFoundation.Application.Models.DTOs.UserDTO;
using DotnetFoundation.Domain.Entities;

Expand All @@ -16,10 +18,23 @@ public UserService(IUserRepository userRepository, IMapper mapper)
_mapper = mapper;
}

public async Task<List<UserResponse>> GetAllUsersAsync()
public async Task<PagedList<UserResponse>> GetAllUsersAsync(PagingRequest pagingRequest)
{
List<User> users = await _userRepository.GetAllUsersAsync().ConfigureAwait(false);
return _mapper.Map<List<UserResponse>>(users);
PagedList<User> response = await _userRepository.GetAllUsersAsync(pagingRequest).ConfigureAwait(false);

if (!response.Items.Any())
{
throw new NotFoundException($"No data fetched on PageNumber = {response.PageNumber} for PageSize = {response.PageSize}");
}

PagedList<UserResponse> pagingResponse = new PagedList<UserResponse>(
_mapper.Map<List<UserResponse>>(response.Items),
response.PageNumber,
response.PageSize,
response.TotalCount
);

return pagingResponse;
}

public async Task<UserResponse?> GetUserByIdAsync(int userId)
Expand Down
Loading