97 lines
4.3 KiB
C#
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;
|
|
}
|