Files
gameauthapi/Services/PasswordResetService.cs

58 lines
2.3 KiB
C#

using System.Security.Cryptography;
using GameAuthApi.Models;
using MongoDB.Driver;
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
namespace GameAuthApi.Services;
public class PasswordResetService
{
private readonly IMongoCollection<PasswordResetChallenge> _challenges;
public PasswordResetService(IConfiguration config)
{
var client = new MongoClient(config["MongoDB:ConnectionString"]);
var database = client.GetDatabase(config["MongoDB:DatabaseName"]);
_challenges = database.GetCollection<PasswordResetChallenge>("PasswordResetChallenges");
}
public virtual async Task<string> CreateAsync(string userId)
{
await _challenges.UpdateManyAsync(x => x.UserId == userId && !x.Used,
Builders<PasswordResetChallenge>.Update.Set(x => x.Used, true));
var code = RandomNumberGenerator.GetInt32(0, 1_000_000).ToString("D6");
await _challenges.InsertOneAsync(new PasswordResetChallenge
{
UserId = userId,
CodeHash = BCrypt.Net.BCrypt.HashPassword(code),
CreatedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(15)
});
return code;
}
public virtual async Task<bool> ConsumeAsync(string userId, string code)
{
var challenge = await _challenges.Find(x => x.UserId == userId && !x.Used && x.ExpiresUtc > DateTime.UtcNow)
.SortByDescending(x => x.CreatedUtc).FirstOrDefaultAsync();
if (challenge == null || challenge.FailedAttempts >= 5)
return false;
if (!BCrypt.Net.BCrypt.Verify(code, challenge.CodeHash))
{
await _challenges.UpdateOneAsync(x => x.Id == challenge.Id,
Builders<PasswordResetChallenge>.Update.Inc(x => x.FailedAttempts, 1));
return false;
}
await _challenges.UpdateOneAsync(x => x.Id == challenge.Id,
Builders<PasswordResetChallenge>.Update.Set(x => x.Used, true));
return true;
}
public async Task EnsureIndexesAsync() => await _challenges.Indexes.CreateOneAsync(
new CreateIndexModel<PasswordResetChallenge>(
Builders<PasswordResetChallenge>.IndexKeys.Ascending(x => x.ExpiresUtc),
new CreateIndexOptions { ExpireAfter = TimeSpan.Zero, Name = "password_reset_ttl" }));
}