diff --git a/README.md b/README.md index 8bd0e1f..41f9e5b 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ - save and share content via CLI - up- and download in CLI possible - rate-limits +- optional auth token for paste creation **Ideas**: - integrated retention/purge function @@ -91,6 +92,24 @@ curl -s https://linedump.com/{path} | base64 -d | openssl enc -d -aes-256-cb +██ Authentication Examples ██ + +If the instance has authentication enabled, include Bearer token: + + █ curl: + +curl -H "Authorization: Bearer YOUR_TOKEN" -X POST -d "Cheers" https://linedump.com/ + + █ wget: + +wget --header="Authorization: Bearer YOUR_TOKEN" --post-data="Cheers" -O- https://linedump.com/ + + █ Powershell: + +Invoke-RestMethod -Uri "https://linedump.com/" -Headers @{"Authorization"="Bearer YOUR_TOKEN"} -Method Post -Body "Cheers" + + + ██ Adv Examples ██ @@ -148,6 +167,9 @@ podman run --replace -d --restart=unless-stopped \ | `MAX_FILE_SIZE_MB` | Maximum file size limit in megabytes | `50` | No | | `RATE_LIMIT` | Rate limit for uploads (format: "requests/timeframe") | `50/hour` | No | | `URL_PATH_LENGTH` | Length of generated URL paths (number of characters) | `6` | No | +| `UPLOAD_TOKENS` | Comma-separated list of Bearer tokens for upload authentication (if set, uploads require valid token) | _(disabled)_ | No | + +Create a secure token with: `openssl rand -base64 32`. --- diff --git a/main.py b/main.py index d4d5a99..50d0de9 100644 --- a/main.py +++ b/main.py @@ -17,6 +17,7 @@ DESCRIPTION = os.getenv('DESCRIPTION', 'CLI-only pastebin powered by linedump.co MAX_FILE_SIZE_MB = int(os.getenv('MAX_FILE_SIZE_MB', '50')) RATE_LIMIT = os.getenv('RATE_LIMIT', '50/hour') URL_PATH_LENGTH = int(os.getenv('URL_PATH_LENGTH', '6')) +UPLOAD_TOKENS = [t.strip() for t in os.getenv('UPLOAD_TOKENS', '').split(',') if t.strip()] if os.getenv('UPLOAD_TOKENS') else [] limiter = Limiter(key_func=get_remote_address) app = FastAPI(title="linedump", version="1.0.0") @@ -42,6 +43,33 @@ def get_client_ip(request: Request) -> str: return request.client.host +def validate_upload_token(request: Request) -> bool: + """Validate upload token if authentication is enabled""" + if not UPLOAD_TOKENS: + # No tokens configured, authentication is disabled + return True + + auth = request.headers.get("Authorization", "") + if not auth.startswith("Bearer "): + raise HTTPException( + status_code=401, + detail="Unauthorized", + headers={"WWW-Authenticate": "Bearer"} + ) + + token = auth[7:] # Remove "Bearer " prefix + + # Use constant-time comparison to prevent timing attacks + if not any(secrets.compare_digest(token, valid_token) for valid_token in UPLOAD_TOKENS): + raise HTTPException( + status_code=401, + detail="Unauthorized", + headers={"WWW-Authenticate": "Bearer"} + ) + + return True + + def validate_content(content: str) -> bool: """Basic validation for content size and encoding""" if len(content) > MAX_FILE_SIZE: @@ -60,7 +88,7 @@ def validate_content(content: str) -> bool: @app.post("/", response_class=PlainTextResponse) @limiter.limit(RATE_LIMIT) -async def upload_text(request: Request): +async def upload_text(request: Request, authorized: bool = Depends(validate_upload_token)): body = await request.body() content = body.decode('utf-8', errors='ignore') @@ -82,8 +110,12 @@ async def upload_text(request: Request): f.write(content) return f"{BASEURL}/{random_path}\n" - + except Exception as e: + # Log the actual error for debugging + import traceback + print(f"Error saving file: {e}") + print(traceback.format_exc()) raise HTTPException(status_code=500, detail="Failed to save file") @app.get("/{file_path}", response_class=PlainTextResponse) @@ -105,6 +137,38 @@ async def get_file(file_path: str): @app.get("/", response_class=PlainTextResponse) async def root(): + # Build authentication notice and examples if tokens are configured + auth_notice = "\n- Authentication: not required" + auth_section = "" + auth_header_curl = "" + auth_header_wget = "" + auth_header_ps = "" + + if UPLOAD_TOKENS: + auth_notice = "\n- Authentication: REQUIRED (Bearer token)" + auth_header_curl = '-H "Authorization: Bearer $LINEDUMP_TOKEN" ' + auth_header_wget = '--header="Authorization: Bearer $LINEDUMP_TOKEN" ' + auth_header_ps = ' -Headers @{"Authorization"="Bearer $env:LINEDUMP_TOKEN"}' + auth_section = f""" + +████ Authentication Examples ████ + +When authentication is enabled, include Bearer token in Authorization header: + +Set token as environment variable (recommended): +export LINEDUMP_TOKEN="your-token-here" + + █ curl: +curl -H "Authorization: Bearer $LINEDUMP_TOKEN" -X POST -d "Cheers" {BASEURL}/ + + █ wget: +wget --header="Authorization: Bearer $LINEDUMP_TOKEN" --post-data="Cheers" -O- {BASEURL}/ + + █ Powershell: +$env:LINEDUMP_TOKEN="your-token-here" +Invoke-RestMethod -Uri "{BASEURL}/" -Headers @{{"Authorization"="Bearer $env:LINEDUMP_TOKEN"}} -Method Post -Body "Cheers" +""" + return f"""LD {BASEURL} ████ General ████ @@ -114,31 +178,31 @@ async def root(): - File limit: {MAX_FILE_SIZE_MB} MB - Rate limit: {RATE_LIMIT} - text-only -- no server-side encryption, consider content public or use client-side encryption - +- no server-side encryption, consider content public or use client-side encryption{auth_notice} +{auth_section} ████ Usage ████ █ Upload curl: -curl -X POST -d "Cheers" {BASEURL}/ # string -curl -X POST {BASEURL} --data-binary @- < file.txt # file -ip -br a | curl -X POST {BASEURL} --data-binary @- # command output +curl {auth_header_curl}-X POST -d "Cheers" {BASEURL}/ # string +curl {auth_header_curl}-X POST {BASEURL} --data-binary @- < file.txt # file +ip -br a | curl {auth_header_curl}-X POST {BASEURL} --data-binary @- # command output █ Upload wget: -echo "Cheers" | wget --post-data=@- -O- {BASEURL}/ # string -wget --post-file=file.txt -O- {BASEURL}/ # file -ip -br a | wget --post-data=@- -O- {BASEURL}/ # command output +echo "Cheers" | wget {auth_header_wget}--post-data=@- -O- {BASEURL}/ # string +wget {auth_header_wget}--post-file=file.txt -O- {BASEURL}/ # file +ip -br a | wget {auth_header_wget}--post-data=@- -O- {BASEURL}/ # command output █ Upload Powershell: -Invoke-RestMethod -Uri "{BASEURL}/" -Method Post -Body "Cheers" # string -Invoke-RestMethod -Uri "{BASEURL}/" -Method Post -InFile "file.txt" # file -ipconfig | Invoke-RestMethod -Uri "{BASEURL}/" -Method Post -Body {{ $_ }} # command output +Invoke-RestMethod -Uri "{BASEURL}/"{auth_header_ps} -Method Post -Body "Cheers" # string +Invoke-RestMethod -Uri "{BASEURL}/"{auth_header_ps} -Method Post -InFile "file.txt" # file +ipconfig | Invoke-RestMethod -Uri "{BASEURL}/"{auth_header_ps} -Method Post -Body {{ $_ }} # command output █ Download: @@ -161,20 +225,20 @@ Invoke-RestMethod -Uri "{BASEURL}/{{path}}" -OutFile "filename.txt" # echo 'Cheers' \ | openssl enc -aes-256-cbc -pbkdf2 -salt -base64 -pass pass:yourkey \ - | curl -X POST -d @- {BASEURL}/ + | curl {auth_header_curl}-X POST -d @- {BASEURL}/ █ Upload file: openssl enc -aes-256-cbc -pbkdf2 -salt -pass pass:yourkey -base64 < file.txt \ - | curl -sS -X POST {BASEURL} --data-binary @- + | curl {auth_header_curl}-sS -X POST {BASEURL} --data-binary @- █ Upload command output: ip -br a \ | openssl enc -aes-256-cbc -pbkdf2 -salt -pass pass:yourkey -base64 \ - | curl -sS -X POST {BASEURL} --data-binary @- + | curl {auth_header_curl}-sS -X POST {BASEURL} --data-binary @- █ Download: @@ -193,7 +257,7 @@ curl -s {BASEURL}/{{path}} \ {{ cmd() {{ printf "\\n# %s\\n" "$*"; "$@"; }}; \\ cmd hostname; \\ cmd ip -br a; \\ - }} 2>&1 | curl -X POST {BASEURL} --data-binary @- + }} 2>&1 | curl {auth_header_curl}-X POST {BASEURL} --data-binary @- █ Continous command: @@ -201,7 +265,7 @@ curl -s {BASEURL}/{{path}} \ (timeout --signal=INT --kill-after=5s 10s \\ ping 127.1; \\ echo "--- Terminated ---") | \\ - curl -X POST --data-binary @- {BASEURL} + curl {auth_header_curl}-X POST --data-binary @- {BASEURL}