58 lines
2.3 KiB
C#
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" }));
|
|
}
|