Skip to content

Instantly share code, notes, and snippets.

@lukaaash
Created December 14, 2015 21:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lukaaash/e1af88c166acecf94a9a to your computer and use it in GitHub Desktop.
Save lukaaash/e1af88c166acecf94a9a to your computer and use it in GitHub Desktop.
SFTP over WebSockets - simple server with JWT authentication and dummy custom filesystem
var SFTP = require("sftp-ws");
var jwt = require("jsonwebtoken");
// prepare host and port
var host = "localhost";
var port = process.env.port || 4001;
// start SFTP over WebSockets server
var server = new SFTP.Server({
host: host,
port: port,
virtualRoot: ".",
readOnly: true,
verifyClient: verifyClient,
log: console // log to console
});
console.log("SFTP server listening at ws://%s:%s", host, port);
// secret for JWT signatures
var secret = "this_is_secret";
// create a dummy API key
var key = jwt.sign({ foo: "bar" }, secret);
console.log("Authenticate with this API key: %s", key);
// to connect, use the following code
/*
var client = new SFTP.Client();
client.connect("ws://localhost:" + port, {
headers: { "Authorization" : "JWT " + key }
});
*/
function verifyClient(info, accept) {
// get credentials
var authorization = info.req.headers["authorization"];
// handle JWT authentication
var match = authorization && authorization.match(/^(JWT|Bearer) (.*)$/i);
if (!match) {
reject("Pass your API key to authenticate.");
return;
}
// get API key
var token = match[2];
// verify the token
jwt.verify(token, secret, prepare);
return;
function prepare(err, decoded) {
// handle errors
if (err) {
// log error
console.error(err);
reject("Token rejected.");
return;
}
// create custom filesystem object
var fs = new CustomFilesystem(token)
// accept credentials and pass the custom filesystem object
return accept({
filesystem: fs,
virtualRoot: "/",
readOnly: false,
});
}
function reject(message) {
// reject credentials
var headers = [
"WWW-Authenticate: Basic realm=\"SFTP\"",
"SFTP-Authenticate-Info: " + message,
];
accept(false, 401, "Not Authorized", headers);
}
}
var CustomFilesystem = (function () {
function CustomFilesystem(token) {
this.token = token;
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, flags, callback) {
this.inner.rename(oldPath, newPath, flags, 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;
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment