lukaaash / sftp-ws

SFTP over WebSockets - client and server package for Node.js
MIT License
34 stars 16 forks source link

Question: Is there a callback on the server side where I can authenticate with jwt? #3

Closed rajeevb2 closed 8 years ago

lukaaash commented 9 years ago

Yes, there is, but it was not fully implemented until v0.5.0 (and thus unusable). Now you can implement a custom verifyClient handler that should be perfectly capable of authenticating with JWT. For simplicity, the following code demonstrates a very simple "Basic" authentication (this is not going to work with web-based clients, of course, but JWT will).

var SFTP = require("sftp-ws");

var server = new SftpServer({
    port: port,
    host: host,
    log: console,
    verifyClient: verifyClient
});

function verifyClient(info, accept) {
    // hard-coded basic authentication (username: guest, password: none)
    var credentials = info.req.headers["authorization"];
    if (credentials === "Basic Z3Vlc3Q6bm9uZQ==") {
        // accept client and supply session info
        return accept({
            userName: "guest",
            virtualRoot: '/data',
            readOnly: false,
        });
    } else {
        // reject request and ask for basic authentication
        accept(false, 401, "Not Authorized", ["WWW-Authenticate: Basic realm=\"SFTP\""]);
    }
}
rajeevb2 commented 9 years ago

Thank you. I will try this out. When is 0.5 coming out? I would love to see if you can somehow support roles (beyond client verification) also -- like Unix/Linux file permissions that allow or disallow fileops on certain files using authorization. I am happy to help here too.

lukaaash commented 9 years ago

0.5 was out 3 days ago, I just forgot to add proper release tags... Sorry! You might use the verifyClient mechanism to add some kind of roles as well - the accept callback makes it possible to pass a custom file system implementation as well in addition to the options in the sample code above:

return accept({
    userName: userName,
    filesystem: new CustomFilesystem(userName),
    virtualRoot: '/data',
    readOnly: false,
});

The filesystem object has to implement functions defined by IFilesystem and the easiest way to get started is to create a wrapper around the built-in LocalFilesystem object that optionally modifies its functionality to some degree:

var CustomFilesystem = (function () {
    function CustomFilesystem(userName) {
        this.userName = userName;
        this.inner = new SFTP.LocalFilesystem();
    }

    CustomFilesystem.prototype.open = function (path, flags, attrs, callback) {
        this.inner.open(path, flags, attrs, callback);
    };

    CustomFilesystem.prototype.close = function (handle, callback) {
        this.inner.close(handle, callback);
    };

    CustomFilesystem.prototype.read = function (handle, buffer, offset, length, position, callback) {
        this.inner.read(handle, buffer, offset, length, position, callback);
    };

    CustomFilesystem.prototype.write = function (handle, buffer, offset, length, position, callback) {
        this.inner.write(handle, buffer, offset, length, position, callback);
    };

    CustomFilesystem.prototype.lstat = function (path, callback) {
        this.inner.lstat(path, callback);
    };

    CustomFilesystem.prototype.fstat = function (handle, callback) {
        this.inner.fsetstat(handle, callback);
    };

    CustomFilesystem.prototype.setstat = function (path, attrs, callback) {
        this.inner.setstat(path, attrs, callback);
    };

    CustomFilesystem.prototype.fsetstat = function (handle, attrs, callback) {
        this.inner.fsetstat(handle, attrs, callback);
    };

    CustomFilesystem.prototype.opendir = function (path, callback) {
        this.inner.opendir(path, callback);
    };

    CustomFilesystem.prototype.readdir = function (handle, callback) {
        this.inner.readdir(handle, callback);
    };

    CustomFilesystem.prototype.unlink = function (path, callback) {
        this.inner.unlink(path, callback);
    };

    CustomFilesystem.prototype.mkdir = function (path, attrs, callback) {
        this.inner.mkdir(path, attrs, callback);
    };

    CustomFilesystem.prototype.rmdir = function (path, callback) {
        this.inner.rmdir(path, callback);
    };

    CustomFilesystem.prototype.realpath = function (path, callback) {
        this.inner.realpath(path, callback);
    };

    CustomFilesystem.prototype.stat = function (path, callback) {
        this.inner.stat(path, callback);
    };

    CustomFilesystem.prototype.rename = function (oldPath, newPath, callback) {
        this.inner.rename(oldPath, newPath, callback);
    };

    CustomFilesystem.prototype.readlink = function (path, callback) {
        this.inner.readlink(path, callback);
    };

    CustomFilesystem.prototype.symlink = function (targetpath, linkpath, callback) {
        this.inner.symlink(targetpath, linkpath, callback);
    };

    CustomFilesystem.prototype.link = function (oldPath, newPath, callback) {
        this.inner.link(oldPath, newPath, callback);
    };

    return CustomFilesystem;
})();

