Files
step3/api/QuestionsApi/Services/AuthUserStore.cs
T

97 lines
4.3 KiB
C#

using MongoDB.Bson;
using MongoDB.Driver;
using QuestionsApi.Models;
using System.Text.RegularExpressions;
namespace QuestionsApi.Services;
public sealed class AuthUserStore(MongoDatabaseProvider mongo)
{
private readonly IMongoCollection<AuthUser> _users = mongo.Database.GetCollection<AuthUser>("Users");
public async Task<AuthUser?> GetByUsernameAsync(string username) =>
await _users.Find(user => user.Username == username.Trim()).FirstOrDefaultAsync();
public async Task<AuthUser?> GetByIdAsync(string id) =>
await _users.Find(user => user.Id == id).FirstOrDefaultAsync();
public async Task<AuthUser?> GetBySteamIdAsync(string steamId) =>
await _users.Find(user => user.SteamId == steamId).FirstOrDefaultAsync();
public async Task<AuthUser?> GetUniqueByEmailAsync(string email)
{
var normalized = AuthValidation.NormalizeEmail(email);
var filter = Builders<AuthUser>.Filter.Eq(user => user.NormalizedEmail, normalized) |
Builders<AuthUser>.Filter.Regex(user => user.Email,
new BsonRegularExpression($"^{Regex.Escape(normalized)}$", "i"));
var matches = await _users.Find(filter).Limit(2).ToListAsync();
return matches.Count == 1 ? matches[0] : null;
}
public async Task<AuthUser?> ResolveUniqueLoginAsync(string login)
{
var username = await GetByUsernameAsync(login);
return username ?? await GetUniqueByEmailAsync(login);
}
public async Task<List<AuthUser>> GetByLoginCandidatesAsync(string login)
{
var username = await GetByUsernameAsync(login);
if (username is not null) return [username];
var email = await GetUniqueByEmailAsync(login);
return email is null ? [] : [email];
}
public Task<bool> EmailExistsAsync(string email, string? excludingUserId = null)
{
var normalized = AuthValidation.NormalizeEmail(email);
var filter = (Builders<AuthUser>.Filter.Eq(user => user.NormalizedEmail, normalized) |
Builders<AuthUser>.Filter.Regex(user => user.Email,
new BsonRegularExpression($"^{Regex.Escape(normalized)}$", "i")));
if (!string.IsNullOrWhiteSpace(excludingUserId))
filter &= Builders<AuthUser>.Filter.Ne(user => user.Id, excludingUserId);
return _users.Find(filter).AnyAsync();
}
public Task CreateAsync(AuthUser user) => _users.InsertOneAsync(user);
public Task UpdateAsync(AuthUser user) =>
_users.ReplaceOneAsync(value => value.Id == user.Id, user);
public async Task<bool> TryLinkSteamAsync(string userId, string steamId, string refreshToken, DateTime refreshExpiry)
{
var filter = Builders<AuthUser>.Filter.Eq(user => user.Id, userId) &
(Builders<AuthUser>.Filter.Eq(user => user.SteamId, null) |
Builders<AuthUser>.Filter.Eq(user => user.SteamId, steamId));
var update = Builders<AuthUser>.Update
.Set(user => user.SteamId, steamId)
.Set(user => user.RefreshToken, refreshToken)
.Set(user => user.RefreshTokenExpiry, refreshExpiry);
return (await _users.UpdateOneAsync(filter, update)).MatchedCount == 1;
}
public async Task<bool> TrySetSteamIdAsync(string userId, string steamId)
{
var filter = Builders<AuthUser>.Filter.Eq(user => user.Id, userId) &
(Builders<AuthUser>.Filter.Eq(user => user.SteamId, null) |
Builders<AuthUser>.Filter.Eq(user => user.SteamId, steamId));
return (await _users.UpdateOneAsync(filter,
Builders<AuthUser>.Update.Set(user => user.SteamId, steamId))).MatchedCount == 1;
}
public async Task<bool> ResetPasswordAsync(string userId, string passwordHash)
{
var update = Builders<AuthUser>.Update
.Set(user => user.PasswordHash, passwordHash)
.Set(user => user.RefreshToken, null)
.Set(user => user.RefreshTokenExpiry, null)
.Inc(user => user.AuthVersion, 1);
return (await _users.UpdateOneAsync(user => user.Id == userId, update)).ModifiedCount == 1;
}
public Task DeleteAsync(string id) => _users.DeleteOneAsync(user => user.Id == id);
public Task<List<AuthUser>> GetAllAsync() => _users.Find(FilterDefinition<AuthUser>.Empty).ToListAsync();
internal IMongoCollection<AuthUser> Collection => _users;
}