Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@
<DocumentationComments>true</DocumentationComments>
<DocumentationFile>none.ignore</DocumentationFile>
<LangVersion>default</LangVersion>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" />
</ItemGroup>

<PropertyGroup>
<WarningsAsErrors>CS8618</WarningsAsErrors>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>


<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.3" />
Expand Down
21 changes: 19 additions & 2 deletions PasswordKeeper.BusinessLogic/Users.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ public async Task<bool> UsersExist(bool? admin = null)
{
return await users.UsersExist(admin);
}

/// <inheritdoc cref="PasswordKeeper.DataAccess.Users.GetAllUsers"/>
public async Task<IEnumerable<UserDto>> GetAllUsers()
{
return await users.GetAllUsers();
}

/// <inheritdoc cref="PasswordKeeper.DataAccess.Users.DeleteUser"/>
public async Task DeleteUser(long id)
{
await users.DeleteUser(id);
}

private const int IterationCount = 1000000;

Expand Down Expand Up @@ -117,6 +129,7 @@ public async Task<LoginResult> Login(string username, string password, byte[] jw
PasswordHash = HashPassword(password, ref salt),
PasswordSalt = Convert.ToBase64String(salt!),
IsAdmin = true,
UserFullName = "Administrator",
};

userDto = await users.UpsertUser(userDto);
Expand All @@ -126,17 +139,21 @@ public async Task<LoginResult> Login(string username, string password, byte[] jw
return new LoginResult(false, Passwords.CreateMessageString(LoginRejectReason.FailedToCreateAdminUser), false, LoginRejectReason.FailedToCreateAdminUser);
}

var token = Passwords.GenerateJwtToken(username, userDto.Id, jwtKey, pseudoDomain);
var token = Passwords.GenerateJwtToken(username, userDto.Id, jwtKey, pseudoDomain, userDto.IsAdmin);
return new LoginResult(true, token, false, LoginRejectReason.None);
}
else
{
userDto = await users.GetUserByName(username);
}

// An existing user, verify the password
if (userDto is not null)
{
if (Users.VerifyPassword(password, userDto.PasswordHash,
Convert.FromBase64String(userDto.PasswordSalt)))
{
var token = Passwords.GenerateJwtToken(username, userDto.Id, jwtKey, pseudoDomain);
var token = Passwords.GenerateJwtToken(username, userDto.Id, jwtKey, pseudoDomain, userDto.IsAdmin);
return new LoginResult(true, token, false, LoginRejectReason.None);
}
}
Expand Down
18 changes: 18 additions & 0 deletions PasswordKeeper.Classes/DatabaseUtilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace PasswordKeeper.Classes;

/// <summary>
/// Database utilities.
/// </summary>
public static class DatabaseUtilities
{
/// <summary>
/// Gets the connection string for a SQLite database.
/// </summary>
/// <param name="databaseName">The name of the database.</param>
/// <returns>The connection string.</returns>
// ReSharper disable once InconsistentNaming (this is how SQLite is written)
public static string GetSQLiteConnectionString(string databaseName)
{
return $"Data Source=./{databaseName}.db;Pooling=False;";
}
}
6 changes: 0 additions & 6 deletions PasswordKeeper.Classes/PasswordKeeper.Classes.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.7.0" />
</ItemGroup>
Expand Down
8 changes: 7 additions & 1 deletion PasswordKeeper.Classes/Passwords.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ public static class Passwords
/// <param name="userId">The user ID to generate the JWT token for.</param>
/// <param name="jwtKey">The JWT key to use for signing the token.</param>
/// <param name="pseudoDomain">The pseudo domain to use for the token issuer and audience.</param>
/// <param name="isAdmin">Whether the user is an admin or not.</param>
/// <returns>The JWT token.</returns>
public static string GenerateJwtToken(string username, long userId, byte[] jwtKey, string pseudoDomain)
public static string GenerateJwtToken(string username, long userId, byte[] jwtKey, string pseudoDomain, bool isAdmin)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.NameId, userId.ToString()),
new Claim(ClaimTypes.Role, isAdmin ? "Admin" : "User"),
};

var key = new SymmetricSecurityKey(jwtKey);
Expand All @@ -35,7 +37,11 @@ public static string GenerateJwtToken(string username, long userId, byte[] jwtKe
issuer: pseudoDomain,
audience: pseudoDomain,
claims: claims,
#if DEBUG
expires: DateTime.Now.AddYears(1),
#else
expires: DateTime.Now.AddMinutes(30),
#endif
signingCredentials: credentials);

return new JwtSecurityTokenHandler().WriteToken(token);
Expand Down
8 changes: 1 addition & 7 deletions PasswordKeeper.DAO/PasswordKeeper.DAO.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>


<ItemGroup>
<ProjectReference Include="..\PasswordKeeper.Interfaces\PasswordKeeper.Interfaces.csproj" />
</ItemGroup>
Expand Down
4 changes: 4 additions & 0 deletions PasswordKeeper.DAO/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ public class User : IUser
[MaxLength(255)]
public string Username { get; set; } = string.Empty;

