maksit-certs-ui/src/LetsEncryptServer/Domain/User.cs
2025-11-09 15:18:08 +01:00

171 lines
4.3 KiB
C#

using MaksIT.Core.Abstractions.Domain;
using MaksIT.Core.Security;
using MaksIT.Results;
namespace MaksIT.LetsEncryptServer.Domain;
public class User(
Guid id,
string name
) : DomainDocumentBase<Guid>(id) {
public string Name { get; private set; } = name;
public string Salt { get; private set; } = string.Empty;
public string Hash { get; private set; } = string.Empty;
public List<JwtToken> JwtTokens { get; private set; } = [];
public DateTime LastLogin { get; private set; }
public User(
string name
) : this(
Guid.NewGuid(),
name
) { }
/// <summary>
/// Change user name
/// </summary>
/// <param name="newName"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public User SetName(string newName) {
Name = newName;
return this;
}
/// <summary>
/// For persistence
/// </summary>
/// <param name="salt"></param>
/// <param name="hash"></param>
/// <returns></returns>
public User SetSaltedHash(string salt, string hash) {
Salt = salt;
Hash = hash;
return this;
}
/// <summary>
///
/// </summary>
/// <param name="tokens"></param>
/// <returns></returns>
public User SetJwtTokens(List<JwtToken> tokens) {
JwtTokens = tokens;
return this;
}
public User SetLastLogin() {
SetLastLogin(DateTime.UtcNow);
return this;
}
public User SetLastLogin(DateTime dateTime) {
LastLogin = dateTime;
return this;
}
#region Password Management
/// <summary>
/// Set or change password (returns this for chaining)
/// </summary>
/// <param name="password"></param>
/// <param name="pepper"></param>
/// <returns></returns>
public Result<User?> SetPassword(string password, string pepper) {
if (!PasswordHasher.TryCreateSaltedHash(password, pepper, out var saltedHash, out var errorMessage))
return Result<User?>.InternalServerError(null, errorMessage);
Salt = saltedHash.Value.Salt;
Hash = saltedHash.Value.Hash;
return Result<User?>.Ok(this);
}
/// <summary>
/// Validate password
/// </summary>
/// <param name="password"></param>
/// <param name="pepper"></param>
/// <returns></returns>
public Result ValidatePassword(string password, string pepper) {
if (PasswordHasher.TryValidateHash(password, Salt, Hash, pepper, out var isValid, out var errorMessage)) {
if (isValid)
return Result.Ok();
return Result.Unauthorized("Invalid password.");
}
return Result<User?>.InternalServerError(null, errorMessage);
}
/// <summary>
/// Reset password to a new value (returns this for chaining)
/// </summary>
/// <param name="newPassword"></param>
/// <param name="pepper"></param>
/// <returns></returns>
public Result<User?> ResetPassword(string newPassword, string pepper) => SetPassword(newPassword, pepper);
#endregion
#region JWT Token Management
public User UpsertJwtToken(JwtToken token) {
var existing = JwtTokens.FirstOrDefault(t => t.Id == token.Id);
if (existing != null)
JwtTokens.Remove(existing);
JwtTokens.Add(token);
return this;
}
public User UpsertJwtTokens(List<JwtToken> tokens) {
foreach (var token in tokens)
UpsertJwtToken(token);
return this;
}
public Result<User?> RemoveJwtToken(Guid tokenId) {
var token = JwtTokens.FirstOrDefault(t => t.Id == tokenId);
if (token == null)
return Result<User?>.NotFound(null, "JWT token not found.");
JwtTokens.Remove(token);
return Result<User?>.Ok(this);
}
public Result<User?> RemoveJwtToken(string token) {
var tokenDomain = JwtTokens.FirstOrDefault(t => t.Token == token);
if (tokenDomain == null)
return Result<User?>.NotFound(null, "JWT token not found.");
JwtTokens.Remove(tokenDomain);
return Result<User?>.Ok(this);
}
public Result<User?> RemoveJwtTokens(List<Guid> tokenIds) {
foreach (var tokenId in tokenIds) {
var removeTokenResult = RemoveJwtToken(tokenId);
if (!removeTokenResult.IsSuccess)
return removeTokenResult;
}
return Result<User?>.Ok(this);
}
public User RemoveRevokedJwtTokens() {
JwtTokens = JwtTokens.Where(t => !t.IsRevoked).ToList();
return this;
}
public User RevokeAllJwtTokens() {
JwtTokens = [];
return this;
}
#endregion
}