Test: Google oauth

This commit is contained in:
Kamigen
2024-04-15 19:10:32 -04:00
parent b6e65b416d
commit bd2410a98e
12 changed files with 132 additions and 15 deletions

View File

@@ -7,11 +7,12 @@
<PackageVersion Include="Ardalis.GuardClauses" Version="4.2.0" /> <PackageVersion Include="Ardalis.GuardClauses" Version="4.2.0" />
<PackageVersion Include="AutoMapper" Version="13.0.1" /> <PackageVersion Include="AutoMapper" Version="13.0.1" />
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.3.0" /> <PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.3.0" />
<PackageVersion Include="Azure.Identity" Version="1.10.4" /> <PackageVersion Include="Azure.Identity" Version="1.11.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.0" /> <PackageVersion Include="coverlet.collector" Version="6.0.0" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" /> <PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="FluentValidation.AspNetCore" Version="11.3.0" /> <PackageVersion Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="11.8.1" /> <PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="11.8.1" />
<PackageVersion Include="Google.Apis.Oauth2.v2" Version="1.67.0.1869" />
<PackageVersion Include="MediatR" Version="12.2.0" /> <PackageVersion Include="MediatR" Version="12.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.3" /> <PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.3" /> <PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.3" />

View File

@@ -9,6 +9,7 @@
<PackageReference Include="Ardalis.GuardClauses" /> <PackageReference Include="Ardalis.GuardClauses" />
<PackageReference Include="AutoMapper" /> <PackageReference Include="AutoMapper" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" /> <PackageReference Include="FluentValidation.DependencyInjectionExtensions" />
<PackageReference Include="Google.Apis.Oauth2.v2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" /> <PackageReference Include="Microsoft.EntityFrameworkCore" />
</ItemGroup> </ItemGroup>

View File

@@ -0,0 +1,8 @@
using Google.Apis.Oauth2.v2.Data;
namespace Hutopy.Application.Common.Interfaces;
public interface IGoogleService
{
Task<Userinfo?> GetUserInfoAsync(string accessToken);
}

View File

@@ -1,4 +1,5 @@
using Hutopy.Application.Common.Models; using Google.Apis.Oauth2.v2.Data;
using Hutopy.Application.Common.Models;
namespace Hutopy.Application.Common.Interfaces; namespace Hutopy.Application.Common.Interfaces;
@@ -11,6 +12,8 @@ public interface IIdentityService
Task<bool> AuthorizeAsync(string userId, string policyName); Task<bool> AuthorizeAsync(string userId, string policyName);
Task<(Result Result, string UserId)> CreateUserAsync(string userName, string password); Task<(Result Result, string UserId)> CreateUserAsync(string userName, string password);
Task<(Result Result, string UserId)> CreateUserAsync(Userinfo userInfo);
Task<Result> DeleteUserAsync(string userId); Task<Result> DeleteUserAsync(string userId);
} }

View File

@@ -0,0 +1,20 @@
using Hutopy.Application.Common.Interfaces;
namespace Hutopy.Application.Google.Commands;
public record CreateGoogleUserCommand : IRequest<Guid>
{
public required string AccessToken { get; init; }
}
public class CreateGoogleUser(
IApplicationDbContext context
) : IRequestHandler<CreateGoogleUserCommand, Guid>
{
public async Task<Guid> Handle(CreateGoogleUserCommand request, CancellationToken cancellationToken)
{
await context.SaveChangesAsync(cancellationToken);
return Guid.NewGuid();
}
}

View File

@@ -6,6 +6,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Google.Apis.Oauth2.v2" />
<PackageReference Include="MediatR" /> <PackageReference Include="MediatR" />
</ItemGroup> </ItemGroup>

View File

@@ -1,4 +1,5 @@
using Hutopy.Domain.Models; using Google.Apis.Oauth2.v2.Data;
using Hutopy.Domain.Models;
namespace Hutopy.Domain.Interfaces; namespace Hutopy.Domain.Interfaces;
@@ -6,6 +7,8 @@ public interface IUserService
{ {
Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password); Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password);
Task CreateUserAsync(Userinfo userInfo);
Task<UserModel?> FindUserByIdAsync(string id); Task<UserModel?> FindUserByIdAsync(string id);
Task<UserModel?> FindUserByEmailAsync(string id); Task<UserModel?> FindUserByEmailAsync(string id);

View File

@@ -6,4 +6,5 @@ public class ApplicationUser : IdentityUser
{ {
public string FirstName { get; set; } = string.Empty; public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty;
//public string Gender { get; set; } = string.Empty;
} }

View File

