diff --git a/dotnet/AipsCore/Application/Abstract/UserContext/ITokenProvider.cs b/dotnet/AipsCore/Application/Abstract/UserContext/ITokenProvider.cs new file mode 100644 index 0000000..7839f5f --- /dev/null +++ b/dotnet/AipsCore/Application/Abstract/UserContext/ITokenProvider.cs @@ -0,0 +1,8 @@ +using AipsCore.Domain.Models.User; + +namespace AipsCore.Application.Abstract.UserContext; + +public interface ITokenProvider +{ + string Generate(User user, IList roles); +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Authentication/Token.cs b/dotnet/AipsCore/Application/Authentication/Token.cs new file mode 100644 index 0000000..85915f3 --- /dev/null +++ b/dotnet/AipsCore/Application/Authentication/Token.cs @@ -0,0 +1,3 @@ +namespace AipsCore.Application.Authentication; + +public record Token(string Value); \ No newline at end of file diff --git a/dotnet/AipsCore/Infrastructure/DI/AipsRegistrationExtensions.cs b/dotnet/AipsCore/Infrastructure/DI/AipsRegistrationExtensions.cs index a213b5d..351ea14 100644 --- a/dotnet/AipsCore/Infrastructure/DI/AipsRegistrationExtensions.cs +++ b/dotnet/AipsCore/Infrastructure/DI/AipsRegistrationExtensions.cs @@ -14,7 +14,7 @@ public static class AipsRegistrationExtensions services.AddPersistence(configuration); - services.AddUserContext(); + services.AddUserContext(configuration); return services; } diff --git a/dotnet/AipsCore/Infrastructure/DI/UserContextRegistrationExtension.cs b/dotnet/AipsCore/Infrastructure/DI/UserContextRegistrationExtension.cs index 7513690..f051b43 100644 --- a/dotnet/AipsCore/Infrastructure/DI/UserContextRegistrationExtension.cs +++ b/dotnet/AipsCore/Infrastructure/DI/UserContextRegistrationExtension.cs @@ -1,13 +1,67 @@ +using System.Text; using AipsCore.Application.Abstract.UserContext; +using AipsCore.Infrastructure.Persistence.Authentication; +using AipsCore.Infrastructure.Persistence.Db; +using AipsCore.Infrastructure.Persistence.User; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; namespace AipsCore.Infrastructure.DI; public static class UserContextRegistrationExtension { - public static IServiceCollection AddUserContext(this IServiceCollection services) + public static IServiceCollection AddUserContext(this IServiceCollection services, IConfiguration configuration) { - services.AddTransient(); + var jwtSettings = new JwtSettings + { + Issuer = configuration["JWT_ISSUER"]!, + Audience = configuration["JWT_AUDIENCE"]!, + Key = configuration["JWT_KEY"]!, + ExpirationMinutes = int.Parse(configuration["JWT_EXPIRATION_MINUTES"] ?? "60") + }; + + services.AddSingleton(jwtSettings); + + services.AddHttpContextAccessor(); + + services.AddIdentityCore(options => + { + options.Password.RequiredLength = 8; + options.Password.RequireDigit = true; + options.Password.RequireLowercase = true; + options.Password.RequireUppercase = true; + options.Password.RequireNonAlphanumeric = true; + + options.User.RequireUniqueEmail = true; + }) + .AddRoles>() + .AddEntityFrameworkStores() + .AddSignInManager() + .AddDefaultTokenProviders(); + + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = jwtSettings.Issuer, + ValidAudience = jwtSettings.Audience, + IssuerSigningKey = new SymmetricSecurityKey( + Encoding.UTF8.GetBytes(jwtSettings.Key)) + }; + }); + + services.AddAuthorization(); + + services.AddTransient(); + services.AddTransient(); return services; } diff --git a/dotnet/AipsCore/Infrastructure/Persistence/Authentication/HttpUserContext.cs b/dotnet/AipsCore/Infrastructure/Persistence/Authentication/HttpUserContext.cs new file mode 100644 index 0000000..a55d734 --- /dev/null +++ b/dotnet/AipsCore/Infrastructure/Persistence/Authentication/HttpUserContext.cs @@ -0,0 +1,35 @@ +using System.Security.Claims; +using AipsCore.Application.Abstract.UserContext; +using AipsCore.Domain.Models.User.ValueObjects; +using Microsoft.AspNetCore.Http; + +namespace AipsCore.Infrastructure.Persistence.Authentication; + +public class HttpUserContext : IUserContext +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public HttpUserContext(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public UserId GetCurrentUserId() + { + var user = _httpContextAccessor.HttpContext?.User; + + if (user is null || !user.Identity!.IsAuthenticated) + { + throw new UnauthorizedAccessException("User is not authenticated"); + } + + var userIdClaim = user.FindFirst(ClaimTypes.NameIdentifier); + + if (userIdClaim is null) + { + throw new UnauthorizedAccessException("User id claim not found"); + } + + return new UserId(userIdClaim.Value); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Infrastructure/Persistence/Authentication/JwtSettings.cs b/dotnet/AipsCore/Infrastructure/Persistence/Authentication/JwtSettings.cs new file mode 100644 index 0000000..b095aef --- /dev/null +++ b/dotnet/AipsCore/Infrastructure/Persistence/Authentication/JwtSettings.cs @@ -0,0 +1,9 @@ +namespace AipsCore.Infrastructure.Persistence.Authentication; + +public sealed class JwtSettings +{ + public string Issuer { get; init; } = null!; + public string Audience { get; init; } = null!; + public string Key { get; init; } = null!; + public int ExpirationMinutes { get; init; } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Infrastructure/Persistence/Authentication/JwtTokenProvider.cs b/dotnet/AipsCore/Infrastructure/Persistence/Authentication/JwtTokenProvider.cs new file mode 100644 index 0000000..c80883b --- /dev/null +++ b/dotnet/AipsCore/Infrastructure/Persistence/Authentication/JwtTokenProvider.cs @@ -0,0 +1,44 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using AipsCore.Application.Abstract.UserContext; +using Microsoft.IdentityModel.Tokens; + +namespace AipsCore.Infrastructure.Persistence.Authentication; + +public class JwtTokenProvider : ITokenProvider +{ + private readonly JwtSettings _jwtSettings; + + public JwtTokenProvider(JwtSettings jwtSettings) + { + _jwtSettings = jwtSettings; + } + + public string Generate(Domain.Models.User.User user, IList roles) + { + var claims = new List + { + new Claim(ClaimTypes.NameIdentifier, user.Id.IdValue), + new Claim(ClaimTypes.Email, user.Email.EmailValue) + }; + + foreach (var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Key)); + + var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var token = new JwtSecurityToken( + issuer: _jwtSettings.Issuer, + audience: _jwtSettings.Audience, + claims: claims, + expires: DateTime.UtcNow.AddMinutes(_jwtSettings.ExpirationMinutes), + signingCredentials: credentials); + + return new JwtSecurityTokenHandler().WriteToken(token); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Infrastructure/Persistence/Db/AipsDbContext.cs b/dotnet/AipsCore/Infrastructure/Persistence/Db/AipsDbContext.cs index cbe5e5d..5aff8f0 100644 --- a/dotnet/AipsCore/Infrastructure/Persistence/Db/AipsDbContext.cs +++ b/dotnet/AipsCore/Infrastructure/Persistence/Db/AipsDbContext.cs @@ -1,10 +1,11 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace AipsCore.Infrastructure.Persistence.Db; -public class AipsDbContext : DbContext +public class AipsDbContext : IdentityDbContext, Guid> { - public DbSet Users { get; set; } public DbSet Whiteboards { get; set; } public DbSet Shapes { get; set; } public DbSet WhiteboardMemberships { get; set; } diff --git a/dotnet/AipsCore/Infrastructure/UserContext.cs b/dotnet/AipsCore/Infrastructure/UserContext.cs deleted file mode 100644 index 41bc36e..0000000 --- a/dotnet/AipsCore/Infrastructure/UserContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AipsCore.Application.Abstract.UserContext; -using AipsCore.Domain.Models.User.ValueObjects; - -namespace AipsCore.Infrastructure; - -public class UserContext : IUserContext -{ - public UserId GetCurrentUserId() - { - return new UserId(new Guid("52a1810c-802f-48b0-a74c-7b517807e392").ToString()); - } -} - -//Ovo je samo trenutno resenje \ No newline at end of file