fido2-server
Version:
A FIDO 2.0 / W3C WebAuthn server
258 lines (225 loc) • 6.76 kB
JavaScript
var _ = require("lodash");
var MDSC = require("mds-client");
module.exports = MetadataManager;
// TODO: handle side-loading certs / metadata
/**
* Manages loading metadata and attestation certs for authenticators
* through the Metadata Service (MDS) and through JSON statments stored
* in a watch folder.
*/
function MetadataManager(opt) {
opt = _.cloneDeep(opt);
var defaults = {
waterlineAdapter: "sails-disk",
metadataDir: "./metadata",
mdsUrl: "https://mds.fidoalliance.org"
};
opt = _.defaultsDeep(opt, defaults);
this.opt = opt;
}
MetadataManager.prototype.init = function(server) {
return waterlineInit.call(this)
.then(function(waterline) {
// TODO: "chokidar" watch on watch folder
return this.update();
}.bind(this))
.then(function(update) {
return this;
});
};
function waterlineInit() {
return new Promise(function(resolve, reject) {
// init waterline
var Waterline = require("waterline");
var waterline = new Waterline();
var adapter = require(this.opt.waterlineAdapter);
var waterlineConfig = {
adapters: {
"mmDefault": adapter
},
connections: {
mm: {
adapter: "mmDefault"
}
}
};
// setup waterline models
var configCollection = Waterline.Collection.extend({
identity: "config",
connection: "mm",
attributes: {
lastMdsUpdate: "datetime",
nextMdsUpdate: "date",
serialNo: "integer"
}
});
var authenticatorCollection = Waterline.Collection.extend({
identity: "authenticator",
connection: "mm",
attributes: {
// TODO: should mirror metadata specification
id: "string", // required, unique, index
aaid: "string",
aaguid: "string",
attestationCertificateKeyIdentifiers: "array",
hash: "string",
url: "string", // required, unique
timeOfLastStatusChange: "date",
statusReports: "json",
// reportedHash: "string",
// certified: "boolean",
metadata: "json"
}
});
// TODO: load authentictors, config data from waterline
waterline.loadCollection(configCollection);
waterline.loadCollection(authenticatorCollection);
waterline.initialize(waterlineConfig, function(err, ontology) {
if (err) {
console.log("Error in waterline init: " + err);
// TODO: audit warn
return reject(err);
}
this.waterline = waterline;
this.config = ontology.collections.config;
this.authenticator = ontology.collections.authenticator;
return resolve(this);
}.bind(this));
}.bind(this));
}
MetadataManager.prototype.shutdown = function() {
return Promise.resolve(null);
};
MetadataManager.prototype.update = function() {
return new Promise(function(resolve, reject) {
// TODO: "scandir" to update new / updated files from watch directory
// TODO: check next update time; if it's past then update
var updateMds = function() {
return new Promise(function(resolve, reject) {
var mdsClient = new MDSC();
var toc;
return mdsClient.fetchToc()
// fetch MDS TOC
.then(function(retToc) {
// TODO: check return?
toc = retToc;
return this.getConfig();
}.bind(this))
// save config
.then(function(c) {
console.log(c);
config = c || {};
config.lastMdsUpdate = new Date();
config.nextMdsUpdate = toc.nextUpdate;
config.serialNo = toc.no;
console.log(config);
if (c === undefined) {
return this.setConfig(config);
} else {
return this.updateConfig(config);
}
// return this.getConfig();
}.bind(this))
// fetch entries
.then(function(config) {
console.log("Got config");
console.log(config);
return mdsClient.fetchEntries();
})
// save / update all entries
.then(function(entries) {
// TODO: filter errors
var i, id, p, promiseList = [];
for (i = 0; i < entries.length; i++) {
// console.log("Doing Entry:", entries[i]);
// TODO: not sure this is the right algorithm for setting an ID
entries[i].id = entries[i].aaid ||
entries[i].aaguid ||
entries[i].attestationCertificateKeyIdentifiers;
console.log("Doing Entry ID:", entries[i].id);
p = this.getAuthenticatorById(id)
.then(function(authn) {
console.log ("Authn:", authn);
console.log ("i:", i);
// if (authn === undefined) {
// console.log("ID not found:", entries[i].id);
// return this.createAuthenticator(entries[i]);
// } else {
// console.log("ID found:", entries[i].id);
// return this.updateAuthenticator(entries[i]);
// }
}.bind(this));
promiseList.push(p);
}
console.log("doing Promise.all");
return Promise.all(promiseList);
}.bind(this))
.then(function(ps) {
console.log("Promise.all done");
return resolve(true);
})
.catch(function(err) {
console.log("ERROR IN UPDATE MDS");
console.log(err);
return reject(err);
});
}.bind(this));
}.bind(this);
// grab our config to see if we need to update
this.getConfig().then(function(config) {
// if we don't have a config entry, grab the MDS
if (config === undefined) {
return updateMds()
// .catch(function(err) {
// console.log("ERR1");
// return reject(err);
// });
}
// if our MDS entry is stale, grab the latest one
var now = new Date();
var next = new Date(config.nextMdsUpdate);
if (now.getTime() > next.getTime()) {
return updateMds()
// .catch(function(err) {
// console.log("ERR2");
// return reject(err);
// });
}
resolve(null);
});
}.bind(this));
};
function updateWatchFolder() {
}
MetadataManager.prototype.getConfig = function() {
return this.config.find().then(function(configs) {
if (configs.length > 1) {
return Promise.reject(new Error("Too many configs found in database: " + configs.length));
} else if (configs.length === 0) {
return undefined;
}
return configs[0];
});
};
MetadataManager.prototype.setConfig = function(config) {
return this.config.create(config);
};
MetadataManager.prototype.updateConfig = function(config) {
console.log("updating config");
return this.config.update({}, config);
};
MetadataManager.prototype.listAuthenticators = function() {
return Promise.resolve([]);
};
MetadataManager.prototype.createAuthenticator = function() {
return Promise.resolve(null);
};
MetadataManager.prototype.getAuthenticatorById = function(id) {
return Promise.resolve(null);
};
MetadataManager.prototype.updateAuthenticator = function() {
return Promise.resolve(null);
};
MetadataManager.prototype.deleteAuthenticator = function() {
return Promise.resolve(null);
};