this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add admin secret authentication for link deletion

Replace localhost-only restriction with configurable admin secret
for DELETE /irclink/{id} endpoint. Secret can be provided via
X-Admin-Secret header or query parameter. Falls back to localhost
check if no admin_secret is configured for backwards compatibility.

+70 -9
+32
README.md
··· 299 299 300 300 For complete API documentation, visit `/docs` on your running instance or see `internal/assets/openapi.json`. 301 301 302 + #### Link Deletion 303 + 304 + Links can be deleted via the API using the `DELETE` method on `/irclink/{id}`. This requires authentication using an admin secret. 305 + 306 + **Configuration:** 307 + 308 + Add an `admin_secret` to your `config.yaml`: 309 + 310 + ```yaml 311 + admin_secret: "your-random-secret-string" 312 + ``` 313 + 314 + You can also set it via environment variable: `TUMBLE_ADMIN_SECRET=your-secret` 315 + 316 + **Usage:** 317 + 318 + ```bash 319 + # Using X-Admin-Secret header (recommended) 320 + curl -X DELETE -H "X-Admin-Secret: your-secret" https://your-server/irclink/123 321 + 322 + # Using query parameter 323 + curl -X DELETE "https://your-server/irclink/123?secret=your-secret" 324 + ``` 325 + 326 + **Responses:** 327 + - **200 OK**: Link deleted successfully 328 + - **400 Bad Request**: Missing or invalid ID 329 + - **403 Forbidden**: Missing or invalid admin secret 330 + - **404 Not Found**: Link does not exist 331 + 332 + If no `admin_secret` is configured, deletion falls back to localhost-only access for backwards compatibility. 333 + 302 334 #### Caching 303 335 304 336 Link previews are cached in the database to reduce external requests.
+1
conf/config-test.yaml
··· 2 2 database: tumble-test.sqlite 3 3 port: "8080" 4 4 baseurl: http://localhost:8080 5 + admin_secret: "test-admin-secret" 5 6 logging: 6 7 level: debug 7 8 output: tumble.log
+1 -1
conf/config.yaml
··· 1 - config-dev-mysql.yaml 1 + config-test-sqlite.yaml
+1
internal/config/config.go
··· 18 18 Mode string `yaml:"mode" mapstructure:"mode"` 19 19 EmbedAssets bool `yaml:"embed_assets" mapstructure:"embed_assets"` 20 20 ClickSigningKey string `yaml:"click_signing_key" mapstructure:"click_signing_key"` 21 + AdminSecret string `yaml:"admin_secret" mapstructure:"admin_secret"` 21 22 Logging Logging `yaml:"logging" mapstructure:"logging"` 22 23 Caching Caching `yaml:"caching" mapstructure:"caching"` 23 24 }
+21 -2
internal/handler/irclink.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "crypto/subtle" 5 6 "encoding/json" 6 7 "fmt" 7 8 "io" ··· 38 39 url := r.URL.Query().Get("url") 39 40 40 41 if r.Method == http.MethodDelete { 41 - // Restrict DELETE to localhost only until proper auth is implemented 42 - if !isLocalhost(r) { 42 + // Authenticate DELETE requests using admin secret 43 + if !h.isAuthorizedAdmin(r) { 43 44 http.Error(w, "Forbidden", http.StatusForbidden) 44 45 return 45 46 } ··· 253 254 254 255 return false 255 256 } 257 + 258 + // isAuthorizedAdmin checks if the request contains a valid admin secret 259 + func (h *Handler) isAuthorizedAdmin(r *http.Request) bool { 260 + // If no admin secret is configured, fall back to localhost check 261 + if h.Config.AdminSecret == "" { 262 + return isLocalhost(r) 263 + } 264 + 265 + // Check X-Admin-Secret header first 266 + secret := r.Header.Get("X-Admin-Secret") 267 + if secret == "" { 268 + // Fall back to query parameter 269 + secret = r.URL.Query().Get("secret") 270 + } 271 + 272 + // Use constant-time comparison to prevent timing attacks 273 + return subtle.ConstantTimeCompare([]byte(secret), []byte(h.Config.AdminSecret)) == 1 274 + }
+3 -3
tests/api_test.sh
··· 91 91 92 92 # 7. Delete Link Test (Create -> Delete -> Verify) 93 93 echo -n "Testing DELETE /irclink/ flow... " 94 - # Create a link firs 94 + # Create a link first 95 95 CREATE_OUT=$(curl -s "$BASE_URL/irclink/?user=testdel&url=http://delete-test.com&source=irc") 96 96 # Check if we got an ID (numeric) 97 97 if [[ "$CREATE_OUT" =~ ^[0-9]+$ ]]; then 98 98 DEL_ID=$CREATE_OUT 99 - # Delete i 100 - DEL_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "$BASE_URL/irclink/?id=$DEL_ID") 99 + # Delete it (using admin secret from test config) 100 + DEL_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE -H "X-Admin-Secret: test-admin-secret" "$BASE_URL/irclink/?id=$DEL_ID") 101 101 if [ "$DEL_STATUS" == "200" ]; then 102 102 # Verify it's gone 103 103 GONE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/irclink/?id=$DEL_ID")
+11 -3
tests/delete_link.sh
··· 1 1 #!/bin/bash 2 2 # Script to delete an IRC Link 3 - # Usage: ./delete_link.sh <id> 3 + # Usage: ./delete_link.sh <id> [admin_secret] 4 + # If admin_secret is not provided, uses TUMBLE_ADMIN_SECRET env var 4 5 5 6 ID=$1 7 + SECRET=${2:-$TUMBLE_ADMIN_SECRET} 6 8 BASE_URL="http://localhost:8080" 7 9 8 10 if [ -z "$ID" ]; then 9 - echo "Usage: $0 <id>" 11 + echo "Usage: $0 <id> [admin_secret]" 12 + echo " Or set TUMBLE_ADMIN_SECRET environment variable" 10 13 exit 1 11 14 fi 12 15 13 - curl -v -X DELETE "$BASE_URL/irclink/?id=$ID" 16 + if [ -z "$SECRET" ]; then 17 + echo "Warning: No admin secret provided. Request may fail." 18 + curl -v -X DELETE "$BASE_URL/irclink/?id=$ID" 19 + else 20 + curl -v -X DELETE -H "X-Admin-Secret: $SECRET" "$BASE_URL/irclink/?id=$ID" 21 + fi 14 22 echo ""