import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"; import { auth } from "~~/lib/auth"; import { verifyFileToken } from "~~/server/utils/file-token"; export default defineEventHandler(async (event) => { const key = getRouterParam(event, 'key'); if (!key) { throw createError({ statusCode: 400, statusMessage: 'Missing file key' }); } const query = getQuery(event); const exp = query.exp ? Number(query.exp) : undefined; const sig = query.sig as string | undefined; let authorized = false; // Path 1: HMAC token (for AI model access) if (exp && sig && process.env.BETTER_AUTH_SECRET) { authorized = verifyFileToken(key, exp, sig, process.env.BETTER_AUTH_SECRET); } // Path 2: Session auth (for client-side access) if (!authorized) { try { const sessionData = await auth.api.getSession(event); if (sessionData) { event.context.user = sessionData.user; authorized = true; } } catch { // No valid session } } if (!authorized) { throw createError({ statusCode: 401, statusMessage: 'Unauthorized' }); } const config = useRuntimeConfig(); const s3 = new S3Client({ region: config.s3.region || 'us-east-1', endpoint: config.s3.endpoint!, credentials: { accessKeyId: config.s3.accessKeyId!, secretAccessKey: config.s3.secret!, }, forcePathStyle: true, }); try { const response = await s3.send(new GetObjectCommand({ Bucket: config.s3.bucket!, Key: key, })); setHeaders(event, { 'Content-Type': response.ContentType || 'application/octet-stream', 'Content-Length': response.ContentLength?.toString() || '', 'Cache-Control': 'public, max-age=3600', }); return response.Body; } catch (error: any) { if (error.name === 'NoSuchKey') { throw createError({ statusCode: 404, statusMessage: 'File not found' }); } throw createError({ statusCode: 500, statusMessage: 'Error fetching file' }); } });