Skip to content

Commit

Permalink
Merge pull request #33 from TomasSirotek/feature/user-login
Browse files Browse the repository at this point in the history
Feature/user login
  • Loading branch information
TomasSirotek authored Nov 21, 2023
2 parents a8dce04 + eb9a68c commit 9dabc5b
Show file tree
Hide file tree
Showing 72 changed files with 1,527 additions and 824 deletions.
4 changes: 3 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@
<PackageVersion Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="11.8.0" />
<PackageVersion Include="MediatR" Version="12.1.1" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Moq" Version="4.20.69" />
<PackageVersion Include="NSwag.AspNetCore" Version="14.0.0-preview009" />
Expand Down
40 changes: 40 additions & 0 deletions src/Application/Auth/Commands/AuthorizeUser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Diagnostics;
using SkillSphere.Application.Common.Interfaces;
using SkillSphere.Application.Common.Models;

namespace SkillSphere.Application.Auth.Commands;

public record AuthUserCommand : IRequest<AuthResult>
{
public string? Email { get; init; }
public string? Password { get; init; }
}

public class AuthenticateCommandHandler : IRequestHandler<AuthUserCommand,AuthResult>
{
private readonly IApplicationDbContext _context;
private readonly IIdentityService _userService;

public AuthenticateCommandHandler(IApplicationDbContext context,IIdentityService userService)
{
_context = context;
_userService = userService;
}
public async Task<AuthResult> Handle(AuthUserCommand request, CancellationToken cancellationToken)
{
// Assuming request.Email and request.Password are provided

Guard.Against.NullOrEmpty(request.Email, nameof(request.Email));
Guard.Against.NullOrEmpty(request.Password, nameof(request.Password));

return await _userService.AuthenticateAsync(request.Email, request.Password);
//return await _userService.AuthenticateAsync(request.Email, request.Password);


}


}



35 changes: 35 additions & 0 deletions src/Application/Auth/Commands/RegisterUser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Diagnostics;
using SkillSphere.Application.Common.Interfaces;
using SkillSphere.Application.Common.Models;

namespace SkillSphere.Application.Auth.Commands;

public record RegisterUserCommand : IRequest<Result>
{
public string? Email { get; init; }
public string? Password { get; init; }
}

public class RegisterUserCommandHandler : IRequestHandler<RegisterUserCommand,Result>
{
private readonly IApplicationDbContext _context;
private readonly IIdentityService _userService;

public RegisterUserCommandHandler(IApplicationDbContext context,IIdentityService userService)
{
_context = context;
_userService = userService;
}
public async Task<Result> Handle(RegisterUserCommand request, CancellationToken cancellationToken)
{
Guard.Against.NullOrEmpty(request.Email, nameof(request.Email));
Guard.Against.NullOrEmpty(request.Password, nameof(request.Password));

var result = await _userService.CreateUserAsync(request.Email, request.Password);
return result;
}

}



4 changes: 3 additions & 1 deletion src/Application/Common/Interfaces/IIdentityService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ public interface IIdentityService

Task<bool> AuthorizeAsync(string userId, string policyName);

Task<(Result Result, string UserId)> CreateUserAsync(string userName, string password);
Task<Result> CreateUserAsync(string userName, string password);

Task<Result> DeleteUserAsync(string userId);

Task<AuthResult> AuthenticateAsync(string requestEmail, string requestPassword);
}
11 changes: 11 additions & 0 deletions src/Application/Common/Models/AuthResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace SkillSphere.Application.Common.Models;

