-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #46 from AngeloDotNet/develop
Sync Main from Develop
- Loading branch information
Showing
23 changed files
with
809 additions
and
0 deletions.
There are no files selected for viewing
18 changes: 18 additions & 0 deletions
18
src/AutenticazioneSvc/AutenticazioneSvc.BusinessLayer/AutenticazioneSvc.BusinessLayer.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.11" /> | ||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\AutenticazioneSvc.DataAccessLayer\AutenticazioneSvc.DataAccessLayer.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
20 changes: 20 additions & 0 deletions
20
src/AutenticazioneSvc/AutenticazioneSvc.BusinessLayer/Extensions/ClaimsExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using System.Security.Claims; | ||
using System.Security.Principal; | ||
|
||
namespace AutenticazioneSvc.BusinessLayer.Extensions; | ||
|
||
public static class ClaimsExtensions | ||
{ | ||
public static Guid GetId(this IPrincipal user) | ||
{ | ||
var value = GetClaimValue(user, ClaimTypes.NameIdentifier); | ||
return Guid.Parse(value); | ||
} | ||
|
||
public static string GetClaimValue(this IPrincipal user, string claimType) | ||
{ | ||
var value = ((ClaimsPrincipal)user).FindFirst(claimType)?.Value; | ||
|
||
return value!; | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
src/AutenticazioneSvc/AutenticazioneSvc.BusinessLayer/HostedService/AuthStartupTask.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using AutenticazioneSvc.DataAccessLayer.Entities; | ||
using Microsoft.AspNetCore.Identity; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Hosting; | ||
|
||
namespace AutenticazioneSvc.BusinessLayer.HostedService; | ||
|
||
public class AuthStartupTask(IServiceProvider serviceProvider) : IHostedService | ||
{ | ||
public async Task StartAsync(CancellationToken cancellationToken) | ||
{ | ||
using var scope = serviceProvider.CreateScope(); | ||
|
||
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<ApplicationRole>>(); | ||
await GenerateRolesAsync(roleManager); | ||
|
||
//TODO: Manca la generazione dell'utente amministratore | ||
} | ||
|
||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; | ||
|
||
private static async Task GenerateRolesAsync(RoleManager<ApplicationRole> roleManager) | ||
{ | ||
var roleNames = new string[] { RoleNames.Administrator, RoleNames.PowerUser, RoleNames.User }; | ||
|
||
foreach (var roleName in roleNames) | ||
{ | ||
var roleExists = await roleManager.RoleExistsAsync(roleName); | ||
|
||
if (!roleExists) | ||
{ | ||
await roleManager.CreateAsync(new ApplicationRole(roleName)); | ||
} | ||
} | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
src/AutenticazioneSvc/AutenticazioneSvc.BusinessLayer/Requirements/UserActiveHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using System.Security.Claims; | ||
using AutenticazioneSvc.BusinessLayer.Extensions; | ||
using AutenticazioneSvc.DataAccessLayer.Entities; | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Identity; | ||
|
||
namespace AutenticazioneSvc.BusinessLayer.Requirements; | ||
|
||
public class UserActiveHandler(UserManager<ApplicationUser> userManager) : AuthorizationHandler<UserActiveRequirement> | ||
{ | ||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, UserActiveRequirement requirement) | ||
{ | ||
if (context.User.Identity.IsAuthenticated) | ||
{ | ||
var userId = context.User.GetId(); | ||
var user = await userManager.FindByIdAsync(userId.ToString()); | ||
var securityStamp = context.User.GetClaimValue(ClaimTypes.SerialNumber); | ||
|
||
if (user != null && user.LockoutEnd.GetValueOrDefault() <= DateTimeOffset.UtcNow && securityStamp == user.SecurityStamp) | ||
{ | ||
context.Succeed(requirement); | ||
} | ||
} | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
src/AutenticazioneSvc/AutenticazioneSvc.BusinessLayer/Requirements/UserActiveRequirement.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
using Microsoft.AspNetCore.Authorization; | ||
|
||
namespace AutenticazioneSvc.BusinessLayer.Requirements; | ||
|
||
public class UserActiveRequirement : IAuthorizationRequirement | ||
{ } |
8 changes: 8 additions & 0 deletions
8
src/AutenticazioneSvc/AutenticazioneSvc.BusinessLayer/RoleNames.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace AutenticazioneSvc.BusinessLayer; | ||
|
||
public static class RoleNames | ||
{ | ||
public const string Administrator = nameof(Administrator); | ||
public const string PowerUser = nameof(PowerUser); | ||
public const string User = nameof(User); | ||
} |
10 changes: 10 additions & 0 deletions
10
src/AutenticazioneSvc/AutenticazioneSvc.BusinessLayer/Services/IIdentityService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using AutenticazioneSvc.Shared.DTO; | ||
|
||
namespace AutenticazioneSvc.BusinessLayer.Services; | ||
|
||
public interface IIdentityService | ||
{ | ||
Task<RegisterResponse> RegisterAsync(RegisterRequest request); | ||
Task<AuthResponse> LoginAsync(LoginRequest request); | ||
Task<AuthResponse> RefreshTokenAsync(RefreshTokenRequest request); | ||
} |
188 changes: 188 additions & 0 deletions
188
src/AutenticazioneSvc/AutenticazioneSvc.BusinessLayer/Services/IdentityService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
using System.IdentityModel.Tokens.Jwt; | ||
using System.Security.Claims; | ||
using System.Security.Cryptography; | ||
using System.Text; | ||
using AutenticazioneSvc.BusinessLayer.Extensions; | ||
using AutenticazioneSvc.BusinessLayer.Settings; | ||
using AutenticazioneSvc.DataAccessLayer.Entities; | ||
using AutenticazioneSvc.Shared.DTO; | ||
using Microsoft.AspNetCore.Identity; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.IdentityModel.Tokens; | ||
|
||
namespace AutenticazioneSvc.BusinessLayer.Services; | ||
|
||
public class IdentityService(IOptions<JwtOptions> jwtOptions, UserManager<ApplicationUser> userManager, | ||
SignInManager<ApplicationUser> signInManager) : IIdentityService | ||
{ | ||
private readonly JwtOptions jwtSettings = jwtOptions.Value; | ||
|
||
public async Task<AuthResponse> LoginAsync(LoginRequest request) | ||
{ | ||
var signInResult = await signInManager.PasswordSignInAsync(request.UserName, request.Password, false, false); | ||
|
||
if (!signInResult.Succeeded) | ||
{ | ||
return null!; | ||
} | ||
|
||
var user = await userManager.FindByNameAsync(request.UserName); | ||
|
||
if (user == null) | ||
{ | ||
return null!; | ||
} | ||
|
||
await userManager.UpdateSecurityStampAsync(user); | ||
|
||
var userRoles = await userManager.GetRolesAsync(user); | ||
|
||
var claims = new List<Claim>() | ||
{ | ||
new(ClaimTypes.NameIdentifier, user.Id.ToString()), | ||
new(ClaimTypes.Name, request.UserName), | ||
new(ClaimTypes.Email, user.Email ?? string.Empty), | ||
new(ClaimTypes.SerialNumber, user.SecurityStamp!.ToString()), | ||
|
||
new("FullName", string.Join(" ", user.LastName, user.FirstName)) | ||
|
||
//TODO: Manca il claim per la licenza | ||
|
||
//TODO: Manca la union della lista dei permessi | ||
|
||
//TODO: Manca la union della lista dei moduli a cui l'utente può accedere | ||
} | ||
.Union(userRoles.Select(role => new Claim(ClaimTypes.Role, role))).ToList(); | ||
|
||
var loginResponse = CreateToken(claims); | ||
|
||
user.RefreshToken = loginResponse.RefreshToken; | ||
user.RefreshTokenExpirationDate = DateTime.UtcNow.AddMinutes(jwtSettings.RefreshTokenExpirationMinutes); | ||
|
||
await userManager.UpdateAsync(user); | ||
|
||
return loginResponse; | ||
} | ||
|
||
public async Task<AuthResponse> RefreshTokenAsync(RefreshTokenRequest request) | ||
{ | ||
var user = ValidateAccessToken(request.AccessToken); | ||
|
||
if (user != null) | ||
{ | ||
var userId = user.GetId(); | ||
var dbUser = await userManager.FindByIdAsync(userId.ToString()); | ||
|
||
if (dbUser?.RefreshToken == null || dbUser?.RefreshTokenExpirationDate < DateTime.UtcNow || dbUser?.RefreshToken != request.RefreshToken) | ||
{ | ||
return null!; | ||
} | ||
|
||
var loginResponse = CreateToken(user.Claims.ToList()); | ||
|
||
dbUser.RefreshToken = loginResponse.RefreshToken; | ||
dbUser.RefreshTokenExpirationDate = DateTime.UtcNow.AddMinutes(jwtSettings.RefreshTokenExpirationMinutes); | ||
|
||
await userManager.UpdateAsync(dbUser); | ||
|
||
return loginResponse; | ||
} | ||
|
||
return null!; | ||
} | ||
|
||
private AuthResponse CreateToken(IList<Claim> claims) | ||
{ | ||
var audienceClaim = claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Aud); | ||
claims.Remove(audienceClaim!); | ||
|
||
var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecurityKey)); | ||
var signingCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256); | ||
|
||
var jwtSecurityToken = new JwtSecurityToken(jwtSettings.Issuer, jwtSettings.Audience, claims, | ||
DateTime.UtcNow, DateTime.UtcNow.AddMinutes(jwtSettings.AccessTokenExpirationMinutes), signingCredentials); | ||
|
||
var accessToken = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); | ||
|
||
var italyTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Central Europe Standard Time"); | ||
var expiredLocalNow = TimeZoneInfo.ConvertTimeFromUtc(jwtSecurityToken.ValidTo, italyTimeZone); | ||
|
||
var response = new AuthResponse | ||
{ | ||
AccessToken = accessToken, | ||
RefreshToken = GenerateRefreshToken(), | ||
ExpiredToken = expiredLocalNow | ||
}; | ||
|
||
return response; | ||
|
||
static string GenerateRefreshToken() | ||
{ | ||
var randomNumber = new byte[256]; | ||
using var generator = RandomNumberGenerator.Create(); | ||
generator.GetBytes(randomNumber); | ||
|
||
return Convert.ToBase64String(randomNumber); | ||
} | ||
} | ||
|
||
private ClaimsPrincipal ValidateAccessToken(string accessToken) | ||
{ | ||
var tokenValidationParameters = new TokenValidationParameters | ||
{ | ||
ValidateIssuer = true, | ||
ValidIssuer = jwtSettings.Issuer, | ||
ValidateAudience = true, | ||
ValidAudience = jwtSettings.Audience, | ||
ValidateLifetime = false, | ||
ValidateIssuerSigningKey = true, | ||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecurityKey)), | ||
RequireExpirationTime = true, | ||
ClockSkew = TimeSpan.Zero | ||
}; | ||
|
||
var tokenHandler = new JwtSecurityTokenHandler(); | ||
|
||
try | ||
{ | ||
var user = tokenHandler.ValidateToken(accessToken, tokenValidationParameters, out var securityToken); | ||
|
||
if (securityToken is JwtSecurityToken jwtSecurityToken && jwtSecurityToken.Header.Alg == SecurityAlgorithms.HmacSha256) | ||
{ | ||
return user; | ||
} | ||
} | ||
catch | ||
{ } | ||
|
||
return null!; | ||
} | ||
|
||
public async Task<RegisterResponse> RegisterAsync(RegisterRequest request) | ||
{ | ||
var user = new ApplicationUser | ||
{ | ||
FirstName = request.FirstName, | ||
LastName = request.LastName, | ||
Email = request.Email, | ||
UserName = request.Email, | ||
|
||
EmailConfirmed = true | ||
}; | ||
|
||
var result = await userManager.CreateAsync(user, request.Password); | ||
|
||
if (result.Succeeded) | ||
{ | ||
result = await userManager.AddToRoleAsync(user, RoleNames.User); | ||
} | ||
|
||
var response = new RegisterResponse | ||
{ | ||
Succeeded = result.Succeeded, | ||
Errors = result.Errors.Select(e => e.Description) | ||
}; | ||
|
||
return response; | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
src/AutenticazioneSvc/AutenticazioneSvc.BusinessLayer/Settings/JwtOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
namespace AutenticazioneSvc.BusinessLayer.Settings; | ||
|
||
public class JwtOptions | ||
{ | ||
public string Issuer { get; init; } = null!; | ||
public string Audience { get; init; } = null!; | ||
public string SecurityKey { get; init; } = null!; | ||
public int AccessTokenExpirationMinutes { get; init; } | ||
public int RefreshTokenExpirationMinutes { get; init; } | ||
} |
18 changes: 18 additions & 0 deletions
18
src/AutenticazioneSvc/AutenticazioneSvc.DataAccessLayer/AppDbContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using System.Reflection; | ||
using AutenticazioneSvc.DataAccessLayer.Entities; | ||
using Microsoft.AspNetCore.Identity; | ||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
namespace AutenticazioneSvc.DataAccessLayer; | ||
|
||
public class AppDbContext(DbContextOptions<AppDbContext> options) : IdentityDbContext<ApplicationUser, ApplicationRole, Guid, | ||
IdentityUserClaim<Guid>, ApplicationUserRole, IdentityUserLogin<Guid>, IdentityRoleClaim<Guid>, IdentityUserToken<Guid>>(options) | ||
{ | ||
protected override void OnModelCreating(ModelBuilder modelBuilder) | ||
{ | ||
base.OnModelCreating(modelBuilder); | ||
|
||
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
...enticazioneSvc/AutenticazioneSvc.DataAccessLayer/AutenticazioneSvc.DataAccessLayer.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<FrameworkReference Include="Microsoft.AspNetCore.App" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" /> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\AutenticazioneSvc.Shared\AutenticazioneSvc.Shared.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
14 changes: 14 additions & 0 deletions
14
src/AutenticazioneSvc/AutenticazioneSvc.DataAccessLayer/Entities/ApplicationRole.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using Microsoft.AspNetCore.Identity; | ||
|
||
namespace AutenticazioneSvc.DataAccessLayer.Entities; | ||
|
||
public class ApplicationRole : IdentityRole<Guid> | ||
{ | ||
public ApplicationRole() | ||
{ } | ||
|
||
public ApplicationRole(string roleName) : base(roleName) | ||
{ } | ||
|
||
public virtual ICollection<ApplicationUserRole> UserRoles { get; set; } | ||
} |
Oops, something went wrong.