Skip to content

Commit

Permalink
Merge pull request #27 from barzin144/fix/refresh-token-response
Browse files Browse the repository at this point in the history
Fix/refresh token response
  • Loading branch information
barzin144 authored Jan 11, 2025
2 parents e1ef973 + 7e89167 commit a699c60
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 66 deletions.
150 changes: 96 additions & 54 deletions WebApi/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,27 @@ public AuthController(IOptions<OAuthOptions> oAuthOptions, IOptions<JwtOptions>
}

[HttpPost("login")]
public async Task<IActionResult> Login(LoginUserViewModel loginUser)
public async Task<ActionResult<ApiResponseViewModel<AuthResponseViewModel>>> Login(LoginUserViewModel loginUser)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

User user = await _userService.FindUserByLoginAsync(loginUser.Email, Provider.Password, loginUser.Password);

if (user == null)
{
return NotFound("User not found.");
return NotFound(
new ApiResponseViewModel
{
Success = false,
Message = "User not found."
});
}
if (user.IsActive == false)
{
return Unauthorized("User account is inactive.");
return Unauthorized(
new ApiResponseViewModel
{
Success = false,
Message = "User account is inactive."
});
}

JwtTokensData jwtToken = _jwtTokenService.CreateJwtTokens(user);
Expand All @@ -67,22 +72,21 @@ public async Task<IActionResult> Login(LoginUserViewModel loginUser)
});

return Ok(
new
new ApiResponseViewModel<AuthResponseViewModel>
{
user.Email,
user.Name,
Provider = user.Provider.ToString()
}
);
Success = true,
Data = new AuthResponseViewModel
{
Email = user.Email,
Name = user.Name,
Provider = user.Provider.ToString()
}
});
}

[HttpPost("register")]
public async Task<IActionResult> Register(RegisterUserViewModel registerUser)
public async Task<ActionResult<ApiResponseViewModel<AuthResponseViewModel>>> Register(RegisterUserViewModel registerUser)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (await _userService.FindUserByEmailAsync(registerUser.Email) == null)
{
User newUser = new User
Expand All @@ -108,22 +112,28 @@ public async Task<IActionResult> Register(RegisterUserViewModel registerUser)
RefreshToken = jwtToken.RefreshToken,
});

return Ok(new
{
newUser.Email,
newUser.Name,
Provider = newUser.Provider.ToString()
});
return Ok(
new ApiResponseViewModel<AuthResponseViewModel>
{
Success = true,
Data = new AuthResponseViewModel
{
Email = newUser.Email,
Name = newUser.Name,
Provider = newUser.Provider.ToString()
}
}
);
}
else
{
return BadRequest("A user with this email already exists.");
return BadRequest(new ApiResponseViewModel { Success = false, Message = "A user with this email already exists." });
}
}

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpPost("change-password")]
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
public async Task<ActionResult<ApiResponseViewModel>> ChangePassword(ChangePasswordViewModel model)
{
if (!ModelState.IsValid)
{
Expand All @@ -134,16 +144,28 @@ public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)

if (user.ProviderKey != _securityService.GetSha256Hash(model.OldPassword))
{
return BadRequest("Incorrect old password.");
return BadRequest(new ApiResponseViewModel
{
Success = false,
Message = "Incorrect old password."
});
}

if (await _userService.ChangePassword(user.Id, model.NewPassword))
{
return Ok(new { message = "Password changed successfully." });
return Ok(new ApiResponseViewModel
{
Success = true,
Message = "Password changed successfully."
});
}

return BadRequest("Failed to change password.");

return BadRequest(
new ApiResponseViewModel
{
Success = false,
Message = "Failed to change password."
});
}


Expand All @@ -158,13 +180,18 @@ public IActionResult GoogleLogin()
}

[HttpGet("google-callback")]
public async Task<IActionResult> GoogleCallbackAsync()
public async Task<ActionResult<ApiResponseViewModel<AuthResponseViewModel>>> GoogleCallbackAsync()
{
var authenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);

