Spaces:
Configuration error
Configuration error
File size: 4,440 Bytes
bcce530 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | import { NextRequest, NextResponse } from "next/server"
import { getAuthUser, type AuthUser } from "./auth"
import { checkRateLimit, getClientIdentifier, getRateLimitHeaders } from "./rate-limit"
/**
* Reusable API route wrapper that enforces authentication.
* Usage:
* export const POST = withAuth(async (req, user) => { ... })
*/
export function withAuth(
handler: (request: NextRequest, user: AuthUser) => Promise<NextResponse | Response>
) {
return async (request: NextRequest) => {
const user = await getAuthUser()
if (!user) {
return NextResponse.json(
{ error: "Authentication required" },
{ status: 401 }
)
}
return handler(request, user)
}
}
/**
* Rate limit configuration presets for different route types.
*/
export const RATE_LIMIT_PRESETS = {
/** Standard write ops: 30 req/hour guest, 100 req/hour user */
write: { guest: { max: 30, window: 3600 }, user: { max: 100, window: 3600 } },
/** Expensive ops (AI search, run): 10 req/hour guest, 50 req/hour user */
expensive: { guest: { max: 10, window: 3600 }, user: { max: 50, window: 3600 } },
/** Read-heavy: 120 req/hour guest, 300 req/hour user */
read: { guest: { max: 120, window: 3600 }, user: { max: 300, window: 3600 } },
} as const
type RateLimitPreset = keyof typeof RATE_LIMIT_PRESETS
/**
* Reusable API route wrapper that enforces rate limiting.
* Usage:
* export const POST = withRateLimit(async (req) => { ... }, "write")
*/
export function withRateLimit(
handler: (request: NextRequest) => Promise<NextResponse | Response>,
preset: RateLimitPreset = "write"
) {
return async (request: NextRequest) => {
const user = await getAuthUser()
const identifier = getClientIdentifier(request, user?.id ?? undefined)
const isAuth = !!user
const rateLimit = await checkRateLimit(
`${preset}:${identifier}`,
isAuth
)
if (!rateLimit.success) {
const headers = getRateLimitHeaders(rateLimit)
return NextResponse.json(
{ error: rateLimit.error },
{
status: 429,
headers: Object.fromEntries(headers.entries()),
}
)
}
const response = await handler(request)
// Append rate limit headers to successful responses
const rlHeaders = getRateLimitHeaders(rateLimit)
rlHeaders.forEach((value, key) => {
response.headers.set(key, value)
})
return response
}
}
/**
* Combined: auth + rate limit wrapper.
* Usage:
* export const POST = withAuthAndRateLimit(async (req, user) => { ... }, "write")
*/
export function withAuthAndRateLimit(
handler: (request: NextRequest, user: AuthUser) => Promise<NextResponse | Response>,
preset: RateLimitPreset = "write"
) {
return async (request: NextRequest) => {
// 1. Auth check
const user = await getAuthUser()
if (!user) {
return NextResponse.json(
{ error: "Authentication required" },
{ status: 401 }
)
}
// 2. Rate limit check
const identifier = getClientIdentifier(request, user.id)
const rateLimit = await checkRateLimit(`${preset}:${identifier}`, true)
if (!rateLimit.success) {
const headers = getRateLimitHeaders(rateLimit)
return NextResponse.json(
{ error: rateLimit.error },
{
status: 429,
headers: Object.fromEntries(headers.entries()),
}
)
}
const response = await handler(request, user)
// Append rate limit headers
const rlHeaders = getRateLimitHeaders(rateLimit)
rlHeaders.forEach((value, key) => {
response.headers.set(key, value)
})
return response
}
}
/**
* Sanitize user-provided text: strip HTML tags and limit length.
*/
export function sanitizeText(text: string, maxLength: number = 10000): string {
return text
.replace(/<[^>]*>/g, "")
.trim()
.slice(0, maxLength)
}
/**
* Clamp a number to a range.
*/
export function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max)
}
|