Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,44 +1,93 @@
using Google.Apis.Auth;
using System.Net;
using Microsoft.AspNetCore.Http;
using Moq;
using System.Text.Json;
using TickAPI.Common.Auth.Abstractions;
using TickAPI.Common.Auth.Responses;
using TickAPI.Common.Auth.Services;
using TickAPI.Common.Result;

namespace TickAPI.Tests.Common.Auth.Services;

public class GoogleAuthServiceTests
{
private readonly Mock<IGoogleDataFetcher> _googleDataFetcherMock;

public GoogleAuthServiceTests()
{
var validMessage = new HttpResponseMessage(HttpStatusCode.OK);
validMessage.Content = new StringContent(JsonSerializer.Serialize(new GoogleUserData("example@test.com", "Name", "Surname")));

var unauthorizedMessage = new HttpResponseMessage(HttpStatusCode.Unauthorized);

var wrongContentMessage = new HttpResponseMessage(HttpStatusCode.OK);
wrongContentMessage.Content = new StringContent("null");

_googleDataFetcherMock = new Mock<IGoogleDataFetcher>();
_googleDataFetcherMock
.Setup(m => m.FetchUserDataAsync("validToken"))
.ReturnsAsync(validMessage);
_googleDataFetcherMock
.Setup(m => m.FetchUserDataAsync("invalidToken"))
.ReturnsAsync(unauthorizedMessage);
_googleDataFetcherMock
.Setup(m => m.FetchUserDataAsync("nullToken"))
.ReturnsAsync(wrongContentMessage);
_googleDataFetcherMock
.Setup(m => m.FetchUserDataAsync("throwToken"))
.ThrowsAsync(new Exception("An exception occured"));
}

[Fact]
public async Task GetUserDataFromToken_WhenTokenValidatorReturnsPayload_ShouldReturnEmailFromPayload()
public async Task GetUserDataFromAccessToken_WhenDataFetcherReturnsValidResponse_ShouldReturnUserDataFromResponse()
{
var googleTokenValidatorMock = new Mock<IGoogleTokenValidator>();
googleTokenValidatorMock
.Setup(m => m.ValidateAsync("validToken"))
.ReturnsAsync(new GoogleJsonWebSignature.Payload { Email = "example@test.com", GivenName = "First", FamilyName = "Last"});
var sut = new GoogleAuthService(googleTokenValidatorMock.Object);
var sut = new GoogleAuthService(_googleDataFetcherMock.Object);

var result = await sut.GetUserDataFromToken("validToken");
var result = await sut.GetUserDataFromAccessToken("validToken");

Assert.NotNull(result);
Assert.True(result.IsSuccess);
Assert.Equal("example@test.com", result.Value?.Email);
Assert.Equal("First", result.Value?.FirstName);
Assert.Equal("Last", result.Value?.LastName);
Assert.Equal("example@test.com", result.Value!.Email);
Assert.Equal("Name", result.Value!.GivenName);
Assert.Equal("Surname", result.Value!.FamilyName);
}

[Fact]
public async Task GetUserDataFromToken_WhenTokenValidatorThrowsException_ShouldReturnFailure()
public async Task
GetUserDataFromAccessToken_WhenDataFetcherReturnsResponseWithErrorStatusCode_ShouldReturnFailure()
{
var googleTokenValidatorMock = new Mock<IGoogleTokenValidator>();
googleTokenValidatorMock
.Setup(m => m.ValidateAsync("invalidToken"))
.Throws(new InvalidJwtException("Invalid Google ID token"));
var sut = new GoogleAuthService(googleTokenValidatorMock.Object);
var sut = new GoogleAuthService(_googleDataFetcherMock.Object);

var result = await sut.GetUserDataFromToken("invalidToken");
var result = await sut.GetUserDataFromAccessToken("invalidToken");

Assert.NotNull(result);
Assert.True(result.IsError);
Assert.Equal(StatusCodes.Status401Unauthorized, result.StatusCode);
Assert.Equal("Invalid Google ID token", result.ErrorMsg);
Assert.Equal("Invalid Google access token", result.ErrorMsg);
}

[Fact]
public async Task GetUserDataFromAccessToken_WhenDataFetcherReturnsNullResponse_ShouldReturnFailure()
{
var sut = new GoogleAuthService(_googleDataFetcherMock.Object);

var result = await sut.GetUserDataFromAccessToken("nullToken");

Assert.NotNull(result);
Assert.True(result.IsError);
Assert.Equal(StatusCodes.Status500InternalServerError, result.StatusCode);
Assert.Equal("Failed to parse Google user info", result.ErrorMsg);
}

[Fact]
public async Task GetUserDataFromAccessToken_WhenDataFetcherThrowsAnException_ShouldReturnFailure()
{
var sut = new GoogleAuthService(_googleDataFetcherMock.Object);

var result = await sut.GetUserDataFromAccessToken("throwToken");

Assert.NotNull(result);
Assert.True(result.IsError);
Assert.Equal(StatusCodes.Status500InternalServerError, result.StatusCode);
Assert.Equal($"Error fetching user data: An exception occured", result.ErrorMsg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ public async Task GoogleLogin_WhenAuthSuccessAndCustomerExists_ShouldReturnToken
{
// Arrange
const string email = "existing@test.com";
const string idToken = "valid-google-token";
const string accessToken = "valid-google-token";
const string jwtToken = "valid-jwt-token";

var googleAuthServiceMock = new Mock<IGoogleAuthService>();
googleAuthServiceMock.Setup(m => m.GetUserDataFromToken(idToken))
googleAuthServiceMock.Setup(m => m.GetUserDataFromAccessToken(accessToken))
.ReturnsAsync(Result<GoogleUserData>.Success(new GoogleUserData(email, "First", "Last")));

var customerServiceMock = new Mock<ICustomerService>();
Expand All @@ -41,7 +41,7 @@ public async Task GoogleLogin_WhenAuthSuccessAndCustomerExists_ShouldReturnToken
customerServiceMock.Object);

// Act
var actionResult = await sut.GoogleLogin(new GoogleLoginDto(idToken));
var actionResult = await sut.GoogleLogin(new GoogleLoginDto(accessToken));

// Assert
Assert.Equal(jwtToken, actionResult.Value?.Token);
Expand All @@ -52,13 +52,13 @@ public async Task GoogleLogin_WhenAuthSuccessAndCustomerDoesNotExist_ShouldCreat
{
// Arrange
const string email = "new@test.com";
const string idToken = "valid-google-token";
const string accessToken = "valid-google-token";
const string firstName = "First";
const string lastName = "Last";
const string jwtToken = "valid-jwt-token";

var googleAuthServiceMock = new Mock<IGoogleAuthService>();
googleAuthServiceMock.Setup(m => m.GetUserDataFromToken(idToken))
googleAuthServiceMock.Setup(m => m.GetUserDataFromAccessToken(accessToken))
.ReturnsAsync(Result<GoogleUserData>.Success(new GoogleUserData(email, "First", "Last")));

var customerServiceMock = new Mock<ICustomerService>();
Expand All @@ -83,7 +83,7 @@ public async Task GoogleLogin_WhenAuthSuccessAndCustomerDoesNotExist_ShouldCreat
customerServiceMock.Object);

// Act
var result = await sut.GoogleLogin(new GoogleLoginDto( idToken ));
var result = await sut.GoogleLogin(new GoogleLoginDto( accessToken ));

// Assert
Assert.Equal(jwtToken, result.Value?.Token);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ namespace TickAPI.Common.Auth.Abstractions;

public interface IGoogleAuthService
{
Task<Result<GoogleUserData>> GetUserDataFromToken(string token);
Task<Result<GoogleUserData>> GetUserDataFromAccessToken(string accessToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Google.Apis.Auth;
using TickAPI.Common.Result;

namespace TickAPI.Common.Auth.Abstractions;

public interface IGoogleDataFetcher
{
Task<HttpResponseMessage> FetchUserDataAsync(string accessToken);
}

This file was deleted.

42 changes: 0 additions & 42 deletions TickAPI/TickAPI/Common/Auth/Controllers/AuthController.cs

This file was deleted.

10 changes: 6 additions & 4 deletions TickAPI/TickAPI/Common/Auth/Responses/GoogleUserData.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
namespace TickAPI.Common.Auth.Responses;
using System.Text.Json.Serialization;

namespace TickAPI.Common.Auth.Responses;

public record GoogleUserData(
string Email,
string FirstName,
string LastName
[property :JsonPropertyName("email")] string Email,
[property :JsonPropertyName("given_name")] string GivenName,
[property :JsonPropertyName("family_name")] string FamilyName
);
34 changes: 24 additions & 10 deletions TickAPI/TickAPI/Common/Auth/Services/GoogleAuthService.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,43 @@
using TickAPI.Common.Auth.Abstractions;
using System.Text.Json;
using TickAPI.Common.Auth.Abstractions;
using TickAPI.Common.Auth.Responses;
using TickAPI.Common.Result;

namespace TickAPI.Common.Auth.Services;

public class GoogleAuthService : IGoogleAuthService
{
private readonly IGoogleTokenValidator _googleTokenValidator;
private IGoogleDataFetcher _googleDataFetcher;

public GoogleAuthService(IGoogleTokenValidator googleTokenValidator)
public GoogleAuthService(IGoogleDataFetcher googleDataFetcher)
{
_googleTokenValidator = googleTokenValidator;
_googleDataFetcher = googleDataFetcher;
}

public async Task<Result<GoogleUserData>> GetUserDataFromToken(string idToken)
public async Task<Result<GoogleUserData>> GetUserDataFromAccessToken(string accessToken)
{
try
{
var payload = await _googleTokenValidator.ValidateAsync(idToken);
var userData = new GoogleUserData(payload.Email, payload.GivenName, payload.FamilyName);
return Result<GoogleUserData>.Success(userData);
var response = await _googleDataFetcher.FetchUserDataAsync(accessToken);

if (!response.IsSuccessStatusCode)
{
return Result<GoogleUserData>.Failure(StatusCodes.Status401Unauthorized, "Invalid Google access token");
}

var jsonResponse = await response.Content.ReadAsStringAsync();
var userInfo = JsonSerializer.Deserialize<GoogleUserData>(jsonResponse, new JsonSerializerOptions());

if (userInfo == null)
{
return Result<GoogleUserData>.Failure(StatusCodes.Status500InternalServerError, "Failed to parse Google user info");
}

return Result<GoogleUserData>.Success(userInfo);
}
catch (Exception)
catch (Exception ex)
{
return Result<GoogleUserData>.Failure(StatusCodes.Status401Unauthorized, "Invalid Google ID token");
return Result<GoogleUserData>.Failure(StatusCodes.Status500InternalServerError, $"Error fetching user data: {ex.Message}");
}
}
}
27 changes: 27 additions & 0 deletions TickAPI/TickAPI/Common/Auth/Services/GoogleDataFetcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Google.Apis.Auth;
using TickAPI.Common.Auth.Abstractions;
using TickAPI.Common.Result;

namespace TickAPI.Common.Auth.Services;

public class GoogleDataFetcher : IGoogleDataFetcher
{
private readonly IConfiguration _configuration;
private readonly IHttpClientFactory _httpClientFactory;

public GoogleDataFetcher(IConfiguration configuration, IHttpClientFactory httpClientFactory)
{
_configuration = configuration;
_httpClientFactory = httpClientFactory;
}

public async Task<HttpResponseMessage> FetchUserDataAsync(string accessToken)
{
var client = _httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);

var response = await client.GetAsync(_configuration["Authentication:Google:UserInfoEndpoint"]);

return response;
}
}
22 changes: 0 additions & 22 deletions TickAPI/TickAPI/Common/Auth/Services/GoogleTokenValidator.cs

This file was deleted.

4 changes: 2 additions & 2 deletions TickAPI/TickAPI/Customers/Controllers/CustomerController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public CustomerController(IGoogleAuthService googleAuthService, IJwtService jwtS
[HttpPost("google-login")]
public async Task<ActionResult<GoogleLoginResponseDto>> GoogleLogin([FromBody] GoogleLoginDto request)
{
var userDataResult = await _googleAuthService.GetUserDataFromToken(request.IdToken);
var userDataResult = await _googleAuthService.GetUserDataFromAccessToken(request.AccessToken);
if(userDataResult.IsError)
return StatusCode(userDataResult.StatusCode, userDataResult.ErrorMsg);

Expand All @@ -36,7 +36,7 @@ public async Task<ActionResult<GoogleLoginResponseDto>> GoogleLogin([FromBody] G
var existingCustomerResult = await _customerService.GetCustomerByEmailAsync(userData.Email);
if (existingCustomerResult.IsError)
{
var newCustomerResult = await _customerService.CreateNewCustomerAsync(userData.Email, userData.FirstName, userData.LastName);
var newCustomerResult = await _customerService.CreateNewCustomerAsync(userData.Email, userData.GivenName, userData.FamilyName);
if (newCustomerResult.IsError)
return StatusCode(newCustomerResult.StatusCode, newCustomerResult.ErrorMsg);
}
Expand Down
2 changes: 1 addition & 1 deletion TickAPI/TickAPI/Customers/DTOs/Request/GoogleLoginDto.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
namespace TickAPI.Customers.DTOs.Request;

public record GoogleLoginDto(
string IdToken
string AccessToken
);
Loading
Loading