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/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 { 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 { 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/src/webdav/webdav-server.ts b/src/webdav/webdav-server.ts index 2ad468dd..5fd110d5 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)); }; + 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.'); + } + + if (!configs.password?.trim()) { + throw new Error('WebDAV CustomAuth is enabled but no password was provided.'); + } + } + }; + start = async () => { const configs = await ConfigService.instance.readWebdavConfig(); + WebDavServer.checkwebDAVCredentials(configs); + this.app.disable('x-powered-by'); this.registerStartMiddlewares(configs); this.registerHandlers(); diff --git a/test/webdav/handlers/DELETE.handler.test.ts b/test/webdav/handlers/DELETE.handler.test.ts index aa27dc3d..fc8d5172 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,10 +15,12 @@ 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(() => { 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 () => { @@ -32,7 +35,6 @@ describe('DELETE request handler', () => { const response = createWebDavResponseFixture({ status: vi.fn().mockReturnValue({ send: vi.fn() }), }); - const getRequestedResourceStub = vi .spyOn(WebDavUtils, 'getRequestedResource') .mockResolvedValue(requestedFileResource); 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 () => {