269 lines
11 KiB
C#
269 lines
11 KiB
C#
using System.Data;
|
|
using Dapper;
|
|
using PlotLine.Models;
|
|
|
|
namespace PlotLine.Data;
|
|
|
|
public interface IUserRepository
|
|
{
|
|
Task<AppUser?> GetByEmailAsync(string email);
|
|
Task<AppUser?> GetByIdAsync(int userId);
|
|
Task<AppUser> CreateAsync(string email, string displayName, string passwordHash, bool emailConfirmed);
|
|
Task<AppUser?> UpdateLoginSuccessAsync(int userId);
|
|
Task<AppUser?> UpdateFailedLoginAsync(int userId, DateTime? lockoutEndUtc);
|
|
Task<bool> ConfirmEmailAsync(int userId, Guid token);
|
|
Task<bool> ResetPasswordAsync(int userId, Guid token, string passwordHash);
|
|
Task<AppUser?> ChangePasswordAsync(int userId, string passwordHash);
|
|
Task<AppUser?> EnableTwoFactorAsync(int userId);
|
|
Task<AppUser?> DisableTwoFactorAsync(int userId);
|
|
Task<IReadOnlyList<string>> ListHardDeleteFilePathsAsync(int userId);
|
|
Task<bool> HardDeleteAccountDataAsync(int userId);
|
|
}
|
|
|
|
public interface IAuthenticationRepository
|
|
{
|
|
Task<UserEmailVerificationToken> CreateEmailVerificationTokenAsync(int userId, Guid token, DateTime expiryUtc);
|
|
Task<UserEmailVerificationToken?> GetEmailVerificationTokenAsync(Guid token);
|
|
Task<UserPasswordResetToken> CreatePasswordResetTokenAsync(int userId, Guid token, DateTime expiryUtc);
|
|
Task<UserPasswordResetToken?> GetPasswordResetTokenAsync(Guid token);
|
|
Task<UserTwoFactor?> GetTwoFactorAsync(int userId);
|
|
Task<UserTwoFactor> SaveTwoFactorAsync(int userId, string secretKey, string? recoveryCodes, DateTime? confirmedUtc);
|
|
Task<AppUser?> EnableTwoFactorAsync(int userId);
|
|
Task<AppUser?> DisableTwoFactorAsync(int userId);
|
|
Task<UserTwoFactor?> SaveRecoveryCodesAsync(int userId, string recoveryCodes);
|
|
Task<string?> GetRecoveryCodesAsync(int userId);
|
|
Task<bool> UpdateLastUsedTimeStepAsync(int userId, long lastUsedTimeStep);
|
|
Task<long> InsertLoginAuditAsync(UserLoginAudit audit);
|
|
}
|
|
|
|
public sealed class UserRepository(ISqlConnectionFactory connectionFactory) : IUserRepository
|
|
{
|
|
public async Task<AppUser?> GetByEmailAsync(string email)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<AppUser>(
|
|
"dbo.User_GetByEmail",
|
|
new { Email = email },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<AppUser?> GetByIdAsync(int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<AppUser>(
|
|
"dbo.User_GetById",
|
|
new { UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<AppUser> CreateAsync(string email, string displayName, string passwordHash, bool emailConfirmed)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleAsync<AppUser>(
|
|
"dbo.User_Create",
|
|
new { Email = email, DisplayName = displayName, PasswordHash = passwordHash, EmailConfirmed = emailConfirmed },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<AppUser?> UpdateLoginSuccessAsync(int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<AppUser>(
|
|
"dbo.User_UpdateLoginSuccess",
|
|
new { UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<AppUser?> UpdateFailedLoginAsync(int userId, DateTime? lockoutEndUtc)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<AppUser>(
|
|
"dbo.User_UpdateFailedLogin",
|
|
new { UserID = userId, LockoutEndUtc = lockoutEndUtc },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<bool> ConfirmEmailAsync(int userId, Guid token)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleAsync<bool>(
|
|
"dbo.User_ConfirmEmail",
|
|
new { UserID = userId, Token = token },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<bool> ResetPasswordAsync(int userId, Guid token, string passwordHash)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleAsync<bool>(
|
|
"dbo.User_ResetPassword",
|
|
new { UserID = userId, Token = token, PasswordHash = passwordHash },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<AppUser?> ChangePasswordAsync(int userId, string passwordHash)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<AppUser>(
|
|
"dbo.User_ChangePassword",
|
|
new { UserID = userId, PasswordHash = passwordHash },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<AppUser?> EnableTwoFactorAsync(int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<AppUser>(
|
|
"dbo.User_EnableTwoFactor",
|
|
new { UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<AppUser?> DisableTwoFactorAsync(int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<AppUser>(
|
|
"dbo.User_DisableTwoFactor",
|
|
new { UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<string>> ListHardDeleteFilePathsAsync(int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
var rows = await connection.QueryAsync<string>(
|
|
"dbo.UserHardDelete_ListFilePaths",
|
|
new { UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
return rows.ToList();
|
|
}
|
|
|
|
public async Task<bool> HardDeleteAccountDataAsync(int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleAsync<bool>(
|
|
"dbo.UserHardDelete_ForAccountDeletion",
|
|
new { UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
}
|
|
|
|
public sealed class AuthenticationRepository(ISqlConnectionFactory connectionFactory) : IAuthenticationRepository
|
|
{
|
|
public async Task<UserEmailVerificationToken> CreateEmailVerificationTokenAsync(int userId, Guid token, DateTime expiryUtc)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleAsync<UserEmailVerificationToken>(
|
|
"dbo.User_CreateEmailVerificationToken",
|
|
new { UserID = userId, Token = token, ExpiryUtc = expiryUtc },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<UserEmailVerificationToken?> GetEmailVerificationTokenAsync(Guid token)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<UserEmailVerificationToken>(
|
|
"dbo.User_GetEmailVerificationToken",
|
|
new { Token = token },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<UserPasswordResetToken> CreatePasswordResetTokenAsync(int userId, Guid token, DateTime expiryUtc)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleAsync<UserPasswordResetToken>(
|
|
"dbo.User_CreatePasswordResetToken",
|
|
new { UserID = userId, Token = token, ExpiryUtc = expiryUtc },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<UserPasswordResetToken?> GetPasswordResetTokenAsync(Guid token)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<UserPasswordResetToken>(
|
|
"dbo.User_GetPasswordResetToken",
|
|
new { Token = token },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<UserTwoFactor?> GetTwoFactorAsync(int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<UserTwoFactor>(
|
|
"dbo.UserTwoFactor_Get",
|
|
new { UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<UserTwoFactor> SaveTwoFactorAsync(int userId, string secretKey, string? recoveryCodes, DateTime? confirmedUtc)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleAsync<UserTwoFactor>(
|
|
"dbo.UserTwoFactor_Save",
|
|
new { UserID = userId, SecretKey = secretKey, RecoveryCodes = recoveryCodes, ConfirmedUtc = confirmedUtc },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<AppUser?> EnableTwoFactorAsync(int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<AppUser>(
|
|
"dbo.UserTwoFactor_Enable",
|
|
new { UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<AppUser?> DisableTwoFactorAsync(int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<AppUser>(
|
|
"dbo.UserTwoFactor_Disable",
|
|
new { UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<UserTwoFactor?> SaveRecoveryCodesAsync(int userId, string recoveryCodes)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<UserTwoFactor>(
|
|
"dbo.UserTwoFactor_SaveRecoveryCodes",
|
|
new { UserID = userId, RecoveryCodes = recoveryCodes },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<string?> GetRecoveryCodesAsync(int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<string?>(
|
|
"dbo.UserTwoFactor_GetRecoveryCodes",
|
|
new { UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<bool> UpdateLastUsedTimeStepAsync(int userId, long lastUsedTimeStep)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleAsync<bool>(
|
|
"dbo.UserTwoFactor_UpdateLastUsedTimeStep",
|
|
new { UserID = userId, LastUsedTimeStep = lastUsedTimeStep },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<long> InsertLoginAuditAsync(UserLoginAudit audit)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleAsync<long>(
|
|
"dbo.User_InsertLoginAudit",
|
|
new
|
|
{
|
|
audit.UserID,
|
|
audit.EmailAttempted,
|
|
audit.IpAddress,
|
|
audit.UserAgent,
|
|
audit.WasSuccessful,
|
|
audit.Reason
|
|
},
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
}
|