@@ -1,3 +1,4 @@
using Google.Apis.Oauth2.v2.Data;
using Hutopy.Application.Common.Interfaces; using Hutopy.Application.Common.Interfaces;
using Hutopy.Application.Common.Models; using Hutopy.Application.Common.Models;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -30,6 +31,23 @@ public class IdentityService(
return (result.ToApplicationResult(), user.Id); return (result.ToApplicationResult(), user.Id);
} }
public async Task<(Result Result, string UserId)> CreateUserAsync(Userinfo userInfo)
{
var user = new ApplicationUser
{
UserName = userInfo.Name,
Email = userInfo.Email,
FirstName = userInfo.GivenName,
LastName = userInfo.FamilyName
};
var password = Guid.NewGuid().ToString("N")[..32];
var result = await userManager.CreateAsync(user, password);
return (result.ToApplicationResult(), user.Id);
}
public async Task<bool> IsInRoleAsync(string userId, string role) public async Task<bool> IsInRoleAsync(string userId, string role)
{ {

View File

@@ -0,0 +1,24 @@
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Oauth2.v2;
using Google.Apis.Oauth2.v2.Data;
using Hutopy.Application.Common.Interfaces;
namespace Hutopy.Infrastructure.Services;
public class GoogleService : IGoogleService
{
public async Task<Userinfo?> GetUserInfoAsync(string accessToken)
{
var user = GoogleCredential.FromAccessToken(accessToken);
var service = new Oauth2Service(
new BaseClientService.Initializer
{
HttpClientInitializer = user,
ApplicationName = "Hutopy"
});
return await service.Userinfo.Get().ExecuteAsync();
}
}

View File

@@ -1,15 +1,13 @@
using Hutopy.Domain.Interfaces; using Google.Apis.Oauth2.v2.Data;
using Hutopy.Domain.Interfaces;
using Hutopy.Domain.Models; using Hutopy.Domain.Models;
using Hutopy.Infrastructure.Identity; using Hutopy.Infrastructure.Identity;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
namespace Hutopy.Infrastructure.Services; namespace Hutopy.Infrastructure.Services;
public class UserService(UserManager<ApplicationUser> userManager) : IUserService public class UserService(UserManager<ApplicationUser> userManager) : IUserService
{ {
private readonly UserManager<ApplicationUser> _userManager = userManager;
public async Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password) public async Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password)
{ {
var applicationUser = new ApplicationUser var applicationUser = new ApplicationUser
@@ -19,19 +17,36 @@ public class UserService(UserManager<ApplicationUser> userManager) : IUserServic
FirstName = firstName, FirstName = firstName,
LastName = lastName LastName = lastName
}; };
//todo: Need to handle errors better for the user. var response = await userManager.CreateAsync(applicationUser, password);
var response = await _userManager.CreateAsync(applicationUser, password);
if (!response.Succeeded)
if (response.Errors.Any())
{ {
throw new InvalidOperationException(response.Errors.First().Description); throw new Exception("Failed to create user");
}
}
public async Task CreateUserAsync(Userinfo userInfo)
{
var applicationUser = new ApplicationUser
{
UserName = userInfo.Name,
Email = userInfo.Email,
FirstName = userInfo.GivenName,
LastName = userInfo.FamilyName
};
var response = await userManager.CreateAsync(applicationUser, Guid.NewGuid().ToString("N")[..32]);
if (!response.Succeeded)
{
throw new Exception("Failed to create user");
} }
} }
public async Task<UserModel?> FindUserByIdAsync(string id) public async Task<UserModel?> FindUserByIdAsync(string id)
{ {
var response = await _userManager.FindByIdAsync(id); var response = await userManager.FindByIdAsync(id);
if (response == null) return null; if (response == null) return null;
@@ -49,7 +64,7 @@ public class UserService(UserManager<ApplicationUser> userManager) : IUserServic
public async Task<UserModel?> FindUserByEmailAsync(string email) public async Task<UserModel?> FindUserByEmailAsync(string email)
{ {
var response = await _userManager.FindByEmailAsync(email); var response = await userManager.FindByEmailAsync(email);
if (response == null) return null; if (response == null) return null;

View File

@@ -0,0 +1,22 @@
using Hutopy.Application.Common.Interfaces;
using Hutopy.Application.Google.Commands;
using Hutopy.Domain.Interfaces;
namespace Hutopy.Web.Endpoints;
public class Google : EndpointGroupBase
{
public override void Map(WebApplication app)
{
app.MapGroup(this)
.MapPost(CreateGoogleUser);
}
public static async Task<Guid> CreateGoogleUser(ISender sender, CreateGoogleUserCommand command, IUserService userService, IGoogleService googleService)
{
var user = await googleService.GetUserInfoAsync(command.AccessToken) ?? throw new Exception("Failed to get user info from Google");
Console.WriteLine(user);
await userService.CreateUserAsync(user);
return await sender.Send(command);
}
}