This commit is contained in:
Your Name
2025-10-09 10:40:46 -04:00
parent 22f6224008
commit 25a871bb31
12 changed files with 655 additions and 85 deletions
View File
View File
View File
View File
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+400 -80
View File
@@ -18,6 +18,9 @@ http {
keepalive_timeout 65;
types_hash_max_size 2048;
# Temporary file paths (relative to nginx prefix)
client_body_temp_path tmp/nginx_temp;
# MIME types (local)
include mime.types;
default_type application/octet-stream;
@@ -30,7 +33,19 @@ http {
server unix:/tmp/ginxsom-fcgi.sock;
}
# Local development server
# Map endpoints to their allowed methods for CORS
map $uri $cors_methods {
default "GET, HEAD, OPTIONS";
/upload "PUT, HEAD, OPTIONS";
/mirror "PUT, OPTIONS";
/report "PUT, OPTIONS";
/auth "GET, OPTIONS";
~^/list/ "GET, OPTIONS";
~^/api/ "GET, PUT, OPTIONS";
~^/[a-f0-9]\{64\} "GET, HEAD, DELETE, OPTIONS";
}
# Local development server - HTTP
server {
listen 9001;
server_name localhost;
@@ -41,10 +56,21 @@ http {
# Maximum upload size (adjust as needed)
client_max_body_size 100M;
# Security headers (applied to all responses)
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
# Security headers
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
# Universal CORS headers for all responses
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
add_header Access-Control-Max-Age 86400 always;
# Universal OPTIONS preflight handler
if ($request_method = OPTIONS) {
return 204;
}
# 1. SPECIFIC ENDPOINTS (most specific first)
@@ -53,6 +79,7 @@ http {
if ($request_method !~ ^(PUT|HEAD)$) {
return 405;
}
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
@@ -73,7 +100,7 @@ http {
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_pass fastcgi_backend;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# GET /list/<pubkey> (BUD-02) - List user's blobs
@@ -81,6 +108,7 @@ http {
if ($request_method !~ ^(GET)$) {
return 405;
}
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
@@ -101,7 +129,7 @@ http {
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_pass fastcgi_backend;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# PUT /mirror (BUD-04) - Mirror content
@@ -109,6 +137,7 @@ http {
if ($request_method !~ ^(PUT)$) {
return 405;
}
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
@@ -129,35 +158,7 @@ http {
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_pass fastcgi_backend;
}
# HEAD/PUT /media (BUD-05) - Media operations
location = /media {
if ($request_method !~ ^(HEAD|PUT)$) {
return 405;
}
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_pass fastcgi_backend;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# PUT /report (BUD-09) - Report content
@@ -165,6 +166,7 @@ http {
if ($request_method !~ ^(PUT)$) {
return 405;
}
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
@@ -185,24 +187,15 @@ http {
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_pass fastcgi_backend;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# GET /auth (NIP-42) - Challenge generation
location = /auth {
if ($request_method !~ ^(GET|OPTIONS)$) {
if ($request_method !~ ^(GET)$) {
return 405;
}
# Handle preflight OPTIONS requests for CORS
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Max-Age 86400;
return 204;
}
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
@@ -223,29 +216,15 @@ http {
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_pass fastcgi_backend;
# CORS headers for NIP-42 access
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# Admin API endpoints (/api/*)
location /api/ {
# Handle preflight OPTIONS requests for CORS
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, PUT, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Max-Age 86400;
return 204;
}
if ($request_method !~ ^(GET|PUT)$) {
return 405;
}
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
@@ -266,17 +245,7 @@ http {
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
# Support for Authorization header
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
fastcgi_pass fastcgi_backend;
# CORS headers for API access
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, PUT, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Max-Age 86400;
}
# 2. BLOB OPERATIONS (SHA256 patterns)
@@ -303,6 +272,10 @@ http {
# Cache headers for blob content
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
add_header Access-Control-Max-Age 86400 always;
}
# Internal handler for DELETE operations
@@ -329,36 +302,47 @@ http {
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# 3. INTERNAL HANDLERS
# FastCGI handler for HEAD requests
location ~ "^/fcgi-head/([a-f0-9]{64}).*$" {
internal;
fastcgi_pass fastcgi_backend;
fastcgi_param REQUEST_METHOD HEAD;
fastcgi_param REQUEST_URI /$1;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD HEAD;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI /$1;
fastcgi_param DOCUMENT_URI /$1;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# 4. UTILITY ENDPOINTS
# 3. UTILITY ENDPOINTS
# Health check endpoint
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
add_header Access-Control-Max-Age 86400 always;
}
# List files endpoint for debugging
@@ -371,6 +355,342 @@ http {
location = / {
return 200 "Ginxsom Blossom Server\nEndpoints: GET /<sha256>, PUT /upload, GET /list/<pubkey>\nHealth: GET /health\n";
add_header Content-Type text/plain;
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
add_header Access-Control-Max-Age 86400 always;
}
}
# Local development server - HTTPS
server {
listen 9443 ssl;
server_name localhost;
# SSL Configuration using existing certificates
ssl_certificate /home/teknari/.ssl_for_local_servers/cert.pem;
ssl_certificate_key /home/teknari/.ssl_for_local_servers/key.pem;
# SSL Security settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
# Root directory for blossom files (local blobs directory)
root blobs;
# Maximum upload size (adjust as needed)
client_max_body_size 100M;
# Security headers
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
# Universal CORS headers for all responses
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
add_header Access-Control-Max-Age 86400 always;
# Universal OPTIONS preflight handler
if ($request_method = OPTIONS) {
return 204;
}
# 1. SPECIFIC ENDPOINTS (most specific first)
# PUT /upload (BUD-02) - File uploads
location = /upload {
if ($request_method !~ ^(PUT|HEAD)$) {
return 405;
}
# CORS headers
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
add_header Access-Control-Max-Age 86400 always;
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# GET /list/<pubkey> (BUD-02) - List user's blobs
location ~ "^/list/([a-f0-9]{64})$" {
if ($request_method !~ ^(GET)$) {
return 405;
}
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# PUT /mirror (BUD-04) - Mirror content
location = /mirror {
if ($request_method !~ ^(PUT)$) {
return 405;
}
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# PUT /report (BUD-09) - Report content
location = /report {
if ($request_method !~ ^(PUT)$) {
return 405;
}
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# GET /auth (NIP-42) - Challenge generation
location = /auth {
if ($request_method !~ ^(GET)$) {
return 405;
}
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# Admin API endpoints (/api/*)
location /api/ {
if ($request_method !~ ^(GET|PUT)$) {
return 405;
}
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# 2. BLOB OPERATIONS (SHA256 patterns)
# GET/HEAD/DELETE /<sha256> (BUD-01) - Blob operations with optional file extensions
location ~ "^/([a-f0-9]{64})(\.[a-zA-Z0-9]+)?$" {
# Handle DELETE method via rewrite to avoid fastcgi_param in if block
if ($request_method = DELETE) {
rewrite ^/(.*)$ /fcgi-delete/$1 last;
}
# Route HEAD requests to FastCGI for metadata
if ($request_method = HEAD) {
rewrite ^/(.*)$ /fcgi-head/$1 last;
}
# Only allow GET for file serving at this point
if ($request_method != GET) {
return 405;
}
# GET requests - serve files directly with extension fallback
try_files /$1.txt /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.md =404;
# Cache headers for blob content
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
add_header Access-Control-Max-Age 86400 always;
}
# Internal handler for DELETE operations
location ~ "^/fcgi-delete/([a-f0-9]{64}).*$" {
internal;
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD DELETE;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI /$1;
fastcgi_param DOCUMENT_URI /$1;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# FastCGI handler for HEAD requests
location ~ "^/fcgi-head/([a-f0-9]{64}).*$" {
internal;
fastcgi_pass fastcgi_backend;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD HEAD;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI /$1;
fastcgi_param DOCUMENT_URI /$1;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# 3. UTILITY ENDPOINTS
# Health check endpoint
location /health {
access_log off;
return 200 "OK (HTTPS)\n";
add_header Content-Type text/plain;
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
add_header Access-Control-Max-Age 86400 always;
}
# List files endpoint for debugging
location /debug/list {
autoindex on;
autoindex_format json;
}
# Root redirect
location = / {
return 200 "Ginxsom Blossom Server (HTTPS)\nEndpoints: GET /<sha256>, PUT /upload, GET /list/<pubkey>\nHealth: GET /health\n";
add_header Content-Type text/plain;
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
add_header Access-Control-Max-Age 86400 always;
}
}
}
BIN
View File
Binary file not shown.
+60 -5
View File
@@ -306,6 +306,7 @@ int run_interactive_setup(const char *config_path) {
*/
// Function declarations
void handle_options_request(void);
void send_error_response(int status_code, const char *error_type,
const char *message, const char *details);
void log_request(const char *method, const char *uri, const char *auth_status,
@@ -452,6 +453,7 @@ void handle_head_request(const char *sha256) {
// Validate SHA-256 format (64 hex characters)
if (strlen(sha256) != 64) {
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Invalid SHA-256 hash format\n");
return;
@@ -460,6 +462,7 @@ void handle_head_request(const char *sha256) {
// Check if blob exists in database - this is the single source of truth
if (!get_blob_metadata(sha256, &metadata)) {
printf("Status: 404 Not Found\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Blob not found\n");
return;
@@ -467,6 +470,7 @@ void handle_head_request(const char *sha256) {
// Return successful HEAD response with metadata from database
printf("Status: 200 OK\r\n");
printf("Content-Type: %s\r\n", metadata.type);
printf("Content-Length: %ld\r\n", metadata.size);
printf("Cache-Control: public, max-age=31536000, immutable\r\n");
@@ -533,6 +537,12 @@ const char *extract_sha256_from_uri(const char *uri) {
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// BUD-01 CORS Compliance System (Now handled by nginx)
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// Enhanced error response helper functions
void send_error_response(int status_code, const char *error_type,
const char *message, const char *details) {
@@ -696,6 +706,7 @@ void handle_list_request(const char *pubkey) {
// Start JSON response
printf("Status: 200 OK\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("[\n");
@@ -911,6 +922,7 @@ void handle_delete_request_with_validation(const char *sha256, nostr_request_res
// Return success response
printf("Status: 200 OK\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"message\": \"Blob deleted successfully\",\n");
@@ -973,6 +985,7 @@ void handle_upload_request(void) {
unsigned char *file_data = malloc(content_length);
if (!file_data) {
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Memory allocation failed\n");
return;
@@ -983,6 +996,7 @@ void handle_upload_request(void) {
free(file_data);
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Failed to read complete file data\n");
return;
@@ -994,6 +1008,7 @@ void handle_upload_request(void) {
if (nostr_sha256(file_data, content_length, hash) != NOSTR_SUCCESS) {
free(file_data);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Hash calculation failed\n");
return;
@@ -1022,6 +1037,7 @@ void handle_upload_request(void) {
if (!outfile) {
free(file_data);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Failed to create file\n");
return;
@@ -1044,6 +1060,7 @@ void handle_upload_request(void) {
// Clean up partial file
unlink(filepath);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Failed to write complete file\n");
return;
@@ -1113,6 +1130,7 @@ void handle_upload_request(void) {
unlink(filepath);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Failed to store blob metadata\n");
return;
@@ -1129,6 +1147,7 @@ void handle_upload_request(void) {
// Return success response with blob descriptor
printf("Status: 200 OK\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"sha256\": \"%s\",\n", sha256_hex);
@@ -1214,6 +1233,7 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
file_data = malloc(content_length);
if (!file_data) {
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Memory allocation failed\n");
return;
@@ -1223,6 +1243,7 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
if (bytes_read != (size_t)content_length) {
free(file_data);
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Failed to read complete file data\n");
return;
@@ -1238,6 +1259,7 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
if (nostr_sha256(file_data, file_size, hash) != NOSTR_SUCCESS) {
if (should_free_file_data) free(file_data);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Hash calculation failed\n");
return;
@@ -1266,6 +1288,7 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
if (!outfile) {
if (should_free_file_data) free(file_data);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Failed to create file\n");
return;
@@ -1291,6 +1314,7 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
// Clean up partial file
unlink(filepath);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Failed to write complete file\n");
return;
@@ -1348,6 +1372,7 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
// consistency
unlink(filepath);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Failed to store blob metadata\n");
return;
@@ -1364,6 +1389,7 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
// Return success response with blob descriptor
printf("Status: 200 OK\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"sha256\": \"%s\",\n", sha256_hex);
@@ -1388,6 +1414,25 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// OPTIONS Preflight Request Handler (BUD-01 CORS)
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// Handle OPTIONS requests for CORS preflight
void handle_options_request(void) {
log_request("OPTIONS", "*", "cors_preflight", 0);
printf("Status: 200 OK\r\n");
printf("Content-Length: 0\r\n");
printf("\r\n");
// No body for OPTIONS response
log_request("OPTIONS", "*", "cors_preflight", 200);
}
// Handle GET /auth requests to provide NIP-42 challenges
void handle_auth_challenge_request(void) {
// Log the incoming request
@@ -1402,6 +1447,7 @@ void handle_auth_challenge_request(void) {
if (result != NOSTR_SUCCESS) {
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"error\": \"challenge_generation_failed\",\n");
@@ -1417,11 +1463,8 @@ void handle_auth_challenge_request(void) {
// Return the challenge as JSON
printf("Status: 200 OK\r\n");
printf("Content-Type: application/json\r\n");
printf("Access-Control-Allow-Origin: *\r\n");
printf("Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n");
printf("Access-Control-Allow-Headers: Content-Type, Authorization\r\n");
printf("\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"challenge\": \"%s\",\n", challenge_buffer);
printf(" \"relay\": \"ginxsom\",\n");
@@ -1490,11 +1533,21 @@ if (!config_loaded /* && !initialize_server_config() */) {
if (!request_method || !request_uri) {
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Invalid request\n");
continue;
}
/////////////////////////////////////////////////////////////////////
// HANDLE OPTIONS PREFLIGHT REQUESTS (BUD-01 CORS)
/////////////////////////////////////////////////////////////////////
if (strcmp(request_method, "OPTIONS") == 0) {
handle_options_request();
continue;
}
/////////////////////////////////////////////////////////////////////
// CENTRALIZED REQUEST VALIDATION SYSTEM
/////////////////////////////////////////////////////////////////////
@@ -1607,6 +1660,7 @@ if (!config_loaded /* && !initialize_server_config() */) {
log_request("HEAD", request_uri, "public", 200);
} else {
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Invalid SHA-256 hash in URI\n");
log_request("HEAD", request_uri, "none", 400);
@@ -1687,6 +1741,7 @@ if (!config_loaded /* && !initialize_server_config() */) {
} else {
// Other methods not implemented yet
printf("Status: 501 Not Implemented\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Method %s not implemented\n", request_method);
log_request(request_method, request_uri, "none", 501);
+1
View File
@@ -0,0 +1 @@
test data for upload
+194
View File
@@ -0,0 +1,194 @@
#!/bin/bash
# CORS Test Suite for Ginxsom Blossom Server
# Tests all endpoints for both HTTP and HTTPS protocols
# Validates presence of required CORS headers per BUD-01 specification
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Test configuration
HTTP_BASE="http://localhost:9001"
HTTPS_BASE="https://localhost:9443"
SAMPLE_SHA256="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" # empty string hash
SAMPLE_PUBKEY="0000000000000000000000000000000000000000000000000000000000000001"
# Required CORS headers for BUD-01 compliance
REQUIRED_HEADERS=(
"Access-Control-Allow-Origin"
"Access-Control-Allow-Methods"
"Access-Control-Allow-Headers"
"Access-Control-Max-Age"
)
# Test counters
TOTAL_TESTS=0
PASSED_TESTS=0
FAILED_TESTS=0
echo -e "${BLUE}===========================================${NC}"
echo -e "${BLUE} GINXSOM CORS TEST SUITE (BUD-01) ${NC}"
echo -e "${BLUE}===========================================${NC}"
echo ""
# Function to check CORS headers
check_cors_headers() {
local endpoint="$1"
local method="$2"
local protocol="$3"
local extra_args="$4"
TOTAL_TESTS=$((TOTAL_TESTS + 1))
echo -e "${YELLOW}Testing:${NC} $method $endpoint ($protocol)"
# Make request and capture headers
if [ "$protocol" = "HTTPS" ]; then
headers=$(curl -k -s -I -X "$method" $extra_args "$endpoint" 2>/dev/null || echo "CURL_FAILED")
else
headers=$(curl -s -I -X "$method" $extra_args "$endpoint" 2>/dev/null || echo "CURL_FAILED")
fi
if [ "$headers" = "CURL_FAILED" ]; then
echo -e " ${RED}✗ FAIL:${NC} Could not connect to $endpoint"
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
fi
# Check HTTP response is valid
if ! echo "$headers" | grep -q "HTTP/"; then
echo -e " ${RED}✗ FAIL:${NC} Invalid HTTP response"
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
fi
local all_headers_present=true
local missing_headers=()
# Check each required header
for header in "${REQUIRED_HEADERS[@]}"; do
if ! echo "$headers" | grep -qi "^$header:"; then
all_headers_present=false
missing_headers+=("$header")
fi
done
if [ "$all_headers_present" = true ]; then
echo -e " ${GREEN}✓ PASS:${NC} All CORS headers present"
# Verify specific header values
local origin_header=$(echo "$headers" | grep -i "Access-Control-Allow-Origin:" | head -1 | sed 's/.*: *//')
if [[ "$origin_header" == *"*"* ]]; then
echo -e " ${GREEN}✓${NC} Access-Control-Allow-Origin: $origin_header"
else
echo -e " ${RED}✗${NC} Access-Control-Allow-Origin should be '*', got: $origin_header"
all_headers_present=false
fi
# Check for duplicate headers (common CORS issue)
local origin_count=$(echo "$headers" | grep -ci "Access-Control-Allow-Origin:" || echo "0")
if [ "$origin_count" -gt 1 ]; then
echo -e " ${RED}✗${NC} WARNING: Multiple Access-Control-Allow-Origin headers detected ($origin_count)"
all_headers_present=false
fi
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo -e " ${RED}✗ FAIL:${NC} Missing CORS headers: ${missing_headers[*]}"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
echo ""
return 0
}
# Test endpoints for both HTTP and HTTPS
test_endpoint() {
local path="$1"
local method="$2"
local extra_args="$3"
# Test HTTP
check_cors_headers "$HTTP_BASE$path" "$method" "HTTP" "$extra_args"
# Test HTTPS
check_cors_headers "$HTTPS_BASE$path" "$method" "HTTPS" "$extra_args"
}
echo -e "${BLUE}=== TESTING CORE BLOSSOM ENDPOINTS ===${NC}"
echo ""
# 1. OPTIONS preflight tests (most critical for CORS)
echo -e "${YELLOW}--- OPTIONS Preflight Requests ---${NC}"
test_endpoint "/" "OPTIONS"
test_endpoint "/upload" "OPTIONS"
test_endpoint "/auth" "OPTIONS"
test_endpoint "/health" "OPTIONS"
test_endpoint "/mirror" "OPTIONS"
test_endpoint "/report" "OPTIONS"
test_endpoint "/$SAMPLE_SHA256" "OPTIONS"
test_endpoint "/list/$SAMPLE_PUBKEY" "OPTIONS"
test_endpoint "/api/config" "OPTIONS"
# 2. GET requests
echo -e "${YELLOW}--- GET Requests ---${NC}"
test_endpoint "/" "GET"
test_endpoint "/health" "GET"
test_endpoint "/auth" "GET"
test_endpoint "/$SAMPLE_SHA256" "GET"
test_endpoint "/list/$SAMPLE_PUBKEY" "GET"
test_endpoint "/api/config" "GET"
# 3. HEAD requests
echo -e "${YELLOW}--- HEAD Requests ---${NC}"
test_endpoint "/" "HEAD"
test_endpoint "/health" "HEAD"
test_endpoint "/$SAMPLE_SHA256" "HEAD"
test_endpoint "/upload" "HEAD"
# 4. PUT requests (will likely fail with 400/401 but should still have CORS)
echo -e "${YELLOW}--- PUT Requests (CORS on Errors) ---${NC}"
test_endpoint "/upload" "PUT" "-H 'Content-Type: text/plain'"
test_endpoint "/mirror" "PUT" "-H 'Content-Type: application/json'"
test_endpoint "/report" "PUT" "-H 'Content-Type: application/json'"
test_endpoint "/api/config" "PUT" "-H 'Content-Type: application/json'"
# 5. DELETE requests
echo -e "${YELLOW}--- DELETE Requests ---${NC}"
test_endpoint "/$SAMPLE_SHA256" "DELETE"
# 6. Edge cases and variations
echo -e "${YELLOW}--- Edge Cases ---${NC}"
test_endpoint "/nonexistent" "GET"
test_endpoint "/$SAMPLE_SHA256.txt" "GET"
test_endpoint "/$SAMPLE_SHA256.jpg" "GET"
test_endpoint "/list/invalid_pubkey" "GET"
echo -e "${BLUE}===========================================${NC}"
echo -e "${BLUE} TEST RESULTS SUMMARY ${NC}"
echo -e "${BLUE}===========================================${NC}"
echo ""
echo -e "Total Tests: $TOTAL_TESTS"
echo -e "${GREEN}Passed: $PASSED_TESTS${NC}"
echo -e "${RED}Failed: $FAILED_TESTS${NC}"
echo ""
if [ $FAILED_TESTS -eq 0 ]; then
echo -e "${GREEN}🎉 ALL TESTS PASSED! 🎉${NC}"
echo -e "${GREEN}✅ CORS implementation is BUD-01 compliant${NC}"
echo -e "${GREEN}✅ All endpoints support cross-origin requests${NC}"
echo -e "${GREEN}✅ No duplicate header issues detected${NC}"
echo ""
exit 0
else
echo -e "${RED}❌ TESTS FAILED! ❌${NC}"
echo -e "${RED}⚠️ CORS implementation needs fixes${NC}"
echo ""
exit 1
fi