:drunk: AI did something

This commit is contained in:
2025-07-08 00:08:35 +08:00
parent 0d47716713
commit 2c67472894
43 changed files with 221 additions and 97 deletions

View File

@@ -1,6 +1,9 @@
using MagicOnion;
using MagicOnion.Server;
using System.Text.Json; using System.Text.Json;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Services;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NodaTime; using NodaTime;
@@ -9,7 +12,7 @@ namespace DysonNetwork.Pass.Permission;
public class PermissionService( public class PermissionService(
AppDatabase db, AppDatabase db,
ICacheService cache ICacheService cache
) ) : ServiceBase<IPermissionService>, IPermissionService
{ {
private static readonly TimeSpan CacheExpiration = TimeSpan.FromMinutes(1); private static readonly TimeSpan CacheExpiration = TimeSpan.FromMinutes(1);
@@ -195,4 +198,11 @@ public class PermissionService(
Value = _SerializePermissionValue(value), Value = _SerializePermissionValue(value),
}; };
} }
public async UnaryResult<bool> CheckPermission(string scope, string permission)
{
// Assuming the actor is always "user:current" for client-side checks
// You might need to adjust this based on how your client identifies itself
return await HasPermissionAsync("user:current", scope, permission);
}
} }

View File

@@ -14,8 +14,9 @@
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="dotnet-etcd" Version="8.0.1" /> <PackageReference Include="dotnet-etcd" Version="8.0.1" />
<PackageReference Include="MagicOnion.Client" Version="7.0.5" /> <PackageReference Include="MagicOnion.Client" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" /> <PackageReference Include="MagicOnion.Server" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />

View File

@@ -0,0 +1,49 @@
using MagicOnion.Server.Filters;
using MagicOnion.Server;
using DysonNetwork.Shared.Services;
using Microsoft.Extensions.DependencyInjection;
using DysonNetwork.Shared.Models;
using System.Threading.Tasks;
using System;
namespace DysonNetwork.Shared.Permission;
public class MagicOnionPermissionFilter : IMagicOnionFilter<RequiredPermissionAttribute>
{
private readonly IPermissionService _permissionService;
public MagicOnionPermissionFilter(IPermissionService permissionService)
{
_permissionService = permissionService;
}
public async ValueTask Invoke(ServiceContext context, RequiredPermissionAttribute attribute, Func<ServiceContext, ValueTask> next)
{
var httpContext = context.GetHttpContext();
if (httpContext == null)
{
throw new InvalidOperationException("HttpContext is not available in ServiceContext.");
}
if (httpContext.Items["CurrentUser"] is not Account currentUser)
{
throw new ReturnStatusException(MagicOnion.Grpc.StatusCode.PermissionDenied, "Unauthorized: Current user not found.");
}
if (currentUser.IsSuperuser)
{
await next(context);
return;
}
var actor = $"user:{currentUser.Id}";
var hasPermission = await _permissionService.CheckPermission(actor, attribute.Scope, attribute.Permission);
if (!hasPermission)
{
throw new ReturnStatusException(MagicOnion.Grpc.StatusCode.PermissionDenied, $"Permission {attribute.Scope}/{attribute.Permission} was required.");
}
await next(context);
}
}

View File

@@ -0,0 +1,18 @@
using System;
using MagicOnion.Server.Filters;
namespace DysonNetwork.Shared.Permission;
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class RequiredPermissionAttribute : MagicOnionFilterAttribute
{
public string Scope { get; }
public string Permission { get; }
public RequiredPermissionAttribute(string scope, string permission) : base(typeof(MagicOnionPermissionFilter))
{
Scope = scope;
Permission = permission;
Order = 999; // Ensure this runs after authentication filters
}
}

View File

@@ -0,0 +1,8 @@
using MagicOnion;
namespace DysonNetwork.Shared.Services;
public interface IPermissionService : IService<IPermissionService>
{
UnaryResult<bool> CheckPermission(string scope, string permission);
}

View File

@@ -1,4 +1,4 @@
using DysonNetwork.Sphere.Account; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Connection.WebReader; using DysonNetwork.Sphere.Connection.WebReader;
using DysonNetwork.Sphere.Discovery; using DysonNetwork.Sphere.Discovery;
using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Post;
@@ -11,7 +11,7 @@ namespace DysonNetwork.Sphere.Activity;
public class ActivityService( public class ActivityService(
AppDatabase db, AppDatabase db,
PublisherService pub, PublisherService pub,
RelationshipService rels, DysonNetwork.Shared.Services.IRelationshipService rels,
PostService ps, PostService ps,
DiscoveryService ds DiscoveryService ds
) )