public class AuthResult
{
public string? Token { get; init; }

public int ExpiresIn { get; init; }
public string? UserId { get; init; }
public string? Email { get; init; }

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
namespace SkillSphere.Application.WeatherForecasts.Queries.GetWeatherForecasts;
using SkillSphere.Application.Common.Security;
using SkillSphere.Domain.Constants;

namespace SkillSphere.Application.WeatherForecasts.Queries.GetWeatherForecasts;

[Authorize(Roles = Roles.Administrator)]
public record GetWeatherForecastsQuery : IRequest<IEnumerable<WeatherForecast>>;


public class GetWeatherForecastsQueryHandler : IRequestHandler<GetWeatherForecastsQuery, IEnumerable<WeatherForecast>>
{
private static readonly string[] Summaries = new[]
Expand Down
15 changes: 15 additions & 0 deletions src/Infrastructure/Authentication/JwtTokenConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;

namespace SkillSphere.Infrastructure.Authentication
{

public class JwtTokenConfig
{
public string Secret { get; init; } = "";
}
}



15 changes: 15 additions & 0 deletions src/Infrastructure/Authentication/Model/TokenModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace SkillSphere.Infrastructure.Authentication.Model;

public record TokenModel
{
public string TokenType { get; }
public string AccessToken { get; }
public DateTime ExpiresAt { get; }

public TokenModel(string tokenType, string accessToken, DateTime expiresAt)
=> (TokenType, AccessToken, ExpiresAt) = (tokenType, accessToken, expiresAt);

public int GetRemainingLifetimeSeconds()
=> Math.Max(0, (int)(ExpiresAt - DateTime.Now).TotalSeconds);
}

60 changes: 60 additions & 0 deletions src/Infrastructure/Authentication/Services/JwtGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using SkillSphere.Infrastructure.Authentication.Model;
using SkillSphere.Infrastructure.Identity;

namespace SkillSphere.Infrastructure.Authentication.Services;


public interface IJwtTokenGen
{
ValueTask<string> CreateToken(ApplicationUser user, CancellationToken cancellationToken = default);
}

public class JwtGenerator : IJwtTokenGen
{
private readonly JwtTokenConfig _authSettings;

public JwtGenerator(JwtTokenConfig authSettings)
{
_authSettings = authSettings;
}

public ValueTask<string> CreateToken(ApplicationUser user, CancellationToken cancellationToken = default)
{
var tokenHandler = new JwtSecurityTokenHandler();

Debug.Assert(_authSettings.Secret != null, "_authSettings.Secret != null");
var key = Encoding.UTF8.GetBytes(_authSettings.Secret);

Debug.Assert(user.Email != null, "user.Email != null");
Debug.Assert(user.UserName != null, "user.UserName != null");

var claimList = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
new Claim(JwtRegisteredClaimNames.Name, user.UserName)
};

// claimList.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));

var tokenDescriptor = new SecurityTokenDescriptor
{
Audience = null,
Issuer = null,
Subject = new ClaimsIdentity(claimList),
Expires = DateTime.UtcNow.AddSeconds(20),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};

var token = tokenHandler.CreateToken(tokenDescriptor);
var jwtToken = tokenHandler.WriteToken(token);

return new ValueTask<string>(jwtToken);
}
}

1 change: 1 addition & 0 deletions src/Infrastructure/Data/ApplicationDbContextInitialiser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using skillSphere.Infrastructure.Data;
using Roles = SkillSphere.Domain.Constants.Roles;

namespace SkillSphere.Infrastructure.Data;

Expand Down
29 changes: 29 additions & 0 deletions src/Infrastructure/Data/InitialData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using SkillSphere.Infrastructure.Identity;

namespace SkillSphere.Infrastructure.Data;

public class InitialData
{
public static List<ApplicationUser> Users { get; }

static InitialData()
{
Users = new List<ApplicationUser>
{
new ApplicationUser
{
Id = Guid.NewGuid().ToString(),
UserName = "admin",
Email = "admin@gmail.com",
SecurityStamp = Guid.NewGuid().ToString()
},
new ApplicationUser
{
Id = Guid.NewGuid().ToString(),
UserName = "developer",
Email = "developer@gmail.com",
SecurityStamp = Guid.NewGuid().ToString()
}
};
}
}
47 changes: 44 additions & 3 deletions src/Infrastructure/DependencyInjection.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
using Microsoft.AspNetCore.Identity;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using SkillSphere.Application.Common.Interfaces;
using SkillSphere.Domain.Constants;
using SkillSphere.Infrastructure.Authentication;
using SkillSphere.Infrastructure.Authentication.Services;
using skillSphere.Infrastructure.Data;
using SkillSphere.Infrastructure.Data;
using SkillSphere.Infrastructure.Data.Interceptors;
using SkillSphere.Infrastructure.Identity;
using testSphere.Infrastructure.Data.Interceptors;
using Roles = SkillSphere.Domain.Constants.Roles;

namespace SkillSphere.Infrastructure;

Expand All @@ -34,17 +40,52 @@ public static IServiceCollection AddInfrastructureServices(this IServiceCollecti
services.AddScoped<IApplicationDbContext>(provider => provider.GetRequiredService<ApplicationDbContext>());

services.AddScoped<ApplicationDbContextInitialiser>();


// Setting up Identity
services
.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();


// Set up Time
services.AddSingleton(TimeProvider.System);

// Register Identity Service and its Interfaces
services.AddTransient<IIdentityService, IdentityService>();

services.AddTransient<IJwtTokenGen, JwtGenerator>();

// Add Authentication schema for JWT

var jwtOptions = new JwtTokenConfig();
services.AddSingleton(jwtOptions);

// Setting up API JWT Authentication
services.AddAuthorization().AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.Secret)),
ClockSkew = TimeSpan.Zero
};
});


// => Adding policy for role Administrator
services.AddAuthorization(options =>
options.AddPolicy(Policies.CanPurge, policy => policy.RequireRole(Roles.Administrator)));


return services;
}
Expand Down
6 changes: 6 additions & 0 deletions src/Infrastructure/Identity/IdentityResultExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@ public static Result ToApplicationResult(this IdentityResult result)
? Result.Success()
: Result.Failure(result.Errors.Select(e => e.Description));
}
public static Result ToResult(this IdentityResult result)
{
return result.Succeeded
? Result.Success()
: Result.Failure(result.Errors.Select(e => e.Description));
}
}
Loading

0 comments on commit 9dabc5b

Please sign in to comment.