|
1 | | -import { eq } from 'drizzle-orm' |
2 | 1 | import { type NextRequest, NextResponse } from 'next/server' |
3 | 2 | import { getSession } from '@/lib/auth' |
| 3 | +import { authorizeCredentialUse } from '@/lib/auth/credential-access' |
4 | 4 | import { createLogger } from '@/lib/logs/console/logger' |
5 | 5 | import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' |
6 | | -import { db } from '@/db' |
7 | | -import { account } from '@/db/schema' |
8 | 6 |
|
9 | 7 | export const dynamic = 'force-dynamic' |
10 | 8 |
|
@@ -32,64 +30,48 @@ export async function GET(request: NextRequest) { |
32 | 30 | const credentialId = searchParams.get('credentialId') |
33 | 31 | const mimeType = searchParams.get('mimeType') |
34 | 32 | const query = searchParams.get('query') || '' |
| 33 | + const folderId = searchParams.get('folderId') || searchParams.get('parentId') || '' |
| 34 | + const workflowId = searchParams.get('workflowId') || undefined |
35 | 35 |
|
36 | 36 | if (!credentialId) { |
37 | 37 | logger.warn(`[${requestId}] Missing credential ID`) |
38 | 38 | return NextResponse.json({ error: 'Credential ID is required' }, { status: 400 }) |
39 | 39 | } |
40 | 40 |
|
41 | | - // Get the credential from the database |
42 | | - const credentials = await db.select().from(account).where(eq(account.id, credentialId)).limit(1) |
43 | | - |
44 | | - if (!credentials.length) { |
45 | | - logger.warn(`[${requestId}] Credential not found`, { credentialId }) |
46 | | - return NextResponse.json({ error: 'Credential not found' }, { status: 404 }) |
47 | | - } |
48 | | - |
49 | | - const credential = credentials[0] |
50 | | - |
51 | | - // Check if the credential belongs to the user |
52 | | - if (credential.userId !== session.user.id) { |
53 | | - logger.warn(`[${requestId}] Unauthorized credential access attempt`, { |
54 | | - credentialUserId: credential.userId, |
55 | | - requestUserId: session.user.id, |
56 | | - }) |
57 | | - return NextResponse.json({ error: 'Unauthorized' }, { status: 403 }) |
| 41 | + // Authorize use of the credential (supports collaborator credentials via workflow) |
| 42 | + const authz = await authorizeCredentialUse(request, { credentialId: credentialId!, workflowId }) |
| 43 | + if (!authz.ok || !authz.credentialOwnerUserId) { |
| 44 | + logger.warn(`[${requestId}] Unauthorized credential access attempt`, authz) |
| 45 | + return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 }) |
58 | 46 | } |
59 | 47 |
|
60 | 48 | // Refresh access token if needed using the utility function |
61 | | - const accessToken = await refreshAccessTokenIfNeeded(credentialId, session.user.id, requestId) |
| 49 | + const accessToken = await refreshAccessTokenIfNeeded( |
| 50 | + credentialId!, |
| 51 | + authz.credentialOwnerUserId, |
| 52 | + requestId |
| 53 | + ) |
62 | 54 |
|
63 | 55 | if (!accessToken) { |
64 | 56 | return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 }) |
65 | 57 | } |
66 | 58 |
|
67 | | - // Build the query parameters for Google Drive API |
68 | | - let queryParams = 'trashed=false' |
69 | | - |
70 | | - // Add mimeType filter if provided |
| 59 | + // Build Drive 'q' expression safely |
| 60 | + const qParts: string[] = ['trashed = false'] |
| 61 | + if (folderId) { |
| 62 | + qParts.push(`'${folderId.replace(/'/g, "\\'")}' in parents`) |
| 63 | + } |
71 | 64 | if (mimeType) { |
72 | | - // For Google Drive API, we need to use 'q' parameter for mimeType filtering |
73 | | - // Instead of using the mimeType parameter directly, we'll add it to the query |
74 | | - if (queryParams.includes('q=')) { |
75 | | - queryParams += ` and mimeType='${mimeType}'` |
76 | | - } else { |
77 | | - queryParams += `&q=mimeType='${mimeType}'` |
78 | | - } |
| 65 | + qParts.push(`mimeType = '${mimeType.replace(/'/g, "\\'")}'`) |
79 | 66 | } |
80 | | - |
81 | | - // Add search query if provided |
82 | 67 | if (query) { |
83 | | - if (queryParams.includes('q=')) { |
84 | | - queryParams += ` and name contains '${query}'` |
85 | | - } else { |
86 | | - queryParams += `&q=name contains '${query}'` |
87 | | - } |
| 68 | + qParts.push(`name contains '${query.replace(/'/g, "\\'")}'`) |
88 | 69 | } |
| 70 | + const q = encodeURIComponent(qParts.join(' and ')) |
89 | 71 |
|
90 | | - // Fetch files from Google Drive API |
| 72 | + // Fetch files from Google Drive API with shared drives support |
91 | 73 | const response = await fetch( |
92 | | - `https://www.googleapis.com/drive/v3/files?${queryParams}&fields=files(id,name,mimeType,iconLink,webViewLink,thumbnailLink,createdTime,modifiedTime,size,owners)`, |
| 74 | + `https://www.googleapis.com/drive/v3/files?q=${q}&supportsAllDrives=true&includeItemsFromAllDrives=true&spaces=drive&fields=files(id,name,mimeType,iconLink,webViewLink,thumbnailLink,createdTime,modifiedTime,size,owners,parents)`, |
93 | 75 | { |
94 | 76 | headers: { |
95 | 77 | Authorization: `Bearer ${accessToken}`, |
|
0 commit comments