mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 02:39:03 +00:00
Format infrastructure dir
This commit is contained in:
@@ -7,7 +7,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.1" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.2.1" />
|
||||
<PackageReference
|
||||
Include="Microsoft.IdentityModel.JsonWebTokens"
|
||||
Version="8.2.1"
|
||||
/>
|
||||
<PackageReference
|
||||
Include="System.IdentityModel.Tokens.Jwt"
|
||||
Version="8.2.1"
|
||||
/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -5,21 +5,27 @@ using Microsoft.IdentityModel.Tokens;
|
||||
using JwtRegisteredClaimNames = System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames;
|
||||
|
||||
namespace Service.Core.Jwt;
|
||||
|
||||
public class JwtService : IJwtService
|
||||
{
|
||||
private readonly string? _secret = Environment.GetEnvironmentVariable("JWT_SECRET");
|
||||
private readonly string? _secret = Environment.GetEnvironmentVariable(
|
||||
"JWT_SECRET"
|
||||
);
|
||||
|
||||
public string GenerateJwt(Guid userId, string username, DateTime expiry)
|
||||
{
|
||||
var handler = new JsonWebTokenHandler();
|
||||
|
||||
var key = Encoding.UTF8.GetBytes(_secret ?? throw new InvalidOperationException("secret not set"));
|
||||
var key = Encoding.UTF8.GetBytes(
|
||||
_secret ?? throw new InvalidOperationException("secret not set")
|
||||
);
|
||||
|
||||
// Base claims (always present)
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
||||
new(JwtRegisteredClaimNames.UniqueName, username),
|
||||
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
||||
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
||||
};
|
||||
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
@@ -28,7 +34,8 @@ public class JwtService : IJwtService
|
||||
Expires = expiry,
|
||||
SigningCredentials = new SigningCredentials(
|
||||
new SymmetricSecurityKey(key),
|
||||
SecurityAlgorithms.HmacSha256)
|
||||
SecurityAlgorithms.HmacSha256
|
||||
),
|
||||
};
|
||||
|
||||
return handler.CreateToken(tokenDescriptor);
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
|
||||
<PackageReference
|
||||
Include="Konscious.Security.Cryptography.Argon2"
|
||||
Version="1.3.1"
|
||||
/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -19,7 +19,7 @@ public class PasswordService : IPasswordService
|
||||
Salt = salt,
|
||||
DegreeOfParallelism = Math.Max(Environment.ProcessorCount, 1),
|
||||
MemorySize = ArgonMemoryKb,
|
||||
Iterations = ArgonIterations
|
||||
Iterations = ArgonIterations,
|
||||
};
|
||||
|
||||
var hash = argon2.GetBytes(HashSize);
|
||||
@@ -30,8 +30,12 @@ public class PasswordService : IPasswordService
|
||||
{
|
||||
try
|
||||
{
|
||||
var parts = stored.Split(':', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2) return false;
|
||||
var parts = stored.Split(
|
||||
':',
|
||||
StringSplitOptions.RemoveEmptyEntries
|
||||
);
|
||||
if (parts.Length != 2)
|
||||
return false;
|
||||
|
||||
var salt = Convert.FromBase64String(parts[0]);
|
||||
var expected = Convert.FromBase64String(parts[1]);
|
||||
@@ -41,7 +45,7 @@ public class PasswordService : IPasswordService
|
||||
Salt = salt,
|
||||
DegreeOfParallelism = Math.Max(Environment.ProcessorCount, 1),
|
||||
MemorySize = ArgonMemoryKb,
|
||||
Iterations = ArgonIterations
|
||||
Iterations = ArgonIterations,
|
||||
};
|
||||
|
||||
var actual = argon2.GetBytes(expected.Length);
|
||||
|
||||
@@ -5,13 +5,12 @@ using Repository.Core.Sql;
|
||||
|
||||
namespace Repository.Core.Repositories.Auth
|
||||
{
|
||||
public class AuthRepository : Repository<Domain.Core.Entities.UserAccount>, IAuthRepository
|
||||
public class AuthRepository
|
||||
: Repository<Domain.Core.Entities.UserAccount>,
|
||||
IAuthRepository
|
||||
{
|
||||
public AuthRepository(ISqlConnectionFactory connectionFactory)
|
||||
: base(connectionFactory)
|
||||
{
|
||||
}
|
||||
|
||||
: base(connectionFactory) { }
|
||||
|
||||
public async Task<Domain.Core.Entities.UserAccount> RegisterUserAsync(
|
||||
string username,
|
||||
@@ -19,7 +18,8 @@ namespace Repository.Core.Repositories.Auth
|
||||
string lastName,
|
||||
string email,
|
||||
DateTime dateOfBirth,
|
||||
string passwordHash)
|
||||
string passwordHash
|
||||
)
|
||||
{
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = connection.CreateCommand();
|
||||
@@ -45,12 +45,13 @@ namespace Repository.Core.Repositories.Auth
|
||||
LastName = lastName,
|
||||
Email = email,
|
||||
DateOfBirth = dateOfBirth,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public async Task<Domain.Core.Entities.UserAccount?> GetUserByEmailAsync(string email)
|
||||
public async Task<Domain.Core.Entities.UserAccount?> GetUserByEmailAsync(
|
||||
string email
|
||||
)
|
||||
{
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = connection.CreateCommand();
|
||||
@@ -63,8 +64,9 @@ namespace Repository.Core.Repositories.Auth
|
||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||
}
|
||||
|
||||
|
||||
public async Task<Domain.Core.Entities.UserAccount?> GetUserByUsernameAsync(string username)
|
||||
public async Task<Domain.Core.Entities.UserAccount?> GetUserByUsernameAsync(
|
||||
string username
|
||||
)
|
||||
{
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = connection.CreateCommand();
|
||||
@@ -77,7 +79,9 @@ namespace Repository.Core.Repositories.Auth
|
||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||
}
|
||||
|
||||
public async Task<UserCredential?> GetActiveCredentialByUserAccountIdAsync(Guid userAccountId)
|
||||
public async Task<UserCredential?> GetActiveCredentialByUserAccountIdAsync(
|
||||
Guid userAccountId
|
||||
)
|
||||
{
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = connection.CreateCommand();
|
||||
@@ -87,10 +91,15 @@ namespace Repository.Core.Repositories.Auth
|
||||
AddParameter(command, "@UserAccountId", userAccountId);
|
||||
|
||||
await using var reader = await command.ExecuteReaderAsync();
|
||||
return await reader.ReadAsync() ? MapToCredentialEntity(reader) : null;
|
||||
return await reader.ReadAsync()
|
||||
? MapToCredentialEntity(reader)
|
||||
: null;
|
||||
}
|
||||
|
||||
public async Task RotateCredentialAsync(Guid userAccountId, string newPasswordHash)
|
||||
public async Task RotateCredentialAsync(
|
||||
Guid userAccountId,
|
||||
string newPasswordHash
|
||||
)
|
||||
{
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = connection.CreateCommand();
|
||||
@@ -106,11 +115,15 @@ namespace Repository.Core.Repositories.Auth
|
||||
/// <summary>
|
||||
/// Maps a data reader row to a UserAccount entity.
|
||||
/// </summary>
|
||||
protected override Domain.Core.Entities.UserAccount MapToEntity(DbDataReader reader)
|
||||
protected override Domain.Core.Entities.UserAccount MapToEntity(
|
||||
DbDataReader reader
|
||||
)
|
||||
{
|
||||
return new Domain.Core.Entities.UserAccount
|
||||
{
|
||||
UserAccountId = reader.GetGuid(reader.GetOrdinal("UserAccountId")),
|
||||
UserAccountId = reader.GetGuid(
|
||||
reader.GetOrdinal("UserAccountId")
|
||||
),
|
||||
Username = reader.GetString(reader.GetOrdinal("Username")),
|
||||
FirstName = reader.GetString(reader.GetOrdinal("FirstName")),
|
||||
LastName = reader.GetString(reader.GetOrdinal("LastName")),
|
||||
@@ -119,10 +132,12 @@ namespace Repository.Core.Repositories.Auth
|
||||
UpdatedAt = reader.IsDBNull(reader.GetOrdinal("UpdatedAt"))
|
||||
? null
|
||||
: reader.GetDateTime(reader.GetOrdinal("UpdatedAt")),
|
||||
DateOfBirth = reader.GetDateTime(reader.GetOrdinal("DateOfBirth")),
|
||||
DateOfBirth = reader.GetDateTime(
|
||||
reader.GetOrdinal("DateOfBirth")
|
||||
),
|
||||
Timer = reader.IsDBNull(reader.GetOrdinal("Timer"))
|
||||
? null
|
||||
: (byte[])reader["Timer"]
|
||||
: (byte[])reader["Timer"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -133,22 +148,34 @@ namespace Repository.Core.Repositories.Auth
|
||||
{
|
||||
var entity = new UserCredential
|
||||
{
|
||||
UserCredentialId = reader.GetGuid(reader.GetOrdinal("UserCredentialId")),
|
||||
UserAccountId = reader.GetGuid(reader.GetOrdinal("UserAccountId")),
|
||||
UserCredentialId = reader.GetGuid(
|
||||
reader.GetOrdinal("UserCredentialId")
|
||||
),
|
||||
UserAccountId = reader.GetGuid(
|
||||
reader.GetOrdinal("UserAccountId")
|
||||
),
|
||||
Hash = reader.GetString(reader.GetOrdinal("Hash")),
|
||||
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt"))
|
||||
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt")),
|
||||
};
|
||||
|
||||
// Optional columns
|
||||
var hasTimer = reader.GetSchemaTable()?.Rows
|
||||
.Cast<System.Data.DataRow>()
|
||||
.Any(r => string.Equals(r["ColumnName"]?.ToString(), "Timer",
|
||||
StringComparison.OrdinalIgnoreCase)) ??
|
||||
false;
|
||||
var hasTimer =
|
||||
reader
|
||||
.GetSchemaTable()
|
||||
?.Rows.Cast<System.Data.DataRow>()
|
||||
.Any(r =>
|
||||
string.Equals(
|
||||
r["ColumnName"]?.ToString(),
|
||||
"Timer",
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
)
|
||||
) ?? false;
|
||||
|
||||
if (hasTimer)
|
||||
{
|
||||
entity.Timer = reader.IsDBNull(reader.GetOrdinal("Timer")) ? null : (byte[])reader["Timer"];
|
||||
entity.Timer = reader.IsDBNull(reader.GetOrdinal("Timer"))
|
||||
? null
|
||||
: (byte[])reader["Timer"];
|
||||
}
|
||||
|
||||
return entity;
|
||||
@@ -157,7 +184,11 @@ namespace Repository.Core.Repositories.Auth
|
||||
/// <summary>
|
||||
/// Helper method to add a parameter to a database command.
|
||||
/// </summary>
|
||||
private static void AddParameter(DbCommand command, string name, object? value)
|
||||
private static void AddParameter(
|
||||
DbCommand command,
|
||||
string name,
|
||||
object? value
|
||||
)
|
||||
{
|
||||
var p = command.CreateParameter();
|
||||
p.ParameterName = name;
|
||||
|
||||
@@ -24,7 +24,8 @@ namespace Repository.Core.Repositories.Auth
|
||||
string lastName,
|
||||
string email,
|
||||
DateTime dateOfBirth,
|
||||
string passwordHash);
|
||||
string passwordHash
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a user account by email address (typically used for login).
|
||||
@@ -32,7 +33,9 @@ namespace Repository.Core.Repositories.Auth
|
||||
/// </summary>
|
||||
/// <param name="email">Email address to search for</param>
|
||||
/// <returns>UserAccount if found, null otherwise</returns>
|
||||
Task<Domain.Core.Entities.UserAccount?> GetUserByEmailAsync(string email);
|
||||
Task<Domain.Core.Entities.UserAccount?> GetUserByEmailAsync(
|
||||
string email
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a user account by username (typically used for login).
|
||||
@@ -40,7 +43,9 @@ namespace Repository.Core.Repositories.Auth
|
||||
/// </summary>
|
||||
/// <param name="username">Username to search for</param>
|
||||
/// <returns>UserAccount if found, null otherwise</returns>
|
||||
Task<Domain.Core.Entities.UserAccount?> GetUserByUsernameAsync(string username);
|
||||
Task<Domain.Core.Entities.UserAccount?> GetUserByUsernameAsync(
|
||||
string username
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the active (non-revoked) credential for a user account.
|
||||
@@ -48,7 +53,9 @@ namespace Repository.Core.Repositories.Auth
|
||||
/// </summary>
|
||||
/// <param name="userAccountId">ID of the user account</param>
|
||||
/// <returns>Active UserCredential if found, null otherwise</returns>
|
||||
Task<UserCredential?> GetActiveCredentialByUserAccountIdAsync(Guid userAccountId);
|
||||
Task<UserCredential?> GetActiveCredentialByUserAccountIdAsync(
|
||||
Guid userAccountId
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Rotates a user's credential by invalidating all existing credentials and creating a new one.
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
using Domain.Core.Entities;
|
||||
|
||||
|
||||
namespace Repository.Core.Repositories.UserAccount
|
||||
{
|
||||
public interface IUserAccountRepository
|
||||
{
|
||||
Task<Domain.Core.Entities.UserAccount?> GetByIdAsync(Guid id);
|
||||
Task<IEnumerable<Domain.Core.Entities.UserAccount>> GetAllAsync(int? limit, int? offset);
|
||||
Task<IEnumerable<Domain.Core.Entities.UserAccount>> GetAllAsync(
|
||||
int? limit,
|
||||
int? offset
|
||||
);
|
||||
Task UpdateAsync(Domain.Core.Entities.UserAccount userAccount);
|
||||
Task DeleteAsync(Guid id);
|
||||
Task<Domain.Core.Entities.UserAccount?> GetByUsernameAsync(string username);
|
||||
Task<Domain.Core.Entities.UserAccount?> GetByUsernameAsync(
|
||||
string username
|
||||
);
|
||||
Task<Domain.Core.Entities.UserAccount?> GetByEmailAsync(string email);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@ using Repository.Core.Sql;
|
||||
namespace Repository.Core.Repositories.UserAccount
|
||||
{
|
||||
public class UserAccountRepository(ISqlConnectionFactory connectionFactory)
|
||||
: Repository<Domain.Core.Entities.UserAccount>(connectionFactory), IUserAccountRepository
|
||||
: Repository<Domain.Core.Entities.UserAccount>(connectionFactory),
|
||||
IUserAccountRepository
|
||||
{
|
||||
public async Task<Domain.Core.Entities.UserAccount?> GetByIdAsync(Guid id)
|
||||
public async Task<Domain.Core.Entities.UserAccount?> GetByIdAsync(
|
||||
Guid id
|
||||
)
|
||||
{
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = connection.CreateCommand();
|
||||
@@ -21,7 +24,9 @@ namespace Repository.Core.Repositories.UserAccount
|
||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Domain.Core.Entities.UserAccount>> GetAllAsync(int? limit, int? offset)
|
||||
public async Task<
|
||||
IEnumerable<Domain.Core.Entities.UserAccount>
|
||||
> GetAllAsync(int? limit, int? offset)
|
||||
{
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = connection.CreateCommand();
|
||||
@@ -45,7 +50,9 @@ namespace Repository.Core.Repositories.UserAccount
|
||||
return users;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Domain.Core.Entities.UserAccount userAccount)
|
||||
public async Task UpdateAsync(
|
||||
Domain.Core.Entities.UserAccount userAccount
|
||||
)
|
||||
{
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = connection.CreateCommand();
|
||||
@@ -73,7 +80,9 @@ namespace Repository.Core.Repositories.UserAccount
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
public async Task<Domain.Core.Entities.UserAccount?> GetByUsernameAsync(string username)
|
||||
public async Task<Domain.Core.Entities.UserAccount?> GetByUsernameAsync(
|
||||
string username
|
||||
)
|
||||
{
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = connection.CreateCommand();
|
||||
@@ -86,7 +95,9 @@ namespace Repository.Core.Repositories.UserAccount
|
||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||
}
|
||||
|
||||
public async Task<Domain.Core.Entities.UserAccount?> GetByEmailAsync(string email)
|
||||
public async Task<Domain.Core.Entities.UserAccount?> GetByEmailAsync(
|
||||
string email
|
||||
)
|
||||
{
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = connection.CreateCommand();
|
||||
@@ -99,11 +110,15 @@ namespace Repository.Core.Repositories.UserAccount
|
||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||
}
|
||||
|
||||
protected override Domain.Core.Entities.UserAccount MapToEntity(DbDataReader reader)
|
||||
protected override Domain.Core.Entities.UserAccount MapToEntity(
|
||||
DbDataReader reader
|
||||
)
|
||||
{
|
||||
return new Domain.Core.Entities.UserAccount
|
||||
{
|
||||
UserAccountId = reader.GetGuid(reader.GetOrdinal("UserAccountId")),
|
||||
UserAccountId = reader.GetGuid(
|
||||
reader.GetOrdinal("UserAccountId")
|
||||
),
|
||||
Username = reader.GetString(reader.GetOrdinal("Username")),
|
||||
FirstName = reader.GetString(reader.GetOrdinal("FirstName")),
|
||||
LastName = reader.GetString(reader.GetOrdinal("LastName")),
|
||||
@@ -112,14 +127,20 @@ namespace Repository.Core.Repositories.UserAccount
|
||||
UpdatedAt = reader.IsDBNull(reader.GetOrdinal("UpdatedAt"))
|
||||
? null
|
||||
: reader.GetDateTime(reader.GetOrdinal("UpdatedAt")),
|
||||
DateOfBirth = reader.GetDateTime(reader.GetOrdinal("DateOfBirth")),
|
||||
DateOfBirth = reader.GetDateTime(
|
||||
reader.GetOrdinal("DateOfBirth")
|
||||
),
|
||||
Timer = reader.IsDBNull(reader.GetOrdinal("Timer"))
|
||||
? null
|
||||
: (byte[])reader["Timer"]
|
||||
: (byte[])reader["Timer"],
|
||||
};
|
||||
}
|
||||
|
||||
private static void AddParameter(DbCommand command, string name, object? value)
|
||||
private static void AddParameter(
|
||||
DbCommand command,
|
||||
string name,
|
||||
object? value
|
||||
)
|
||||
{
|
||||
var p = command.CreateParameter();
|
||||
p.ParameterName = name;
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
Version="160.1000.6"
|
||||
/>
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.9.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
<PackageReference
|
||||
Include="Microsoft.Extensions.Configuration.Abstractions"
|
||||
Version="8.0.0"
|
||||
/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Domain\Domain.csproj" />
|
||||
|
||||
@@ -2,17 +2,21 @@ using System.Data.Common;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
|
||||
namespace Repository.Core.Sql
|
||||
{
|
||||
public class DefaultSqlConnectionFactory(IConfiguration configuration) : ISqlConnectionFactory
|
||||
public class DefaultSqlConnectionFactory(IConfiguration configuration)
|
||||
: ISqlConnectionFactory
|
||||
{
|
||||
private readonly string _connectionString = GetConnectionString(configuration);
|
||||
private readonly string _connectionString = GetConnectionString(
|
||||
configuration
|
||||
);
|
||||
|
||||
private static string GetConnectionString(IConfiguration configuration)
|
||||
{
|
||||
// Check for full connection string first
|
||||
var fullConnectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
|
||||
var fullConnectionString = Environment.GetEnvironmentVariable(
|
||||
"DB_CONNECTION_STRING"
|
||||
);
|
||||
if (!string.IsNullOrEmpty(fullConnectionString))
|
||||
{
|
||||
return fullConnectionString;
|
||||
|
||||
@@ -12,18 +12,30 @@ namespace Repository.Core.Sql
|
||||
/// <returns>A properly formatted SQL Server connection string.</returns>
|
||||
public static string BuildConnectionString(string? databaseName = null)
|
||||
{
|
||||
var server = Environment.GetEnvironmentVariable("DB_SERVER")
|
||||
?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
|
||||
var server =
|
||||
Environment.GetEnvironmentVariable("DB_SERVER")
|
||||
?? throw new InvalidOperationException(
|
||||
"DB_SERVER environment variable is not set"
|
||||
);
|
||||
|
||||
var dbName = databaseName
|
||||
var dbName =
|
||||
databaseName
|
||||
?? Environment.GetEnvironmentVariable("DB_NAME")
|
||||
?? throw new InvalidOperationException("DB_NAME environment variable is not set");
|
||||
?? throw new InvalidOperationException(
|
||||
"DB_NAME environment variable is not set"
|
||||
);
|
||||
|
||||
var user = Environment.GetEnvironmentVariable("DB_USER")
|
||||
?? throw new InvalidOperationException("DB_USER environment variable is not set");
|
||||
var user =
|
||||
Environment.GetEnvironmentVariable("DB_USER")
|
||||
?? throw new InvalidOperationException(
|
||||
"DB_USER environment variable is not set"
|
||||
);
|
||||
|
||||
var password = Environment.GetEnvironmentVariable("DB_PASSWORD")
|
||||
?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
|
||||
var password =
|
||||
Environment.GetEnvironmentVariable("DB_PASSWORD")
|
||||
?? throw new InvalidOperationException(
|
||||
"DB_PASSWORD environment variable is not set"
|
||||
);
|
||||
|
||||
var builder = new SqlConnectionStringBuilder
|
||||
{
|
||||
@@ -32,7 +44,7 @@ namespace Repository.Core.Sql
|
||||
UserID = user,
|
||||
Password = password,
|
||||
TrustServerCertificate = true,
|
||||
Encrypt = true
|
||||
Encrypt = true,
|
||||
};
|
||||
|
||||
return builder.ConnectionString;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
using Apps72.Dev.Data.DbMocker;
|
||||
using Repository.Core.Repositories.Auth;
|
||||
using FluentAssertions;
|
||||
using Repository.Tests.Database;
|
||||
using System.Data;
|
||||
using Apps72.Dev.Data.DbMocker;
|
||||
using FluentAssertions;
|
||||
using Repository.Core.Repositories.Auth;
|
||||
using Repository.Tests.Database;
|
||||
|
||||
namespace Repository.Tests.Auth;
|
||||
|
||||
public class AuthRepositoryTest
|
||||
{
|
||||
private static AuthRepository CreateRepo(MockDbConnection conn)
|
||||
=> new(new TestConnectionFactory(conn));
|
||||
private static AuthRepository CreateRepo(MockDbConnection conn) =>
|
||||
new(new TestConnectionFactory(conn));
|
||||
|
||||
[Fact]
|
||||
public async Task RegisterUserAsync_CreatesUserWithCredential_ReturnsUserAccount()
|
||||
@@ -17,10 +17,12 @@ public class AuthRepositoryTest
|
||||
var expectedUserId = Guid.NewGuid();
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks
|
||||
.When(cmd => cmd.CommandText == "USP_RegisterUser")
|
||||
.ReturnsTable(MockTable.WithColumns(("UserAccountId", typeof(Guid)))
|
||||
.AddRow(expectedUserId));
|
||||
conn.Mocks.When(cmd => cmd.CommandText == "USP_RegisterUser")
|
||||
.ReturnsTable(
|
||||
MockTable
|
||||
.WithColumns(("UserAccountId", typeof(Guid)))
|
||||
.AddRow(expectedUserId)
|
||||
);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.RegisterUserAsync(
|
||||
@@ -47,9 +49,10 @@ public class AuthRepositoryTest
|
||||
var userId = Guid.NewGuid();
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks
|
||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
||||
.ReturnsTable(MockTable.WithColumns(
|
||||
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
||||
.ReturnsTable(
|
||||
MockTable
|
||||
.WithColumns(
|
||||
("UserAccountId", typeof(Guid)),
|
||||
("Username", typeof(string)),
|
||||
("FirstName", typeof(string)),
|
||||
@@ -59,7 +62,8 @@ public class AuthRepositoryTest
|
||||
("UpdatedAt", typeof(DateTime?)),
|
||||
("DateOfBirth", typeof(DateTime)),
|
||||
("Timer", typeof(byte[]))
|
||||
).AddRow(
|
||||
)
|
||||
.AddRow(
|
||||
userId,
|
||||
"emailuser",
|
||||
"Email",
|
||||
@@ -69,7 +73,8 @@ public class AuthRepositoryTest
|
||||
null,
|
||||
new DateTime(1990, 5, 15),
|
||||
null
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.GetUserByEmailAsync("emailuser@example.com");
|
||||
@@ -87,8 +92,7 @@ public class AuthRepositoryTest
|
||||
{
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks
|
||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
||||
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
||||
.ReturnsTable(MockTable.Empty());
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
@@ -103,9 +107,12 @@ public class AuthRepositoryTest
|
||||
var userId = Guid.NewGuid();
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks
|
||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountByUsername")
|
||||
.ReturnsTable(MockTable.WithColumns(
|
||||
conn.Mocks.When(cmd =>
|
||||
cmd.CommandText == "usp_GetUserAccountByUsername"
|
||||
)
|
||||
.ReturnsTable(
|
||||
MockTable
|
||||
.WithColumns(
|
||||
("UserAccountId", typeof(Guid)),
|
||||
("Username", typeof(string)),
|
||||
("FirstName", typeof(string)),
|
||||
@@ -115,7 +122,8 @@ public class AuthRepositoryTest
|
||||
("UpdatedAt", typeof(DateTime?)),
|
||||
("DateOfBirth", typeof(DateTime)),
|
||||
("Timer", typeof(byte[]))
|
||||
).AddRow(
|
||||
)
|
||||
.AddRow(
|
||||
userId,
|
||||
"usernameuser",
|
||||
"Username",
|
||||
@@ -125,7 +133,8 @@ public class AuthRepositoryTest
|
||||
null,
|
||||
new DateTime(1985, 8, 20),
|
||||
null
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.GetUserByUsernameAsync("usernameuser");
|
||||
@@ -141,8 +150,9 @@ public class AuthRepositoryTest
|
||||
{
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks
|
||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountByUsername")
|
||||
conn.Mocks.When(cmd =>
|
||||
cmd.CommandText == "usp_GetUserAccountByUsername"
|
||||
)
|
||||
.ReturnsTable(MockTable.Empty());
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
@@ -158,21 +168,26 @@ public class AuthRepositoryTest
|
||||
var credentialId = Guid.NewGuid();
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks
|
||||
.When(cmd => cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId")
|
||||
.ReturnsTable(MockTable.WithColumns(
|
||||
conn.Mocks.When(cmd =>
|
||||
cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId"
|
||||
)
|
||||
.ReturnsTable(
|
||||
MockTable
|
||||
.WithColumns(
|
||||
("UserCredentialId", typeof(Guid)),
|
||||
("UserAccountId", typeof(Guid)),
|
||||
("Hash", typeof(string)),
|
||||
("CreatedAt", typeof(DateTime)),
|
||||
("Timer", typeof(byte[]))
|
||||
).AddRow(
|
||||
)
|
||||
.AddRow(
|
||||
credentialId,
|
||||
userId,
|
||||
"hashed_password_value",
|
||||
DateTime.UtcNow,
|
||||
null
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.GetActiveCredentialByUserAccountIdAsync(userId);
|
||||
@@ -189,8 +204,9 @@ public class AuthRepositoryTest
|
||||
var userId = Guid.NewGuid();
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks
|
||||
.When(cmd => cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId")
|
||||
conn.Mocks.When(cmd =>
|
||||
cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId"
|
||||
)
|
||||
.ReturnsTable(MockTable.Empty());
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
@@ -206,14 +222,14 @@ public class AuthRepositoryTest
|
||||
var newPasswordHash = "new_hashed_password";
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks
|
||||
.When(cmd => cmd.CommandText == "USP_RotateUserCredential")
|
||||
conn.Mocks.When(cmd => cmd.CommandText == "USP_RotateUserCredential")
|
||||
.ReturnsScalar(1);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
|
||||
// Should not throw
|
||||
var act = async () => await repo.RotateCredentialAsync(userId, newPasswordHash);
|
||||
var act = async () =>
|
||||
await repo.RotateCredentialAsync(userId, newPasswordHash);
|
||||
await act.Should().NotThrowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,18 @@
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.9.0" />
|
||||
<PackageReference Include="DbMocker" Version="1.26.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
|
||||
<PackageReference
|
||||
Include="Microsoft.Extensions.Configuration"
|
||||
Version="9.0.0"
|
||||
/>
|
||||
<PackageReference
|
||||
Include="Microsoft.Extensions.Configuration.Abstractions"
|
||||
Version="9.0.0"
|
||||
/>
|
||||
<PackageReference
|
||||
Include="Microsoft.Extensions.Configuration.Binder"
|
||||
Version="9.0.0"
|
||||
/>
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
using Apps72.Dev.Data.DbMocker;
|
||||
using Repository.Core.Repositories.UserAccount;
|
||||
using FluentAssertions;
|
||||
using Repository.Core.Repositories.UserAccount;
|
||||
using Repository.Tests.Database;
|
||||
|
||||
namespace Repository.Tests.UserAccount;
|
||||
|
||||
public class UserAccountRepositoryTest
|
||||
{
|
||||
private static UserAccountRepository CreateRepo(MockDbConnection conn)
|
||||
=> new(new TestConnectionFactory(conn));
|
||||
private static UserAccountRepository CreateRepo(MockDbConnection conn) =>
|
||||
new(new TestConnectionFactory(conn));
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_ReturnsRow_Mapped()
|
||||
{
|
||||
var conn = new MockDbConnection();
|
||||
conn.Mocks
|
||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountById")
|
||||
.ReturnsTable(MockTable.WithColumns(
|
||||
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountById")
|
||||
.ReturnsTable(
|
||||
MockTable
|
||||
.WithColumns(
|
||||
("UserAccountId", typeof(Guid)),
|
||||
("Username", typeof(string)),
|
||||
("FirstName", typeof(string)),
|
||||
@@ -26,13 +27,24 @@ public class UserAccountRepositoryTest
|
||||
("UpdatedAt", typeof(DateTime?)),
|
||||
("DateOfBirth", typeof(DateTime)),
|
||||
("Timer", typeof(byte[]))
|
||||
).AddRow(Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
|
||||
"yerb", "Aaron", "Po", "aaronpo@example.com",
|
||||
new DateTime(2020, 1, 1), null,
|
||||
new DateTime(1990, 1, 1), null));
|
||||
)
|
||||
.AddRow(
|
||||
Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
|
||||
"yerb",
|
||||
"Aaron",
|
||||
"Po",
|
||||
"aaronpo@example.com",
|
||||
new DateTime(2020, 1, 1),
|
||||
null,
|
||||
new DateTime(1990, 1, 1),
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.GetByIdAsync(Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"));
|
||||
var result = await repo.GetByIdAsync(
|
||||
Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
|
||||
);
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result!.Username.Should().Be("yerb");
|
||||
@@ -43,9 +55,10 @@ public class UserAccountRepositoryTest
|
||||
public async Task GetAllAsync_ReturnsMultipleRows()
|
||||
{
|
||||
var conn = new MockDbConnection();
|
||||
conn.Mocks
|
||||
.When(cmd => cmd.CommandText == "usp_GetAllUserAccounts")
|
||||
.ReturnsTable(MockTable.WithColumns(
|
||||
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetAllUserAccounts")
|
||||
.ReturnsTable(
|
||||
MockTable
|
||||
.WithColumns(
|
||||
("UserAccountId", typeof(Guid)),
|
||||
("Username", typeof(string)),
|
||||
("FirstName", typeof(string)),
|
||||
@@ -55,25 +68,50 @@ public class UserAccountRepositoryTest
|
||||
("UpdatedAt", typeof(DateTime?)),
|
||||
("DateOfBirth", typeof(DateTime)),
|
||||
("Timer", typeof(byte[]))
|
||||
).AddRow(Guid.NewGuid(), "a", "A", "A", "a@example.com", DateTime.UtcNow, null, DateTime.UtcNow.Date,
|
||||
null)
|
||||
.AddRow(Guid.NewGuid(), "b", "B", "B", "b@example.com", DateTime.UtcNow, null, DateTime.UtcNow.Date,
|
||||
null));
|
||||
)
|
||||
.AddRow(
|
||||
Guid.NewGuid(),
|
||||
"a",
|
||||
"A",
|
||||
"A",
|
||||
"a@example.com",
|
||||
DateTime.UtcNow,
|
||||
null,
|
||||
DateTime.UtcNow.Date,
|
||||
null
|
||||
)
|
||||
.AddRow(
|
||||
Guid.NewGuid(),
|
||||
"b",
|
||||
"B",
|
||||
"B",
|
||||
"b@example.com",
|
||||
DateTime.UtcNow,
|
||||
null,
|
||||
DateTime.UtcNow.Date,
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var results = (await repo.GetAllAsync(null, null)).ToList();
|
||||
results.Should().HaveCount(2);
|
||||
results.Select(r => r.Username).Should().BeEquivalentTo(new[] { "a", "b" });
|
||||
results
|
||||
.Select(r => r.Username)
|
||||
.Should()
|
||||
.BeEquivalentTo(new[] { "a", "b" });
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task GetByUsername_ReturnsRow()
|
||||
{
|
||||
var conn = new MockDbConnection();
|
||||
conn.Mocks
|
||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountByUsername")
|
||||
.ReturnsTable(MockTable.WithColumns(
|
||||
conn.Mocks.When(cmd =>
|
||||
cmd.CommandText == "usp_GetUserAccountByUsername"
|
||||
)
|
||||
.ReturnsTable(
|
||||
MockTable
|
||||
.WithColumns(
|
||||
("UserAccountId", typeof(Guid)),
|
||||
("Username", typeof(string)),
|
||||
("FirstName", typeof(string)),
|
||||
@@ -83,8 +121,19 @@ public class UserAccountRepositoryTest
|
||||
("UpdatedAt", typeof(DateTime?)),
|
||||
("DateOfBirth", typeof(DateTime)),
|
||||
("Timer", typeof(byte[]))
|
||||
).AddRow(Guid.NewGuid(), "lookupuser", "L", "U", "lookup@example.com", DateTime.UtcNow, null,
|
||||
DateTime.UtcNow.Date, null));
|
||||
)
|
||||
.AddRow(
|
||||
Guid.NewGuid(),
|
||||
"lookupuser",
|
||||
"L",
|
||||
"U",
|
||||
"lookup@example.com",
|
||||
DateTime.UtcNow,
|
||||
null,
|
||||
DateTime.UtcNow.Date,
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.GetByUsernameAsync("lookupuser");
|
||||
@@ -96,9 +145,10 @@ public class UserAccountRepositoryTest
|
||||
public async Task GetByEmail_ReturnsRow()
|
||||
{
|
||||
var conn = new MockDbConnection();
|
||||
conn.Mocks
|
||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
||||
.ReturnsTable(MockTable.WithColumns(
|
||||
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
||||
.ReturnsTable(
|
||||
MockTable
|
||||
.WithColumns(
|
||||
("UserAccountId", typeof(Guid)),
|
||||
("Username", typeof(string)),
|
||||
("FirstName", typeof(string)),
|
||||
@@ -108,8 +158,19 @@ public class UserAccountRepositoryTest
|
||||
("UpdatedAt", typeof(DateTime?)),
|
||||
("DateOfBirth", typeof(DateTime)),
|
||||
("Timer", typeof(byte[]))
|
||||
).AddRow(Guid.NewGuid(), "byemail", "B", "E", "byemail@example.com", DateTime.UtcNow, null,
|
||||
DateTime.UtcNow.Date, null));
|
||||
)
|
||||
.AddRow(
|
||||
Guid.NewGuid(),
|
||||
"byemail",
|
||||
"B",
|
||||
"E",
|
||||
"byemail@example.com",
|
||||
DateTime.UtcNow,
|
||||
null,
|
||||
DateTime.UtcNow.Date,
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.GetByEmailAsync("byemail@example.com");
|
||||
|
||||
Reference in New Issue
Block a user