For example, you might modify these functions to check for appropriate permissions before performing the desired action, and/or to use file attributes located in some kind of a database instead of real filesystem ones.

In the future, I would like to add a high-level API for creating custom filesystems more easily, but I'm currently busy with other features. However, a custom SFTP-enabled file storage that stores file data in the filesystem and attributes (uid/gid/permissions) in a database sounds like a great idea! If you would like to help with this, please let me know at lukas@nuane.com

rajeevb2 commented 9 years ago

Thanks Lukas. I will try this out.

Rajeev

On Oct 14, 2015, at 3:22 PM, Lukas Pokorny notifications@github.com wrote:

0.5 was out 3 days ago, I just forgot to add proper release tags... Sorry! You might use the verifyClient mechanism to add some kind of roles as well - the accept callback makes it possible to pass a custom file system implementation as well in addition to the options in the sample code above:

return accept({ userName: userName, filesystem: new CustomFilesystem(userName), virtualRoot: '/data', readOnly: false, }); The filesystem object has to implement functions defined by IFilesystem https://github.com/lukaaash/sftp-ws/blob/master/src/lib/fs-api.ts and the easiest way to get started is to create a wrapper around the built-in LocalFilesystem object that optionally modifies its functionality to some degree:

var CustomFilesystem = (function () { function CustomFilesystem(userName) { this.userName = userName; this.inner = new SFTP.LocalFilesystem(); }

CustomFilesystem.prototype.open = function (path, flags, attrs, callback) {
    this.inner.open(path, flags, attrs, callback);
};

CustomFilesystem.prototype.close = function (handle, callback) {
    this.inner.close(handle, callback);
};

CustomFilesystem.prototype.read = function (handle, buffer, offset, length, position, callback) {
    this.inner.read(handle, buffer, offset, length, position, callback);
};

CustomFilesystem.prototype.write = function (handle, buffer, offset, length, position, callback) {
    this.inner.write(handle, buffer, offset, length, position, callback);
};

CustomFilesystem.prototype.lstat = function (path, callback) {
    this.inner.lstat(path, callback);
};

CustomFilesystem.prototype.fstat = function (handle, callback) {
    this.inner.fsetstat(handle, callback);
};

CustomFilesystem.prototype.setstat = function (path, attrs, callback) {
    this.inner.setstat(path, attrs, callback);
};

CustomFilesystem.prototype.fsetstat = function (handle, attrs, callback) {
    this.inner.fsetstat(handle, attrs, callback);
};

CustomFilesystem.prototype.opendir = function (path, callback) {
    this.inner.opendir(path, callback);
};

CustomFilesystem.prototype.readdir = function (handle, callback) {
    this.inner.readdir(handle, callback);
};

CustomFilesystem.prototype.unlink = function (path, callback) {
    this.inner.unlink(path, callback);
};

CustomFilesystem.prototype.mkdir = function (path, attrs, callback) {
    this.inner.mkdir(path, attrs, callback);
};

CustomFilesystem.prototype.rmdir = function (path, callback) {
    this.inner.rmdir(path, callback);
};

CustomFilesystem.prototype.realpath = function (path, callback) {
    this.inner.realpath(path, callback);
};

CustomFilesystem.prototype.stat = function (path, callback) {
    this.inner.stat(path, callback);
};

CustomFilesystem.prototype.rename = function (oldPath, newPath, callback) {
    this.inner.rename(oldPath, newPath, callback);
};

CustomFilesystem.prototype.readlink = function (path, callback) {
    this.inner.readlink(path, callback);
};

CustomFilesystem.prototype.symlink = function (targetpath, linkpath, callback) {
    this.inner.symlink(targetpath, linkpath, callback);
};

CustomFilesystem.prototype.link = function (oldPath, newPath, callback) {
    this.inner.link(oldPath, newPath, callback);
};

return CustomFilesystem;

})(); For example, you might modify these functions to check for appropriate permissions before performing the desired action, and/or to use file attributes located in some kind of a database instead of real filesystem ones.

In the future, I would like to add a high-level API for creating custom filesystems more easily, but I'm currently busy with other features. However, a custom SFTP-enabled file storage that stores file data in the filesystem and attributes (uid/gid/permissions) in a database sounds like a great idea! If you would like to help with this, please let me know at lukas@nuane.com mailto:lukas@nuane.com — Reply to this email directly or view it on GitHub https://github.com/lukaaash/sftp-ws/issues/3#issuecomment-148220196.