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

0
Accept: Normal file
View File

0
GET Normal file
View File

0
Host: Normal file
View File

0
Origin: Normal file
View File

0
User-Agent: Normal file
View File

Binary file not shown.

Binary file not shown.

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;
}
}
}

Binary file not shown.

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);

View File

@@ -0,0 +1 @@
test data for upload

194
tests/cors_test.sh Executable file
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