if (!authenticateResult.Succeeded)
{
return BadRequest("Google authentication failed.");
return BadRequest(new ApiResponseViewModel
{
Success = false,
Message =
"Google authentication failed."
});
}

var claims = authenticateResult.Principal.Claims;
Expand Down Expand Up @@ -197,7 +224,7 @@ public async Task<IActionResult> GoogleCallbackAsync()

if (user.IsActive == false)
{
return Unauthorized("User account is inactive.");
return Unauthorized(new ApiResponseViewModel { Success = false, Message = "User account is inactive." });
}

JwtTokensData jwtToken = _jwtTokenService.CreateJwtTokens(user);
Expand All @@ -210,34 +237,39 @@ public async Task<IActionResult> GoogleCallbackAsync()
RefreshToken = jwtToken.RefreshToken,
});

return Ok(new
{
user.Email,
user.Name,
Provider = user.Provider.ToString()
});
return Ok(
new ApiResponseViewModel<AuthResponseViewModel>
{
Success = true,
Data = new AuthResponseViewModel
{
Email = user.Email,
Name = user.Name,
Provider = user.Provider.ToString()
}
});
}

[HttpGet("refresh-token")]
public async Task<IActionResult> RefreshToken()
public async Task<ActionResult<ApiResponseViewModel<AuthResponseViewModel>>> RefreshToken()
{
AuthCookie authResponse = ReadCookie(Request);
AuthCookie? authResponse = ReadCookie(Request);
if (authResponse == null)
{
return BadRequest("No authentication cookie found.");
return BadRequest(new ApiResponseViewModel { Success = false, Message = "No authentication cookie found." });
}
string refreshToken = authResponse.RefreshToken;
if (string.IsNullOrWhiteSpace(refreshToken))
{
return BadRequest("Refresh token is not set.");
return BadRequest(new ApiResponseViewModel { Success = false, Message = "Refresh token is not set." });
}

try
{
(Token token, User user) = await _jwtTokenService.FindUserAndTokenByRefreshTokenAsync(refreshToken);
if (token == null)
{
return BadRequest("Invalid refresh token.");
return BadRequest(new ApiResponseViewModel { Success = false, Message = "Invalid refresh token." });
}

var result = _jwtTokenService.CreateJwtTokens(user);
Expand All @@ -249,21 +281,31 @@ public async Task<IActionResult> RefreshToken()
RefreshToken = result.RefreshToken,
});

return Ok(new { message = "Token refreshed successfully." });
return Ok(
new ApiResponseViewModel<AuthResponseViewModel>
{
Success = true,
Data = new AuthResponseViewModel
{
Email = user.Email,
Name = user.Name,
Provider = user.Provider.ToString()
}
});
}
catch
{
return BadRequest("Invalid refresh token.");
return BadRequest(new ApiResponseViewModel { Success = false, Message = "Invalid refresh token." });
}
}

[HttpGet("logout")]
public async Task<IActionResult> Logout()
public async Task<ActionResult<ApiResponseViewModel>> Logout()
{
AuthCookie authResponse = ReadCookie(Request);
AuthCookie? authResponse = ReadCookie(Request);
if (authResponse == null)
{
return Ok(new { message = "You have logged out successfully." });
return Ok(new ApiResponseViewModel { Success = true, Message = "You have logged out successfully." });
}
string refreshToken = authResponse.RefreshToken;
(Token token, User user) = await _jwtTokenService.FindUserAndTokenByRefreshTokenAsync(refreshToken);
Expand All @@ -274,7 +316,7 @@ public async Task<IActionResult> Logout()
}

Response.Cookies.Delete(_jwtOptions.CookieName);
return Ok(new { message = "You have logged out successfully." });
return Ok(new ApiResponseViewModel { Success = true, Message = "You have logged out successfully." });
}

