Account flags
This commit is contained in:
@ -5,5 +5,11 @@ namespace NostrStreamer.ApiModel;
|
|||||||
public class PatchAccount
|
public class PatchAccount
|
||||||
{
|
{
|
||||||
[JsonProperty("accept_tos")]
|
[JsonProperty("accept_tos")]
|
||||||
public bool AcceptTos { get; init; }
|
public bool? AcceptTos { get; init; }
|
||||||
|
|
||||||
|
[JsonProperty("blocked")]
|
||||||
|
public bool? Blocked { get; init; }
|
||||||
|
|
||||||
|
[JsonProperty("admin")]
|
||||||
|
public bool? Admin { get; init; }
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,28 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NostrStreamer.ApiModel;
|
||||||
|
using NostrStreamer.Services;
|
||||||
|
using NostrStreamer.Services.Dvr;
|
||||||
using NostrStreamer.Services.StreamManager;
|
using NostrStreamer.Services.StreamManager;
|
||||||
|
|
||||||
namespace NostrStreamer.Controllers;
|
namespace NostrStreamer.Controllers;
|
||||||
|
|
||||||
|
[Authorize(AuthenticationSchemes = NostrAuth.Scheme, Roles = NostrAuth.RoleAdmin)]
|
||||||
[Route("/api/admin")]
|
[Route("/api/admin")]
|
||||||
public class AdminController : Controller
|
public class AdminController : Controller
|
||||||
{
|
{
|
||||||
private readonly ILogger<AdminController> _logger;
|
private readonly ILogger<AdminController> _logger;
|
||||||
private readonly StreamManagerFactory _streamManagerFactory;
|
private readonly StreamManagerFactory _streamManagerFactory;
|
||||||
|
private readonly IDvrStore _dvrStore;
|
||||||
|
private readonly UserService _userService;
|
||||||
|
|
||||||
public AdminController(ILogger<AdminController> logger, StreamManagerFactory streamManagerFactory)
|
public AdminController(ILogger<AdminController> logger, StreamManagerFactory streamManagerFactory, IDvrStore dvrStore,
|
||||||
|
UserService userService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_streamManagerFactory = streamManagerFactory;
|
_streamManagerFactory = streamManagerFactory;
|
||||||
|
_dvrStore = dvrStore;
|
||||||
|
_userService = userService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPatch("stream/{id:guid}")]
|
[HttpPatch("stream/{id:guid}")]
|
||||||
@ -21,4 +31,26 @@ public class AdminController : Controller
|
|||||||
var stream = await _streamManagerFactory.ForStream(id);
|
var stream = await _streamManagerFactory.ForStream(id);
|
||||||
await stream.UpdateEvent();
|
await stream.UpdateEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpDelete("stream/{id:guid}")]
|
||||||
|
public async Task DeleteEvent([FromRoute] Guid id)
|
||||||
|
{
|
||||||
|
var mgr = await _streamManagerFactory.ForStream(id);
|
||||||
|
var stream = mgr.GetStream();
|
||||||
|
await _dvrStore.DeleteRecordings(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("account/{pubkey}")]
|
||||||
|
public async Task UpdateAccount([FromRoute] string pubkey, [FromBody] PatchAccount req)
|
||||||
|
{
|
||||||
|
if (req.Blocked.HasValue)
|
||||||
|
{
|
||||||
|
await _userService.SetBlocked(pubkey, req.Blocked.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.Admin.HasValue)
|
||||||
|
{
|
||||||
|
await _userService.SetAdmin(pubkey, req.Admin.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,7 +136,7 @@ public class NostrController : Controller
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (patch.AcceptTos)
|
if (patch.AcceptTos.HasValue && patch.AcceptTos.Value)
|
||||||
{
|
{
|
||||||
await _userService.AcceptTos(user.PubKey);
|
await _userService.AcceptTos(user.PubKey);
|
||||||
}
|
}
|
||||||
@ -267,7 +267,7 @@ public class NostrController : Controller
|
|||||||
var existing = await _db.PushSubscriptions.FirstOrDefaultAsync(a => a.Key == sub.Key);
|
var existing = await _db.PushSubscriptions.FirstOrDefaultAsync(a => a.Key == sub.Key);
|
||||||
if (existing != default)
|
if (existing != default)
|
||||||
{
|
{
|
||||||
return Json(new { id = existing.Id });
|
return Json(new {id = existing.Id});
|
||||||
}
|
}
|
||||||
|
|
||||||
var newId = Guid.NewGuid();
|
var newId = Guid.NewGuid();
|
||||||
@ -298,7 +298,7 @@ public class NostrController : Controller
|
|||||||
|
|
||||||
var sub = await _db.PushSubscriptionTargets
|
var sub = await _db.PushSubscriptionTargets
|
||||||
.Join(_db.PushSubscriptions, a => a.SubscriberPubkey, b => b.Pubkey,
|
.Join(_db.PushSubscriptions, a => a.SubscriberPubkey, b => b.Pubkey,
|
||||||
(a, b) => new { a.SubscriberPubkey, a.TargetPubkey, b.Auth })
|
(a, b) => new {a.SubscriberPubkey, a.TargetPubkey, b.Auth})
|
||||||
.Where(a => a.SubscriberPubkey == userPubkey && a.Auth == auth)
|
.Where(a => a.SubscriberPubkey == userPubkey && a.Auth == auth)
|
||||||
.Select(a => a.TargetPubkey)
|
.Select(a => a.TargetPubkey)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
@ -22,5 +22,7 @@ public class UserConfiguration : IEntityTypeConfiguration<User>
|
|||||||
builder.Property(a => a.Tags);
|
builder.Property(a => a.Tags);
|
||||||
builder.Property(a => a.ContentWarning);
|
builder.Property(a => a.ContentWarning);
|
||||||
builder.Property(a => a.Goal);
|
builder.Property(a => a.Goal);
|
||||||
|
builder.Property(a => a.IsAdmin);
|
||||||
|
builder.Property(a => a.IsBlocked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,16 @@ public class User
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public uint Version { get; set; }
|
public uint Version { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User is service admin
|
||||||
|
/// </summary>
|
||||||
|
public bool IsAdmin { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User is blocked and cannot stream
|
||||||
|
/// </summary>
|
||||||
|
public bool IsBlocked { get; set; }
|
||||||
|
|
||||||
public List<Payment> Payments { get; init; } = new();
|
public List<Payment> Payments { get; init; } = new();
|
||||||
public List<UserStream> Streams { get; init; } = new();
|
public List<UserStream> Streams { get; init; } = new();
|
||||||
public List<UserStreamForwards> Forwards { get; init; } = new();
|
public List<UserStreamForwards> Forwards { get; init; } = new();
|
||||||
|
475
NostrStreamer/Migrations/20240826103113_AccountFlags.Designer.cs
generated
Normal file
475
NostrStreamer/Migrations/20240826103113_AccountFlags.Designer.cs
generated
Normal file
@ -0,0 +1,475 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using NostrStreamer.Database;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NostrStreamer.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(StreamerContext))]
|
||||||
|
[Migration("20240826103113_AccountFlags")]
|
||||||
|
partial class AccountFlags
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "7.0.8")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.IngestEndpoint", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("App")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<List<string>>("Capabilities")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<int>("Cost")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Forward")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("App")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Endpoints");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.Payment", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("PaymentHash")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric(20,0)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<decimal>("Fee")
|
||||||
|
.HasColumnType("numeric(20,0)");
|
||||||
|
|
||||||
|
b.Property<string>("Invoice")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("IsPaid")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Nostr")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PubKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("PaymentHash");
|
||||||
|
|
||||||
|
b.HasIndex("PubKey");
|
||||||
|
|
||||||
|
b.ToTable("Payments");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.PushSubscription", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Auth")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Endpoint")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastUsed")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Pubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("Scope")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("PushSubscriptions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.PushSubscriptionTarget", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("SubscriberPubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("TargetPubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TargetPubkey");
|
||||||
|
|
||||||
|
b.HasIndex("SubscriberPubkey", "TargetPubkey")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("PushSubscriptionTargets");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("PubKey")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("Balance")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("ContentWarning")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Goal")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Image")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAdmin")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<bool>("IsBlocked")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("StreamKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Tags")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("TosAccepted")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<uint>("Version")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("xid")
|
||||||
|
.HasColumnName("xmin");
|
||||||
|
|
||||||
|
b.HasKey("PubKey");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStream", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("EdgeIp")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("EndpointId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("Ends")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Event")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ForwardClientId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSegment")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<decimal>("Length")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal>("MilliSatsCollected")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("PubKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Starts")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("State")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("StreamId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Thumbnail")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("EndpointId");
|
||||||
|
|
||||||
|
b.HasIndex("PubKey");
|
||||||
|
|
||||||
|
b.ToTable("Streams");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamClip", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("TakenByPubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserStreamId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserStreamId");
|
||||||
|
|
||||||
|
b.ToTable("Clips");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamForwards", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Target")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UserPubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserPubkey");
|
||||||
|
|
||||||
|
b.ToTable("Forwards");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamGuest", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("PubKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Relay")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Role")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Sig")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("StreamId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<decimal>("ZapSplit")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StreamId", "PubKey")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Guests");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<double>("Duration")
|
||||||
|
.HasColumnType("double precision");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Timestamp")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserStreamId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserStreamId");
|
||||||
|
|
||||||
|
b.ToTable("Recordings");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.Payment", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NostrStreamer.Database.User", "User")
|
||||||
|
.WithMany("Payments")
|
||||||
|
.HasForeignKey("PubKey")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStream", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NostrStreamer.Database.IngestEndpoint", "Endpoint")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("EndpointId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("NostrStreamer.Database.User", "User")
|
||||||
|
.WithMany("Streams")
|
||||||
|
.HasForeignKey("PubKey")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Endpoint");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamClip", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NostrStreamer.Database.UserStream", "UserStream")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserStreamId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("UserStream");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamForwards", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NostrStreamer.Database.User", "User")
|
||||||
|
.WithMany("Forwards")
|
||||||
|
.HasForeignKey("UserPubkey")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamGuest", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NostrStreamer.Database.UserStream", "Stream")
|
||||||
|
.WithMany("Guests")
|
||||||
|
.HasForeignKey("StreamId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Stream");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NostrStreamer.Database.UserStream", "Stream")
|
||||||
|
.WithMany("Recordings")
|
||||||
|
.HasForeignKey("UserStreamId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Stream");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.User", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Forwards");
|
||||||
|
|
||||||
|
b.Navigation("Payments");
|
||||||
|
|
||||||
|
b.Navigation("Streams");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStream", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Guests");
|
||||||
|
|
||||||
|
b.Navigation("Recordings");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
NostrStreamer/Migrations/20240826103113_AccountFlags.cs
Normal file
40
NostrStreamer/Migrations/20240826103113_AccountFlags.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NostrStreamer.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AccountFlags : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "IsAdmin",
|
||||||
|
table: "Users",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "IsBlocked",
|
||||||
|
table: "Users",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "IsAdmin",
|
||||||
|
table: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "IsBlocked",
|
||||||
|
table: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -174,6 +174,12 @@ namespace NostrStreamer.Migrations
|
|||||||
b.Property<string>("Image")
|
b.Property<string>("Image")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAdmin")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<bool>("IsBlocked")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
b.Property<string>("StreamKey")
|
b.Property<string>("StreamKey")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
@ -2,16 +2,19 @@ using System.Security.Claims;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Nostr.Client.Json;
|
using Nostr.Client.Json;
|
||||||
using Nostr.Client.Messages;
|
using Nostr.Client.Messages;
|
||||||
|
using NostrStreamer.Database;
|
||||||
|
|
||||||
namespace NostrStreamer;
|
namespace NostrStreamer;
|
||||||
|
|
||||||
public static class NostrAuth
|
public static class NostrAuth
|
||||||
{
|
{
|
||||||
public const string Scheme = "Nostr";
|
public const string Scheme = "Nostr";
|
||||||
|
public const string RoleAdmin = "admin";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NostrAuthOptions : AuthenticationSchemeOptions
|
public class NostrAuthOptions : AuthenticationSchemeOptions
|
||||||
@ -20,9 +23,12 @@ public class NostrAuthOptions : AuthenticationSchemeOptions
|
|||||||
|
|
||||||
public class NostrAuthHandler : AuthenticationHandler<NostrAuthOptions>
|
public class NostrAuthHandler : AuthenticationHandler<NostrAuthOptions>
|
||||||
{
|
{
|
||||||
public NostrAuthHandler(IOptionsMonitor<NostrAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) :
|
private readonly StreamerContext _db;
|
||||||
base(options, logger, encoder, clock)
|
public NostrAuthHandler(IOptionsMonitor<NostrAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock,
|
||||||
|
StreamerContext db)
|
||||||
|
: base(options, logger, encoder, clock)
|
||||||
{
|
{
|
||||||
|
_db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||||
@ -69,14 +75,14 @@ public class NostrAuthHandler : AuthenticationHandler<NostrAuthOptions>
|
|||||||
|
|
||||||
var urlTag = ev.Tags!.FirstOrDefault(a => a.TagIdentifier == "u");
|
var urlTag = ev.Tags!.FirstOrDefault(a => a.TagIdentifier == "u");
|
||||||
var methodTag = ev.Tags!.FirstOrDefault(a => a.TagIdentifier == "method");
|
var methodTag = ev.Tags!.FirstOrDefault(a => a.TagIdentifier == "method");
|
||||||
if (string.IsNullOrEmpty(urlTag?.AdditionalData[0] as string) ||
|
if (string.IsNullOrEmpty(urlTag?.AdditionalData[0]) ||
|
||||||
!new Uri((urlTag.AdditionalData[0] as string)!).AbsolutePath.Equals(Request.Path, StringComparison.InvariantCultureIgnoreCase))
|
!new Uri(urlTag.AdditionalData[0]).AbsolutePath.Equals(Request.Path, StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
return AuthenticateResult.Fail("Invalid nostr event, url tag invalid");
|
return AuthenticateResult.Fail("Invalid nostr event, url tag invalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(methodTag?.AdditionalData[0] as string) ||
|
if (string.IsNullOrEmpty(methodTag?.AdditionalData[0]) ||
|
||||||
!((methodTag.AdditionalData[0] as string)?.Equals(Request.Method, StringComparison.InvariantCultureIgnoreCase) ?? false))
|
!methodTag.AdditionalData[0].Equals(Request.Method, StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
return AuthenticateResult.Fail("Invalid nostr event, method tag invalid");
|
return AuthenticateResult.Fail("Invalid nostr event, method tag invalid");
|
||||||
}
|
}
|
||||||
@ -86,6 +92,15 @@ public class NostrAuthHandler : AuthenticationHandler<NostrAuthOptions>
|
|||||||
new Claim(ClaimTypes.Name, ev.Pubkey!)
|
new Claim(ClaimTypes.Name, ev.Pubkey!)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var user = await _db.Users
|
||||||
|
.AsNoTracking()
|
||||||
|
.SingleOrDefaultAsync(a => a.PubKey == ev.Pubkey!);
|
||||||
|
|
||||||
|
if (user is {IsAdmin: true})
|
||||||
|
{
|
||||||
|
principal.AddClaim(new Claim(ClaimTypes.Role, NostrAuth.RoleAdmin));
|
||||||
|
}
|
||||||
|
|
||||||
return AuthenticateResult.Success(new(new ClaimsPrincipal(new[] {principal}), Scheme.Name));
|
return AuthenticateResult.Success(new(new ClaimsPrincipal(new[] {principal}), Scheme.Name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,11 @@ public class StreamManagerFactory
|
|||||||
throw new Exception("TOS not accepted");
|
throw new Exception("TOS not accepted");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user.IsBlocked)
|
||||||
|
{
|
||||||
|
throw new Exception("User account blocked");
|
||||||
|
}
|
||||||
|
|
||||||
var existingLive = await _db.Streams
|
var existingLive = await _db.Streams
|
||||||
.SingleOrDefaultAsync(a => a.State == UserStreamState.Live && a.PubKey == user.PubKey);
|
.SingleOrDefaultAsync(a => a.State == UserStreamState.Live && a.PubKey == user.PubKey);
|
||||||
|
|
||||||
|
@ -105,6 +105,7 @@ public class UserService
|
|||||||
await _db.Users
|
await _db.Users
|
||||||
.Where(a => a.PubKey == pubkey)
|
.Where(a => a.PubKey == pubkey)
|
||||||
.ExecuteUpdateAsync(p => p.SetProperty(o => o.Balance, b => b.Balance - pr.MinimumAmount.MilliSatoshi));
|
.ExecuteUpdateAsync(p => p.SetProperty(o => o.Balance, b => b.Balance - pr.MinimumAmount.MilliSatoshi));
|
||||||
|
|
||||||
_db.Payments.Add(new()
|
_db.Payments.Add(new()
|
||||||
{
|
{
|
||||||
PubKey = pubkey,
|
PubKey = pubkey,
|
||||||
@ -134,6 +135,7 @@ public class UserService
|
|||||||
await _db.Users
|
await _db.Users
|
||||||
.Where(a => a.PubKey == pubkey)
|
.Where(a => a.PubKey == pubkey)
|
||||||
.ExecuteUpdateAsync(p => p.SetProperty(o => o.Balance, b => b.Balance - result.FeeSat));
|
.ExecuteUpdateAsync(p => p.SetProperty(o => o.Balance, b => b.Balance - result.FeeSat));
|
||||||
|
|
||||||
return (result.FeeMsat, result.PaymentPreimage);
|
return (result.FeeMsat, result.PaymentPreimage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +147,7 @@ public class UserService
|
|||||||
await _db.Users
|
await _db.Users
|
||||||
.Where(a => a.PubKey == pubkey)
|
.Where(a => a.PubKey == pubkey)
|
||||||
.ExecuteUpdateAsync(p => p.SetProperty(o => o.Balance, b => b.Balance + pr.MinimumAmount.MilliSatoshi));
|
.ExecuteUpdateAsync(p => p.SetProperty(o => o.Balance, b => b.Balance + pr.MinimumAmount.MilliSatoshi));
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,6 +221,18 @@ public class UserService
|
|||||||
if (change != 1) throw new Exception($"Failed to accept TOS, {change} rows updated.");
|
if (change != 1) throw new Exception($"Failed to accept TOS, {change} rows updated.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SetBlocked(string pubkey, bool val)
|
||||||
|
{
|
||||||
|
await _db.Users.Where(a => a.PubKey.Equals(pubkey))
|
||||||
|
.ExecuteUpdateAsync(o => o.SetProperty(v => v.IsBlocked, val));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetAdmin(string pubkey, bool val)
|
||||||
|
{
|
||||||
|
await _db.Users.Where(a => a.PubKey.Equals(pubkey))
|
||||||
|
.ExecuteUpdateAsync(o => o.SetProperty(v => v.IsAdmin, val));
|
||||||
|
}
|
||||||
|
|
||||||
public async Task AddForward(string pubkey, string name, string dest)
|
public async Task AddForward(string pubkey, string name, string dest)
|
||||||
{
|
{
|
||||||
var protector = _dataProtector.CreateProtector("forward-targets");
|
var protector = _dataProtector.CreateProtector("forward-targets");
|
||||||
|
Reference in New Issue
Block a user