btcpayserver-plugin-builder/PluginBuilder/Services/PluginOwnershipService.cs
2026-01-15 23:47:43 -06:00

92 lines
3.4 KiB
C#

using Microsoft.AspNetCore.Identity;
using PluginBuilder.Controllers.Logic;
using PluginBuilder.Util.Extensions;
using PluginBuilder.ViewModels;
namespace PluginBuilder.Services;
public sealed class PluginOwnershipService(
DBConnectionFactory connectionFactory,
UserManager<IdentityUser> userManager,
IUserClaimsPrincipalFactory<IdentityUser> principalFactory,
UserVerifiedLogic userVerifiedLogic)
{
public async Task<ServiceResult> AddOwnerByEmailAsync(PluginSlug pluginSlug, string email)
{
email = (email ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(email))
return ServiceResult.Fail("Email is required.");
var user = await userManager.FindByEmailAsync(email);
if (user is null)
return ServiceResult.Fail("User not found.");
await using var conn = await connectionFactory.Open();
if (await conn.UserOwnsPlugin(user.Id, pluginSlug))
return ServiceResult.Fail("User is already an owner.");
var principal = await principalFactory.CreateAsync(user);
if (!await userVerifiedLogic.IsUserEmailVerifiedForPublish(principal))
return ServiceResult.Fail("Owner must have a confirmed email.");
if (!await userVerifiedLogic.IsUserGithubVerified(principal, conn))
return ServiceResult.Fail("Owner must have a verified Github account.");
await conn.AddUserPlugin(pluginSlug, user.Id, false);
return ServiceResult.Ok();
}
public async Task<ServiceResult> TransferPrimaryAsync(PluginSlug pluginSlug, string targetUserId)
{
await using var conn = await connectionFactory.Open();
if (!await conn.UserOwnsPlugin(targetUserId, pluginSlug))
return ServiceResult.Fail("Target user is not an owner.");
var ok = await conn.AssignPluginPrimaryOwner(pluginSlug, targetUserId);
return ok ? ServiceResult.Ok() : ServiceResult.Fail("Failed to assign primary owner.");
}
public async Task<ServiceResult> RemoveOwnerAsync(
PluginSlug pluginSlug,
string targetUserId,
string? currentUserId,
bool isServerAdmin)
{
await using var conn = await connectionFactory.Open();
await using var tx = await conn.BeginTransactionAsync();
var owners = await conn.GetPluginOwnersForUpdate(pluginSlug, tx);
if (owners.All(o => o.UserId != targetUserId))
return ServiceResult.Fail("User not an owner.");
var primaryOwnerId = owners.FirstOrDefault(o => o.IsPrimary).UserId;
if (!isServerAdmin)
{
if (currentUserId is null)
return ServiceResult.Fail("Not authenticated.");
var currentIsPrimary = primaryOwnerId == currentUserId;
if (!currentIsPrimary && targetUserId != currentUserId)
return ServiceResult.Fail("Only primary owner can remove other owners.");
}
if (targetUserId == primaryOwnerId)
return ServiceResult.Fail("Primary owner cannot be removed. Transfer primary ownership first.");
if (owners.Count <= 1)
return ServiceResult.Fail("Cannot remove the last owner.");
var deleted = await conn.RemovePluginOwner(pluginSlug, targetUserId);
if (deleted != 1)
return ServiceResult.Fail("Failed to remove owner.");
await tx.CommitAsync();
return ServiceResult.Ok();
}
}