using System.ComponentModel.DataAnnotations.Schema;
using System.Security.Cryptography;
using System.Text;
using Cysharp.Text;
using OliverBooth.Common.Data.Blog;
using OtpNet;
namespace OliverBooth.Common.Data.Web.Users;
///
/// Represents a user.
///
internal sealed class User : IUser, IAuthor
{
///
[NotMapped]
public Uri AvatarUrl => GetAvatarUrl();
///
public string EmailAddress { get; set; } = string.Empty;
///
public string DisplayName { get; set; } = string.Empty;
///
public Guid Id { get; private set; } = Guid.NewGuid();
///
public IReadOnlyList Permissions { get; private set; } = ArraySegment.Empty;
///
public DateTimeOffset Registered { get; private set; } = DateTimeOffset.UtcNow;
///
public string? Totp { get; private set; }
///
/// Gets or sets the password hash.
///
/// The password hash.
internal string Password { get; set; } = string.Empty;
///
/// Gets or sets the salt used to hash the password.
///
/// The salt used to hash the password.
internal string Salt { get; set; } = string.Empty;
///
public Uri GetAvatarUrl(int size = 28)
{
if (string.IsNullOrWhiteSpace(EmailAddress))
{
return new Uri($"https://www.gravatar.com/avatar/0?size={size}");
}
ReadOnlySpan span = EmailAddress.AsSpan();
int byteCount = Encoding.UTF8.GetByteCount(span);
Span bytes = stackalloc byte[byteCount];
Encoding.UTF8.GetBytes(span, bytes);
Span hash = stackalloc byte[16];
MD5.TryHashData(bytes, hash, out _);
using Utf8ValueStringBuilder builder = ZString.CreateUtf8StringBuilder();
Span hex = stackalloc char[2];
for (var index = 0; index < hash.Length; index++)
{
if (hash[index].TryFormat(hex, out _, "x2"))
{
builder.Append(hex);
}
else
{
builder.Append("00");
}
}
return new Uri($"https://www.gravatar.com/avatar/{builder}?size={size}");
}
///
public bool HasPermission(Permission permission)
{
return HasPermission(permission.Name);
}
///
public bool HasPermission(string permission)
{
return (Permissions.Any(p => p.IsAllowed && p.Name == permission) ||
Permissions.Any(p => p is { IsAllowed: true, Name: "*" })) &&
!Permissions.Any(p => !p.IsAllowed && p.Name == permission);
}
///
public bool TestCredentials(string password)
{
return false;
}
///
public bool TestTotp(string value)
{
byte[]? key = Base32Encoding.ToBytes(Totp);
var totp = new Totp(key);
return totp.VerifyTotp(value, out _, VerificationWindow.RfcSpecifiedNetworkDelay);
}
}