feat: file upload retry, secure file tokens, UI polish
- Add HMAC-based file token auth for secure AI model file access - Add file upload retry with exponential backoff (max 3 retries) - File endpoint now requires session auth or signed token - Support assistant role messages in chat input - Optimistic UI for attachments on message send - Verify topic ownership before allowing messages - Switch web scraping to Firecrawl API - Agent profile page layout fixes (proper flex overflow) - Add quick switcher (Ctrl+K) to sidenav - Clean up longcat.ts and stale comments
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
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');
|
||||
@@ -6,6 +8,34 @@ export default defineEventHandler(async (event) => {
|
||||
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({
|
||||
@@ -24,14 +54,12 @@ export default defineEventHandler(async (event) => {
|
||||
Key: key,
|
||||
}));
|
||||
|
||||
// 3. Set the correct headers so the browser knows what it's receiving
|
||||
setHeaders(event, {
|
||||
'Content-Type': response.ContentType || 'application/octet-stream',
|
||||
'Content-Length': response.ContentLength?.toString() || '',
|
||||
'Cache-Control': 'public, max-age=3600', // Optional: cache for 1 hour
|
||||
'Cache-Control': 'public, max-age=3600',
|
||||
});
|
||||
|
||||
// 4. Return the body as a stream directly to the client
|
||||
return response.Body;
|
||||
} catch (error: any) {
|
||||
if (error.name === 'NoSuchKey') {
|
||||
|
||||
Reference in New Issue
Block a user