diff --git a/BrightGlimmer.Auth/BrightGlimmer.Auth.csproj b/BrightGlimmer.Auth/BrightGlimmer.Auth.csproj
index 64e8e0b..d85fa85 100644
--- a/BrightGlimmer.Auth/BrightGlimmer.Auth.csproj
+++ b/BrightGlimmer.Auth/BrightGlimmer.Auth.csproj
@@ -18,6 +18,7 @@
+
diff --git a/BrightGlimmer.Auth/Controllers/UserController.cs b/BrightGlimmer.Auth/Controllers/UserController.cs
index ff4aef9..3ece5cf 100644
--- a/BrightGlimmer.Auth/Controllers/UserController.cs
+++ b/BrightGlimmer.Auth/Controllers/UserController.cs
@@ -5,35 +5,40 @@ using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
+using BrightGlimmer.Auth.Request;
+using BrightGlimmer.Domain.Auth;
+using BrightGlimmer.Service.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
namespace BrightGlimmer.Auth.Controllers
{
+ [Authorize]
[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
+ private readonly IUserService userService;
+
+ public UserController(IUserService userService)
+ {
+ this.userService = userService;
+ }
+
[AllowAnonymous]
[HttpGet]
public ActionResult Login(string username, string password)
{
- /* TODO: Move token creation to service */
- var tokenHandler = new JwtSecurityTokenHandler();
- var key = Encoding.UTF8.GetBytes(Startup.Configuration.GetSection("Keys")["JwtPrivateKey"]);
- var tokenDescriptor = new SecurityTokenDescriptor
- {
- Subject = new ClaimsIdentity(new Claim[]
- {
- new Claim(ClaimTypes.NameIdentifier, username)
- }),
- Expires = DateTime.UtcNow.AddDays(3),
- SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
- };
- var tokenSecurity = tokenHandler.CreateToken(tokenDescriptor);
- var token = tokenHandler.WriteToken(tokenSecurity);
+ var token = userService.Login(username, password);
+ return new JsonResult(token);
+ }
+ [AllowAnonymous]
+ [HttpPost]
+ public async Task Register(RegisterUserRequest request)
+ {
+ var token = await userService.RegisterAsync(request.User, request.Password);
return new JsonResult(token);
}
}
diff --git a/BrightGlimmer.Auth/Request/RegisterUserRequest.cs b/BrightGlimmer.Auth/Request/RegisterUserRequest.cs
new file mode 100644
index 0000000..5569566
--- /dev/null
+++ b/BrightGlimmer.Auth/Request/RegisterUserRequest.cs
@@ -0,0 +1,14 @@
+using BrightGlimmer.Domain.Auth;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace BrightGlimmer.Auth.Request
+{
+ public class RegisterUserRequest
+ {
+ public User User { get; set; }
+ public string Password { get; set; }
+ }
+}
diff --git a/BrightGlimmer.Auth/Startup.cs b/BrightGlimmer.Auth/Startup.cs
index fea6ab3..f8e7907 100644
--- a/BrightGlimmer.Auth/Startup.cs
+++ b/BrightGlimmer.Auth/Startup.cs
@@ -4,6 +4,8 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BrightGlimmer.Data;
+using BrightGlimmer.Service.Interfaces;
+using BrightGlimmer.Service.Services;
using JsonNet.PrivateSettersContractResolvers;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
@@ -26,7 +28,7 @@ namespace BrightGlimmer.Auth
Configuration = configuration;
}
- public static IConfiguration Configuration { get; private set; }
+ public IConfiguration Configuration { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
@@ -63,6 +65,9 @@ namespace BrightGlimmer.Auth
services.AddDbContext(options => options.UseLazyLoadingProxies()
.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
services.AddTransient();
+
+ /* Inject Services */
+ services.AddTransient();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
diff --git a/BrightGlimmer.Data/BrightGlimmer.Data.csproj b/BrightGlimmer.Data/BrightGlimmer.Data.csproj
index bbf2d69..d3c701c 100644
--- a/BrightGlimmer.Data/BrightGlimmer.Data.csproj
+++ b/BrightGlimmer.Data/BrightGlimmer.Data.csproj
@@ -13,6 +13,7 @@
+
diff --git a/BrightGlimmer.Data/Services/UserService.cs b/BrightGlimmer.Data/Services/UserService.cs
deleted file mode 100644
index 7cef23b..0000000
--- a/BrightGlimmer.Data/Services/UserService.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace BrightGlimmer.Data.Services
-{
- public class UserService
- {
- }
-}
diff --git a/BrightGlimmer.Data/bright_glimmer_auth.db b/BrightGlimmer.Data/bright_glimmer_auth.db
index 26f4594..0e700af 100644
Binary files a/BrightGlimmer.Data/bright_glimmer_auth.db and b/BrightGlimmer.Data/bright_glimmer_auth.db differ
diff --git a/BrightGlimmer.Domain/Auth/User.cs b/BrightGlimmer.Domain/Auth/User.cs
index cd400a6..574ffba 100644
--- a/BrightGlimmer.Domain/Auth/User.cs
+++ b/BrightGlimmer.Domain/Auth/User.cs
@@ -11,7 +11,10 @@ namespace BrightGlimmer.Domain.Auth
public string FirstName { get; private set; }
public string MiddleName { get; private set; }
public string LastName { get; private set; }
- public string PasswordHash { get; private set; }
+
+ /* TODO: Missing DOB */
+
+ public string PasswordHash { get; set; }
public bool EmailConfirmed { get; private set; }
public bool AccountLocked { get; private set; }
public int RetryAttempts { get; private set; }
diff --git a/BrightGlimmer.Service/BrightGlimmer.Service.csproj b/BrightGlimmer.Service/BrightGlimmer.Service.csproj
index d66e379..f2dff36 100644
--- a/BrightGlimmer.Service/BrightGlimmer.Service.csproj
+++ b/BrightGlimmer.Service/BrightGlimmer.Service.csproj
@@ -4,10 +4,6 @@
netstandard2.0
-
-
-
-
diff --git a/BrightGlimmer.Service/Interfaces/IUserService.cs b/BrightGlimmer.Service/Interfaces/IUserService.cs
new file mode 100644
index 0000000..675f93f
--- /dev/null
+++ b/BrightGlimmer.Service/Interfaces/IUserService.cs
@@ -0,0 +1,14 @@
+using BrightGlimmer.Domain.Auth;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BrightGlimmer.Service.Interfaces
+{
+ public interface IUserService
+ {
+ string Login(string username, string password);
+ Task RegisterAsync(User user, string password);
+ }
+}
diff --git a/BrightGlimmer.Service/Services/UserService.cs b/BrightGlimmer.Service/Services/UserService.cs
new file mode 100644
index 0000000..4aa0db1
--- /dev/null
+++ b/BrightGlimmer.Service/Services/UserService.cs
@@ -0,0 +1,55 @@
+using BrightGlimmer.Data;
+using BrightGlimmer.Domain.Auth;
+using BrightGlimmer.Service.Interfaces;
+using BrightGlimmer.Tools;
+using Microsoft.Extensions.Configuration;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace BrightGlimmer.Service.Services
+{
+ public class UserService : IUserService
+ {
+ private readonly IConfiguration configuration;
+ private readonly AuthContext context;
+
+ public UserService(IConfiguration configuration, AuthContext context)
+ {
+ this.configuration = configuration;
+ this.context = context;
+ }
+
+ public string Login(string username, string password)
+ {
+ var user = context.Users.SingleOrDefault(x => x.Username == username);
+ var hasher = new PasswordHasher();
+
+ if (user == null || hasher.Verify(password, user.PasswordHash))
+ {
+ return CreateAuthToken(username, user.Email);
+ }
+
+ return null;
+ }
+
+ public async Task RegisterAsync(User user, string password)
+ {
+ /* TODO: Perform validation on user */
+
+ var hasher = new PasswordHasher();
+ var hash = hasher.GetHash(password);
+ user.PasswordHash = hash;
+
+ context.Users.Add(user);
+ await context.SaveChangesAsync();
+
+ return CreateAuthToken(user.Username, user.Email);
+ }
+
+ private string CreateAuthToken(string username, string email)
+ {
+ var tokenCreator = new JwtTokenCreator(configuration.GetSection("Keys")["JwtPrivateKey"]);
+ return tokenCreator.CreateToken(username, email);
+ }
+ }
+}
diff --git a/BrightGlimmer.Tools/BrightGlimmer.Tools.csproj b/BrightGlimmer.Tools/BrightGlimmer.Tools.csproj
new file mode 100644
index 0000000..d6d489e
--- /dev/null
+++ b/BrightGlimmer.Tools/BrightGlimmer.Tools.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netstandard2.0
+
+
+
+
+ ..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.identitymodel.tokens\5.3.0\lib\netstandard2.0\Microsoft.IdentityModel.Tokens.dll
+
+
+ ..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\system.identitymodel.tokens.jwt\5.3.0\lib\netstandard2.0\System.IdentityModel.Tokens.Jwt.dll
+
+
+
+
diff --git a/BrightGlimmer.Tools/JwtTokenCreator.cs b/BrightGlimmer.Tools/JwtTokenCreator.cs
new file mode 100644
index 0000000..9e4c848
--- /dev/null
+++ b/BrightGlimmer.Tools/JwtTokenCreator.cs
@@ -0,0 +1,41 @@
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+
+namespace BrightGlimmer.Tools
+{
+ /* TODO: WIP */
+ public class JwtTokenCreator
+ {
+ private readonly string privateKey;
+
+ public JwtTokenCreator(string privateKey)
+ {
+ this.privateKey = privateKey;
+ }
+
+ public string CreateToken(string username, string email)
+ {
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var key = Encoding.UTF8.GetBytes(privateKey);
+
+ var tokenDescriptor = new SecurityTokenDescriptor
+ {
+ Subject = new ClaimsIdentity(new Claim[]
+ {
+ new Claim(ClaimTypes.NameIdentifier, username),
+ new Claim(ClaimTypes.Email, email)
+ }),
+ Expires = DateTime.UtcNow.AddDays(1),
+ SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
+ };
+ var tokenSecurity = tokenHandler.CreateToken(tokenDescriptor);
+ var token = tokenHandler.WriteToken(tokenSecurity);
+
+ return token;
+ }
+ }
+}
diff --git a/BrightGlimmer.Tools/PasswordHasher.cs b/BrightGlimmer.Tools/PasswordHasher.cs
new file mode 100644
index 0000000..21d2805
--- /dev/null
+++ b/BrightGlimmer.Tools/PasswordHasher.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Security.Cryptography;
+
+namespace BrightGlimmer.Tools
+{
+ public class PasswordHasher
+ {
+ private readonly int SALT_LENGTH = 16;
+ private readonly int PASS_LENGTH = 20;
+ private readonly int ITERATIONS = 10000;
+
+ public bool Verify(string password, string hash)
+ {
+ var storedHash = Convert.FromBase64String(hash);
+
+ var salt = new byte[SALT_LENGTH];
+ Array.Copy(storedHash, 0, salt, 0, SALT_LENGTH);
+
+ var givenHash = GetHash(password, salt);
+
+ return hash == givenHash;
+ }
+
+ public string GetHash(string password, byte[] salt = null)
+ {
+ if (salt == null)
+ {
+ salt = CreateSalt();
+ }
+ var hash = CreateHash(password, salt);
+
+ var completeHash = new byte[SALT_LENGTH + PASS_LENGTH];
+ Array.Copy(salt, 0, completeHash, 0, SALT_LENGTH);
+ Array.Copy(hash, 0, completeHash, SALT_LENGTH, PASS_LENGTH);
+
+ return Convert.ToBase64String(completeHash);
+ }
+
+ private byte[] CreateHash(string password, byte[] salt)
+ {
+ var pbkdf2 = new Rfc2898DeriveBytes(password, salt, ITERATIONS);
+ return pbkdf2.GetBytes(PASS_LENGTH);
+ }
+
+ private byte[] CreateSalt()
+ {
+ var salt = new byte[SALT_LENGTH];
+ new RNGCryptoServiceProvider().GetBytes(salt);
+
+ return salt;
+ }
+ }
+}
diff --git a/BrightGlimmer.sln b/BrightGlimmer.sln
index 3e9e9ef..383bcb7 100644
--- a/BrightGlimmer.sln
+++ b/BrightGlimmer.sln
@@ -13,7 +13,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrightGlimmer.Service", "Br
EndProject
Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "BrightGlimmer.Client", "BrightGlimmer.Client\BrightGlimmer.Client.njsproj", "{6971AB72-FBC2-4F3F-A59C-319E377A9A51}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrightGlimmer.Auth", "BrightGlimmer.Auth\BrightGlimmer.Auth.csproj", "{E3265892-2D6C-4D51-A7A4-DF845C17CF49}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrightGlimmer.Auth", "BrightGlimmer.Auth\BrightGlimmer.Auth.csproj", "{E3265892-2D6C-4D51-A7A4-DF845C17CF49}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrightGlimmer.Tools", "BrightGlimmer.Tools\BrightGlimmer.Tools.csproj", "{FE3B361C-BC0D-4D48-A861-9B10908F5E37}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -45,6 +47,10 @@ Global
{E3265892-2D6C-4D51-A7A4-DF845C17CF49}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3265892-2D6C-4D51-A7A4-DF845C17CF49}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3265892-2D6C-4D51-A7A4-DF845C17CF49}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FE3B361C-BC0D-4D48-A861-9B10908F5E37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FE3B361C-BC0D-4D48-A861-9B10908F5E37}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FE3B361C-BC0D-4D48-A861-9B10908F5E37}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FE3B361C-BC0D-4D48-A861-9B10908F5E37}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE