From 6155a7f37a120005ac5e7133470c6eab297b6e67 Mon Sep 17 00:00:00 2001 From: larryrider Date: Fri, 6 Mar 2026 16:48:20 +0100 Subject: [PATCH 1/8] fix: auth check hook --- src/hooks/prerun/auth_check.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hooks/prerun/auth_check.ts b/src/hooks/prerun/auth_check.ts index d8cd39fa..5f09b7da 100644 --- a/src/hooks/prerun/auth_check.ts +++ b/src/hooks/prerun/auth_check.ts @@ -21,9 +21,12 @@ const hook: Hook<'prerun'> = async function (opts) { const { Command, argv } = opts; const jsonFlag = argv.includes('--json'); + const commandsToSkipNames = CommandsToSkip.map((command) => command.name?.toLowerCase()).filter(Boolean); + const commandsToSkipIds = CommandsToSkip.map((command) => command.id?.toLowerCase()).filter(Boolean); + if ( - !CommandsToSkip.map((command) => command.name.toLowerCase()).includes(Command.name.toLowerCase()) && - !CommandsToSkip.map((command) => command.id.toLowerCase()).includes(Command.id.toLowerCase()) + !commandsToSkipNames.includes(Command.name?.toLowerCase()) && + !commandsToSkipIds.includes(Command.id?.toLowerCase()) ) { CLIUtils.doing('Checking credentials', jsonFlag); try { From b25fd5367b49653c2d9a6720916bbb7ba87155df Mon Sep 17 00:00:00 2001 From: larryrider Date: Fri, 6 Mar 2026 16:50:30 +0100 Subject: [PATCH 2/8] feat: enforce credentials check when customAuth is enabled --- src/webdav/webdav-server.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/webdav/webdav-server.ts b/src/webdav/webdav-server.ts index 2ad468dd..bceffd6a 100644 --- a/src/webdav/webdav-server.ts +++ b/src/webdav/webdav-server.ts @@ -59,8 +59,22 @@ export class WebDavServer { this.app.copy(serverListenPath, asyncHandler(new COPYRequestHandler().handle)); }; + private checkCredentials = (configs: WebdavConfig) => { + if (configs.customAuth) { + if (!configs.username?.trim()) { + throw new Error('WebDAV CustomAuth is enabled but no username was provided.'); + } + + if (!configs.password?.trim()) { + throw new Error('WebDAV CustomAuth is enabled but no password was provided.'); + } + } + }; + start = async () => { const configs = await ConfigService.instance.readWebdavConfig(); + this.checkCredentials(configs); + this.app.disable('x-powered-by'); this.registerStartMiddlewares(configs); this.registerHandlers(); From 443de6d8dbe94530ea3b6e4a5490171b5591062b Mon Sep 17 00:00:00 2001 From: larryrider Date: Fri, 6 Mar 2026 17:21:48 +0100 Subject: [PATCH 3/8] fix: sonarcloud maintainability issue --- src/webdav/webdav-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webdav/webdav-server.ts b/src/webdav/webdav-server.ts index bceffd6a..3e2c54cb 100644 --- a/src/webdav/webdav-server.ts +++ b/src/webdav/webdav-server.ts @@ -59,7 +59,7 @@ export class WebDavServer { this.app.copy(serverListenPath, asyncHandler(new COPYRequestHandler().handle)); }; - private checkCredentials = (configs: WebdavConfig) => { + private readonly checkCredentials = (configs: WebdavConfig) => { if (configs.customAuth) { if (!configs.username?.trim()) { throw new Error('WebDAV CustomAuth is enabled but no username was provided.'); From 6493c30182e887507e19604fc62a36e9d1526ae9 Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 9 Mar 2026 17:07:26 +0100 Subject: [PATCH 4/8] feat: enforce checkwebDAVCredentials on webdav start --- src/commands/webdav.ts | 6 ++++++ src/webdav/webdav-server.ts | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/commands/webdav.ts b/src/commands/webdav.ts index 5a8d342e..074b8262 100644 --- a/src/commands/webdav.ts +++ b/src/commands/webdav.ts @@ -3,6 +3,7 @@ import { PM2Utils } from '../utils/pm2.utils'; import { CLIUtils } from '../utils/cli.utils'; import { ConfigService } from '../services/config.service'; import { AuthService } from '../services/auth.service'; +import { WebDavServer } from '../webdav/webdav-server'; export default class Webdav extends Command { static readonly args = { @@ -28,10 +29,14 @@ export default class Webdav extends Command { let message = ''; let success = true; await PM2Utils.connect(); + + const configs = await ConfigService.instance.readWebdavConfig(); + switch (args.action) { case 'start': case 'enable': { await AuthService.instance.getAuthDetails(); + WebDavServer.checkwebDAVCredentials(configs); message = await this.enableWebDav(flags['json']); break; } @@ -44,6 +49,7 @@ export default class Webdav extends Command { case 'restart': { await AuthService.instance.getAuthDetails(); + WebDavServer.checkwebDAVCredentials(configs); message = await this.restartWebDav(flags['json']); break; } diff --git a/src/webdav/webdav-server.ts b/src/webdav/webdav-server.ts index 3e2c54cb..5fd110d5 100644 --- a/src/webdav/webdav-server.ts +++ b/src/webdav/webdav-server.ts @@ -59,7 +59,7 @@ export class WebDavServer { this.app.copy(serverListenPath, asyncHandler(new COPYRequestHandler().handle)); }; - private readonly checkCredentials = (configs: WebdavConfig) => { + public static readonly checkwebDAVCredentials = (configs: WebdavConfig) => { if (configs.customAuth) { if (!configs.username?.trim()) { throw new Error('WebDAV CustomAuth is enabled but no username was provided.'); @@ -73,7 +73,7 @@ export class WebDavServer { start = async () => { const configs = await ConfigService.instance.readWebdavConfig(); - this.checkCredentials(configs); + WebDavServer.checkwebDAVCredentials(configs); this.app.disable('x-powered-by'); this.registerStartMiddlewares(configs); From 59964e5c3a1d6d552ae30e318aebd9f8ad14091c Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 9 Mar 2026 17:08:18 +0100 Subject: [PATCH 5/8] fix: add exists filter to files and folders --- src/services/drive/drive-file.service.ts | 5 +++-- src/services/drive/drive-folder.service.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/services/drive/drive-file.service.ts b/src/services/drive/drive-file.service.ts index d63f0c17..52328668 100644 --- a/src/services/drive/drive-file.service.ts +++ b/src/services/drive/drive-file.service.ts @@ -8,6 +8,7 @@ import { DriveFolderService } from './drive-folder.service'; import { NotFoundError } from '../../utils/errors.utils'; import { PathUtils } from '../../utils/path.utils'; import { logger } from '../../utils/logger.utils'; +import { FileStatus } from '@internxt/sdk/dist/drive/storage/types'; export class DriveFileService { static readonly instance = new DriveFileService(); @@ -102,7 +103,7 @@ export class DriveFileService { const fileMeta = subFiles.find( (file) => (file.plainName === name || file.name === name) && (file.type ?? null) === type, ); - if (!fileMeta) { + if (!fileMeta || fileMeta.status !== FileStatus.EXISTS) { throw new NotFoundError('File not found'); } return DriveUtils.driveFileMetaToItem(fileMeta); @@ -117,7 +118,7 @@ export class DriveFileService { if (localFileDB) { try { const file = await this.getFileMetadata(localFileDB.uuid); - if (file) { + if (file && file.status === FileStatus.EXISTS) { return file; } } catch { diff --git a/src/services/drive/drive-folder.service.ts b/src/services/drive/drive-folder.service.ts index e0b2237f..21930172 100644 --- a/src/services/drive/drive-folder.service.ts +++ b/src/services/drive/drive-folder.service.ts @@ -202,7 +202,7 @@ export class DriveFolderService { public getByParentUuidAndName = async (parentUuid: string, name: string): Promise => { const subFolders = await this.getFolderSubfolders(parentUuid); const folderMeta = subFolders.find((folder) => folder.plainName === name || folder.name === name); - if (!folderMeta) { + if (!folderMeta || folderMeta.status !== FileStatus.EXISTS) { throw new NotFoundError('Folder not found'); } @@ -245,7 +245,7 @@ export class DriveFolderService { if (localFolderDB) { try { const folder = await this.getFolderMetaByUuid(localFolderDB.uuid); - if (folder) { + if (folder && folder.status === FileStatus.EXISTS) { return folder; } } catch { From a481abfb08dbd890990347677f2ee6ff0c40afcc Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 9 Mar 2026 17:11:03 +0100 Subject: [PATCH 6/8] fix: improve head handler and allow folders --- src/webdav/handlers/HEAD.handler.ts | 26 +++++++++++------------ test/webdav/handlers/HEAD.handler.test.ts | 15 ++++++++++++- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/webdav/handlers/HEAD.handler.ts b/src/webdav/handlers/HEAD.handler.ts index 38a921a6..2f78a73b 100644 --- a/src/webdav/handlers/HEAD.handler.ts +++ b/src/webdav/handlers/HEAD.handler.ts @@ -9,34 +9,32 @@ export class HEADRequestHandler implements WebDavMethodHandler { handle = async (req: Request, res: Response) => { const resource = await WebDavUtils.getRequestedResource(req.url); - webdavLogger.info(`[HEAD] Request received for file at ${resource.url}`); + webdavLogger.info(`[HEAD] Request received for item at ${resource.url}`); - try { - const driveFile = await WebDavUtils.getDriveFileFromResource(resource.url); + const driveItem = await WebDavUtils.getDriveItemFromResource(resource); - if (!driveFile) { - throw new NotFoundError(`Resource not found on Internxt Drive at ${resource.url}`); - } + if (!driveItem) { + throw new NotFoundError(`Resource not found on Internxt Drive at ${resource.url}`); + } - webdavLogger.info(`[HEAD] [${driveFile.uuid}] Found Drive File`); + webdavLogger.info(`[HEAD] [${driveItem.uuid}] Found Drive item`); + if (driveItem.itemType === 'file') { const range = req.headers['range']; const rangeOptions = NetworkUtils.parseRangeHeader({ range, - totalFileSize: driveFile.size, + totalFileSize: driveItem.size, }); - let contentLength = driveFile.size; + let contentLength = driveItem.size; if (rangeOptions) { - webdavLogger.info(`[HEAD] [${driveFile.uuid}] Range request received:`, { rangeOptions }); + webdavLogger.info(`[HEAD] [${driveItem.uuid}] Range request received:`, { rangeOptions }); contentLength = rangeOptions.rangeSize; } res.header('Content-Type', 'application/octet-stream'); res.header('Content-length', contentLength.toString()); - res.status(200).send(); - } catch { - res.header('Content-Type', 'application/octet-stream'); - res.status(200).send(); } + + res.status(200).send(); }; } diff --git a/test/webdav/handlers/HEAD.handler.test.ts b/test/webdav/handlers/HEAD.handler.test.ts index 3d681ba5..959a7046 100644 --- a/test/webdav/handlers/HEAD.handler.test.ts +++ b/test/webdav/handlers/HEAD.handler.test.ts @@ -8,9 +8,10 @@ import { } from '../../fixtures/webdav.fixture'; import { DriveFileService } from '../../../src/services/drive/drive-file.service'; import { WebDavRequestedResource } from '../../../src/types/webdav.types'; -import { newFileItem } from '../../fixtures/drive.fixture'; +import { newFileItem, newFolderItem } from '../../fixtures/drive.fixture'; import { WebDavUtils } from '../../../src/utils/webdav.utils'; import { randomInt } from 'crypto'; +import { DriveFolderService } from '../../../src/services/drive/drive-folder.service'; describe('HEAD request handler', () => { let sut: HEADRequestHandler; @@ -30,8 +31,20 @@ describe('HEAD request handler', () => { status: vi.fn().mockReturnValue({ send: vi.fn() }), }); + const mockFolder = newFolderItem(); + + vi.spyOn(DriveFileService.instance, 'getFileMetadataByPath').mockRejectedValue(undefined); + const getRequestedResourceStub = vi + .spyOn(WebDavUtils, 'getRequestedResource') + .mockResolvedValue(requestedFolderResource); + const getFolderMetadataStub = vi + .spyOn(DriveFolderService.instance, 'getFolderMetadataByPath') + .mockResolvedValue(mockFolder); + await sut.handle(request, response); expect(response.status).toHaveBeenCalledWith(200); + expect(getRequestedResourceStub).toHaveBeenCalledOnce(); + expect(getFolderMetadataStub).toHaveBeenCalledOnce(); }); it('When a file is requested, it should reply with a 200 with the correct headers', async () => { From 0c1c2c609544dd216420a03a15fb9633a21d5e05 Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 9 Mar 2026 17:12:16 +0100 Subject: [PATCH 7/8] fix: delete handler tests --- test/webdav/handlers/DELETE.handler.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/webdav/handlers/DELETE.handler.test.ts b/test/webdav/handlers/DELETE.handler.test.ts index aa27dc3d..d85b739f 100644 --- a/test/webdav/handlers/DELETE.handler.test.ts +++ b/test/webdav/handlers/DELETE.handler.test.ts @@ -6,6 +6,7 @@ import { createWebDavResponseFixture, getRequestedFileResource, getRequestedFolderResource, + getWebdavConfigMock, } from '../../fixtures/webdav.fixture'; import { TrashService } from '../../../src/services/drive/trash.service'; import { NotFoundError } from '../../../src/utils/errors.utils'; @@ -14,6 +15,7 @@ import { newFileItem, newFolderItem } from '../../fixtures/drive.fixture'; import { WebDavRequestedResource } from '../../../src/types/webdav.types'; import { AuthService } from '../../../src/services/auth.service'; import { UserCredentialsFixture } from '../../fixtures/login.fixture'; +import { ConfigService } from '../../../src/services/config.service'; describe('DELETE request handler', () => { beforeEach(() => { @@ -32,7 +34,9 @@ describe('DELETE request handler', () => { const response = createWebDavResponseFixture({ status: vi.fn().mockReturnValue({ send: vi.fn() }), }); + const mockWebdavConfig = getWebdavConfigMock(); + vi.spyOn(ConfigService.instance, 'readWebdavConfig').mockResolvedValue(mockWebdavConfig); const getRequestedResourceStub = vi .spyOn(WebDavUtils, 'getRequestedResource') .mockResolvedValue(requestedFileResource); @@ -63,7 +67,9 @@ describe('DELETE request handler', () => { }); const mockFile = newFileItem(); + const mockWebdavConfig = getWebdavConfigMock(); + vi.spyOn(ConfigService.instance, 'readWebdavConfig').mockResolvedValue(mockWebdavConfig); const getRequestedResourceStub = vi .spyOn(WebDavUtils, 'getRequestedResource') .mockResolvedValue(requestedFileResource); @@ -93,7 +99,9 @@ describe('DELETE request handler', () => { }); const mockFolder = newFolderItem(); + const mockWebdavConfig = getWebdavConfigMock(); + vi.spyOn(ConfigService.instance, 'readWebdavConfig').mockResolvedValue(mockWebdavConfig); const getRequestedResourceStub = vi .spyOn(WebDavUtils, 'getRequestedResource') .mockResolvedValue(requestedFolderResource); From b7b4f11eaaa475c1c635d0ab7d0d8a880865f607 Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 9 Mar 2026 17:32:24 +0100 Subject: [PATCH 8/8] feat: improve delete request tests --- test/webdav/handlers/DELETE.handler.test.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/webdav/handlers/DELETE.handler.test.ts b/test/webdav/handlers/DELETE.handler.test.ts index d85b739f..fc8d5172 100644 --- a/test/webdav/handlers/DELETE.handler.test.ts +++ b/test/webdav/handlers/DELETE.handler.test.ts @@ -20,6 +20,7 @@ import { ConfigService } from '../../../src/services/config.service'; describe('DELETE request handler', () => { beforeEach(() => { vi.spyOn(AuthService.instance, 'getAuthDetails').mockResolvedValue(UserCredentialsFixture); + vi.spyOn(ConfigService.instance, 'readWebdavConfig').mockResolvedValue(getWebdavConfigMock()); }); it('When the item does not exist, it should reply with a 404 error', async () => { @@ -34,9 +35,6 @@ describe('DELETE request handler', () => { const response = createWebDavResponseFixture({ status: vi.fn().mockReturnValue({ send: vi.fn() }), }); - const mockWebdavConfig = getWebdavConfigMock(); - - vi.spyOn(ConfigService.instance, 'readWebdavConfig').mockResolvedValue(mockWebdavConfig); const getRequestedResourceStub = vi .spyOn(WebDavUtils, 'getRequestedResource') .mockResolvedValue(requestedFileResource); @@ -67,9 +65,7 @@ describe('DELETE request handler', () => { }); const mockFile = newFileItem(); - const mockWebdavConfig = getWebdavConfigMock(); - vi.spyOn(ConfigService.instance, 'readWebdavConfig').mockResolvedValue(mockWebdavConfig); const getRequestedResourceStub = vi .spyOn(WebDavUtils, 'getRequestedResource') .mockResolvedValue(requestedFileResource); @@ -99,9 +95,7 @@ describe('DELETE request handler', () => { }); const mockFolder = newFolderItem(); - const mockWebdavConfig = getWebdavConfigMock(); - vi.spyOn(ConfigService.instance, 'readWebdavConfig').mockResolvedValue(mockWebdavConfig); const getRequestedResourceStub = vi .spyOn(WebDavUtils, 'getRequestedResource') .mockResolvedValue(requestedFolderResource);