#nullable enable using System; using System.Threading.Tasks; using BTCPayServer.Data; using BTCPayServer.Security; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; namespace BTCPayServer { public static class UserManagerExtensions { private const string InvitationPurpose = "invitation"; public static async Task FindByIdOrEmail(this UserManager userManager, string? idOrEmail) where TUser : class { if (string.IsNullOrEmpty(idOrEmail)) return null; if (idOrEmail.Contains('@')) return await userManager.FindByEmailAsync(idOrEmail); return await userManager.FindByIdAsync(idOrEmail); } public static async Task IsAuthenticatorConfigured(this UserManager userManager, ApplicationUser user) { return (await userManager.GetValidTwoFactorProvidersAsync(user)).Contains(userManager.Options.Tokens.AuthenticatorTokenProvider); } public static async Task GenerateInvitationTokenAsync(this UserManager userManager, string userId) { var token = Guid.NewGuid().ToString("n")[..12]; return await userManager.SetInvitationTokenAsync(userId, token) ? token : null; } public static async Task TwoFactorSignInAsync(this SignInManager signInManager, ApplicationUser user) { var userIdAsync = await signInManager.UserManager.GetUserIdAsync(user); var storeTwoFactorInfoMethod = typeof(SignInManager).GetMethod("StoreTwoFactorInfo", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); var principal = (System.Security.Claims.ClaimsPrincipal)storeTwoFactorInfoMethod!.Invoke(null, new object[] { userIdAsync, (null as string)! })!; await signInManager.Context.SignInAsync(IdentityConstants.TwoFactorUserIdScheme, principal!); } public static Task UnsetInvitationTokenAsync(this UserManager userManager, string userId) => userManager.SetInvitationTokenAsync(userId, null); public static bool HasInvitationToken(this UserManager userManager, ApplicationUser user, string? token = null) { var blob = user.GetBlob() ?? new UserBlob(); return token == null ? !string.IsNullOrEmpty(blob.InvitationToken) : blob.InvitationToken == token; } private static async Task SetInvitationTokenAsync(this UserManager userManager, string userId, string? token) { var user = await userManager.FindByIdAsync(userId); if (user == null) return false; var blob = user.GetBlob() ?? new UserBlob(); blob.InvitationToken = token; user.SetBlob(blob); await userManager.UpdateAsync(user); return true; } public static async Task FindByInvitationTokenAsync(this UserManager userManager, string userId, string token) { var user = await userManager.FindByIdAsync(userId); var isValid = user is not null && ( user.GetBlob()?.InvitationToken == token || // backwards-compatibility with old tokens await userManager.VerifyUserTokenAsync(user, InvitationTokenProviderOptions.ProviderName, InvitationPurpose, token)); return isValid ? user : null; } } }