mirror of
https://github.com/docmost/docmost.git
synced 2025-11-12 19:22:39 +10:00
make page attachments private
This commit is contained in:
@ -5,6 +5,7 @@ import { getBackendUrl } from "@/lib/config.ts";
|
|||||||
|
|
||||||
const api: AxiosInstance = axios.create({
|
const api: AxiosInstance = axios.create({
|
||||||
baseURL: getBackendUrl(),
|
baseURL: getBackendUrl(),
|
||||||
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
api.interceptors.request.use(
|
api.interceptors.request.use(
|
||||||
|
|||||||
@ -31,6 +31,7 @@
|
|||||||
"@aws-sdk/client-s3": "^3.600.0",
|
"@aws-sdk/client-s3": "^3.600.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.600.0",
|
"@aws-sdk/s3-request-presigner": "^3.600.0",
|
||||||
"@casl/ability": "^6.7.1",
|
"@casl/ability": "^6.7.1",
|
||||||
|
"@fastify/cookie": "^9.3.1",
|
||||||
"@fastify/multipart": "^8.3.0",
|
"@fastify/multipart": "^8.3.0",
|
||||||
"@fastify/static": "^7.0.4",
|
"@fastify/static": "^7.0.4",
|
||||||
"@nestjs/bullmq": "^10.1.1",
|
"@nestjs/bullmq": "^10.1.1",
|
||||||
|
|||||||
@ -45,7 +45,6 @@ import {
|
|||||||
import WorkspaceAbilityFactory from '../casl/abilities/workspace-ability.factory';
|
import WorkspaceAbilityFactory from '../casl/abilities/workspace-ability.factory';
|
||||||
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
||||||
import { AttachmentRepo } from '@docmost/db/repos/attachment/attachment.repo';
|
import { AttachmentRepo } from '@docmost/db/repos/attachment/attachment.repo';
|
||||||
import { Public } from '../../common/decorators/public.decorator';
|
|
||||||
import { validate as isValidUUID } from 'uuid';
|
import { validate as isValidUUID } from 'uuid';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
@ -129,12 +128,11 @@ export class AttachmentController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Public()
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Get('/files/:fileId/:fileName')
|
@Get('/files/:fileId/:fileName')
|
||||||
async getFile(
|
async getFile(
|
||||||
@Res() res: FastifyReply,
|
@Res() res: FastifyReply,
|
||||||
//@AuthUser() user: User,
|
@AuthUser() user: User,
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
@Param('fileId') fileId: string,
|
@Param('fileId') fileId: string,
|
||||||
@Param('fileName') fileName?: string,
|
@Param('fileName') fileName?: string,
|
||||||
@ -144,18 +142,29 @@ export class AttachmentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const attachment = await this.attachmentRepo.findById(fileId);
|
const attachment = await this.attachmentRepo.findById(fileId);
|
||||||
if (attachment.workspaceId !== workspace.id) {
|
if (
|
||||||
|
!attachment ||
|
||||||
|
attachment.workspaceId !== workspace.id ||
|
||||||
|
!attachment.pageId ||
|
||||||
|
!attachment.spaceId
|
||||||
|
) {
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!attachment || !attachment.pageId) {
|
const spaceAbility = await this.spaceAbility.createForUser(
|
||||||
throw new NotFoundException('File record not found');
|
user,
|
||||||
|
attachment.spaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (spaceAbility.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) {
|
||||||
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fileStream = await this.storageService.read(attachment.filePath);
|
const fileStream = await this.storageService.read(attachment.filePath);
|
||||||
res.headers({
|
res.headers({
|
||||||
'Content-Type': getMimeType(attachment.filePath),
|
'Content-Type': getMimeType(attachment.filePath),
|
||||||
|
'Cache-Control': 'public, max-age=3600',
|
||||||
});
|
});
|
||||||
return res.send(fileStream);
|
return res.send(fileStream);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -268,6 +277,7 @@ export class AttachmentController {
|
|||||||
const fileStream = await this.storageService.read(filePath);
|
const fileStream = await this.storageService.read(filePath);
|
||||||
res.headers({
|
res.headers({
|
||||||
'Content-Type': getMimeType(filePath),
|
'Content-Type': getMimeType(filePath),
|
||||||
|
'Cache-Control': 'public, max-age=86400',
|
||||||
});
|
});
|
||||||
return res.send(fileStream);
|
return res.send(fileStream);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@ -4,11 +4,12 @@ import {
|
|||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
import { Strategy } from 'passport-jwt';
|
||||||
import { EnvironmentService } from '../../../integrations/environment/environment.service';
|
import { EnvironmentService } from '../../../integrations/environment/environment.service';
|
||||||
import { JwtPayload, JwtType } from '../dto/jwt-payload';
|
import { JwtPayload, JwtType } from '../dto/jwt-payload';
|
||||||
import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo';
|
import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo';
|
||||||
import { UserRepo } from '@docmost/db/repos/user/user.repo';
|
import { UserRepo } from '@docmost/db/repos/user/user.repo';
|
||||||
|
import { FastifyRequest } from 'fastify';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||||
@ -18,7 +19,15 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|||||||
private readonly environmentService: EnvironmentService,
|
private readonly environmentService: EnvironmentService,
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
jwtFromRequest: (req: FastifyRequest) => {
|
||||||
|
let accessToken = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
accessToken = JSON.parse(req.cookies?.authTokens)?.accessToken;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
return accessToken || this.extractTokenFromHeader(req);
|
||||||
|
},
|
||||||
ignoreExpiration: false,
|
ignoreExpiration: false,
|
||||||
secretOrKey: environmentService.getAppSecret(),
|
secretOrKey: environmentService.getAppSecret(),
|
||||||
passReqToCallback: true,
|
passReqToCallback: true,
|
||||||
@ -50,4 +59,9 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|||||||
|
|
||||||
return { user, workspace };
|
return { user, workspace };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extractTokenFromHeader(request: FastifyRequest): string | undefined {
|
||||||
|
const [type, token] = request.headers.authorization?.split(' ') ?? [];
|
||||||
|
return type === 'Bearer' ? token : undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { TransformHttpResponseInterceptor } from './common/interceptors/http-res
|
|||||||
import fastifyMultipart from '@fastify/multipart';
|
import fastifyMultipart from '@fastify/multipart';
|
||||||
import { WsRedisIoAdapter } from './ws/adapter/ws-redis.adapter';
|
import { WsRedisIoAdapter } from './ws/adapter/ws-redis.adapter';
|
||||||
import { InternalLogFilter } from './common/logger/internal-log-filter';
|
import { InternalLogFilter } from './common/logger/internal-log-filter';
|
||||||
|
import fastifyCookie from '@fastify/cookie';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create<NestFastifyApplication>(
|
const app = await NestFactory.create<NestFastifyApplication>(
|
||||||
@ -31,6 +32,7 @@ async function bootstrap() {
|
|||||||
app.useWebSocketAdapter(redisIoAdapter);
|
app.useWebSocketAdapter(redisIoAdapter);
|
||||||
|
|
||||||
await app.register(fastifyMultipart as any);
|
await app.register(fastifyMultipart as any);
|
||||||
|
await app.register(fastifyCookie as any);
|
||||||
|
|
||||||
app
|
app
|
||||||
.getHttpAdapter()
|
.getHttpAdapter()
|
||||||
@ -56,7 +58,16 @@ async function bootstrap() {
|
|||||||
transform: true,
|
transform: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
app.enableCors();
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
// make development easy
|
||||||
|
app.enableCors({
|
||||||
|
origin: ['http://localhost:5173'],
|
||||||
|
credentials: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
app.enableCors();
|
||||||
|
}
|
||||||
|
|
||||||
app.useGlobalInterceptors(new TransformHttpResponseInterceptor());
|
app.useGlobalInterceptors(new TransformHttpResponseInterceptor());
|
||||||
app.enableShutdownHooks();
|
app.enableShutdownHooks();
|
||||||
|
|||||||
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@ -331,6 +331,9 @@ importers:
|
|||||||
'@casl/ability':
|
'@casl/ability':
|
||||||
specifier: ^6.7.1
|
specifier: ^6.7.1
|
||||||
version: 6.7.1
|
version: 6.7.1
|
||||||
|
'@fastify/cookie':
|
||||||
|
specifier: ^9.3.1
|
||||||
|
version: 9.3.1
|
||||||
'@fastify/multipart':
|
'@fastify/multipart':
|
||||||
specifier: ^8.3.0
|
specifier: ^8.3.0
|
||||||
version: 8.3.0
|
version: 8.3.0
|
||||||
@ -1875,6 +1878,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
'@fastify/cookie@9.3.1':
|
||||||
|
resolution: {integrity: sha512-h1NAEhB266+ZbZ0e9qUE6NnNR07i7DnNXWG9VbbZ8uC6O/hxHpl+Zoe5sw1yfdZ2U6XhToUGDnzQtWJdCaPwfg==}
|
||||||
|
|
||||||
'@fastify/cors@9.0.1':
|
'@fastify/cors@9.0.1':
|
||||||
resolution: {integrity: sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==}
|
resolution: {integrity: sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==}
|
||||||
|
|
||||||
@ -4594,6 +4600,10 @@ packages:
|
|||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
|
cookie-signature@1.2.1:
|
||||||
|
resolution: {integrity: sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==}
|
||||||
|
engines: {node: '>=6.6.0'}
|
||||||
|
|
||||||
cookie@0.4.2:
|
cookie@0.4.2:
|
||||||
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
|
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@ -9789,6 +9799,11 @@ snapshots:
|
|||||||
|
|
||||||
'@fastify/busboy@2.1.1': {}
|
'@fastify/busboy@2.1.1': {}
|
||||||
|
|
||||||
|
'@fastify/cookie@9.3.1':
|
||||||
|
dependencies:
|
||||||
|
cookie-signature: 1.2.1
|
||||||
|
fastify-plugin: 4.5.1
|
||||||
|
|
||||||
'@fastify/cors@9.0.1':
|
'@fastify/cors@9.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
fastify-plugin: 4.5.1
|
fastify-plugin: 4.5.1
|
||||||
@ -12947,6 +12962,8 @@ snapshots:
|
|||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
|
cookie-signature@1.2.1: {}
|
||||||
|
|
||||||
cookie@0.4.2: {}
|
cookie@0.4.2: {}
|
||||||
|
|
||||||
cookie@0.6.0: {}
|
cookie@0.6.0: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user