remove API key requirement from POST /api/shorten
This commit is contained in:
@@ -124,15 +124,14 @@ Health check endpoint.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### `POST /api/shorten` 🔒
|
### `POST /api/shorten`
|
||||||
Create a new short URL.
|
Create a new short URL. No API key required.
|
||||||
|
|
||||||
Fields can be passed as **query parameters** (URL-encoded) or in a **JSON request body**.
|
Fields can be passed as **query parameters** (URL-encoded) or in a **JSON request body**.
|
||||||
Query parameters take precedence over body fields.
|
Query parameters take precedence over body fields.
|
||||||
|
|
||||||
| Field | Required | Description |
|
| Field | Required | Description |
|
||||||
|------------------|----------|-----------------------------------------------------------|
|
|------------------|----------|-----------------------------------------------------------|
|
||||||
| `api_key` | ✅ | API key for authentication |
|
|
||||||
| `url` | ✅ | The URL to shorten (must start with `http://` or `https://`) |
|
| `url` | ✅ | The URL to shorten (must start with `http://` or `https://`) |
|
||||||
| `retention_days` | | Override the default retention period for this URL |
|
| `retention_days` | | Override the default retention period for this URL |
|
||||||
|
|
||||||
@@ -221,10 +220,10 @@ Increments `visit_count` on each hit.
|
|||||||
# Shorten a URL (JSON body)
|
# Shorten a URL (JSON body)
|
||||||
curl -X POST http://localhost:8080/s/api/shorten \
|
curl -X POST http://localhost:8080/s/api/shorten \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"api_key": "change-this-secret-key", "url": "https://github.com"}'
|
-d '{"url": "https://github.com"}'
|
||||||
|
|
||||||
# Shorten a URL (query parameters)
|
# Shorten a URL (query parameters)
|
||||||
curl -X POST "http://localhost:8080/s/api/shorten?api_key=change-this-secret-key&url=https%3A%2F%2Fgithub.com&retention_days=30"
|
curl -X POST "http://localhost:8080/s/api/shorten?url=https%3A%2F%2Fgithub.com&retention_days=30"
|
||||||
|
|
||||||
# Follow the redirect
|
# Follow the redirect
|
||||||
curl -L http://localhost:8080/s/aB3xYz
|
curl -L http://localhost:8080/s/aB3xYz
|
||||||
@@ -248,7 +247,7 @@ curl "http://localhost:8080/s/api/lookup?url=https%3A%2F%2Fexample.com"
|
|||||||
|
|
||||||
A clean single-page frontend is included in `static/`. It provides:
|
A clean single-page frontend is included in `static/`. It provides:
|
||||||
|
|
||||||
- **URL shortening** — paste a URL and press Enter to create a short URL (requires API key)
|
- **URL shortening** — paste a URL and press Enter to create a short URL
|
||||||
- **Existing URL lookup** — as you type a URL, the frontend checks if it already exists and shows its metadata
|
- **Existing URL lookup** — as you type a URL, the frontend checks if it already exists and shows its metadata
|
||||||
- **Admin table** — enter a valid API key to see all shortened URLs in a sortable, paginated table
|
- **Admin table** — enter a valid API key to see all shortened URLs in a sortable, paginated table
|
||||||
- **Copy & Delete** — per-row copy and delete buttons (delete on hover only)
|
- **Copy & Delete** — per-row copy and delete buttons (delete on hover only)
|
||||||
|
|||||||
+18
-10
@@ -218,6 +218,7 @@ async function lookupUrl(url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleEnter() {
|
async function handleEnter() {
|
||||||
|
clearTimeout(_lookupTimer); // cancel any pending debounced lookup
|
||||||
const input = document.getElementById('url-input');
|
const input = document.getElementById('url-input');
|
||||||
const url = input.value.trim();
|
const url = input.value.trim();
|
||||||
if (!url || !isValidUrl(url)) {
|
if (!url || !isValidUrl(url)) {
|
||||||
@@ -225,17 +226,27 @@ async function handleEnter() {
|
|||||||
setStatus('', '');
|
setStatus('', '');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!state.apiKey) {
|
|
||||||
// No API key — just do a lookup
|
// First, check if this URL already exists
|
||||||
await lookupUrl(url);
|
setStatus('checking…', '');
|
||||||
return;
|
try {
|
||||||
}
|
const lookupRes = await apiGet('/api/lookup?url=' + encodeURIComponent(url));
|
||||||
|
if (lookupRes.ok) {
|
||||||
|
// URL already exists — just show its metadata
|
||||||
|
const data = await lookupRes.json();
|
||||||
|
state.currentMeta = data;
|
||||||
|
renderMeta(data);
|
||||||
|
setStatus('exists', 'ok');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch { /* lookup failed, proceed to create */ }
|
||||||
|
|
||||||
|
// URL does not exist — attempt to create
|
||||||
setStatus('creating…', '');
|
setStatus('creating…', '');
|
||||||
try {
|
try {
|
||||||
const res = await apiPost('/api/shorten', { api_key: state.apiKey, url: url });
|
const res = await apiPost('/api/shorten', { url: url });
|
||||||
if (res.status === 201) {
|
if (res.status === 201) {
|
||||||
const shortUrl = await res.text();
|
const shortUrl = await res.text();
|
||||||
// Fetch metadata for the newly created URL
|
|
||||||
const code = shortUrl.split('/').pop();
|
const code = shortUrl.split('/').pop();
|
||||||
const metaRes = await apiGet('/api/urls/' + encodeURIComponent(code));
|
const metaRes = await apiGet('/api/urls/' + encodeURIComponent(code));
|
||||||
if (metaRes.ok) {
|
if (metaRes.ok) {
|
||||||
@@ -243,11 +254,8 @@ async function handleEnter() {
|
|||||||
state.currentMeta = data;
|
state.currentMeta = data;
|
||||||
renderMeta(data);
|
renderMeta(data);
|
||||||
setStatus('created ✓', 'ok');
|
setStatus('created ✓', 'ok');
|
||||||
// Refresh admin list
|
|
||||||
if (state.isAdmin) fetchAdminUrls();
|
if (state.isAdmin) fetchAdminUrls();
|
||||||
}
|
}
|
||||||
} else if (res.status === 403) {
|
|
||||||
setStatus('invalid key', 'err');
|
|
||||||
} else {
|
} else {
|
||||||
const err = await res.json().catch(() => null);
|
const err = await res.json().catch(() => null);
|
||||||
setStatus(err?.error || 'error', 'err');
|
setStatus(err?.error || 'error', 'err');
|
||||||
|
|||||||
+1
-5
@@ -6,7 +6,7 @@ Usage: python urlshort.py <config.json>
|
|||||||
API (all routes are prefixed with base_path, e.g. /s):
|
API (all routes are prefixed with base_path, e.g. /s):
|
||||||
GET / Frontend (or health check if no static/)
|
GET / Frontend (or health check if no static/)
|
||||||
GET /api/health Health check
|
GET /api/health Health check
|
||||||
POST /api/shorten Create a short URL (API key required)
|
POST /api/shorten Create a short URL (no API key required)
|
||||||
GET /api/urls List all short URLs (API key required)
|
GET /api/urls List all short URLs (API key required)
|
||||||
GET /api/urls/<code> Get info for a code (no API key required)
|
GET /api/urls/<code> Get info for a code (no API key required)
|
||||||
GET /api/lookup?url=<url> Look up by original URL (no API key required)
|
GET /api/lookup?url=<url> Look up by original URL (no API key required)
|
||||||
@@ -433,10 +433,6 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self._error(400, "Invalid JSON body")
|
self._error(400, "Invalid JSON body")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Auth: check api_key from query param or body
|
|
||||||
if not self._check_api_key(body):
|
|
||||||
self._send_empty(403)
|
|
||||||
return
|
|
||||||
|
|
||||||
# url: query param takes precedence, then body
|
# url: query param takes precedence, then body
|
||||||
original_url = qs.get("url", [""])[0] or str(body.get("url", "")).strip()
|
original_url = qs.get("url", [""])[0] or str(body.get("url", "")).strip()
|
||||||
|
|||||||
Reference in New Issue
Block a user