Skip to content
Merged
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
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"express": "5.1.0",
"express-async-handler": "1.2.0",
"express-basic-auth": "1.2.1",
"fast-xml-parser": "5.2.1",
"fast-xml-parser": "5.2.2",
"mime-types": "3.0.1",
"openpgp": "5.11.2",
"pm2": "6.0.5",
Expand All @@ -71,21 +71,21 @@
"@types/cli-progress": "3.11.6",
"@types/express": "5.0.1",
"@types/mime-types": "2.1.4",
"@types/node": "22.15.3",
"@types/node": "22.15.16",
"@types/range-parser": "1.2.7",
"@vitest/coverage-istanbul": "3.1.2",
"@vitest/spy": "3.1.2",
"eslint": "9.25.1",
"@vitest/coverage-istanbul": "3.1.3",
"@vitest/spy": "3.1.3",
"eslint": "9.26.0",
"husky": "9.1.7",
"lint-staged": "15.5.1",
"lint-staged": "15.5.2",
"nock": "14.0.4",
"nodemon": "3.1.10",
"oclif": "4.17.46",
"prettier": "3.5.3",
"rimraf": "6.0.1",
"ts-node": "10.9.2",
"typescript": "5.8.3",
"vitest": "3.1.2",
"vitest": "3.1.3",
"vitest-mock-express": "2.2.0"
},
"engines": {
Expand Down
6 changes: 3 additions & 3 deletions src/utils/xml.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ export class XMLUtils {
return builder.build(object);
}

static toWebDavXML(object: object, options: XmlBuilderOptions) {
static toWebDavXML(object: object, options: XmlBuilderOptions, rootObject = 'multistatus') {
const xmlContent = this.toXML(object, options);
return (
'<?xml version="1.0" encoding="utf-8" ?>' +
`<${XMLUtils.addDefaultNamespace('multistatus')} xmlns:${XMLUtils.DEFAULT_NAMESPACE_LETTER}="DAV:">` +
`<${XMLUtils.addDefaultNamespace(rootObject)} xmlns:${XMLUtils.DEFAULT_NAMESPACE_LETTER}="DAV:">` +
`${xmlContent}` +
`</${XMLUtils.addDefaultNamespace('multistatus')}>`
`</${XMLUtils.addDefaultNamespace(rootObject)}>`
);
}

Expand Down
18 changes: 17 additions & 1 deletion src/webdav/handlers/PROPFIND.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,23 @@ export class PROPFINDRequestHandler implements WebDavMethodHandler {
}
}
} catch {
res.status(207).send();
res.status(207).send(
XMLUtils.toWebDavXML(
{
[XMLUtils.addDefaultNamespace('response')]: {
[XMLUtils.addDefaultNamespace('href')]: XMLUtils.encodeWebDavUri(resource.url),
[XMLUtils.addDefaultNamespace('propstat')]: {
[XMLUtils.addDefaultNamespace('status')]: 'HTTP/1.1 404 Not Found',
[XMLUtils.addDefaultNamespace('prop')]: {},
},
},
},
{
ignoreAttributes: false,
suppressEmptyNode: true,
},
),
);
}
};

Expand Down
18 changes: 16 additions & 2 deletions src/webdav/middewares/auth.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { RequestHandler } from 'express';
import { SdkManager } from '../../services/sdk-manager.service';
import { AuthService } from '../../services/auth.service';
import { webdavLogger } from '../../utils/logger.utils';
import { XMLUtils } from '../../utils/xml.utils';

