mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 08:23:12 +00:00
88 lines
3.6 KiB
C#
88 lines
3.6 KiB
C#
using Data.SeaHavenIndustries;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using SeaHaven.DataServices.Interfaces;
|
|
|
|
namespace SeaHaven.DataServices.Implementation
|
|
{
|
|
public class ForgetPasswordDataService : IForgetPasswordDataService
|
|
{
|
|
private readonly ApplicationDbContext _context;
|
|
|
|
public ForgetPasswordDataService(ApplicationDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
public async Task ReplaceCodeAsync(string email, string userId, string codeHash, string codeSalt, DateTime expiresAtUtc, CancellationToken cancellationToken)
|
|
{
|
|
var normalizedEmail = Normalize(email);
|
|
|
|
var existing = await _context.ForgetPasswordCodes
|
|
.Where(u => u.Email.ToLower().Trim() == normalizedEmail)
|
|
.ToListAsync(cancellationToken);
|
|
_context.ForgetPasswordCodes.RemoveRange(existing);
|
|
|
|
_context.ForgetPasswordCodes.Add(new ForgetPasswordCode
|
|
{
|
|
Email = email,
|
|
UserId = userId,
|
|
Code = string.Empty,
|
|
CodeHash = codeHash,
|
|
CodeSalt = codeSalt,
|
|
ExpiresAtUtc = expiresAtUtc,
|
|
FailedAttempts = 0
|
|
});
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<ForgetPasswordCode?> GetByEmailAsync(string email, CancellationToken cancellationToken)
|
|
{
|
|
var normalizedEmail = Normalize(email);
|
|
|
|
return await _context.ForgetPasswordCodes
|
|
.AsNoTracking()
|
|
.Where(u => u.Email.ToLower().Trim() == normalizedEmail)
|
|
.OrderByDescending(u => u.Id)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<bool> TryConsumeAttemptAsync(int id, int maxAttempts, DateTime nowUtc, CancellationToken cancellationToken)
|
|
{
|
|
// A single conditional UPDATE, so concurrent checks can never consume
|
|
// more than maxAttempts between them.
|
|
var updated = await _context.ForgetPasswordCodes
|
|
.Where(u => u.Id == id && u.FailedAttempts < maxAttempts && u.ExpiresAtUtc > nowUtc)
|
|
.ExecuteUpdateAsync(
|
|
setters => setters.SetProperty(u => u.FailedAttempts, u => u.FailedAttempts + 1),
|
|
cancellationToken);
|
|
return updated == 1;
|
|
}
|
|
|
|
public async Task RefundAttemptAsync(int id, CancellationToken cancellationToken)
|
|
{
|
|
await _context.ForgetPasswordCodes
|
|
.Where(u => u.Id == id && u.FailedAttempts > 0)
|
|
.ExecuteUpdateAsync(
|
|
setters => setters.SetProperty(u => u.FailedAttempts, u => u.FailedAttempts - 1),
|
|
cancellationToken);
|
|
}
|
|
|
|
public async Task RemoveByEmailAsync(string email, CancellationToken cancellationToken)
|
|
{
|
|
var normalizedEmail = Normalize(email);
|
|
await _context.ForgetPasswordCodes
|
|
.Where(code => code.Email.ToLower().Trim() == normalizedEmail)
|
|
.ExecuteDeleteAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task RemoveIssuedThroughAsync(string email, int throughId, CancellationToken cancellationToken)
|
|
{
|
|
var normalizedEmail = Normalize(email);
|
|
await _context.ForgetPasswordCodes
|
|
.Where(code => code.Email.ToLower().Trim() == normalizedEmail && code.Id <= throughId)
|
|
.ExecuteDeleteAsync(cancellationToken);
|
|
}
|
|
|
|
private static string Normalize(string email) => email.ToLower().Trim();
|
|
}
|
|
}
|