Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/commands/webdav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down
7 changes: 5 additions & 2 deletions src/hooks/prerun/auth_check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 3 additions & 2 deletions src/services/drive/drive-file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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();
Expand Down Expand Up @@ -102,7 +103,7 @@
const fileMeta = subFiles.find(
(file) => (file.plainName === name || file.name === name) && (file.type ?? null) === type,
);
if (!fileMeta) {
if (!fileMeta || fileMeta.status !== FileStatus.EXISTS) {

Check warning on line 106 in src/services/drive/drive-file.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=internxt_cli&issues=AZzTZ9vHNYLULcTcs3ta&open=AZzTZ9vHNYLULcTcs3ta&pullRequest=517
throw new NotFoundError('File not found');
}
return DriveUtils.driveFileMetaToItem(fileMeta);
Expand All @@ -117,7 +118,7 @@
if (localFileDB) {
try {
const file = await this.getFileMetadata(localFileDB.uuid);
if (file) {
if (file && file.status === FileStatus.EXISTS) {
return file;
}
} catch {
Expand Down
4 changes: 2 additions & 2 deletions src/services/drive/drive-folder.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@
public getByParentUuidAndName = async (parentUuid: string, name: string): Promise<DriveFolderItem> => {
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) {

Check warning on line 205 in src/services/drive/drive-folder.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=internxt_cli&issues=AZzTZ9xZNYLULcTcs3tb&open=AZzTZ9xZNYLULcTcs3tb&pullRequest=517
throw new NotFoundError('Folder not found');
}

Expand Down Expand Up @@ -245,7 +245,7 @@
if (localFolderDB) {
try {
const folder = await this.getFolderMetaByUuid(localFolderDB.uuid);
if (folder) {
if (folder && folder.status === FileStatus.EXISTS) {
return folder;
}
} catch {
Expand Down
26 changes: 12 additions & 14 deletions src/webdav/handlers/HEAD.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
};
}
14 changes: 14 additions & 0 deletions src/webdav/webdav-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 3 additions & 1 deletion test/webdav/handlers/DELETE.handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 () => {
Expand All @@ -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);
Expand Down
15 changes: 14 additions & 1 deletion test/webdav/handlers/HEAD.handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 () => {
Expand Down