export const AuthMiddleware = (authService: AuthService): RequestHandler => {
return (req, res, next) => {
Expand All @@ -18,8 +19,21 @@ export const AuthMiddleware = (authService: AuthService): RequestHandler => {
};
next();
} catch (error) {
webdavLogger.error('Error from AuthMiddleware: ' + (error as Error).message);
res.status(401).send({ error: (error as Error).message });
let message = 'Authentication required to access this resource.';
if ('message' in (error as Error) && (error as Error).message.trim().length > 0) {
message = (error as Error).message;
}

webdavLogger.error('Error from AuthMiddleware: ' + message);

const errorBodyXML = XMLUtils.toWebDavXML(
{
[XMLUtils.addDefaultNamespace('responsedescription')]: message,
},
{},
'error',
);
res.status(401).send(errorBodyXML);
}
})();
};
Expand Down
27 changes: 15 additions & 12 deletions src/webdav/middewares/errors.middleware.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { ErrorRequestHandler } from 'express';
import { webdavLogger } from '../../utils/logger.utils';
import { XMLUtils } from '../../utils/xml.utils';

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const ErrorHandlingMiddleware: ErrorRequestHandler = (err, req, res, _) => {
webdavLogger.error(`[ERROR MIDDLEWARE] [${req.method.toUpperCase()} - ${req.url}]`, err);
if ('statusCode' in err) {
res.status(err.statusCode as number).send({
error: {
message: err.message,
},
});
} else {
res.status(500).send({
error: {
message: 'message' in err ? err.message : 'Something went wrong',
},
});

const errorBodyXML = XMLUtils.toWebDavXML(
{
[XMLUtils.addDefaultNamespace('responsedescription')]: 'message' in err ? err.message : 'Something went wrong',
},
{},
'error',
);

let statusCode = 500;
if ('statusCode' in err && !isNaN(err.statusCode)) {
statusCode = err.statusCode;
}

res.status(statusCode).send(errorBodyXML);
};
10 changes: 7 additions & 3 deletions src/webdav/webdav-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ export class WebDavServer {
return networkFacade;
};

private readonly registerMiddlewares = async () => {
this.app.use(ErrorHandlingMiddleware);
private readonly registerStartMiddlewares = () => {
this.app.use(AuthMiddleware(AuthService.instance));
this.app.use(
RequestLoggerMiddleware({
Expand All @@ -78,6 +77,10 @@ export class WebDavServer {
this.app.use(MkcolMiddleware);
};

private readonly registerEndMiddleWares = () => {
this.app.use(ErrorHandlingMiddleware);
};

private readonly registerHandlers = async () => {
const serverListenPath = /(.*)/;
const networkFacade = await this.getNetworkFacade();
Expand Down Expand Up @@ -159,8 +162,9 @@ export class WebDavServer {
start = async () => {
const configs = await this.configService.readWebdavConfig();
this.app.disable('x-powered-by');
await this.registerMiddlewares();
this.registerStartMiddlewares();
await this.registerHandlers();
this.registerEndMiddleWares();

const plainHttp = configs.protocol === 'http';
let server: http.Server | https.Server;
Expand Down
11 changes: 10 additions & 1 deletion test/webdav/middlewares/auth.middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createWebDavRequestFixture, createWebDavResponseFixture } from '../../f
import { UserCredentialsFixture } from '../../fixtures/login.fixture';
import { AuthService } from '../../../src/services/auth.service';
import { MissingCredentialsError } from '../../../src/types/command.types';
import { XMLUtils } from '../../../src/utils/xml.utils';

describe('Auth middleware', () => {
beforeEach(() => {
Expand All @@ -28,7 +29,15 @@ describe('Auth middleware', () => {
expect(authServiceStub).toHaveBeenCalledOnce();
expect(next).not.toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(401);
expect(res.send).toHaveBeenCalledWith({ error: new MissingCredentialsError().message });
expect(res.send).toHaveBeenCalledWith(
XMLUtils.toWebDavXML(
{
[XMLUtils.addDefaultNamespace('responsedescription')]: new MissingCredentialsError().message,
},
{},
'error',
),
);
});

it('When the user is authenticated, then it should add the user to the request', async () => {
Expand Down
51 changes: 44 additions & 7 deletions test/webdav/middlewares/errors.middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ErrorHandlingMiddleware } from '../../../src/webdav/middewares/errors.middleware';
import { createWebDavRequestFixture, createWebDavResponseFixture } from '../../fixtures/webdav.fixture';
import { BadRequestError, NotFoundError, NotImplementedError } from '../../../src/utils/errors.utils';
import { XMLUtils } from '../../../src/utils/xml.utils';

describe('Error handling middleware', () => {
beforeEach(() => {
vi.restoreAllMocks();
});

it('When a not found error is received, should respond with a 404', () => {
const errorMessage = 'Item not found';
const error = new NotFoundError('Item not found');
const res = createWebDavResponseFixture({
status: vi.fn().mockReturnValue({ send: vi.fn() }),
Expand All @@ -21,11 +23,20 @@ describe('Error handling middleware', () => {
ErrorHandlingMiddleware(error, req, res, () => {});

expect(res.status).toHaveBeenCalledWith(404);
expect(res.send).toHaveBeenCalledWith({ error: { message: 'Item not found' } });
expect(res.send).toHaveBeenCalledWith(
XMLUtils.toWebDavXML(
{
[XMLUtils.addDefaultNamespace('responsedescription')]: errorMessage,
},
{},
'error',
),
);
});

it('When a bad request error is received, should respond with a 400', () => {
const error = new BadRequestError('Missing property "size"');
const errorMessage = 'Missing property "size"';
const error = new BadRequestError(errorMessage);
const res = createWebDavResponseFixture({
status: vi.fn().mockReturnValue({ send: vi.fn() }),
});
Expand All @@ -37,11 +48,20 @@ describe('Error handling middleware', () => {
ErrorHandlingMiddleware(error, req, res, () => {});

expect(res.status).toHaveBeenCalledWith(400);
expect(res.send).toHaveBeenCalledWith({ error: { message: 'Missing property "size"' } });
expect(res.send).toHaveBeenCalledWith(
XMLUtils.toWebDavXML(
{
[XMLUtils.addDefaultNamespace('responsedescription')]: errorMessage,
},
{},
'error',
),
);
});

it('When a not implement error is received, should respond with a 501', () => {
const error = new NotImplementedError('Content-range is not supported');
const errorMessage = 'Content-range is not supported';
const error = new NotImplementedError(errorMessage);
const res = createWebDavResponseFixture({
status: vi.fn().mockReturnValue({ send: vi.fn() }),
});
Expand All @@ -53,11 +73,20 @@ describe('Error handling middleware', () => {
ErrorHandlingMiddleware(error, req, res, () => {});

expect(res.status).toHaveBeenCalledWith(501);
expect(res.send).toHaveBeenCalledWith({ error: { message: 'Content-range is not supported' } });
expect(res.send).toHaveBeenCalledWith(
XMLUtils.toWebDavXML(
{
[XMLUtils.addDefaultNamespace('responsedescription')]: errorMessage,
},
{},
'error',
),
);
});

it('When something that does not have status code arrives, should return a 500 status code', () => {
const error = new TypeError('Cannot read property "id" of undefined');
const errorMessage = 'Cannot read property "id" of undefined';
const error = new TypeError(errorMessage);
const res = createWebDavResponseFixture({
status: vi.fn().mockReturnValue({ send: vi.fn() }),
});
Expand All @@ -69,6 +98,14 @@ describe('Error handling middleware', () => {
ErrorHandlingMiddleware(error, req, res, () => {});

expect(res.status).toHaveBeenCalledWith(500);
expect(res.send).toHaveBeenCalledWith({ error: { message: 'Cannot read property "id" of undefined' } });
expect(res.send).toHaveBeenCalledWith(
XMLUtils.toWebDavXML(
{
[XMLUtils.addDefaultNamespace('responsedescription')]: errorMessage,
},
{},
'error',
),
);
});
});
Loading
Loading