NeahStable/app/api/storage/files/content/route.ts
2026-01-16 11:23:13 +01:00

184 lines
5.3 KiB
TypeScript

import { NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/app/api/auth/options";
import { getObjectContent } from '@/lib/s3';
// Error types for better error handling
enum StorageError {
UNAUTHORIZED = 'UNAUTHORIZED',
FORBIDDEN = 'FORBIDDEN',
NOT_FOUND = 'NOT_FOUND',
VALIDATION_ERROR = 'VALIDATION_ERROR',
S3_ERROR = 'S3_ERROR',
INTERNAL_ERROR = 'INTERNAL_ERROR'
}
// Helper function to create error response
function createErrorResponse(
error: StorageError,
message: string,
status: number,
details?: any
) {
return NextResponse.json(
{
error: error,
message: message,
...(details && { details })
},
{ status }
);
}
// Helper function to check authentication
async function checkAuth(request: Request) {
const session = await getServerSession(authOptions);
if (!session?.user?.id) {
console.error('Unauthorized access attempt:', {
url: request.url,
method: request.method,
headers: Object.fromEntries(request.headers)
});
return { authorized: false, userId: null };
}
return { authorized: true, userId: session.user.id };
}
export async function GET(request: Request) {
try {
const { authorized, userId } = await checkAuth(request);
if (!authorized || !userId) {
return createErrorResponse(
StorageError.UNAUTHORIZED,
'Authentication required',
401
);
}
const { searchParams } = new URL(request.url);
const path = searchParams.get('path');
const id = searchParams.get('id');
if (!path && !id) {
return createErrorResponse(
StorageError.VALIDATION_ERROR,
'Path or ID parameter is required',
400,
{ missing: 'path or id' }
);
}
// Determine the key to use
let key: string;
if (id) {
// If id is provided directly, use it as the key
key = id;
// Ensure the user can only access their own files
if (!key.startsWith(`user-${userId}/`)) {
console.error('Unauthorized file access attempt:', { userId, fileId: id });
return createErrorResponse(
StorageError.FORBIDDEN,
'Unauthorized access to file',
403,
{ fileId: id }
);
}
} else if (path) {
// If a path is provided, ensure it contains the user's ID
if (!path.includes(`/files/cube-${userId}/`) && !path.includes(`user-${userId}/`)) {
// For backward compatibility, convert NextCloud path to S3 path
if (path.startsWith('/files/') || path.includes('/Private/')) {
// Extract folder and filename from path
const parts = path.split('/').filter(Boolean);
const file = parts[parts.length - 1];
let folder = 'notes'; // Default folder
// Try to determine folder from path
if (path.includes('/Notes/')) folder = 'notes';
else if (path.includes('/Diary/')) folder = 'diary';
else if (path.includes('/Contacts/')) folder = 'contacts';
else if (path.includes('/Health/')) folder = 'health';
// Use direct user path without pages prefix
key = `user-${userId}/${folder}/${file}`;
console.log('Converted NextCloud path to S3 key:', { path, key });
} else {
console.error('Unauthorized file access attempt:', { userId, filePath: path });
return createErrorResponse(
StorageError.FORBIDDEN,
'Unauthorized access to file',
403,
{ filePath: path }
);
}
} else {
// If it already contains user ID, use the path directly
key = path;
}
} else {
return createErrorResponse(
StorageError.VALIDATION_ERROR,
'Invalid parameters',
400
);
}
// Validate key format (prevent path traversal)
if (key.includes('..') || key.includes('//')) {
return createErrorResponse(
StorageError.VALIDATION_ERROR,
'Invalid file path',
400,
{ key }
);
}
console.log('Fetching file content from S3:', { key });
try {
// Get the file content
const content = await getObjectContent(key);
if (!content) {
return createErrorResponse(
StorageError.NOT_FOUND,
'File not found',
404,
{ key }
);
}
return NextResponse.json({ content });
} catch (s3Error) {
console.error('S3 error fetching file content:', s3Error);
// Check if file doesn't exist
if (s3Error instanceof Error && s3Error.message.includes('NoSuchKey')) {
return createErrorResponse(
StorageError.NOT_FOUND,
'File not found',
404,
{ key }
);
}
return createErrorResponse(
StorageError.S3_ERROR,
'Failed to fetch file content from storage',
503,
{
error: s3Error instanceof Error ? s3Error.message : String(s3Error),
key
}
);
}
} catch (error) {
console.error('Error fetching file content:', error);
return createErrorResponse(
StorageError.INTERNAL_ERROR,
'An unexpected error occurred',
500,
{ error: error instanceof Error ? error.message : String(error) }
);
}
}