-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WebDAV interface for filesystem #1146
Comments
Hey @KernelDeimos I would like to take up this issue. Could you assign me to this issue, please? |
Assigned! Let me know if you run into any hurdles |
Hey @KernelDeimos , It's Haiballa from Headstarter could you assign me to this issue, please? |
Hello @HAIBALLA1 , are you working on this with @dawit2123 ? They are currently assigned to this issue |
@HAIBALLA1 @KernelDeimos I wanted to let you know that I’ve been assigned to this issue and have already started working on it. |
@dawit2123 You mentioned a week ago that you started working on this, how far along is it? Is there anything I can do to help? |
@KernelDeimos I have an issue with making a WebDev driver interface. I used a WebDAV server to make the WebDAV, and then the authentication part should be done in the PUTer JS. So my approach was validateUser will validate the user if it appears, and if not, it will return unauthenticated. And I used PuterFSProvider and FSNodeContext to build it on top of the low-level filesystem. Then I exported startWebDAVServer and called it in the run-selfhosted.js. The validateUser method works fine with authentication, but even if the user is found, it's returning 404. Unauthorized when I access it using PROPFIND in Postman using the URL http://localhost:1900/webdav and with the correct username and password. I haven't used the authentication in webda-server because it should be dependable on the computer, but I am getting unauthorized, and I have tried so many approaches. Do you see it and recommend to me what I should do and also if there's any other package that I should use to do it? The code is attached down below: webdav-server.js code is down belowconst webdav = require('webdav-server').v2;
const bcrypt = require('bcrypt');
const express = require('express');
const { FSNodeContext } = require('../src/filesystem/FSNodeContext.js');
const { PuterFSProvider } = require('../src/modules/puterfs/lib/PuterFSProvider.js');
const { get_user } = require('../src/helpers');
const path = require('path');
class PuterFileSystem extends webdav.FileSystem {
constructor(fsProvider, user, Context) {
super("puter", { uid: 1, gid: 1 });
this.fsProvider = fsProvider;
this.user = user;
this.Context = Context;
}
_getPath(filePath) {
return path.normalize(filePath);
}
async _getFSNode(filePath) {
const normalizedPath = this._getPath(filePath);
return new FSNodeContext({
services: this.Context.get('services'),
selector: { path: normalizedPath },
provider: this.fsProvider,
fs: { node: this._getFSNode }
});
}
// Implement required WebDAV methods
async _openReadStream(ctx, filePath, callback) {
try {
const node = await this._getFSNode(filePath);
const stream = await node.read();
callback(null, stream);
} catch (e) {
callback(e);
}
}
async _openWriteStream(ctx, filePath, callback) {
try {
const node = await this._getFSNode(filePath);
const stream = await node.write();
callback(null, stream);
} catch (e) {
callback(e);
}
}
async _create(ctx, filePath, type, callback) {
try {
const node = await this._getFSNode(filePath);
if (type === webdav.ResourceType.Directory) {
await node.mkdir();
} else {
await node.create();
}
callback();
} catch (e) {
callback(e);
}
}
async _delete(ctx, filePath, callback) {
try {
const node = await this._getFSNode(filePath);
await node.delete();
callback();
} catch (e) {
callback(e);
}
}
// Implement other required methods (size, lastModifiedDate, etc.)
async _size(ctx, filePath, callback) {
try {
const node = await this._getFSNode(filePath);
const size = await node.size();
callback(null, size);
} catch (e) {
callback(e);
}
}
}
async function validateUser(username, password, Context) {
try {
const services = Context.get('services');
// Fetch user from Puter's authentication service
const user = await get_user({ username, cached: false });
if (!user) {
console.log(`Authentication failed: User '${username}' not found.`);
return null;
}
// Validate password with bcrypt
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
console.log(`Authentication failed: Incorrect password.`);
return null;
}
console.log(`Authentication successful for user: ${username}`);
return user;
} catch (error) {
console.error('Error during authentication:', error);
return null;
}
}
async function startWebDAVServer(port, Context) {
const app = express();
// Initialize Puter filesystem components
const services = Context.get('services');
const fsProvider = new PuterFSProvider(services);
const puterFS = new PuterFileSystem(fsProvider, null, Context);
const server = new webdav.WebDAVServer({
port: port,
autoSave: false,
rootFileSystem: puterFS // Use Puter filesystem as root
});
// Authentication middleware
app.use(async (req, res, next) => {
const authHeader = req.headers.authorization;
const credentials = Buffer.from(authHeader.split(' ')[1], 'base64').toString();
const [username, password] = credentials.split(':');
try {
const user = await validateUser(username, password, Context);
if (!user) return res.status(401).send('Invalid credentials');
req.user = user;
next();
} catch (error) {
console.error('Authentication error:', error);
res.status(500).send('Internal server error');
}
});
// Mount WebDAV server
app.use(webdav.extensions.express('/webdav', server));
// Start server
app.listen(port, () => {
console.log(`Puter WebDAV server running on port ${port}`);
console.log(`Access via: http://puter.localhost:${port}/webdav`);
});
return server;
}
module.exports = {
startWebDAVServer
}; run-selfhosted.js code that I have changed is down belowimport {startWebDAVServer} from '../src/backend/webdav/webdav-server.js';
const main = async () => {
const {
Kernel,
EssentialModules,
DatabaseModule,
LocalDiskStorageModule,
SelfHostedModule,
BroadcastModule,
TestDriversModule,
PuterAIModule,
PuterExecModule,
InternetModule,
MailModule,
ConvertModule,
DevelopmentModule,
} = (await import('@heyputer/backend')).default;
const k = new Kernel({
entry_path: import.meta.filename
});
for ( const mod of EssentialModules ) {
k.add_module(new mod());
}
k.add_module(new DatabaseModule());
k.add_module(new LocalDiskStorageModule());
k.add_module(new SelfHostedModule());
k.add_module(new BroadcastModule());
k.add_module(new TestDriversModule());
k.add_module(new PuterAIModule());
k.add_module(new PuterExecModule());
k.add_module(new InternetModule());
k.add_module(new MailModule());
k.add_module(new ConvertModule());
if ( process.env.UNSAFE_PUTER_DEV ) {
k.add_module(new DevelopmentModule());
}
k.boot();
const webdavContext = {
get: (serviceName) => {
// Special case to get the services container itself
if (serviceName === 'services') return k.services;
// Normal service resolution
return k.services.get(serviceName, { optional: true });
}
};
startWebDAVServer(1900, webdavContext);
}; Edited by @KernelDeimos for readability |
Hi, I found this is a bit difficult to figure out but so far it seems like your authentication logic is perfectly fine - I do get the "Authentication successful for user" message in the console but the response is always 401 Unauthorized. After some debugging, I think what's happening is the instance of 2025-03-14_15-04-38.mp4It turns out we can remove the header before passing along to the next middleware. I get another error after, but this looks like progress. |
@KernelDeimos The code that I have updated is down below>>> class PuterFileSystem extends webdav.FileSystem {
async function validateUser(username, password, Context) {
} async function startWebDAVServer(port, Context) {
} module.exports = { startWebDAVServer };` |
this error happens when there's no instance of Context from asyncLocalSorage. most likely it's because of how the new code is initialized. The snippet in your comment is difficult to review, can you open a draft PR instead? |
@KernelDeimos |
WebDAV interface for filesystem
A WebDAV interface for accessing Puter's filesystem would allow Puter files to be accessed in any environment where a WebDAV driver is available. This would significantly increase the use-cases for Puter.
Puter's Filesystem
puter.fs.write
)The items above are roughly in order from (closest to client) to (closest to storage). WebDAV should be implemented just above "low-level filesystem operations".
The WebDAV interface does not need puter.js support.
The text was updated successfully, but these errors were encountered: