#oauth changed GoogleController for the jwt flow ( using a common token if we connect from our app or from google )
This commit is contained in:
@@ -48,13 +48,20 @@ public static class DependencyInjection
|
||||
.AddBearerToken(IdentityConstants.BearerScheme);
|
||||
|
||||
services.AddAuthorizationBuilder();
|
||||
|
||||
// Might need to change and use AddIdentity<User, Role>() when we need to integrate connection via third party ( facebook, google )
|
||||
services
|
||||
.AddIdentityCore<ApplicationUser>()
|
||||
|
||||
services.AddIdentityCore<ApplicationUser>(options =>
|
||||
{
|
||||
options.Password.RequireDigit = false;
|
||||
options.Password.RequireLowercase = false;
|
||||
options.Password.RequireUppercase = false;
|
||||
options.Password.RequireNonAlphanumeric = false;
|
||||
options.Password.RequiredLength = 8;
|
||||
})
|
||||
.AddRoles<IdentityRole>()
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddApiEndpoints();
|
||||
.AddApiEndpoints()
|
||||
.AddSignInManager<SignInManager<ApplicationUser>>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
services.AddSingleton(TimeProvider.System);
|
||||
services.AddScoped<IIdentityService, IdentityService>();
|
||||
|
||||
@@ -2,17 +2,21 @@ using Google.Apis.Oauth2.v2.Data;
|
||||
using System.Security.Claims;
|
||||
using Hutopy.Application.Common.Interfaces;
|
||||
using Hutopy.Application.Common.Models;
|
||||
using Hutopy.Infrastructure.Utils;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Hutopy.Infrastructure.Identity;
|
||||
|
||||
public class IdentityService(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IUserClaimsPrincipalFactory<ApplicationUser> userClaimsPrincipalFactory,
|
||||
IAuthorizationService authorizationService,
|
||||
IHttpContextAccessor contextAccessor
|
||||
IHttpContextAccessor contextAccessor,
|
||||
IConfiguration configuration
|
||||
)
|
||||
: IIdentityService
|
||||
{
|
||||
@@ -205,4 +209,23 @@ public class IdentityService(
|
||||
|
||||
return userRoles;
|
||||
}
|
||||
|
||||
public async Task<string?> LoginAsync(string userName, string password)
|
||||
{
|
||||
var result = await signInManager.PasswordSignInAsync(userName, password, isPersistent: false, lockoutOnFailure: false);
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var user = await GetUserByUserNameAsync(userName);
|
||||
var token = JwtTokenHelper.GenerateJwtToken(
|
||||
issuer: configuration["Jwt:Issuer"] ?? "",
|
||||
audience: configuration["Jwt:Audience"] ?? "",
|
||||
key: configuration["Jwt:Key"] ?? "",
|
||||
userId: user?.Id ?? "");
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
using System.Text;
|
||||
using System.Security.Claims;
|
||||
using Hutopy.Domain.Interfaces;
|
||||
using Hutopy.Domain.Models;
|
||||
using Hutopy.Infrastructure.Identity;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Hutopy.Infrastructure.Services;
|
||||
|
||||
public class UserService(UserManager<ApplicationUser> userManager, IHttpContextAccessor contextAccessor) : IUserService
|
||||
{
|
||||
public async Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password)
|
||||
{
|
||||
var applicationUser = new ApplicationUser
|
||||
{
|
||||
UserName = userName,
|
||||
Email = email,
|
||||
FirstName = firstName,
|
||||
LastName = lastName
|
||||
};
|
||||
|
||||
//todo: Need to handle errors better for the user.
|
||||
var response = await userManager.CreateAsync(applicationUser, password);
|
||||
|
||||
if (response.Errors.Any())
|
||||
{
|
||||
throw new Exception("Failed to create user", new AggregateException(response.Errors.Select(e => new Exception(e.Description))));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<UserModel?> FindUserByIdAsync(string id)
|
||||
{
|
||||
var response = await userManager.FindByIdAsync(id);
|
||||
|
||||
if (response == null) return null;
|
||||
|
||||
var userModel = new UserModel
|
||||
{
|
||||
Id = response.Id,
|
||||
UserName = response.UserName,
|
||||
FirstName = response.FirstName,
|
||||
LastName = response.LastName,
|
||||
Email = response.Email,
|
||||
};
|
||||
|
||||
return userModel;
|
||||
}
|
||||
|
||||
public async Task<UserModel?> GetCurrentUserAsync()
|
||||
{
|
||||
// todo: Get the id of the user doing the request.
|
||||
var currentUserId = contextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(currentUserId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await FindUserByIdAsync(currentUserId);
|
||||
}
|
||||
|
||||
public async Task<UserModel?> FindUserByEmailAsync(string email)
|
||||
{
|
||||
var response = await userManager.FindByEmailAsync(email);
|
||||
|
||||
if (response == null) return null;
|
||||
|
||||
var userModel = new UserModel
|
||||
{
|
||||
Id = response.Id,
|
||||
UserName = response.UserName,
|
||||
FirstName = response.FirstName,
|
||||
LastName = response.LastName,
|
||||
Email = response.Email
|
||||
};
|
||||
|
||||
return userModel;
|
||||
}
|
||||
}
|
||||
|
||||
// If we need to add special characters we can alternate between 2 pools.
|
||||
public class RandomGenerator
|
||||
{
|
||||
// For the moment, numbers and special characters don't work because
|
||||
// the random generator is designed to handle a single integer.
|
||||
// We can modify this in the future.
|
||||
private const string LetterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
+ "0123456789"
|
||||
+ "!@#$%^&*()_+"
|
||||
+ "-=[];',./`~{}|:\"<>?";
|
||||
private const int LetterIdxBits = 6;
|
||||
private const int LetterIdxMask = 1 << LetterIdxBits;
|
||||
private const int LetterIdxMax = 64 / LetterIdxBits;
|
||||
|
||||
private static readonly Random Src = new();
|
||||
|
||||
public static byte[] RandBytesMaskSrc(int n)
|
||||
{
|
||||
var b = new byte[n];
|
||||
|
||||
for (var i = n - 1; i >= 0;)
|
||||
{
|
||||
long cache = Src.NextInt64();
|
||||
int remain = LetterIdxMax;
|
||||
|
||||
while (remain != 0)
|
||||
{
|
||||
if (i < 0)
|
||||
break;
|
||||
|
||||
if (cache == 0)
|
||||
cache = Src.NextInt64();
|
||||
|
||||
var idx = (int)(cache & LetterIdxMask);
|
||||
if (idx < LetterBytes.Length)
|
||||
{
|
||||
b[i] = (byte)LetterBytes[idx];
|
||||
i--;
|
||||
}
|
||||
|
||||
cache >>= LetterIdxBits;
|
||||
remain--;
|
||||
}
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
public static string RandomString(int length)
|
||||
{
|
||||
var bytes = RandBytesMaskSrc(length);
|
||||
return Encoding.UTF8.GetString(bytes); // Equivalent for *(string*)(&bytes[0])
|
||||
}
|
||||
}
|
||||
31
src/Infrastructure/Utils/GenerateJwtToken.cs
Normal file
31
src/Infrastructure/Utils/GenerateJwtToken.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace Hutopy.Infrastructure.Utils;
|
||||
|
||||
public static class JwtTokenHelper
|
||||
{
|
||||
public static string GenerateJwtToken(string issuer, string audience, string key, string userId)
|
||||
{
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, userId),
|
||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
||||
new Claim(ClaimTypes.NameIdentifier, userId)
|
||||
};
|
||||
|
||||
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
|
||||
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: issuer,
|
||||
audience: audience,
|
||||
claims: claims,
|
||||
expires: DateTime.Now.AddMinutes(30),
|
||||
signingCredentials: credentials);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
}
|
||||
58
src/Infrastructure/Utils/RandomGenerator.cs
Normal file
58
src/Infrastructure/Utils/RandomGenerator.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Hutopy.Infrastructure.Utils;
|
||||
|
||||
// If we need to add special characters we can alternate between 2 pools.
|
||||
public class RandomGenerator
|
||||
{
|
||||
// For the moment, numbers and special characters don't work because
|
||||
// the random generator is designed to handle a single integer.
|
||||
// We can modify this in the future.
|
||||
private const string LetterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
+ "0123456789"
|
||||
+ "!@#$%^&*()_+"
|
||||
+ "-=[];',./`~{}|:\"<>?";
|
||||
private const int LetterIdxBits = 6;
|
||||
private const int LetterIdxMask = 1 << LetterIdxBits;
|
||||
private const int LetterIdxMax = 64 / LetterIdxBits;
|
||||
|
||||
private static readonly Random Src = new();
|
||||
|
||||
public static byte[] RandBytesMaskSrc(int n)
|
||||
{
|
||||
var b = new byte[n];
|
||||
|
||||
for (var i = n - 1; i >= 0;)
|
||||
{
|
||||
long cache = Src.NextInt64();
|
||||
int remain = LetterIdxMax;
|
||||
|
||||
while (remain != 0)
|
||||
{
|
||||
if (i < 0)
|
||||
break;
|
||||
|
||||
if (cache == 0)
|
||||
cache = Src.NextInt64();
|
||||
|
||||
var idx = (int)(cache & LetterIdxMask);
|
||||
if (idx < LetterBytes.Length)
|
||||
{
|
||||
b[i] = (byte)LetterBytes[idx];
|
||||
i--;
|
||||
}
|
||||
|
||||
cache >>= LetterIdxBits;
|
||||
remain--;
|
||||
}
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
public static string RandomString(int length)
|
||||
{
|
||||
var bytes = RandBytesMaskSrc(length);
|
||||
return Encoding.UTF8.GetString(bytes); // Equivalent for *(string*)(&bytes[0])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user