Spaces:
Sleeping
Sleeping
File size: 2,506 Bytes
57a889c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | import { Controller, Get, HttpException, Param, Req, Res } from '@nestjs/common';
import type { Request, Response } from 'express';
import path from 'path';
import fs from 'fs';
import { FilesService } from './files.service';
/**
* GET /api/trips/:tripId/files/:id/download — authenticated file download.
*
* Deliberately NOT behind the JwtAuthGuard: it accepts a cookie, a Bearer header
* OR a one-shot `?token=` query param (so links can be opened directly), all via
* the legacy authenticateDownload helper. Byte-identical to the legacy route:
* 401 token, 404 trip/file, 403 path traversal, .pkpass served inline for Wallet.
*/
@Controller('api/trips/:tripId/files')
export class FilesDownloadController {
constructor(private readonly files: FilesService) {}
@Get(':id/download')
download(@Req() req: Request, @Res() res: Response, @Param('tripId') tripId: string, @Param('id') id: string): void {
const auth = this.files.authenticateDownload(req);
if ('error' in auth) {
throw new HttpException({ error: auth.error }, auth.status);
}
const trip = this.files.verifyTripAccess(tripId, auth.userId);
if (!trip) {
throw new HttpException({ error: 'Trip not found' }, 404);
}
const file = this.files.getFileById(id, tripId);
if (!file) {
throw new HttpException({ error: 'File not found' }, 404);
}
const { resolved, safe } = this.files.resolveFilePath(file.filename);
if (!safe) {
throw new HttpException({ error: 'Forbidden' }, 403);
}
if (!fs.existsSync(resolved)) {
throw new HttpException({ error: 'File not found' }, 404);
}
// Serve Apple Wallet passes inline with the canonical MIME type so Safari
// (iOS/macOS) hands them to Wallet instead of downloading as a blob.
if (path.extname(resolved).toLowerCase() === '.pkpass') {
res.setHeader('Content-Type', 'application/vnd.apple.pkpass');
res.setHeader('Content-Disposition', `inline; filename="${path.basename(file.original_name || resolved)}"`);
}
// Serve with an explicit { root } + basename rather than an absolute path:
// under the Nest ExpressAdapter, res.sendFile(absolutePath) resolves the
// file relative to the (rewritten) req.url and fails with a spurious
// "Not Found", whereas the root-relative form streams correctly. The
// resolveFilePath guard above already pins this to the uploads dir.
res.sendFile(path.basename(resolved), { root: path.dirname(resolved) });
}
}
|