|
|
|
@ -17,6 +17,7 @@ import ( |
|
|
|
"lol.mleku.dev/chk" |
|
|
|
"lol.mleku.dev/chk" |
|
|
|
"next.orly.dev/app/config" |
|
|
|
"next.orly.dev/app/config" |
|
|
|
"next.orly.dev/pkg/acl" |
|
|
|
"next.orly.dev/pkg/acl" |
|
|
|
|
|
|
|
"next.orly.dev/pkg/blossom" |
|
|
|
"next.orly.dev/pkg/database" |
|
|
|
"next.orly.dev/pkg/database" |
|
|
|
"next.orly.dev/pkg/encoders/event" |
|
|
|
"next.orly.dev/pkg/encoders/event" |
|
|
|
"next.orly.dev/pkg/encoders/filter" |
|
|
|
"next.orly.dev/pkg/encoders/filter" |
|
|
|
@ -29,7 +30,6 @@ import ( |
|
|
|
"next.orly.dev/pkg/protocol/publish" |
|
|
|
"next.orly.dev/pkg/protocol/publish" |
|
|
|
"next.orly.dev/pkg/spider" |
|
|
|
"next.orly.dev/pkg/spider" |
|
|
|
dsync "next.orly.dev/pkg/sync" |
|
|
|
dsync "next.orly.dev/pkg/sync" |
|
|
|
blossom "next.orly.dev/pkg/blossom" |
|
|
|
|
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
type Server struct { |
|
|
|
type Server struct { |
|
|
|
@ -91,19 +91,9 @@ func (s *Server) isIPBlacklisted(remote string) bool { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
|
|
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
|
|
|
// Set comprehensive CORS headers for proxy compatibility
|
|
|
|
// CORS headers should be handled by the reverse proxy (Caddy/nginx)
|
|
|
|
w.Header().Set("Access-Control-Allow-Origin", "*") |
|
|
|
// to avoid duplicate headers. If running without a reverse proxy,
|
|
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") |
|
|
|
// uncomment the CORS configuration below or configure via environment variable.
|
|
|
|
w.Header().Set("Access-Control-Allow-Headers", |
|
|
|
|
|
|
|
"Origin, X-Requested-With, Content-Type, Accept, Authorization, "+ |
|
|
|
|
|
|
|
"X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host, X-Real-IP, "+ |
|
|
|
|
|
|
|
"Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, "+ |
|
|
|
|
|
|
|
"Sec-WebSocket-Protocol, Sec-WebSocket-Extensions") |
|
|
|
|
|
|
|
w.Header().Set("Access-Control-Allow-Credentials", "true") |
|
|
|
|
|
|
|
w.Header().Set("Access-Control-Max-Age", "86400") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add proxy-friendly headers
|
|
|
|
|
|
|
|
w.Header().Set("Vary", "Origin, Access-Control-Request-Method, Access-Control-Request-Headers") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Handle preflight OPTIONS requests
|
|
|
|
// Handle preflight OPTIONS requests
|
|
|
|
if r.Method == "OPTIONS" { |
|
|
|
if r.Method == "OPTIONS" { |
|
|
|
@ -245,7 +235,9 @@ func (s *Server) UserInterface() { |
|
|
|
s.mux.HandleFunc("/api/sprocket/update", s.handleSprocketUpdate) |
|
|
|
s.mux.HandleFunc("/api/sprocket/update", s.handleSprocketUpdate) |
|
|
|
s.mux.HandleFunc("/api/sprocket/restart", s.handleSprocketRestart) |
|
|
|
s.mux.HandleFunc("/api/sprocket/restart", s.handleSprocketRestart) |
|
|
|
s.mux.HandleFunc("/api/sprocket/versions", s.handleSprocketVersions) |
|
|
|
s.mux.HandleFunc("/api/sprocket/versions", s.handleSprocketVersions) |
|
|
|
s.mux.HandleFunc("/api/sprocket/delete-version", s.handleSprocketDeleteVersion) |
|
|
|
s.mux.HandleFunc( |
|
|
|
|
|
|
|
"/api/sprocket/delete-version", s.handleSprocketDeleteVersion, |
|
|
|
|
|
|
|
) |
|
|
|
s.mux.HandleFunc("/api/sprocket/config", s.handleSprocketConfig) |
|
|
|
s.mux.HandleFunc("/api/sprocket/config", s.handleSprocketConfig) |
|
|
|
// NIP-86 management endpoint
|
|
|
|
// NIP-86 management endpoint
|
|
|
|
s.mux.HandleFunc("/api/nip86", s.handleNIP86Management) |
|
|
|
s.mux.HandleFunc("/api/nip86", s.handleNIP86Management) |
|
|
|
@ -343,7 +335,9 @@ func (s *Server) handleAuthChallenge(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
|
|
|
|
|
|
|
jsonData, err := json.Marshal(response) |
|
|
|
jsonData, err := json.Marshal(response) |
|
|
|
if chk.E(err) { |
|
|
|
if chk.E(err) { |
|
|
|
http.Error(w, "Error generating challenge", http.StatusInternalServerError) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, "Error generating challenge", http.StatusInternalServerError, |
|
|
|
|
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -561,7 +555,10 @@ func (s *Server) handleExport(w http.ResponseWriter, r *http.Request) { |
|
|
|
// Check permissions - require write, admin, or owner level
|
|
|
|
// Check permissions - require write, admin, or owner level
|
|
|
|
accessLevel := acl.Registry.GetAccessLevel(pubkey, r.RemoteAddr) |
|
|
|
accessLevel := acl.Registry.GetAccessLevel(pubkey, r.RemoteAddr) |
|
|
|
if accessLevel != "write" && accessLevel != "admin" && accessLevel != "owner" { |
|
|
|
if accessLevel != "write" && accessLevel != "admin" && accessLevel != "owner" { |
|
|
|
http.Error(w, "Write, admin, or owner permission required", http.StatusForbidden) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, "Write, admin, or owner permission required", |
|
|
|
|
|
|
|
http.StatusForbidden, |
|
|
|
|
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -610,7 +607,9 @@ func (s *Server) handleExport(w http.ResponseWriter, r *http.Request) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/x-ndjson") |
|
|
|
w.Header().Set("Content-Type", "application/x-ndjson") |
|
|
|
w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"") |
|
|
|
w.Header().Set( |
|
|
|
|
|
|
|
"Content-Disposition", "attachment; filename=\""+filename+"\"", |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
// Stream export
|
|
|
|
// Stream export
|
|
|
|
s.D.Export(s.Ctx, w, pks...) |
|
|
|
s.D.Export(s.Ctx, w, pks...) |
|
|
|
@ -725,7 +724,9 @@ func (s *Server) handleImport(w http.ResponseWriter, r *http.Request) { |
|
|
|
// Check permissions - require admin or owner level
|
|
|
|
// Check permissions - require admin or owner level
|
|
|
|
accessLevel := acl.Registry.GetAccessLevel(pubkey, r.RemoteAddr) |
|
|
|
accessLevel := acl.Registry.GetAccessLevel(pubkey, r.RemoteAddr) |
|
|
|
if accessLevel != "admin" && accessLevel != "owner" { |
|
|
|
if accessLevel != "admin" && accessLevel != "owner" { |
|
|
|
http.Error(w, "Admin or owner permission required", http.StatusForbidden) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, "Admin or owner permission required", http.StatusForbidden, |
|
|
|
|
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -785,7 +786,9 @@ func (s *Server) handleSprocketStatus(w http.ResponseWriter, r *http.Request) { |
|
|
|
w.Header().Set("Content-Type", "application/json") |
|
|
|
w.Header().Set("Content-Type", "application/json") |
|
|
|
jsonData, err := json.Marshal(status) |
|
|
|
jsonData, err := json.Marshal(status) |
|
|
|
if chk.E(err) { |
|
|
|
if chk.E(err) { |
|
|
|
http.Error(w, "Error generating response", http.StatusInternalServerError) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, "Error generating response", http.StatusInternalServerError, |
|
|
|
|
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -826,7 +829,10 @@ func (s *Server) handleSprocketUpdate(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
|
|
|
|
|
|
|
// Update the sprocket script
|
|
|
|
// Update the sprocket script
|
|
|
|
if err := s.sprocketManager.UpdateSprocket(string(body)); chk.E(err) { |
|
|
|
if err := s.sprocketManager.UpdateSprocket(string(body)); chk.E(err) { |
|
|
|
http.Error(w, fmt.Sprintf("Failed to update sprocket: %v", err), http.StatusInternalServerError) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, fmt.Sprintf("Failed to update sprocket: %v", err), |
|
|
|
|
|
|
|
http.StatusInternalServerError, |
|
|
|
|
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -861,7 +867,10 @@ func (s *Server) handleSprocketRestart(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
|
|
|
|
|
|
|
// Restart the sprocket script
|
|
|
|
// Restart the sprocket script
|
|
|
|
if err := s.sprocketManager.RestartSprocket(); chk.E(err) { |
|
|
|
if err := s.sprocketManager.RestartSprocket(); chk.E(err) { |
|
|
|
http.Error(w, fmt.Sprintf("Failed to restart sprocket: %v", err), http.StatusInternalServerError) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, fmt.Sprintf("Failed to restart sprocket: %v", err), |
|
|
|
|
|
|
|
http.StatusInternalServerError, |
|
|
|
|
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -870,7 +879,9 @@ func (s *Server) handleSprocketRestart(w http.ResponseWriter, r *http.Request) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// handleSprocketVersions returns all sprocket script versions
|
|
|
|
// handleSprocketVersions returns all sprocket script versions
|
|
|
|
func (s *Server) handleSprocketVersions(w http.ResponseWriter, r *http.Request) { |
|
|
|
func (s *Server) handleSprocketVersions( |
|
|
|
|
|
|
|
w http.ResponseWriter, r *http.Request, |
|
|
|
|
|
|
|
) { |
|
|
|
if r.Method != http.MethodGet { |
|
|
|
if r.Method != http.MethodGet { |
|
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) |
|
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) |
|
|
|
return |
|
|
|
return |
|
|
|
@ -896,14 +907,19 @@ func (s *Server) handleSprocketVersions(w http.ResponseWriter, r *http.Request) |
|
|
|
|
|
|
|
|
|
|
|
versions, err := s.sprocketManager.GetSprocketVersions() |
|
|
|
versions, err := s.sprocketManager.GetSprocketVersions() |
|
|
|
if chk.E(err) { |
|
|
|
if chk.E(err) { |
|
|
|
http.Error(w, fmt.Sprintf("Failed to get sprocket versions: %v", err), http.StatusInternalServerError) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, fmt.Sprintf("Failed to get sprocket versions: %v", err), |
|
|
|
|
|
|
|
http.StatusInternalServerError, |
|
|
|
|
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json") |
|
|
|
w.Header().Set("Content-Type", "application/json") |
|
|
|
jsonData, err := json.Marshal(versions) |
|
|
|
jsonData, err := json.Marshal(versions) |
|
|
|
if chk.E(err) { |
|
|
|
if chk.E(err) { |
|
|
|
http.Error(w, "Error generating response", http.StatusInternalServerError) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, "Error generating response", http.StatusInternalServerError, |
|
|
|
|
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -911,7 +927,9 @@ func (s *Server) handleSprocketVersions(w http.ResponseWriter, r *http.Request) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// handleSprocketDeleteVersion deletes a specific sprocket version
|
|
|
|
// handleSprocketDeleteVersion deletes a specific sprocket version
|
|
|
|
func (s *Server) handleSprocketDeleteVersion(w http.ResponseWriter, r *http.Request) { |
|
|
|
func (s *Server) handleSprocketDeleteVersion( |
|
|
|
|
|
|
|
w http.ResponseWriter, r *http.Request, |
|
|
|
|
|
|
|
) { |
|
|
|
if r.Method != http.MethodPost { |
|
|
|
if r.Method != http.MethodPost { |
|
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) |
|
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) |
|
|
|
return |
|
|
|
return |
|
|
|
@ -957,7 +975,10 @@ func (s *Server) handleSprocketDeleteVersion(w http.ResponseWriter, r *http.Requ |
|
|
|
|
|
|
|
|
|
|
|
// Delete the sprocket version
|
|
|
|
// Delete the sprocket version
|
|
|
|
if err := s.sprocketManager.DeleteSprocketVersion(request.Filename); chk.E(err) { |
|
|
|
if err := s.sprocketManager.DeleteSprocketVersion(request.Filename); chk.E(err) { |
|
|
|
http.Error(w, fmt.Sprintf("Failed to delete sprocket version: %v", err), http.StatusInternalServerError) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, fmt.Sprintf("Failed to delete sprocket version: %v", err), |
|
|
|
|
|
|
|
http.StatusInternalServerError, |
|
|
|
|
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -982,7 +1003,9 @@ func (s *Server) handleSprocketConfig(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
|
|
|
|
|
|
|
jsonData, err := json.Marshal(response) |
|
|
|
jsonData, err := json.Marshal(response) |
|
|
|
if chk.E(err) { |
|
|
|
if chk.E(err) { |
|
|
|
http.Error(w, "Error generating response", http.StatusInternalServerError) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, "Error generating response", http.StatusInternalServerError, |
|
|
|
|
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -1006,7 +1029,9 @@ func (s *Server) handleACLMode(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
|
|
|
|
|
|
|
jsonData, err := json.Marshal(response) |
|
|
|
jsonData, err := json.Marshal(response) |
|
|
|
if chk.E(err) { |
|
|
|
if chk.E(err) { |
|
|
|
http.Error(w, "Error generating response", http.StatusInternalServerError) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, "Error generating response", http.StatusInternalServerError, |
|
|
|
|
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -1016,7 +1041,9 @@ func (s *Server) handleACLMode(w http.ResponseWriter, r *http.Request) { |
|
|
|
// handleSyncCurrent handles requests for the current serial number
|
|
|
|
// handleSyncCurrent handles requests for the current serial number
|
|
|
|
func (s *Server) handleSyncCurrent(w http.ResponseWriter, r *http.Request) { |
|
|
|
func (s *Server) handleSyncCurrent(w http.ResponseWriter, r *http.Request) { |
|
|
|
if s.syncManager == nil { |
|
|
|
if s.syncManager == nil { |
|
|
|
http.Error(w, "Sync manager not initialized", http.StatusServiceUnavailable) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, "Sync manager not initialized", http.StatusServiceUnavailable, |
|
|
|
|
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -1031,7 +1058,9 @@ func (s *Server) handleSyncCurrent(w http.ResponseWriter, r *http.Request) { |
|
|
|
// handleSyncEventIDs handles requests for event IDs with their serial numbers
|
|
|
|
// handleSyncEventIDs handles requests for event IDs with their serial numbers
|
|
|
|
func (s *Server) handleSyncEventIDs(w http.ResponseWriter, r *http.Request) { |
|
|
|
func (s *Server) handleSyncEventIDs(w http.ResponseWriter, r *http.Request) { |
|
|
|
if s.syncManager == nil { |
|
|
|
if s.syncManager == nil { |
|
|
|
http.Error(w, "Sync manager not initialized", http.StatusServiceUnavailable) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, "Sync manager not initialized", http.StatusServiceUnavailable, |
|
|
|
|
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -1044,12 +1073,16 @@ func (s *Server) handleSyncEventIDs(w http.ResponseWriter, r *http.Request) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// validatePeerRequest validates NIP-98 authentication and checks if the requesting peer is authorized
|
|
|
|
// validatePeerRequest validates NIP-98 authentication and checks if the requesting peer is authorized
|
|
|
|
func (s *Server) validatePeerRequest(w http.ResponseWriter, r *http.Request) bool { |
|
|
|
func (s *Server) validatePeerRequest( |
|
|
|
|
|
|
|
w http.ResponseWriter, r *http.Request, |
|
|
|
|
|
|
|
) bool { |
|
|
|
// Validate NIP-98 authentication
|
|
|
|
// Validate NIP-98 authentication
|
|
|
|
valid, pubkey, err := httpauth.CheckAuth(r) |
|
|
|
valid, pubkey, err := httpauth.CheckAuth(r) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
log.Printf("NIP-98 auth validation error: %v", err) |
|
|
|
log.Printf("NIP-98 auth validation error: %v", err) |
|
|
|
http.Error(w, "Authentication validation failed", http.StatusUnauthorized) |
|
|
|
http.Error( |
|
|
|
|
|
|
|
w, "Authentication validation failed", http.StatusUnauthorized, |
|
|
|
|
|
|
|
) |
|
|
|
return false |
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
if !valid { |
|
|
|
if !valid { |
|
|
|
|