/// <inheritdoc cref="IUser.UserFullName" />
[MaxLength(512)]
public string UserFullName { get; set; } = string.Empty;

/// <inheritdoc cref="IUser.PasswordHash" />
[MaxLength(1000)]
public string PasswordHash { get; set; } = string.Empty;
Expand Down
6 changes: 0 additions & 6 deletions PasswordKeeper.DTO/PasswordKeeper.DTO.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\PasswordKeeper.Interfaces\PasswordKeeper.Interfaces.csproj" />
</ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions PasswordKeeper.DTO/UserDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public class UserDto : IUser
/// <inheritdoc cref="IUser.Username" />
public string Username { get; set; } = string.Empty;

/// <inheritdoc cref="IUser.UserFullName" />
public string UserFullName { get; set; } = string.Empty;

/// <inheritdoc cref="IUser.PasswordHash" />
[JsonIgnore]
public string PasswordHash { get; set; } = string.Empty;
Expand Down
6 changes: 0 additions & 6 deletions PasswordKeeper.DataAccess/PasswordKeeper.DataAccess.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="9.0.2" />
Expand Down
31 changes: 31 additions & 0 deletions PasswordKeeper.DataAccess/Users.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,35 @@ public async Task<bool> UsersExist(bool? admin = null)

return mapper.Map<UserDto>(user);
}

/// <summary>
/// Deletes the user with the given ID.
/// </summary>
/// <param name="id">The ID of the user to delete.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task DeleteUser(long id)
{
await using var context = await dbContextFactory.CreateDbContextAsync();

var user = await context.Users.FirstOrDefaultAsync(user => user.Id == id);

if (user != null)
{
context.Users.Remove(user);
await context.SaveChangesAsync();
}

// TODO:Delete user data
}

/// <summary>
/// Gets all users.
/// </summary>
/// <returns>A collection of all users.</returns>
public async Task<IEnumerable<UserDto>> GetAllUsers()
{
await using var context = await dbContextFactory.CreateDbContextAsync();

return mapper.Map<IEnumerable<UserDto>>(await context.Users.ToListAsync());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public override void Up()
this.Create.Table(nameof(User)).InSchemaIf(Program.DatabaseName, !isSqlite)
.WithColumn(nameof(User.Id)).AsInt64().NotNullable().PrimaryKey().Identity()
.WithColumn(nameof(User.Username)).AsString(255).NotNullable().Unique()
.WithColumn(nameof(User.UserFullName)).AsString(512).NotNullable()
.WithColumn(nameof(User.PasswordHash)).AsString(1000).NotNullable()
.WithColumn(nameof(User.PasswordSalt)).AsString(1000).NotNullable()
.WithColumn(nameof(User.IsAdmin)).AsBoolean().NotNullable().WithDefaultValue(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
Expand All @@ -18,6 +15,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\PasswordKeeper.Classes\PasswordKeeper.Classes.csproj" />
<ProjectReference Include="..\PasswordKeeper.DAO\PasswordKeeper.DAO.csproj" />
</ItemGroup>

Expand Down
4 changes: 3 additions & 1 deletion PasswordKeeper.DatabaseMigrations/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using FluentMigrator.Runner;
using Microsoft.Extensions.DependencyInjection;
using McMaster.Extensions.CommandLineUtils;
using PasswordKeeper.Classes;

// ReSharper disable MemberCanBePrivate.Global

namespace PasswordKeeper.DatabaseMigrations;
Expand Down Expand Up @@ -51,7 +53,7 @@ public void OnExecute()
if (TestDbName != null)
{
// NOTE: Pooling=False is required for SQLite for the database file to be released after migrations!
connectionString = $"Data Source=./{TestDbName}.db;Pooling=False;";
connectionString = DatabaseUtilities.GetSQLiteConnectionString(TestDbName);
IsTestDb = true;
}
else
Expand Down
5 changes: 5 additions & 0 deletions PasswordKeeper.Interfaces/IUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ public interface IUser : IHasId
/// </summary>
string Username { get; set; }

/// <summary>
/// The full name of the user.
/// </summary>
string UserFullName { get; set; }

/// <summary>
/// The password of the user.
/// </summary>
Expand Down
6 changes: 0 additions & 6 deletions PasswordKeeper.Interfaces/PasswordKeeper.Interfaces.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
22 changes: 22 additions & 0 deletions PasswordKeeper.Server/Controllers/AliveController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace PasswordKeeper.Server.Controllers;

/// <summary>
/// The alive controller.
/// </summary>
public class AliveController : ControllerBase
{
/// <summary>
/// Gets the current date and time.S
/// </summary>
/// <returns>The current date and time.</returns>
[Route("")]
[AllowAnonymous]
[HttpGet]
public DateTimeOffset Get()
{
return DateTimeOffset.UtcNow;
}
}
Loading