Authinator/Backend/Database/Tables/User.cs
2023-06-01 06:14:24 +02:00

114 lines
4.1 KiB
C#

using System.Diagnostics.CodeAnalysis;
using System.Web;
using Authinator.Backend.Utils;
using Isopoh.Cryptography.Argon2;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Authinator.Backend.Database.Tables;
public class User {
public int Id { get; set; }
public int Iteration { get; set; }
public string Reference { get; set; } = null!;
public string? Username { get; set; }
public string? Password { get; set; }
public string? Email { get; set; }
public List<Group> Groups { get; set; } = new();
public bool IsSignupComplete => !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password) && !string.IsNullOrEmpty(Email);
public string GetAuthToken() => $"auth:{Id}:{Iteration}:{GetAuthTokenExpiry()}:".HmacWithMessage(ConfigCache.HmacSecret);
private static long GetAuthTokenExpiry() => DateTimeOffset.UtcNow.Add(ConfigCache.AuthTokenLifetime).ToUnixTimeSeconds();
public string GetSignupToken() => $"register:{Id}:".HmacWithMessage(ConfigCache.HmacSecret);
public string GetPwResetToken() => $"reset:{Id}:{Iteration}:{GetPwResetTokenExpiry()}:".HmacWithMessage(ConfigCache.HmacSecret);
private static long GetPwResetTokenExpiry() => DateTimeOffset.UtcNow.Add(ConfigCache.PwResetTokenLifetime).ToUnixTimeSeconds();
[SuppressMessage("ReSharper.DPA", "DPA0001: Memory allocation issues")]
public bool ValidatePassword(string password) => Argon2.Verify(Password ?? string.Empty, password);
[SuppressMessage("ReSharper.DPA", "DPA0001: Memory allocation issues")]
public void SetPassword(string password) => Password = Argon2.Hash(password);
public class Configuration : IEntityTypeConfiguration<User> {
public void Configure(EntityTypeBuilder<User> user) {
user.ToTable("Users");
user.HasKey(p => p.Id);
user.Property(b => b.Iteration).HasColumnName("Iteration").HasDefaultValue(0);
user.Property(b => b.Reference).HasColumnName("Reference").IsRequired();
user.Property(b => b.Username).HasColumnName("Username");
user.Property(b => b.Password).HasColumnName("Password");
user.Property(b => b.Email).HasColumnName("Email");
user.HasMany(p => p.Groups).WithMany();
}
}
private bool Equals(User other) => Id == other.Id;
public override bool Equals(object? obj) {
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
return obj.GetType() == GetType() && Equals((User)obj);
}
public override int GetHashCode() => Id;
public static bool operator ==(User? left, User? right) => Equals(left, right);
public static bool operator !=(User? left, User? right) => !Equals(left, right);
}
public static class UserUtils {
public static User? ValidateAuthToken(this DbSet<User> users, string token) {
if (string.IsNullOrWhiteSpace(token) || !token.StartsWith("auth:") || token.Split(":").Length != 5)
return null;
var index = token.LastIndexOf(":", StringComparison.Ordinal) + 1;
var hmac = token[index..].FixUrlEncodedBase64();
var message = token[..index];
if (message.Hmac(ConfigCache.HmacSecret) != hmac)
return null;
var expiry = long.Parse(message.Split(":")[3]);
if (DateTimeOffset.FromUnixTimeSeconds(expiry) < DateTimeOffset.UtcNow)
return null;
var userId = int.Parse(message.Split(":")[1]);
var userIteration = int.Parse(message.Split(":")[2]);
return users.Include(p => p.Groups).FirstOrDefault(p => p.Id == userId && p.Iteration == userIteration);
}
public static bool ValidateResetToken(this User user, string token) {
if (string.IsNullOrWhiteSpace(token) || !token.StartsWith("reset:") || token.Split(":").Length != 5)
return false;
var index = token.LastIndexOf(":", StringComparison.Ordinal) + 1;
var hmac = token[index..].FixUrlEncodedBase64();
var message = token[..index];
if (message.Hmac(ConfigCache.HmacSecret) != hmac)
return false;
var expiry = long.Parse(message.Split(":")[3]);
if (DateTimeOffset.FromUnixTimeSeconds(expiry) < DateTimeOffset.UtcNow)
return false;
var userId = int.Parse(message.Split(":")[1]);
var userIteration = int.Parse(message.Split(":")[2]);
return user.Id == userId && user.Iteration == userIteration;
}
}