diff --git a/app/server.go b/app/server.go index cb9777e..1a2dfac 100644 --- a/app/server.go +++ b/app/server.go @@ -14,12 +14,19 @@ import ( "sync" "time" + "git.mleku.dev/mleku/nostr/encoders/event" + "git.mleku.dev/mleku/nostr/encoders/filter" + "git.mleku.dev/mleku/nostr/encoders/hex" + "git.mleku.dev/mleku/nostr/encoders/tag" + "git.mleku.dev/mleku/nostr/httpauth" + "git.mleku.dev/mleku/nostr/protocol/auth" "lol.mleku.dev/chk" "next.orly.dev/app/branding" "next.orly.dev/app/config" "next.orly.dev/pkg/acl" - acliface "next.orly.dev/pkg/interfaces/acl" + "next.orly.dev/pkg/archive" "next.orly.dev/pkg/blossom" + "next.orly.dev/pkg/bunker" "next.orly.dev/pkg/database" domainevents "next.orly.dev/pkg/domain/events" "next.orly.dev/pkg/domain/events/subscribers" @@ -29,25 +36,18 @@ import ( "next.orly.dev/pkg/event/routing" "next.orly.dev/pkg/event/specialkinds" "next.orly.dev/pkg/event/validation" - "git.mleku.dev/mleku/nostr/encoders/event" - "git.mleku.dev/mleku/nostr/encoders/filter" - "git.mleku.dev/mleku/nostr/encoders/hex" - "git.mleku.dev/mleku/nostr/encoders/tag" + acliface "next.orly.dev/pkg/interfaces/acl" "next.orly.dev/pkg/policy" - "git.mleku.dev/mleku/nostr/protocol/auth" - "git.mleku.dev/mleku/nostr/httpauth" "next.orly.dev/pkg/protocol/graph" "next.orly.dev/pkg/protocol/nip43" - "next.orly.dev/pkg/protocol/publish" - "next.orly.dev/pkg/bunker" "next.orly.dev/pkg/protocol/nrc" + "next.orly.dev/pkg/protocol/publish" "next.orly.dev/pkg/ratelimit" "next.orly.dev/pkg/spider" "next.orly.dev/pkg/storage" dsync "next.orly.dev/pkg/sync" - "next.orly.dev/pkg/wireguard" - "next.orly.dev/pkg/archive" "next.orly.dev/pkg/tor" + "next.orly.dev/pkg/wireguard" ) type Server struct { @@ -57,7 +57,7 @@ type Server struct { Config *config.C // Ctx holds the server context. // Deprecated: Use Context() method instead of accessing directly. - Ctx context.Context + Ctx context.Context publishers *publish.S // Admins holds the admin pubkeys. // Deprecated: Use IsAdmin() method instead of accessing directly. @@ -84,30 +84,30 @@ type Server struct { // Use RLock() for normal message processing, Lock() for updates messagePauseMutex sync.RWMutex - paymentProcessor *PaymentProcessor - sprocketManager *SprocketManager - policyManager *policy.P - spiderManager *spider.Spider - directorySpider *spider.DirectorySpider - syncManager *dsync.Manager - relayGroupMgr *dsync.RelayGroupManager - clusterManager *dsync.ClusterManager - blossomServer *blossom.Server - InviteManager *nip43.InviteManager - graphExecutor *graph.Executor - rateLimiter *ratelimit.Limiter - cfg *config.C - db database.Database // Changed from *database.D to interface + paymentProcessor *PaymentProcessor + sprocketManager *SprocketManager + policyManager *policy.P + spiderManager *spider.Spider + directorySpider *spider.DirectorySpider + syncManager *dsync.Manager + relayGroupMgr *dsync.RelayGroupManager + clusterManager *dsync.ClusterManager + blossomServer *blossom.Server + InviteManager *nip43.InviteManager + graphExecutor *graph.Executor + rateLimiter *ratelimit.Limiter + cfg *config.C + db database.Database // Changed from *database.D to interface // Domain services for event handling - eventValidator *validation.Service - eventAuthorizer *authorization.Service - eventRouter *routing.DefaultRouter - eventProcessor *processing.Service - eventDispatcher *domainevents.Dispatcher - ingestionService *ingestion.Service - specialKinds *specialkinds.Registry - aclRegistry acliface.Registry + eventValidator *validation.Service + eventAuthorizer *authorization.Service + eventRouter *routing.DefaultRouter + eventProcessor *processing.Service + eventDispatcher *domainevents.Dispatcher + ingestionService *ingestion.Service + specialKinds *specialkinds.Registry + aclRegistry acliface.Registry // WireGuard VPN and NIP-46 Bunker wireguardServer *wireguard.Server @@ -1118,7 +1118,7 @@ func (s *Server) handleEventsMine(w http.ResponseWriter, r *http.Request) { w.Write(jsonData) } -// handleImport receives a JSONL/NDJSON file or body and enqueues an async import using NIP-98 authentication. Admins only. +// handleImport receives a JSONL/NDJSON file or body and enqueues an async import using NIP-98 authentication. Write, admin, or owner roles required. func (s *Server) handleImport(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) @@ -1138,11 +1138,11 @@ func (s *Server) handleImport(w http.ResponseWriter, r *http.Request) { return } - // Check permissions - require admin or owner level + // Check permissions - require write, admin, or owner level accessLevel := acl.Registry.GetAccessLevel(pubkey, r.RemoteAddr) - if accessLevel != "admin" && accessLevel != "owner" { + if accessLevel != "write" && accessLevel != "admin" && accessLevel != "owner" { http.Error( - w, "Admin or owner permission required", http.StatusForbidden, + w, "Write, admin, or owner permission required", http.StatusForbidden, ) return } diff --git a/app/web/src/App.svelte b/app/web/src/App.svelte index fd75a0d..a211c4d 100644 --- a/app/web/src/App.svelte +++ b/app/web/src/App.svelte @@ -895,7 +895,7 @@ // Restore signer for extension method if (storedAuthMethod === "extension") { if (window.nostr) { - userSigner = window.nostr; + userSigner = window.nostr; } else { // Extension might not be loaded yet, try again after a short delay setTimeout(() => { @@ -921,9 +921,9 @@ async function loadRelayData() { // Fetch user role for already logged in users if (isLoggedIn) { - fetchUserRole(); + await fetchUserRole(); } - fetchACLMode(); + await fetchACLMode(); // Load sprocket configuration loadSprocketConfig(); @@ -2410,10 +2410,25 @@ async function importEvents() { // Skip login/permission check when ACL is "none" (open relay mode) - if (aclMode !== "none" && (!isLoggedIn || (userRole !== "admin" && userRole !== "owner"))) { - importMessage = "Admin or owner permission required"; - setTimeout(() => { importMessage = ""; }, 5000); - return; + if (aclMode !== "none") { + // Ensure user is logged in + if (!isLoggedIn) { + importMessage = "Please log in first"; + setTimeout(() => { importMessage = ""; }, 5000); + return; + } + + // If role is not yet loaded, fetch it first + if (!userRole && isLoggedIn && userPubkey) { + await fetchUserRole(); + } + + // Check permissions + if (userRole !== "write" && userRole !== "admin" && userRole !== "owner") { + importMessage = "Write, admin, or owner permission required"; + setTimeout(() => { importMessage = ""; }, 5000); + return; + } } if (!selectedFile) { @@ -2429,6 +2444,11 @@ // Build headers - only include auth when ACL is not "none" const headers = {}; if (aclMode !== "none" && isLoggedIn) { + // Ensure signer is available for extension users + if (!userSigner && authMethod === "extension" && window.nostr) { + userSigner = window.nostr; + console.log("Restored extension signer for import"); + } headers.Authorization = await createNIP98AuthHeader( `${getApiBase()}/api/import`, "POST", diff --git a/app/web/src/EventsView.svelte b/app/web/src/EventsView.svelte index 9b6e4f6..fa73fc6 100644 --- a/app/web/src/EventsView.svelte +++ b/app/web/src/EventsView.svelte @@ -67,8 +67,8 @@ // Check cache first if (profileCache.has(pubkey)) { return profileCache.get(pubkey); - } - + } + // Fetch profile try { const profile = await fetchUserProfile(pubkey); @@ -136,7 +136,7 @@ class="avatar-image" /> {:else} -