View File

@@ -1,6 +1,7 @@
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Connection.WebReader;
using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Post;
using DysonNetwork.Sphere.Sticker; using DysonNetwork.Sphere.Sticker;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -9,15 +10,6 @@ using Microsoft.EntityFrameworkCore.Query;
using NodaTime; using NodaTime;
using Quartz; using Quartz;
namespace DysonNetwork.Sphere;
public abstract class ModelBase
{
public Instant CreatedAt { get; set; }
public Instant UpdatedAt { get; set; }
public Instant? DeletedAt { get; set; }
}
public class AppDatabase( public class AppDatabase(
DbContextOptions<AppDatabase> options, DbContextOptions<AppDatabase> options,
IConfiguration configuration IConfiguration configuration
@@ -26,18 +18,18 @@ public class AppDatabase(
public DbSet<CloudFile> Files { get; set; } public DbSet<CloudFile> Files { get; set; }
public DbSet<CloudFileReference> FileReferences { get; set; } public DbSet<CloudFileReference> FileReferences { get; set; }
public DbSet<Shared.Models.Publisher> Publishers { get; set; } public DbSet<Publisher> Publishers { get; set; }
public DbSet<PublisherMember> PublisherMembers { get; set; } public DbSet<PublisherMember> PublisherMembers { get; set; }
public DbSet<PublisherSubscription> PublisherSubscriptions { get; set; } public DbSet<PublisherSubscription> PublisherSubscriptions { get; set; }
public DbSet<PublisherFeature> PublisherFeatures { get; set; } public DbSet<PublisherFeature> PublisherFeatures { get; set; }
public DbSet<Post.Post> Posts { get; set; } public DbSet<Post> Posts { get; set; }
public DbSet<PostReaction> PostReactions { get; set; } public DbSet<PostReaction> PostReactions { get; set; }
public DbSet<PostTag> PostTags { get; set; } public DbSet<PostTag> PostTags { get; set; }
public DbSet<PostCategory> PostCategories { get; set; } public DbSet<PostCategory> PostCategories { get; set; }
public DbSet<PostCollection> PostCollections { get; set; } public DbSet<PostCollection> PostCollections { get; set; }
public DbSet<Shared.Models.Realm> Realms { get; set; } public DbSet<Realm> Realms { get; set; }
public DbSet<RealmMember> RealmMembers { get; set; } public DbSet<RealmMember> RealmMembers { get; set; }
public DbSet<ChatRoom> ChatRooms { get; set; } public DbSet<ChatRoom> ChatRooms { get; set; }
@@ -46,10 +38,10 @@ public class AppDatabase(
public DbSet<RealtimeCall> ChatRealtimeCall { get; set; } public DbSet<RealtimeCall> ChatRealtimeCall { get; set; }
public DbSet<MessageReaction> ChatReactions { get; set; } public DbSet<MessageReaction> ChatReactions { get; set; }
public DbSet<Sticker.Sticker> Stickers { get; set; } public DbSet<Sticker> Stickers { get; set; }
public DbSet<StickerPack> StickerPacks { get; set; } public DbSet<StickerPack> StickerPacks { get; set; }
public DbSet<Shared.Models.Wallet> Wallets { get; set; } public DbSet<Wallet> Wallets { get; set; }
public DbSet<WalletPocket> WalletPockets { get; set; } public DbSet<WalletPocket> WalletPockets { get; set; }
public DbSet<Order> PaymentOrders { get; set; } public DbSet<Order> PaymentOrders { get; set; }
public DbSet<Transaction> PaymentTransactions { get; set; } public DbSet<Transaction> PaymentTransactions { get; set; }
@@ -59,8 +51,8 @@ public class AppDatabase(
public DbSet<Subscription> WalletSubscriptions { get; set; } public DbSet<Subscription> WalletSubscriptions { get; set; }
public DbSet<Coupon> WalletCoupons { get; set; } public DbSet<Coupon> WalletCoupons { get; set; }
public DbSet<Connection.WebReader.WebArticle> WebArticles { get; set; } public DbSet<WebArticle> WebArticles { get; set; }
public DbSet<Connection.WebReader.WebFeed> WebFeeds { get; set; } public DbSet<WebFeed> WebFeeds { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
@@ -103,7 +95,7 @@ public class AppDatabase(
.HasForeignKey(ps => ps.AccountId) .HasForeignKey(ps => ps.AccountId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Post.Post>() modelBuilder.Entity<Post>()
.HasGeneratedTsVectorColumn(p => p.SearchVector, "simple", p => new { p.Title, p.Description, p.Content }) .HasGeneratedTsVectorColumn(p => p.SearchVector, "simple", p => new { p.Title, p.Description, p.Content })
.HasIndex(p => p.SearchVector) .HasIndex(p => p.SearchVector)
.HasMethod("GIN"); .HasMethod("GIN");
@@ -118,25 +110,25 @@ public class AppDatabase(
.HasForeignKey(s => s.AppId) .HasForeignKey(s => s.AppId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Post.Post>() modelBuilder.Entity<Post>()
.HasOne(p => p.RepliedPost) .HasOne(p => p.RepliedPost)
.WithMany() .WithMany()
.HasForeignKey(p => p.RepliedPostId) .HasForeignKey(p => p.RepliedPostId)
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Post.Post>() modelBuilder.Entity<Post>()
.HasOne(p => p.ForwardedPost) .HasOne(p => p.ForwardedPost)
.WithMany() .WithMany()
.HasForeignKey(p => p.ForwardedPostId) .HasForeignKey(p => p.ForwardedPostId)
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Post.Post>() modelBuilder.Entity<Post>()
.HasMany(p => p.Tags) .HasMany(p => p.Tags)
.WithMany(t => t.Posts) .WithMany(t => t.Posts)
.UsingEntity(j => j.ToTable("post_tag_links")); .UsingEntity(j => j.ToTable("post_tag_links"));
modelBuilder.Entity<Post.Post>() modelBuilder.Entity<Post>()
.HasMany(p => p.Categories) .HasMany(p => p.Categories)
.WithMany(c => c.Posts) .WithMany(c => c.Posts)
.UsingEntity(j => j.ToTable("post_category_links")); .UsingEntity(j => j.ToTable("post_category_links"));
modelBuilder.Entity<Post.Post>() modelBuilder.Entity<Post>()
.HasMany(p => p.Collections) .HasMany(p => p.Collections)
.WithMany(c => c.Posts) .WithMany(c => c.Posts)
.UsingEntity(j => j.ToTable("post_collection_links")); .UsingEntity(j => j.ToTable("post_collection_links"));
@@ -189,11 +181,11 @@ public class AppDatabase(
.HasForeignKey(m => m.SenderId) .HasForeignKey(m => m.SenderId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Connection.WebReader.WebFeed>() modelBuilder.Entity<WebFeed>()
.HasIndex(f => f.Url) .HasIndex(f => f.Url)
.IsUnique(); .IsUnique();
modelBuilder.Entity<Connection.WebReader.WebArticle>() modelBuilder.Entity<WebArticle>()
.HasIndex(a => a.Url) .HasIndex(a => a.Url)
.IsUnique(); .IsUnique();

View File

@@ -137,7 +137,7 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ
[HttpPost("{roomId:guid}/messages")] [HttpPost("{roomId:guid}/messages")]
[Authorize] [Authorize]
[RequiredPermission("global", "chat.messages.create")] [RequiredPermissionAttribute("global", "chat.messages.create")]
public async Task<ActionResult> SendMessage([FromBody] SendMessageRequest request, Guid roomId) public async Task<ActionResult> SendMessage([FromBody] SendMessageRequest request, Guid roomId)
{ {
if (HttpContext.Items["CurrentUser"] is not Shared.Models.Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not Shared.Models.Account currentUser) return Unauthorized();

View File

@@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Account; using DysonNetwork.Shared.Services;
using DysonNetwork.Sphere.Localization; using DysonNetwork.Sphere.Localization;
using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Permission;
using DysonNetwork.Sphere.Realm; using DysonNetwork.Sphere.Realm;
@@ -20,11 +20,10 @@ public class ChatRoomController(
FileReferenceService fileRefService, FileReferenceService fileRefService,
ChatRoomService crs, ChatRoomService crs,
RealmService rs, RealmService rs,
ActionLogService als, DysonNetwork.Shared.Services.IActionLogService als,
NotificationService nty, DysonNetwork.Shared.Services.INotificationService nty,
RelationshipService rels, DysonNetwork.Shared.Services.IRelationshipService rels,
IStringLocalizer<NotificationResource> localizer, DysonNetwork.Shared.Services.IAccountEventService aes
AccountEventService aes
) : ControllerBase ) : ControllerBase
{ {
[HttpGet("{id:guid}")] [HttpGet("{id:guid}")]
@@ -162,7 +161,7 @@ public class ChatRoomController(
[HttpPost] [HttpPost]
[Authorize] [Authorize]
[RequiredPermission("global", "chat.create")] [RequiredPermissionAttribute("global", "chat.create")]
public async Task<ActionResult<ChatRoom>> CreateChatRoom(ChatRoomRequest request) public async Task<ActionResult<ChatRoom>> CreateChatRoom(ChatRoomRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Shared.Models.Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not Shared.Models.Account currentUser) return Unauthorized();

View File

@@ -1,6 +1,5 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Chat.Realtime; using DysonNetwork.Sphere.Chat.Realtime;
using DysonNetwork.Sphere.Connection; using DysonNetwork.Sphere.Connection;
using DysonNetwork.Sphere.Storage; using DysonNetwork.Sphere.Storage;

View File

@@ -59,7 +59,7 @@ public class WebReaderController(WebReaderService reader, ILogger<WebReaderContr
/// </summary> /// </summary>
[HttpDelete("link/cache")] [HttpDelete("link/cache")]
[Authorize] [Authorize]
[RequiredPermission("maintenance", "cache.scrap")] [RequiredPermissionAttribute("maintenance", "cache.scrap")]
public async Task<IActionResult> InvalidateCache([FromQuery] string url) public async Task<IActionResult> InvalidateCache([FromQuery] string url)
{ {
if (string.IsNullOrEmpty(url)) if (string.IsNullOrEmpty(url))
@@ -76,7 +76,7 @@ public class WebReaderController(WebReaderService reader, ILogger<WebReaderContr
/// </summary> /// </summary>
[HttpDelete("cache/all")] [HttpDelete("cache/all")]
[Authorize] [Authorize]
[RequiredPermission("maintenance", "cache.scrap")] [RequiredPermissionAttribute("maintenance", "cache.scrap")]
public async Task<IActionResult> InvalidateAllCache() public async Task<IActionResult> InvalidateAllCache()
{ {
await reader.InvalidateAllCachedPreviewsAsync(); await reader.InvalidateAllCachedPreviewsAsync();

View File

@@ -1,6 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Publisher; using DysonNetwork.Sphere.Publisher;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;

View File

@@ -1,5 +1,4 @@
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Permission;
using DysonNetwork.Sphere.Publisher; using DysonNetwork.Sphere.Publisher;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -14,7 +13,7 @@ namespace DysonNetwork.Sphere.Developer;
public class DeveloperController( public class DeveloperController(
AppDatabase db, AppDatabase db,
PublisherService ps, PublisherService ps,
ActionLogService als DysonNetwork.Shared.Services.IActionLogService als
) )
: ControllerBase : ControllerBase
{ {
@@ -91,7 +90,7 @@ public class DeveloperController(
[HttpPost("{name}/enroll")] [HttpPost("{name}/enroll")]
[Authorize] [Authorize]
[RequiredPermission("global", "developers.create")] [RequiredPermissionAttribute("global", "developers.create")]
public async Task<ActionResult<Shared.Models.Publisher>> EnrollDeveloperProgram(string name) public async Task<ActionResult<Shared.Models.Publisher>> EnrollDeveloperProgram(string name)
{ {
if (HttpContext.Items["CurrentUser"] is not Shared.Models.Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not Shared.Models.Account currentUser) return Unauthorized();

View File

@@ -172,6 +172,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" /> <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
<ProjectReference Include="..\DysonNetwork.Pass\DysonNetwork.Pass.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,4 +1,4 @@
using DysonNetwork.Sphere.Auth; using DysonNetwork.Pass.Auth;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;

View File

@@ -1,14 +1,14 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using DysonNetwork.Sphere.Auth.OidcProvider.Services; using DysonNetwork.Pass.Auth;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Auth.OidcProvider.Responses; using DysonNetwork.Pass.Auth;
using DysonNetwork.Sphere.Developer; using DysonNetwork.Sphere.Developer;
namespace DysonNetwork.Sphere.Pages.Auth; namespace DysonNetwork.Sphere.Pages.Auth;
public class AuthorizeModel(OidcProviderService oidcService, IConfiguration configuration) : PageModel public class AuthorizeModel( DysonNetwork.Pass.Auth.OidcProvider.Services.OidcProviderService oidcService, IConfiguration configuration) : PageModel
{ {
[BindProperty(SupportsGet = true)] public string? ReturnUrl { get; set; } [BindProperty(SupportsGet = true)] public string? ReturnUrl { get; set; }

View File

@@ -2,8 +2,8 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Auth; using DysonNetwork.Pass.Auth;
using DysonNetwork.Sphere.Account; using DysonNetwork.Shared.Services;
using DysonNetwork.Sphere.Connection; using DysonNetwork.Sphere.Connection;
using NodaTime; using NodaTime;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -12,10 +12,10 @@ namespace DysonNetwork.Sphere.Pages.Auth
{ {
public class LoginModel( public class LoginModel(
AppDatabase db, AppDatabase db,
AccountService accounts, DysonNetwork.Shared.Services.IAccountService accounts,
AuthService auth, DysonNetwork.Pass.Auth.AuthService auth,
GeoIpService geo, GeoIpService geo,
ActionLogService als DysonNetwork.Shared.Services.IActionLogService als
) : PageModel ) : PageModel
{ {
[BindProperty] [Required] public string Username { get; set; } = string.Empty; [BindProperty] [Required] public string Username { get; set; } = string.Empty;

View File

@@ -1,6 +1,5 @@
@page "/web/auth/challenge/{id:guid}/select-factor" @page "/web/auth/challenge/{id:guid}/select-factor"
@using DysonNetwork.Shared.Models @using DysonNetwork.Shared.Models
@using DysonNetwork.Sphere.Account
@model DysonNetwork.Sphere.Pages.Auth.SelectFactorModel @model DysonNetwork.Sphere.Pages.Auth.SelectFactorModel
@{ @{
ViewData["Title"] = "Select Authentication Method"; ViewData["Title"] = "Select Authentication Method";

View File

@@ -2,8 +2,8 @@ using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using DysonNetwork.Sphere.Auth; using DysonNetwork.Pass.Auth;
using DysonNetwork.Sphere.Account; using DysonNetwork.Pass.Account;
namespace DysonNetwork.Sphere.Pages.Auth; namespace DysonNetwork.Sphere.Pages.Auth;

View File

@@ -1,6 +1,6 @@
@page "/web/auth/challenge/{id:guid}/verify/{factorId:guid}" @page "/web/auth/challenge/{id:guid}/verify/{factorId:guid}"
@using DysonNetwork.Shared.Models @using DysonNetwork.Shared.Models
@using DysonNetwork.Sphere.Account @using DysonNetwork.Shared.Models
@model DysonNetwork.Sphere.Pages.Auth.VerifyFactorModel @model DysonNetwork.Sphere.Pages.Auth.VerifyFactorModel
@{ @{
ViewData["Title"] = "Verify Your Identity"; ViewData["Title"] = "Verify Your Identity";

View File

@@ -3,18 +3,18 @@ using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using DysonNetwork.Sphere.Auth; using DysonNetwork.Shared.Services;
using DysonNetwork.Sphere.Account; using DysonNetwork.Shared.Services;
using NodaTime; using NodaTime;
namespace DysonNetwork.Sphere.Pages.Auth namespace DysonNetwork.Sphere.Pages.Auth
{ {
public class VerifyFactorModel( public class VerifyFactorModel(
AppDatabase db, AppDatabase db,
AccountService accounts, DysonNetwork.Shared.Services.IAccountService accountService,
AuthService auth, DysonNetwork.Pass.Auth.AuthService authService,
ActionLogService als, DysonNetwork.Shared.Services.IActionLogService actionLogService,
IConfiguration configuration, IConfiguration configuration,
IHttpClientFactory httpClientFactory IHttpClientFactory httpClientFactory
) )
: PageModel : PageModel

View File

@@ -1,4 +1,4 @@
@using DysonNetwork.Sphere.Auth using DysonNetwork.Pass.Auth;
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="h-full"> <html lang="en" class="h-full">
<head> <head>

View File

@@ -1,5 +1,5 @@
@page "/spells/{spellWord}" @page "/spells/{spellWord}"
@using DysonNetwork.Sphere.Account @using DysonNetwork.Shared.Models
@model DysonNetwork.Sphere.Pages.Spell.MagicSpellPage @model DysonNetwork.Sphere.Pages.Spell.MagicSpellPage
@{ @{

View File

@@ -1,4 +1,4 @@
using DysonNetwork.Sphere.Account; using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -6,7 +6,7 @@ using NodaTime;
namespace DysonNetwork.Sphere.Pages.Spell; namespace DysonNetwork.Sphere.Pages.Spell;
public class MagicSpellPage(AppDatabase db, MagicSpellService spells) : PageModel public class MagicSpellPage(AppDatabase db, DysonNetwork.Shared.Services.IMagicSpellService magicSpellService) : PageModel
{ {
[BindProperty] public MagicSpell? CurrentSpell { get; set; } [BindProperty] public MagicSpell? CurrentSpell { get; set; }
[BindProperty] public string? NewPassword { get; set; } [BindProperty] public string? NewPassword { get; set; }

View File

@@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using DysonNetwork.Shared.Services;
using MagicOnion;
namespace DysonNetwork.Sphere.Permission;
public class RequiredPermissionAttribute : TypeFilterAttribute
{
public RequiredPermissionAttribute(string scope, string permission) : base(typeof(RequiredPermissionFilter))
{
Arguments = new object[] { scope, permission };
}
private class RequiredPermissionFilter : IAsyncActionFilter
{
private readonly IPermissionService _permissionService;
private readonly string _scope;
private readonly string _permission;
public RequiredPermissionFilter(IPermissionService permissionService, string scope, string permission)
{
_permissionService = permissionService;
_scope = scope;
_permission = permission;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Assuming the actor is always "user:current" for client-side checks
// You might need to adjust this based on how your client identifies itself
var hasPermission = await _permissionService.CheckPermission(_scope, _permission);
if (!hasPermission)
{
context.Result = new ForbidResult();
return;
}
await next();
}
}
}

View File

@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text.Json; using System.Text.Json;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Account; using DysonNetwork.Shared.Services;
using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Permission;
using DysonNetwork.Sphere.Publisher; using DysonNetwork.Sphere.Publisher;
using DysonNetwork.Sphere.Storage; using DysonNetwork.Sphere.Storage;
@@ -19,8 +19,8 @@ public class PostController(
AppDatabase db, AppDatabase db,
PostService ps, PostService ps,
PublisherService pub, PublisherService pub,
RelationshipService rels, DysonNetwork.Shared.Services.IRelationshipService rels,
ActionLogService als DysonNetwork.Shared.Services.IActionLogService als
) )
: ControllerBase : ControllerBase
{ {
@@ -280,7 +280,7 @@ public class PostController(
[HttpPost("{id:guid}/reactions")] [HttpPost("{id:guid}/reactions")]
[Authorize] [Authorize]
[RequiredPermission("global", "posts.react")] [RequiredPermissionAttribute("global", "posts.react")]
public async Task<ActionResult<PostReaction>> ReactPost(Guid id, [FromBody] PostReactionRequest request) public async Task<ActionResult<PostReaction>> ReactPost(Guid id, [FromBody] PostReactionRequest request)
{ {
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);

View File

@@ -1,6 +1,6 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Sphere.Account; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Connection.WebReader; using DysonNetwork.Sphere.Connection.WebReader;
using DysonNetwork.Sphere.Localization; using DysonNetwork.Sphere.Localization;
using DysonNetwork.Sphere.Publisher; using DysonNetwork.Sphere.Publisher;

View File

@@ -48,7 +48,7 @@ var tusDiskStore = app.Services.GetRequiredService<TusDiskStore>();
app.ConfigureAppMiddleware(builder.Configuration, tusDiskStore); app.ConfigureAppMiddleware(builder.Configuration, tusDiskStore);
// Map gRPC services // Map gRPC services
app.MapGrpcService<DysonNetwork.Sphere.Auth.AuthGrpcService>(); app.MapGrpcService<DysonNetwork.Pass.Auth.AuthGrpcService>();
app.MapGrpcService<DysonNetwork.Sphere.Account.AccountGrpcService>(); app.MapGrpcService<DysonNetwork.Pass.Account.AccountGrpcService>();
app.Run(); app.Run();

View File

@@ -1,6 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Permission;
using DysonNetwork.Sphere.Realm; using DysonNetwork.Sphere.Realm;
using DysonNetwork.Sphere.Storage; using DysonNetwork.Sphere.Storage;
@@ -17,7 +16,7 @@ public class PublisherController(
AppDatabase db, AppDatabase db,
PublisherService ps, PublisherService ps,
FileReferenceService fileRefService, FileReferenceService fileRefService,
ActionLogService als) DysonNetwork.Shared.Services.IActionLogService als)
: ControllerBase : ControllerBase
{ {
[HttpGet("{name}")] [HttpGet("{name}")]
@@ -531,7 +530,7 @@ public class PublisherController(
[HttpPost("{name}/features")] [HttpPost("{name}/features")]
[Authorize] [Authorize]
[RequiredPermission("maintenance", "publishers.features")] [RequiredPermissionAttribute("maintenance", "publishers.features")]
public async Task<ActionResult<PublisherFeature>> AddPublisherFeature(string name, public async Task<ActionResult<PublisherFeature>> AddPublisherFeature(string name,
[FromBody] PublisherFeatureRequest request) [FromBody] PublisherFeatureRequest request)
{ {
@@ -555,7 +554,7 @@ public class PublisherController(
[HttpDelete("{name}/features/{flag}")] [HttpDelete("{name}/features/{flag}")]
[Authorize] [Authorize]
[RequiredPermission("maintenance", "publishers.features")] [RequiredPermissionAttribute("maintenance", "publishers.features")]
public async Task<ActionResult> RemovePublisherFeature(string name, string flag) public async Task<ActionResult> RemovePublisherFeature(string name, string flag)
{ {
var publisher = await db.Publishers var publisher = await db.Publishers

View File

@@ -1,6 +1,6 @@
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Account; using DysonNetwork.Pass.Account;
using DysonNetwork.Sphere.Localization; using DysonNetwork.Sphere.Localization;
using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Post;
using DysonNetwork.Sphere.Storage; using DysonNetwork.Sphere.Storage;

View File

@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Account; using DysonNetwork.Pass.Account;
using DysonNetwork.Sphere.Storage; using DysonNetwork.Sphere.Storage;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View File

@@ -1,12 +1,11 @@
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Localization; using DysonNetwork.Sphere.Localization;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
namespace DysonNetwork.Sphere.Realm; namespace DysonNetwork.Sphere.Realm;
public class RealmService(AppDatabase db, NotificationService nty, IStringLocalizer<NotificationResource> localizer) public class RealmService(AppDatabase db, DysonNetwork.Shared.Services.INotificationService nty, IStringLocalizer<NotificationResource> localizer)
{ {
public async Task SendInviteNotify(RealmMember member) public async Task SendInviteNotify(RealmMember member)
{ {

View File

@@ -1,5 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using DysonNetwork.Sphere.Account; using DysonNetwork.Pass.Account;
using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Permission;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -122,7 +122,7 @@ public class AbuseReportController(
[HttpPost("{id}/resolve")] [HttpPost("{id}/resolve")]
[Authorize] [Authorize]
[RequiredPermission("safety", "reports.resolve")] [RequiredPermissionAttribute("safety", "reports.resolve")]
[ProducesResponseType<AbuseReport>(StatusCodes.Status200OK)] [ProducesResponseType<AbuseReport>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<AbuseReport>> ResolveReport(Guid id, [FromBody] ResolveReportRequest request) public async Task<ActionResult<AbuseReport>> ResolveReport(Guid id, [FromBody] ResolveReportRequest request)

View File

@@ -1,4 +1,4 @@
using DysonNetwork.Sphere.Account; using DysonNetwork.Pass.Account;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NodaTime; using NodaTime;

View File

@@ -27,6 +27,8 @@ using DysonNetwork.Sphere.Discovery;
using DysonNetwork.Sphere.Safety; using DysonNetwork.Sphere.Safety;
using DysonNetwork.Sphere.Wallet.PaymentHandlers; using DysonNetwork.Sphere.Wallet.PaymentHandlers;
using tusdotnet.Stores; using tusdotnet.Stores;
using DysonNetwork.Shared.Etcd;
using DysonNetwork.Shared.Services;
namespace DysonNetwork.Sphere.Startup; namespace DysonNetwork.Sphere.Startup;
@@ -206,6 +208,15 @@ public static class ServiceCollectionExtensions
services.AddScoped<SafetyService>(); services.AddScoped<SafetyService>();
services.AddScoped<DiscoveryService>(); services.AddScoped<DiscoveryService>();
services.AddScoped<CustomAppService>(); services.AddScoped<CustomAppService>();
// Add MagicOnion services
services.AddMagicOnionService<IAccountService>();
services.AddMagicOnionService<IAccountEventService>();
services.AddMagicOnionService<IAccountUsernameService>();
services.AddMagicOnionService<IActionLogService>();
services.AddMagicOnionService<IMagicSpellService>();
services.AddMagicOnionService<INotificationService>();
services.AddMagicOnionService<IRelationshipService>();
return services; return services;
} }

View File

@@ -271,7 +271,7 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa
public const int MaxStickersPerPack = 24; public const int MaxStickersPerPack = 24;
[HttpPost("{packId:guid}/content")] [HttpPost("{packId:guid}/content")]
[RequiredPermission("global", "stickers.create")] [RequiredPermissionAttribute("global", "stickers.create")]
public async Task<IActionResult> CreateSticker(Guid packId, [FromBody] StickerRequest request) public async Task<IActionResult> CreateSticker(Guid packId, [FromBody] StickerRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Shared.Models.Account currentUser) if (HttpContext.Items["CurrentUser"] is not Shared.Models.Account currentUser)

View File

@@ -111,7 +111,7 @@ public class FileController(
[HttpPost("/maintenance/migrateReferences")] [HttpPost("/maintenance/migrateReferences")]
[Authorize] [Authorize]
[RequiredPermission("maintenance", "files.references")] [RequiredPermissionAttribute("maintenance", "files.references")]
public async Task<ActionResult> MigrateFileReferences() public async Task<ActionResult> MigrateFileReferences()
{ {
await rms.ScanAndMigrateReferences(); await rms.ScanAndMigrateReferences();

View File

@@ -1,5 +1,4 @@
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Account;
using EFCore.BulkExtensions; using EFCore.BulkExtensions;
using Quartz; using Quartz;

View File

@@ -1,7 +1,7 @@
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using DysonNetwork.Sphere.Permission; using DysonNetwork.Pass.Permission;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using tusdotnet.Interfaces; using tusdotnet.Interfaces;
@@ -10,7 +10,7 @@ using tusdotnet.Models.Configuration;
namespace DysonNetwork.Sphere.Storage; namespace DysonNetwork.Sphere.Storage;
public abstract class TusService public class TusService(DefaultTusConfiguration config, ITusStore store)
{ {
public static DefaultTusConfiguration BuildConfiguration(ITusStore store) => new() public static DefaultTusConfiguration BuildConfiguration(ITusStore store) => new()
{ {

View File

@@ -1,5 +1,5 @@
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Auth; using DysonNetwork.Pass.Auth;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;

View File

@@ -1,6 +1,5 @@
using System.Globalization; using System.Globalization;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Localization; using DysonNetwork.Sphere.Localization;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage;
@@ -12,7 +11,7 @@ namespace DysonNetwork.Sphere.Wallet;
public class PaymentService( public class PaymentService(
AppDatabase db, AppDatabase db,
WalletService wat, WalletService wat,
NotificationService nty, DysonNetwork.Shared.Services.INotificationService nty,
IStringLocalizer<NotificationResource> localizer IStringLocalizer<NotificationResource> localizer
) )
{ {

View File

@@ -1,7 +1,7 @@
using System.Text.Json; using System.Text.Json;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.Account; using DysonNetwork.Shared.Services;
using DysonNetwork.Sphere.Localization; using DysonNetwork.Sphere.Localization;
using DysonNetwork.Sphere.Storage; using DysonNetwork.Sphere.Storage;
using DysonNetwork.Sphere.Wallet.PaymentHandlers; using DysonNetwork.Sphere.Wallet.PaymentHandlers;
@@ -14,8 +14,8 @@ namespace DysonNetwork.Sphere.Wallet;
public class SubscriptionService( public class SubscriptionService(
AppDatabase db, AppDatabase db,
PaymentService payment, PaymentService payment,
AccountService accounts, DysonNetwork.Shared.Services.IAccountService account,
NotificationService nty, DysonNetwork.Shared.Services.INotificationService nty,
IStringLocalizer<NotificationResource> localizer, IStringLocalizer<NotificationResource> localizer,
IConfiguration configuration, IConfiguration configuration,
ICacheService cache, ICacheService cache,

View File

@@ -75,7 +75,7 @@ public class WalletController(AppDatabase db, WalletService ws, PaymentService p
[HttpPost("balance")] [HttpPost("balance")]
[Authorize] [Authorize]
[RequiredPermission("maintenance", "wallets.balance.modify")] [RequiredPermissionAttribute("maintenance", "wallets.balance.modify")]
public async Task<ActionResult<Transaction>> ModifyWalletBalance([FromBody] WalletBalanceRequest request) public async Task<ActionResult<Transaction>> ModifyWalletBalance([FromBody] WalletBalanceRequest request)
{ {
var wallet = await ws.GetWalletAsync(request.AccountId); var wallet = await ws.GetWalletAsync(request.AccountId);