import express from 'express'; import { OAuth2Client } from 'google-auth-library'; import https from 'node:https'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const appRoot = path.resolve(__dirname, '..'); const distDir = path.join(appRoot, 'dist'); const PORT = Number(process.env.PORT || 7860); const HOST = process.env.HOST || '0.0.0.0'; const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID || process.env.VITE_GOOGLE_CLIENT_ID || ''; const DRIVE_FOLDER_ID = process.env.DRIVE_FOLDER_ID || process.env.VITE_DRIVE_FOLDER_ID || '1xK0ZNYBpVeYBKca48xxKEFpGWy9bbRaw'; const ALLOWED_DOMAIN = 'baby-helmet.com'; const DRIVE_API_BASE = 'https://www.googleapis.com/drive/v3'; const ORIGINAL_OBJ_PATTERN = /^ldsm_fit_original_\d{5}_[^_]+_\d{8}\.obj$/i; const oauthClient = new OAuth2Client(GOOGLE_CLIENT_ID); const app = express(); function jsonError(res, status, message) { return res.status(status).json({ error: message }); } function getBearerToken(req) { const header = req.get('authorization') || ''; const match = header.match(/^Bearer\s+(.+)$/i); return match?.[1] || ''; } async function verifyBabyHelmetUser(req, res, next) { if (!GOOGLE_CLIENT_ID) { return jsonError(res, 500, 'GOOGLE_CLIENT_ID is not configured.'); } const idToken = req.get('x-google-id-token') || ''; if (!idToken) { return jsonError(res, 401, 'Google ID token is required.'); } try { const ticket = await oauthClient.verifyIdToken({ idToken, audience: GOOGLE_CLIENT_ID, }); const payload = ticket.getPayload(); if (!payload) { return jsonError(res, 403, 'Invalid Google ID token.'); } const email = String(payload.email || '').toLowerCase(); const hostedDomain = String(payload.hd || '').toLowerCase(); if (payload.aud !== GOOGLE_CLIENT_ID) { return jsonError(res, 403, 'Invalid token audience.'); } if (hostedDomain !== ALLOWED_DOMAIN) { return jsonError(res, 403, 'Google Workspace domain is not allowed.'); } if (payload.email_verified !== true) { return jsonError(res, 403, 'Google email is not verified.'); } if (!email.endsWith(`@${ALLOWED_DOMAIN}`)) { return jsonError(res, 403, 'Email domain is not allowed.'); } req.verifiedUser = { email }; return next(); } catch { return jsonError(res, 403, 'Google ID token verification failed.'); } } function requireDriveAccessToken(req, res, next) { const accessToken = getBearerToken(req); if (!accessToken) { return jsonError(res, 401, 'Google Drive access token is required.'); } req.driveAccessToken = accessToken; return next(); } async function driveFetch(pathname, accessToken, options = {}) { const url = new URL(`${DRIVE_API_BASE}${pathname}`); const headers = { Authorization: `Bearer ${accessToken}`, ...(options.headers || {}), }; return new Promise((resolve, reject) => { const req = https.request( url, { method: options.method || 'GET', headers, timeout: 20000, }, (res) => { const chunks = []; res.on('data', (chunk) => { chunks.push(chunk); }); res.on('end', () => { clearTimeout(timeoutId); const body = Buffer.concat(chunks).toString('utf8'); const status = res.statusCode || 0; const response = { ok: status >= 200 && status < 300, status, json: async () => JSON.parse(body), text: async () => body, }; if (!response.ok) { reject(new Error(`Google Drive API error ${response.status}: ${body}`)); return; } resolve(response); }); }, ); const timeoutId = setTimeout(() => { req.destroy(new Error('Google Drive API request timed out.')); }, 20000); req.on('error', (error) => { clearTimeout(timeoutId); reject(error); }); req.end(); }); } async function listFolderObjFiles(accessToken) { const files = []; let pageToken = ''; do { const params = new URLSearchParams({ q: `'${DRIVE_FOLDER_ID}' in parents and trashed = false and name contains 'ldsm_fit_original_'`, fields: 'files(id,name,size,modifiedTime,mimeType),nextPageToken', orderBy: 'name_natural', pageSize: '1000', supportsAllDrives: 'true', includeItemsFromAllDrives: 'true', }); if (pageToken) { params.set('pageToken', pageToken); } const response = await driveFetch(`/files?${params}`, accessToken); const data = await response.json(); files.push(...(data.files || [])); pageToken = data.nextPageToken || ''; } while (pageToken); return files.filter((file) => ORIGINAL_OBJ_PATTERN.test(file.name)); } async function getObjMetadata(fileId, accessToken) { const params = new URLSearchParams({ fields: 'id,name,size,modifiedTime,mimeType,parents', supportsAllDrives: 'true', }); const response = await driveFetch(`/files/${encodeURIComponent(fileId)}?${params}`, accessToken); return response.json(); } function isObjInFixedFolder(file) { const parents = Array.isArray(file.parents) ? file.parents : []; return ORIGINAL_OBJ_PATTERN.test(file.name || '') && parents.includes(DRIVE_FOLDER_ID); } app.get('/env.js', (_req, res) => { res.type('application/javascript').send( `window.__APP_CONFIG__ = ${JSON.stringify({ googleClientId: GOOGLE_CLIENT_ID, driveFolderId: DRIVE_FOLDER_ID, })};`, ); }); app.get('/api/health', (_req, res) => { res.json({ ok: true }); }); app.get('/api/files', verifyBabyHelmetUser, requireDriveAccessToken, async (req, res) => { try { const files = await listFolderObjFiles(req.driveAccessToken); res.json({ files }); } catch (error) { jsonError(res, 502, error.message || 'Failed to list Drive files.'); } }); app.get('/api/files/:id', verifyBabyHelmetUser, requireDriveAccessToken, async (req, res) => { try { const file = await getObjMetadata(req.params.id, req.driveAccessToken); if (!isObjInFixedFolder(file)) { return jsonError(res, 403, 'Requested file is not an OBJ in the configured folder.'); } const response = await driveFetch( `/files/${encodeURIComponent(file.id)}?alt=media&supportsAllDrives=true`, req.driveAccessToken, ); const text = await response.text(); res.type('text/plain; charset=utf-8').send(text); } catch (error) { jsonError(res, 502, error.message || 'Failed to download OBJ file.'); } }); app.use(express.static(distDir)); app.use((_req, res) => { res.sendFile(path.join(distDir, 'index.html')); }); app.listen(PORT, HOST, () => { console.log(`OBJ Drive Viewer listening on ${HOST}:${PORT}`); });