* Windows WebDav support

* feat: 2fa support for dav

* fix auth cookie when Basic auth present

* dev: dav: refactor handler, support root level navigation

* dev: dav: JSDOCing important things

* fix: possible edge case of password containing colon character

* feat: direct token login for webdav

* set cookie to not be session cookie

* fx: webdav undefined origin support
This commit is contained in:
Neal Shah
2025-08-18 17:32:29 -04:00
committed by GitHub
parent b1e3fb136a
commit fcb5aa4078
6 changed files with 1260 additions and 13 deletions

View File

@@ -388,6 +388,8 @@ const install = async ({ services, app, useapi, modapi }) => {
const { WispService } = require('./services/WispService');
services.registerService('wisp', WispService);
const { WebDavFS } = require('./services/WebDavFS');
services.registerService('dav', WebDavFS);
const { RequestMeasureService } = require('./services/RequestMeasureService');
services.registerService('request-measure', RequestMeasureService);

View File

@@ -117,7 +117,7 @@ class LLRead extends LLFilesystemOperation {
const context = a.iget('context');
const storage = context.get('storage');
const { fsNode, version_id, offset, length, has_range } = a.values();
const { fsNode, version_id, offset, length, has_range, range } = a.values();
// Empty object here is in the case of local fiesystem,
// where s3:location will return null.
@@ -130,9 +130,9 @@ class LLRead extends LLFilesystemOperation {
bucket_region: location.bucket_region,
version_id,
key: location.key,
...(has_range ? {
...(range? {range} : (has_range ? {
range: `bytes=${offset}-${offset+length-1}`
} : {}),
} : {})),
}));
a.set('stream', stream);

View File

@@ -55,7 +55,7 @@ const configurable_auth = options => async (req, res, next) => {
if(req.body && req.body.auth_token)
token = req.body.auth_token;
// HTTML Auth header
else if(req.header && req.header('Authorization')) {
else if (req.header && req.header('Authorization') && !req.header('Authorization').startsWith("Basic ")) {
token = req.header('Authorization');
token = token.replace('Bearer ', '').trim();
if ( token === 'undefined' ) {
@@ -74,7 +74,7 @@ const configurable_auth = options => async (req, res, next) => {
else if(req.handshake && req.handshake.query && req.handshake.query.auth_token)
token = req.handshake.query.auth_token;
if(!token) {
if(!token || token.startsWith("Basic ")) {
if ( optional ) {
next();
return;

View File

@@ -628,10 +628,11 @@ class WebServerService extends BaseService {
}
// Request methods to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK');
const allowed_headers = [
"Origin", "X-Requested-With", "Content-Type", "Accept", "Authorization", "sentry-trace", "baggage"
"Origin", "X-Requested-With", "Content-Type", "Accept", "Authorization", "sentry-trace", "baggage",
"Depth", "Destination", "Overwrite", "If", "Lock-Token", "DAV"
];
// Request headers to allow
@@ -663,7 +664,22 @@ class WebServerService extends BaseService {
});
// Options for all requests (for CORS)
app.options('/*', (_, res) => {
app.options('/*', (req, res) => {
if (req.path.startsWith('/dav/')) {
res.set({
'Allow': 'OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, ORDERPATCH',
'DAV': '1, 2, ordered-collections', // WebDAV compliance classes with ordered-collections for macOS
'MS-Author-Via': 'DAV', // Microsoft compatibility
'Server': 'Puter/WebDAV', // Server identification
'Accept-Ranges': 'bytes',
'Content-Type': 'text/plain; charset=utf-8', // Explicit content type
'Content-Length': '0',
'Cache-Control': 'no-cache', // Prevent caching issues
'Connection': 'Keep-Alive' // Keep connection alive for macOS
});
res.status(200).end();
console.log("OPTIONS request completed for macOS compatibility");
}
return res.sendStatus(200);
});
}

View File

@@ -193,22 +193,57 @@ module.exports = function eggspress (route, settings, handler) {
}
}
};
if ( settings.allowedMethods.includes('GET') ) {
if (settings.allowedMethods.includes('GET')) {
router.get(route, ...mw, errorHandledHandler, ...afterMW);
}
if ( settings.allowedMethods.includes('POST') ) {
if (settings.allowedMethods.includes('HEAD')) {
router.head(route, ...mw, errorHandledHandler, ...afterMW);
}
if (settings.allowedMethods.includes('POST')) {
router.post(route, ...mw, errorHandledHandler, ...afterMW);
}
if ( settings.allowedMethods.includes('PUT') ) {
if (settings.allowedMethods.includes('PUT')) {
router.put(route, ...mw, errorHandledHandler, ...afterMW);
}
if ( settings.allowedMethods.includes('DELETE') ) {
if (settings.allowedMethods.includes('DELETE')) {
router.delete(route, ...mw, errorHandledHandler, ...afterMW);
}
if (settings.allowedMethods.includes('PROPFIND')) {
router.propfind(route, ...mw, errorHandledHandler, ...afterMW);
}
if (settings.allowedMethods.includes('PROPPATCH')) {
router.proppatch(route, ...mw, errorHandledHandler, ...afterMW);
}
if (settings.allowedMethods.includes('MKCOL')) {
router.mkcol(route, ...mw, errorHandledHandler, ...afterMW);
}
if (settings.allowedMethods.includes('COPY')) {
router.copy(route, ...mw, errorHandledHandler, ...afterMW);
}
if (settings.allowedMethods.includes('MOVE')) {
router.move(route, ...mw, errorHandledHandler, ...afterMW);
}
if (settings.allowedMethods.includes('LOCK')) {
router.lock(route, ...mw, errorHandledHandler, ...afterMW);
}
if (settings.allowedMethods.includes('UNLOCK')) {
router.unlock(route, ...mw, errorHandledHandler, ...afterMW);
}
if (settings.allowedMethods.includes('OPTIONS')) {
router.options(route, ...mw, errorHandledHandler, ...afterMW);
}
return router;
}

File diff suppressed because it is too large Load Diff