Files
AOS/src/app/api/v1/licenses/route.ts
T
Z User d5dc2a1832 feat: Rate limiting on all public endpoints (PRD §5.6)
In-memory rate limiter with configurable limits per scope:
- auth: 5 req/min (login, passkey — future)
- public: 20 req/min (setup mail test, public forms)
- api: 100 req/min (public REST API v1)
- license: 30 req/min (license validate/activate — future)
- mcp: 60 req/min (MCP server)
- webhook: 10 req/min (webhook test trigger)

Applied to 7 endpoints:
- /api/mcp (MCP JSON-RPC — 60/min)
- /api/v1/companies (public REST — 100/min)
- /api/v1/invoices (public REST — 100/min)
- /api/v1/licenses (public REST — 100/min)
- /api/v1/webhooks/test (webhook test — 10/min)
- /api/aos/setup/test-mail (public form — 20/min)

429 response with Retry-After + X-RateLimit headers.
Recreated /api/v1/webhooks/test/route.ts (lost in git merge).
2026-10-27 21:33:43 +00:00

50 lines
1.7 KiB
TypeScript
Executable File

import { db } from '@/lib/db'
import { verifyToken, requireScope } from '@/lib/api-auth'
import { checkRateLimit } from '@/lib/rate-limit'
export const dynamic = 'force-dynamic'
// GET /api/v1/licenses
// Public API: list issued licenses. Requires scope "licenses.license:read".
export async function GET(req: Request) {
try {
const rl = checkRateLimit(req, 'api')
if (rl) return rl
const auth = await verifyToken(req.headers.get('authorization'))
const check = requireScope(auth, 'licenses.license:read')
if (!check.allowed) return check.error!
const { searchParams } = new URL(req.url)
const limit = Math.min(Number(searchParams.get('limit') ?? 50), 200)
const status = searchParams.get('status')
const where = status ? { status } : {}
const licenses = await db.license.findMany({
where,
take: limit,
orderBy: { createdAt: 'desc' },
include: { company: true, activations: true },
})
return Response.json({
data: licenses.map((l) => ({
id: l.id,
key: l.key,
product: l.product,
plan: l.plan,
status: l.status,
maxActivations: l.maxActivations,
validUntil: l.validUntil,
createdAt: l.createdAt,
company: { id: l.company.id, name: l.company.name },
activations: l.activations.map((a) => ({ domain: a.domain, environment: a.environment, version: a.version, lastCheck: a.lastCheck })),
})),
pagination: { total: licenses.length, limit },
token: auth.token!.name,
})
} catch (err) {
const msg = err instanceof Error ? err.message : 'internal server error'
return Response.json({ error: msg }, { status: 500 })
}
}