feat(api): public API POC — API-key auth + /api/v1 decode & OEM xref
Kapalı-beta public API temeli (rapor: api-widget-analizi-2026-07-03): - better-auth apiKey plugin (defaultPrefix sase_, enableMetadata, anahtar başına 120 istek/dk); enableSessionForAPIKeys KAPALI — anahtar dashboard oturumu yerine geçmez, cookie AuthGuard davranışı değişmez - apikeys tablosu (migration 0023, plugin şemasının birebir karşılığı) - ApiKeyGuard: Authorization Bearer / x-api-key → verifyApiKey → kullanıcı yüklenir (status kontrolü), request.user + request.apiKey doldurulur - /api/v1/vin/decode: mevcut slim kontrat + aday akışı; günlük başarılı-decode kotası (Redis, TR günü, aynı VIN idempotent; limit: key metadata dailyDecodeLimit → PUBLIC_API_DAILY_DECODE_LIMIT → 100) ve X-Decode-Quota-* header'ları - /api/v1/oem/:code: P çapraz-referans (kotasız) - internal/admin/api-keys: x-internal-token ile anahtar üret/listele/kapat (düz metin yalnız create cevabında) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
||||
import { generateReferralCode, normalizeName } from "@sase/shared";
|
||||
import { betterAuth } from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { captcha } from "better-auth/plugins";
|
||||
import { apiKey, captcha } from "better-auth/plugins";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
@@ -224,6 +224,21 @@ export function createAuth(
|
||||
"http://localhost:4000",
|
||||
],
|
||||
plugins: [
|
||||
// Public API anahtarları (api.sase.tr / /api/v1). Doğrulama ApiKeyGuard
|
||||
// üzerinden auth.api.verifyApiKey ile yapılır; enableSessionForAPIKeys
|
||||
// bilinçli olarak KAPALI — API anahtarı dashboard oturumu yerine geçmez,
|
||||
// cookie tabanlı AuthGuard'ın davranışı değişmez.
|
||||
apiKey({
|
||||
defaultPrefix: "sase_",
|
||||
enableMetadata: true,
|
||||
// Anahtar başına istek tavanı (verify başına sayaç). Günlük decode
|
||||
// kotasından ayrıdır — o, PublicApiQuotaService'te Redis ile tutulur.
|
||||
rateLimit: {
|
||||
enabled: true,
|
||||
timeWindow: 60_000, // 1 dakika
|
||||
maxRequests: 120,
|
||||
},
|
||||
}),
|
||||
// Cloudflare Turnstile: yalnızca secret tanımlıysa aktif. sign-in/sign-up
|
||||
// uçları "x-captcha-response" header'ındaki token ile doğrulanır.
|
||||
...(process.env.TURNSTILE_SECRET_KEY
|
||||
@@ -247,3 +262,42 @@ export function getAuth() {
|
||||
}
|
||||
return authInstance;
|
||||
}
|
||||
|
||||
/** apiKey plugin'inin döndürdüğü anahtar kaydı (verify/create cevapları). */
|
||||
export interface ApiKeyRecord {
|
||||
id: string;
|
||||
userId: string;
|
||||
name: string | null;
|
||||
start: string | null;
|
||||
prefix: string | null;
|
||||
enabled: boolean;
|
||||
expiresAt: Date | null;
|
||||
metadata: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* apiKey plugin uçları. `authInstance` geniş `ReturnType<typeof betterAuth>`
|
||||
* ile tiplendiği için plugin uçları tipte görünmüyor (runtime'da mevcutlar);
|
||||
* bu erişimci onları dar ve doğru imzalarla expose eder.
|
||||
*/
|
||||
export function getApiKeyApi() {
|
||||
return getAuth().api as unknown as {
|
||||
verifyApiKey: (input: { body: { key: string } }) => Promise<{
|
||||
valid: boolean;
|
||||
error: { message?: string; code: string } | null;
|
||||
key: ApiKeyRecord | null;
|
||||
}>;
|
||||
createApiKey: (input: {
|
||||
body: {
|
||||
userId: string;
|
||||
name?: string;
|
||||
prefix?: string;
|
||||
expiresIn?: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
rateLimitEnabled?: boolean;
|
||||
rateLimitTimeWindow?: number;
|
||||
rateLimitMax?: number;
|
||||
};
|
||||
}) => Promise<ApiKeyRecord & { key: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user