diff options
| author | Fuwn <[email protected]> | 2026-02-07 05:03:35 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-07 05:03:35 -0800 |
| commit | 17f0f1877764e9c1d79a2b86dac8ff2110aa6a34 (patch) | |
| tree | 1cabf70d230dc614c3ee2408bdc0acaf13a502d8 /apps | |
| parent | feat: dynamically compute sidebar max width from item text widths (diff) | |
| download | asa.news-17f0f1877764e9c1d79a2b86dac8ff2110aa6a34.tar.xz asa.news-17f0f1877764e9c1d79a2b86dac8ff2110aa6a34.zip | |
fix: api key prefix rename, revoke fix, and webhook validation
Rename API key prefix from asn_ to asa_, fix key revoke by aligning
response property names with frontend interface, and add server/client
validation to prevent enabling webhooks without a URL.
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/web/app/api/v1/keys/route.ts | 27 | ||||
| -rw-r--r-- | apps/web/app/api/webhook-config/route.ts | 21 | ||||
| -rw-r--r-- | apps/web/app/reader/settings/_components/api-settings.tsx | 4 | ||||
| -rw-r--r-- | apps/web/lib/api-auth.ts | 2 | ||||
| -rw-r--r-- | apps/web/lib/api-key.ts | 2 |
5 files changed, 40 insertions, 16 deletions
diff --git a/apps/web/app/api/v1/keys/route.ts b/apps/web/app/api/v1/keys/route.ts index 1461532..de63a46 100644 --- a/apps/web/app/api/v1/keys/route.ts +++ b/apps/web/app/api/v1/keys/route.ts @@ -31,14 +31,15 @@ export async function GET() { ) } + const activeKeys = keys.filter((key) => key.revoked_at === null) + return NextResponse.json({ - keys: keys.map((key) => ({ - identifier: key.id, + keys: activeKeys.map((key) => ({ + keyIdentifier: key.id, keyPrefix: key.key_prefix, label: key.label, createdAt: key.created_at, lastUsedAt: key.last_used_at, - isRevoked: key.revoked_at !== null, })), }) } @@ -94,12 +95,16 @@ export async function POST(request: Request) { const { fullKey, keyHash, keyPrefix } = generateApiKey() - const { error: insertError } = await adminClient.from("api_keys").insert({ - user_id: user.id, - key_hash: keyHash, - key_prefix: keyPrefix, - label, - }) + const { data: insertedKey, error: insertError } = await adminClient + .from("api_keys") + .insert({ + user_id: user.id, + key_hash: keyHash, + key_prefix: keyPrefix, + label, + }) + .select("id") + .single() if (insertError) { return NextResponse.json( @@ -109,8 +114,8 @@ export async function POST(request: Request) { } return NextResponse.json({ - key: fullKey, + fullKey, keyPrefix, - label, + keyIdentifier: insertedKey.id, }) } diff --git a/apps/web/app/api/webhook-config/route.ts b/apps/web/app/api/webhook-config/route.ts index 049f4f3..bd38d0f 100644 --- a/apps/web/app/api/webhook-config/route.ts +++ b/apps/web/app/api/webhook-config/route.ts @@ -91,10 +91,29 @@ export async function PUT(request: Request) { } if (typeof body.webhookEnabled === "boolean") { - updates.webhook_enabled = body.webhookEnabled if (body.webhookEnabled) { + const { data: currentProfile } = await adminClient + .from("user_profiles") + .select("webhook_url") + .eq("id", user.id) + .single() + + const effectiveUrl = + typeof body.webhookUrl === "string" + ? body.webhookUrl.trim() + : currentProfile?.webhook_url + + if (!effectiveUrl) { + return NextResponse.json( + { error: "cannot enable webhooks without a url" }, + { status: 400 } + ) + } + updates.webhook_consecutive_failures = 0 } + + updates.webhook_enabled = body.webhookEnabled } if (Object.keys(updates).length === 0) { diff --git a/apps/web/app/reader/settings/_components/api-settings.tsx b/apps/web/app/reader/settings/_components/api-settings.tsx index 0ae6a8d..cca673f 100644 --- a/apps/web/app/reader/settings/_components/api-settings.tsx +++ b/apps/web/app/reader/settings/_components/api-settings.tsx @@ -431,7 +431,7 @@ function WebhookSection() { </button> <button onClick={handleToggleEnabled} - disabled={updateWebhookConfig.isPending} + disabled={updateWebhookConfig.isPending || (!webhookConfig?.webhookEnabled && !webhookConfig?.webhookUrl)} className="border border-border px-4 py-1.5 text-text-secondary transition-colors hover:text-text-primary disabled:opacity-50" > {webhookConfig?.webhookEnabled ? "disable" : "enable"} @@ -511,7 +511,7 @@ export function ApiSettings() { authenticate requests with an api key in the authorization header: </p> <code className="block bg-background-tertiary px-3 py-2 text-text-secondary"> - Authorization: Bearer asn_your_key_here + Authorization: Bearer asa_your_key_here </code> <div className="mt-3 space-y-1 text-text-dim"> <p>get /api/v1/profile — your account info and limits</p> diff --git a/apps/web/lib/api-auth.ts b/apps/web/lib/api-auth.ts index 3f819f2..d2efdd7 100644 --- a/apps/web/lib/api-auth.ts +++ b/apps/web/lib/api-auth.ts @@ -25,7 +25,7 @@ export async function authenticateApiRequest( const apiKey = authorizationHeader.slice(7) - if (!apiKey.startsWith("asn_")) { + if (!apiKey.startsWith("asa_")) { return { authenticated: false, status: 401, error: "invalid api key format" } } diff --git a/apps/web/lib/api-key.ts b/apps/web/lib/api-key.ts index ce59f89..f5beaac 100644 --- a/apps/web/lib/api-key.ts +++ b/apps/web/lib/api-key.ts @@ -1,6 +1,6 @@ import { randomBytes, createHash } from "crypto" -const API_KEY_PREFIX = "asn_" +const API_KEY_PREFIX = "asa_" export function generateApiKey(): { fullKey: string |