private void AppendCookie(HttpResponse response, AuthCookie authCookie)
Expand All @@ -288,11 +330,11 @@ private void AppendCookie(HttpResponse response, AuthCookie authCookie)
});
}

private AuthCookie ReadCookie(HttpRequest request)
private AuthCookie? ReadCookie(HttpRequest request)
{
if (request.Cookies.TryGetValue(_jwtOptions.CookieName, out string cookieValue))
if (request.Cookies.TryGetValue(_jwtOptions.CookieName, out string? cookieValue))
{
return JsonSerializer.Deserialize<AuthCookie>(_dataProtector.Unprotect(cookieValue));
return JsonSerializer.Deserialize<AuthCookie>(_dataProtector.Unprotect(cookieValue)) ?? null;
}
return null;
}
Expand Down
16 changes: 15 additions & 1 deletion WebApi/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using IoCConfig;
using Microsoft.AspNetCore.Mvc;
using Serilog;
using WebApi.ViewModels;

var builder = WebApplication.CreateBuilder(args);

Expand All @@ -12,7 +14,19 @@
services.AddCustomServices();
services.AddCustomAuthentication(configuration);
services.AddCustomCors(configuration);
services.AddControllers();
services.AddControllers().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var errors = context.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage);

return new BadRequestObjectResult(new ApiResponseViewModel
{
Success = false,
Message = string.Join("\n", errors)
});
};
});
services.AddCustomSwagger();
services.AddCustomMongoDbService(configuration);
services.AddSerilog();
Expand Down
10 changes: 10 additions & 0 deletions WebApi/ViewModels/ApiResponseViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace WebApi.ViewModels;

public class ApiResponseViewModel : ApiResponseViewModel<object> { }

public class ApiResponseViewModel<T>
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
public T? Data { get; set; }
}
10 changes: 10 additions & 0 deletions WebApi/ViewModels/AuthResponseViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace WebApi.ViewModels;

public class AuthResponseViewModel
{
public required string Email { get; set; }
public required string Name { get; set; }
public required string Provider { get; set; }
}
10 changes: 6 additions & 4 deletions WebApi/ViewModels/ChangePasswordViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ namespace WebApi.ViewModels
public class ChangePasswordViewModel
{
[Required]
public string OldPassword { get; set; }
[MinLength(8, ErrorMessage = "Password must be at least 8 characters long")]
public required string OldPassword { get; set; }

[Required]
[MinLength(6)]
public string NewPassword { get; set; }
[MinLength(8, ErrorMessage = "Password must be at least 8 characters long")]
public required string NewPassword { get; set; }

[Required]
[MinLength(8, ErrorMessage = "Password must be at least 8 characters long")]
[Compare(nameof(NewPassword))]
public string ConfirmNewPassword { get; set; }
public required string ConfirmNewPassword { get; set; }
}
}
6 changes: 3 additions & 3 deletions WebApi/ViewModels/LoginUserViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ namespace WebApi.ViewModels
public class LoginUserViewModel
{
[Required]
public string Email { get; set; }
public required string Email { get; set; }

[Required]
[MinLength(6)]
public string Password { get; set; }
[MinLength((8), ErrorMessage = "Password must be at least 8 characters long")]
public required string Password { get; set; }
}
}
8 changes: 4 additions & 4 deletions WebApi/ViewModels/RegisteUserViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ public class RegisterUserViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
public required string Email { get; set; }

[Required]
[MinLength(6)]
public string Password { get; set; }
[MinLength((8), ErrorMessage = "Password must be at least 8 characters long")]
public required string Password { get; set; }

[Required]
public string Name { get; set; }
public required string Name { get; set; }
}
}
1 change: 1 addition & 0 deletions WebApi/WebApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Domain\Domain.csproj" />
Expand Down

0 comments on commit a699c60

Please sign in to comment.