Compare commits

..

8 Commits

Author SHA1 Message Date
Philipinho 9142eea924 fix: duplicate PDF uploads 2026-04-29 09:56:17 +01:00
Philipinho 980521f957 v0.80.1 2026-04-27 16:06:32 +01:00
Philipinho fe44dc92a9 sync 2026-04-27 15:51:23 +01:00
Philip Okugbe fad410ef23 chore: add undici for oidc proxy support (#2132) 2026-04-27 15:50:42 +01:00
Philipinho 15b8908b1a update postcss 2026-04-27 15:23:47 +01:00
Philipinho 8e15b22d8c package updates 2026-04-27 15:22:02 +01:00
Philipinho ec83fc82d5 fix: refactor sanitize 2026-04-27 15:16:26 +01:00
Philipinho a573acedd0 fix: local storage, and package overrides 2026-04-22 14:13:25 +01:00
11 changed files with 979 additions and 947 deletions
+6 -6
View File
@@ -1,7 +1,7 @@
{ {
"name": "client", "name": "client",
"private": true, "private": true,
"version": "0.80.0", "version": "0.80.1",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
@@ -31,8 +31,8 @@
"emoji-mart": "^5.6.0", "emoji-mart": "^5.6.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"highlightjs-sap-abap": "^0.3.0", "highlightjs-sap-abap": "^0.3.0",
"i18next": "^25.10.1", "i18next": "25.10.1",
"i18next-http-backend": "^3.0.2", "i18next-http-backend": "3.0.6",
"jotai": "^2.18.1", "jotai": "^2.18.1",
"jotai-optics": "^0.4.0", "jotai-optics": "^0.4.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
@@ -42,7 +42,7 @@
"mantine-form-zod-resolver": "^1.3.0", "mantine-form-zod-resolver": "^1.3.0",
"mermaid": "^11.13.0", "mermaid": "^11.13.0",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"posthog-js": "1.363.1", "posthog-js": "1.372.2",
"react": "^18.3.1", "react": "^18.3.1",
"react-arborist": "3.4.0", "react-arborist": "3.4.0",
"react-clear-modal": "^2.0.18", "react-clear-modal": "^2.0.18",
@@ -50,7 +50,7 @@
"react-drawio": "^1.0.7", "react-drawio": "^1.0.7",
"react-error-boundary": "^6.1.1", "react-error-boundary": "^6.1.1",
"react-helmet-async": "^3.0.0", "react-helmet-async": "^3.0.0",
"react-i18next": "^16.5.8", "react-i18next": "16.5.8",
"react-router-dom": "^7.13.1", "react-router-dom": "^7.13.1",
"semver": "^7.7.4", "semver": "^7.7.4",
"socket.io-client": "^4.8.3", "socket.io-client": "^4.8.3",
@@ -74,7 +74,7 @@
"eslint-plugin-react-refresh": "^0.5.2", "eslint-plugin-react-refresh": "^0.5.2",
"globals": "^15.13.0", "globals": "^15.13.0",
"optics-ts": "^2.4.1", "optics-ts": "^2.4.1",
"postcss": "^8.5.8", "postcss": "^8.5.12",
"postcss-preset-mantine": "^1.18.0", "postcss-preset-mantine": "^1.18.0",
"postcss-simple-vars": "^7.0.1", "postcss-simple-vars": "^7.0.1",
"prettier": "^3.8.1", "prettier": "^3.8.1",
@@ -19,7 +19,9 @@ export const uploadAttachmentAction = handleAttachmentUpload({
}, },
validateFn: (file, allowMedia: boolean) => { validateFn: (file, allowMedia: boolean) => {
if ( if (
(file.type.includes("image/") || file.type.includes("video/")) && (file.type.includes("image/") ||
file.type.includes("video/") ||
file.type === "application/pdf") &&
!allowMedia !allowMedia
) { ) {
return false; return false;
+21 -17
View File
@@ -1,6 +1,6 @@
{ {
"name": "server", "name": "server",
"version": "0.80.0", "version": "0.80.1",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -33,13 +33,13 @@
"@ai-sdk/google": "^3.0.52", "@ai-sdk/google": "^3.0.52",
"@ai-sdk/openai": "^3.0.47", "@ai-sdk/openai": "^3.0.47",
"@ai-sdk/openai-compatible": "^2.0.37", "@ai-sdk/openai-compatible": "^2.0.37",
"@aws-sdk/client-s3": "3.1014.0", "@aws-sdk/client-s3": "3.1037.0",
"@aws-sdk/lib-storage": "3.1014.0", "@aws-sdk/lib-storage": "3.1037.0",
"@aws-sdk/s3-request-presigner": "3.1014.0", "@aws-sdk/s3-request-presigner": "3.1037.0",
"@clickhouse/client": "^1.18.2", "@clickhouse/client": "^1.18.2",
"@fastify/cookie": "^11.0.2", "@fastify/cookie": "^11.0.2",
"@fastify/multipart": "^9.4.0", "@fastify/multipart": "^10.0.0",
"@fastify/static": "^9.0.0", "@fastify/static": "^9.1.3",
"@keyv/redis": "^5.1.6", "@keyv/redis": "^5.1.6",
"@langchain/core": "1.1.39", "@langchain/core": "1.1.39",
"@langchain/textsplitters": "1.0.1", "@langchain/textsplitters": "1.0.1",
@@ -48,19 +48,19 @@
"@nestjs-labs/nestjs-ioredis": "^11.0.4", "@nestjs-labs/nestjs-ioredis": "^11.0.4",
"@nestjs/bullmq": "^11.0.4", "@nestjs/bullmq": "^11.0.4",
"@nestjs/cache-manager": "^3.1.0", "@nestjs/cache-manager": "^3.1.0",
"@nestjs/common": "^11.1.18", "@nestjs/common": "^11.1.19",
"@nestjs/config": "^4.0.3", "@nestjs/config": "^4.0.4",
"@nestjs/core": "^11.1.18", "@nestjs/core": "^11.1.19",
"@nestjs/event-emitter": "^3.0.1", "@nestjs/event-emitter": "^3.0.1",
"@nestjs/jwt": "11.0.2", "@nestjs/jwt": "11.0.2",
"@nestjs/mapped-types": "^2.1.1", "@nestjs/mapped-types": "^2.1.1",
"@nestjs/passport": "^11.0.5", "@nestjs/passport": "^11.0.5",
"@nestjs/platform-fastify": "^11.1.18", "@nestjs/platform-fastify": "^11.1.19",
"@nestjs/platform-socket.io": "^11.1.18", "@nestjs/platform-socket.io": "^11.1.19",
"@nestjs/schedule": "^6.1.1", "@nestjs/schedule": "^6.1.3",
"@nestjs/terminus": "^11.1.1", "@nestjs/terminus": "^11.1.1",
"@nestjs/throttler": "^6.5.0", "@nestjs/throttler": "^6.5.0",
"@nestjs/websockets": "^11.1.18", "@nestjs/websockets": "^11.1.19",
"@node-saml/passport-saml": "^5.1.0", "@node-saml/passport-saml": "^5.1.0",
"@react-email/components": "1.0.10", "@react-email/components": "1.0.10",
"@react-email/render": "2.0.4", "@react-email/render": "2.0.4",
@@ -69,7 +69,7 @@
"ai-sdk-ollama": "^3.8.1", "ai-sdk-ollama": "^3.8.1",
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
"bowser": "^2.14.1", "bowser": "^2.14.1",
"bullmq": "^5.71.0", "bullmq": "^5.76.0",
"cache-manager": "^7.2.8", "cache-manager": "^7.2.8",
"cheerio": "^1.2.0", "cheerio": "^1.2.0",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
@@ -110,22 +110,23 @@
"react": "^18.3.1", "react": "^18.3.1",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"sanitize-filename-ts": "1.0.2", "sanitize-filename": "1.6.3",
"socket.io": "^4.8.3", "socket.io": "^4.8.3",
"stripe": "^17.7.0", "stripe": "^17.7.0",
"tlds": "^1.261.0", "tlds": "^1.261.0",
"tmp-promise": "^3.0.3", "tmp-promise": "^3.0.3",
"tseep": "^1.3.1", "tseep": "^1.3.1",
"typesense": "^3.0.5", "typesense": "^3.0.5",
"undici": "7.24.0",
"ws": "^8.20.0", "ws": "^8.20.0",
"yauzl": "^3.2.1", "yauzl": "^3.2.1",
"zod": "^4.3.6" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.28.0", "@eslint/js": "^9.28.0",
"@nestjs/cli": "^11.0.18", "@nestjs/cli": "^11.0.21",
"@nestjs/schematics": "^11.0.10", "@nestjs/schematics": "^11.0.10",
"@nestjs/testing": "^11.1.18", "@nestjs/testing": "^11.1.19",
"@types/bcrypt": "^6.0.0", "@types/bcrypt": "^6.0.0",
"@types/debounce": "^1.2.4", "@types/debounce": "^1.2.4",
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
@@ -165,6 +166,9 @@
"transform": { "transform": {
"^.+\\.(t|j)s$": "ts-jest" "^.+\\.(t|j)s$": "ts-jest"
}, },
"transformIgnorePatterns": [
"/node_modules/(?!(\\.pnpm/)?(nanoid|uuid|image-dimensions|marked)(@|/))"
],
"collectCoverageFrom": [ "collectCoverageFrom": [
"**/*.(t|j)s" "**/*.(t|j)s"
], ],
Binary file not shown.
+28 -6
View File
@@ -1,6 +1,6 @@
import * as path from 'path'; import * as path from 'path';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { sanitize } from 'sanitize-filename-ts'; import sanitize = require('sanitize-filename');
import { FastifyRequest } from 'fastify'; import { FastifyRequest } from 'fastify';
import { Readable, Transform } from 'stream'; import { Readable, Transform } from 'stream';
@@ -72,11 +72,33 @@ export function extractDateFromUuid7(uuid7: string) {
return new Date(timestamp); return new Date(timestamp);
} }
export function sanitizeFileName(fileName: string): string { export type SanitizeFileNameOptions = {
const sanitizedFilename = sanitize(fileName) /** Keep spaces and `#` instead of replacing them with `_`. Useful for
.replace(/ /g, '_') * download filenames where readability matters. Defaults to false. */
.replace(/#/g, '_'); preserveSpaces?: boolean;
return sanitizedFilename.slice(0, 255); };
export function sanitizeFileName(
fileName: string,
options: SanitizeFileNameOptions = {},
): string {
// Decode percent-encoded sequences so that bypasses like "..%2F" reach
// sanitize() as literal "../" and get stripped. sanitize-filename only
// strips literal characters and won't catch encoded path separators
// on its own.
const decoded = fileName.replace(/%[0-9a-fA-F]{2}/g, (m) => {
try {
return decodeURIComponent(m);
} catch {
return m;
}
});
const sanitized = sanitize(decoded);
if (options.preserveSpaces) {
return sanitized;
}
return sanitized.replace(/ /g, '_').replace(/#/g, '_');
} }
export function removeAccent(str: string): string { export function removeAccent(str: string): string {
@@ -53,7 +53,6 @@ import { EnvironmentService } from '../../integrations/environment/environment.s
import { TokenService } from '../auth/services/token.service'; import { TokenService } from '../auth/services/token.service';
import { JwtAttachmentPayload, JwtType } from '../auth/dto/jwt-payload'; import { JwtAttachmentPayload, JwtType } from '../auth/dto/jwt-payload';
import * as path from 'path'; import * as path from 'path';
import { sanitize } from 'sanitize-filename-ts';
import { AttachmentInfoDto, RemoveIconDto } from './dto/attachment.dto'; import { AttachmentInfoDto, RemoveIconDto } from './dto/attachment.dto';
import { PageAccessService } from '../page/page-access/page-access.service'; import { PageAccessService } from '../page/page-access/page-access.service';
import { AuditEvent, AuditResource } from '../../common/events/audit-events'; import { AuditEvent, AuditResource } from '../../common/events/audit-events';
@@ -357,13 +356,19 @@ export class AttachmentController {
throw new BadRequestException('Invalid image attachment type'); throw new BadRequestException('Invalid image attachment type');
} }
if (!fileName || sanitize(fileName) !== fileName) { if (!fileName) {
throw new BadRequestException('Invalid file name'); throw new BadRequestException('Invalid file name');
} }
const filenameWithoutExt = path.basename(fileName, path.extname(fileName)); const ext = path.extname(fileName);
if (!isValidUUID(filenameWithoutExt)) { const filenameWithoutExt = path.basename(fileName, ext);
throw new BadRequestException('Invalid file id');
if (
!ext ||
!isValidUUID(filenameWithoutExt) ||
`${filenameWithoutExt}${ext}` !== fileName
) {
throw new BadRequestException('Invalid file name');
} }
const filePath = `${getAttachmentFolderPath(attachmentType, workspace.id)}/${fileName}`; const filePath = `${getAttachmentFolderPath(attachmentType, workspace.id)}/${fileName}`;
@@ -23,9 +23,12 @@ import {
SpaceCaslSubject, SpaceCaslSubject,
} from '../../core/casl/interfaces/space-ability.type'; } from '../../core/casl/interfaces/space-ability.type';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import { sanitize } from 'sanitize-filename-ts';
import { getExportExtension } from './utils'; import { getExportExtension } from './utils';
import { getMimeType, getPageTitle } from '../../common/helpers'; import {
getMimeType,
getPageTitle,
sanitizeFileName,
} from '../../common/helpers';
import * as path from 'path'; import * as path from 'path';
import { AuditEvent, AuditResource } from '../../common/events/audit-events'; import { AuditEvent, AuditResource } from '../../common/events/audit-events';
import { import {
@@ -85,7 +88,9 @@ export class ExportController {
if (result.type === 'file') { if (result.type === 'file') {
const ext = getExportExtension(dto.format); const ext = getExportExtension(dto.format);
const fileName = sanitize(page.title || 'untitled') + ext; const fileName =
sanitizeFileName(page.title || 'untitled', { preserveSpaces: true }) +
ext;
const contentType = getMimeType(path.extname(fileName)); const contentType = getMimeType(path.extname(fileName));
res.headers({ res.headers({
@@ -96,7 +101,9 @@ export class ExportController {
res.send(result.content); res.send(result.content);
} else { } else {
const fileName = sanitize(page.title || 'untitled') + '.zip'; const fileName =
sanitizeFileName(page.title || 'untitled', { preserveSpaces: true }) +
'.zip';
res.headers({ res.headers({
'Content-Type': 'application/zip', 'Content-Type': 'application/zip',
@@ -144,7 +151,9 @@ export class ExportController {
'Content-Type': 'application/zip', 'Content-Type': 'application/zip',
'Content-Disposition': 'Content-Disposition':
'attachment; filename="' + 'attachment; filename="' +
encodeURIComponent(sanitize(exportFile.fileName)) + encodeURIComponent(
sanitizeFileName(exportFile.fileName, { preserveSpaces: true }),
) +
'"', '"',
}); });
@@ -1,7 +1,6 @@
import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import { PageRepo } from '@docmost/db/repos/page/page.repo'; import { PageRepo } from '@docmost/db/repos/page/page.repo';
import { MultipartFile } from '@fastify/multipart'; import { MultipartFile } from '@fastify/multipart';
import { sanitize } from 'sanitize-filename-ts';
import * as path from 'path'; import * as path from 'path';
import { import {
htmlToJson, htmlToJson,
@@ -53,8 +52,8 @@ export class ImportService {
const file = await filePromise; const file = await filePromise;
const fileBuffer = await file.toBuffer(); const fileBuffer = await file.toBuffer();
const fileExtension = path.extname(file.filename).toLowerCase(); const fileExtension = path.extname(file.filename).toLowerCase();
const fileName = sanitize( const fileName = sanitizeFileName(
path.basename(file.filename, fileExtension).slice(0, 255), path.basename(file.filename, fileExtension),
); );
const fileContent = fileBuffer.toString(); const fileContent = fileBuffer.toString();
+10 -9
View File
@@ -1,7 +1,7 @@
{ {
"name": "docmost", "name": "docmost",
"homepage": "https://docmost.com", "homepage": "https://docmost.com",
"version": "0.80.0", "version": "0.80.1",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "nx run-many -t build", "build": "nx run-many -t build",
@@ -62,7 +62,7 @@
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"diff": "8.0.3", "diff": "8.0.3",
"dompurify": "^3.3.3", "dompurify": "3.4.1",
"fractional-indexing-jittered": "^1.0.0", "fractional-indexing-jittered": "^1.0.0",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"image-dimensions": "^2.5.0", "image-dimensions": "^2.5.0",
@@ -72,7 +72,7 @@
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"rfc6902": "5.2.0", "rfc6902": "5.2.0",
"uuid": "^13.0.0", "uuid": "^14.0.0",
"y-indexeddb": "^9.0.12", "y-indexeddb": "^9.0.12",
"y-prosemirror": "1.3.7", "y-prosemirror": "1.3.7",
"yjs": "^13.6.30" "yjs": "^13.6.30"
@@ -102,9 +102,9 @@
"y-prosemirror": "1.3.7", "y-prosemirror": "1.3.7",
"glob": "13.0.6", "glob": "13.0.6",
"ws": "8.20.0", "ws": "8.20.0",
"dompurify": "3.3.3", "dompurify": "3.4.1",
"tmp": "0.2.5", "tmp": "0.2.5",
"hono": "4.12.12", "hono": "4.12.14",
"mermaid": "11.13.0", "mermaid": "11.13.0",
"nanoid@^3": "3.3.8", "nanoid@^3": "3.3.8",
"socket.io-parser": "4.2.6", "socket.io-parser": "4.2.6",
@@ -123,16 +123,17 @@
"flatted": "3.4.2", "flatted": "3.4.2",
"picomatch@<2.3.2": "2.3.2", "picomatch@<2.3.2": "2.3.2",
"picomatch@>=4.0.0 <4.0.4": "4.0.4", "picomatch@>=4.0.0 <4.0.4": "4.0.4",
"fastify": "5.8.3", "fastify": "5.8.5",
"yaml@>=1.0.0 <1.10.3": "1.10.3", "yaml@>=1.0.0 <1.10.3": "1.10.3",
"yaml@>=2.0.0 <2.8.3": "2.8.3", "yaml@>=2.0.0 <2.8.3": "2.8.3",
"path-to-regexp@^8": "8.4.0", "path-to-regexp@^8": "8.4.0",
"brace-expansion@^5": "5.0.5", "brace-expansion@^5": "5.0.5",
"@xmldom/xmldom": "0.8.12", "@xmldom/xmldom": "0.8.13",
"handlebars": "4.7.9", "handlebars": "4.7.9",
"axios": "1.15.0", "axios": "1.15.0",
"langsmith": "0.5.18", "langsmith": "0.5.19",
"follow-redirects": "1.16.0" "follow-redirects": "1.16.0",
"protobufjs": "7.5.5"
}, },
"neverBuiltDependencies": [] "neverBuiltDependencies": []
} }
+884 -894
View File
File diff suppressed because it is too large Load Diff