Completely remove saving ssb keys to the filesystem, now the key is used on every request from the cookies and stays just in memory
This commit is contained in:
parent
37ff8c895e
commit
4e430255b6
|
@ -5,13 +5,8 @@ const port = process.env.PORT || 7624;
|
|||
const bodyParser = require("body-parser");
|
||||
const {
|
||||
asyncRouter,
|
||||
writeKey,
|
||||
nextIdentityFilename,
|
||||
reconstructKeys,
|
||||
readKey,
|
||||
uploadPicture,
|
||||
identityFilename,
|
||||
ssbFolder,
|
||||
isPhone,
|
||||
} = require("./utils");
|
||||
const queries = require("./queries");
|
||||
|
@ -77,18 +72,15 @@ app.use(async (req, res, next) => {
|
|||
};
|
||||
res.locals.context = req.context;
|
||||
try {
|
||||
const identities = await ssb.client().identities.list();
|
||||
const key = req.signedCookies["ssb_key"];
|
||||
if (!key) return next();
|
||||
|
||||
const parsedKey = JSON.parse(key);
|
||||
if (!identities.includes(parsedKey.id)) {
|
||||
const filename = await nextIdentityFilename(ssb.client());
|
||||
if (!parsedKey.id) return next();
|
||||
|
||||
writeKey(key, `/identities/${filename}`);
|
||||
ssb.client().identities.refresh();
|
||||
}
|
||||
ssb.client().identities.addUnboxer(parsedKey);
|
||||
req.context.profile = await queries.getProfile(parsedKey.id);
|
||||
req.context.profile.key = parsedKey;
|
||||
|
||||
const isRootUser =
|
||||
req.context.profile.id == ssb.client().id ||
|
||||
|
@ -199,11 +191,11 @@ const doLogin = async (submittedKey, res) => {
|
|||
};
|
||||
|
||||
router.get("/login", { public: true }, async (req, res) => {
|
||||
const login_key =
|
||||
const loginKey =
|
||||
req.query.key && Buffer.from(req.query.key, "base64").toString("utf8");
|
||||
|
||||
if (login_key) {
|
||||
await doLogin(JSON.parse(login_key), res);
|
||||
if (loginKey) {
|
||||
await doLogin(loginKey, res);
|
||||
} else {
|
||||
res.render("shared/login", { mode });
|
||||
}
|
||||
|
@ -241,31 +233,26 @@ router.post("/signup", { public: true }, async (req, res) => {
|
|||
|
||||
const pictureLink = picture && (await uploadPicture(ssb.client(), picture));
|
||||
|
||||
const filename = await nextIdentityFilename(ssb.client());
|
||||
const profileId = await ssb.client().identities.create();
|
||||
const key = readKey(`/identities/${filename}`);
|
||||
if (key.id != profileId)
|
||||
throw "profileId and key.id don't match, probably race condition, bailing out for safety";
|
||||
|
||||
debug("Created new user with id", profileId);
|
||||
|
||||
const key = await ssb.client().identities.createNewKey();
|
||||
res.cookie("ssb_key", JSON.stringify(key), cookieOptions);
|
||||
key.private = "[removed]";
|
||||
debug("Generated key", key);
|
||||
|
||||
await ssb.client().identities.publishAs({
|
||||
id: profileId,
|
||||
key,
|
||||
private: false,
|
||||
content: {
|
||||
type: "about",
|
||||
about: profileId,
|
||||
about: key.id,
|
||||
name: name,
|
||||
...(pictureLink ? { image: pictureLink } : {}),
|
||||
},
|
||||
});
|
||||
debug("Published about", { about: profileId, name, image: pictureLink });
|
||||
|
||||
await queries.autofollow(profileId);
|
||||
const debugKey = { ...key, private: "[removed]" };
|
||||
debug("Generated key", debugKey);
|
||||
|
||||
debug("Published about", { about: key.id, name, image: pictureLink });
|
||||
|
||||
await queries.autofollow(key.id);
|
||||
|
||||
res.redirect("/keys");
|
||||
});
|
||||
|
@ -273,7 +260,7 @@ router.post("/signup", { public: true }, async (req, res) => {
|
|||
router.get("/keys", (req, res) => {
|
||||
res.render("shared/keys", {
|
||||
useEmail: process.env.SENDGRID_API_KEY,
|
||||
key: req.signedCookies["ssb_key"],
|
||||
key: req.context.profile.key,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -287,8 +274,8 @@ router.post("/keys/email", async (req, res) => {
|
|||
*/
|
||||
const email = req.body.email;
|
||||
const origin = req.body.origin;
|
||||
const ssb_key = req.signedCookies["ssb_key"];
|
||||
const login_key = Buffer.from(JSON.stringify(ssb_key)).toString("base64");
|
||||
const ssb_key = JSON.stringify(req.context.profile.key);
|
||||
const login_key = Buffer.from(ssb_key).toString("base64");
|
||||
|
||||
if (process.env.NODE_ENV == "production") {
|
||||
let html = await ejs.renderFile("views/shared/email_sign_in.ejs", {
|
||||
|
@ -313,17 +300,33 @@ router.post("/keys/email", async (req, res) => {
|
|||
});
|
||||
|
||||
router.get("/keys/copy", (req, res) => {
|
||||
res.render("shared/keys_copy", { key: req.signedCookies["ssb_key"] });
|
||||
res.render("shared/keys_copy", {
|
||||
key: JSON.stringify(req.context.profile.key),
|
||||
});
|
||||
});
|
||||
|
||||
router.get("/keys/download", async (req, res) => {
|
||||
const identities = await ssb.client().identities.list();
|
||||
const index = identities.indexOf(req.context.profile.id) - 1;
|
||||
const filename = identityFilename(index);
|
||||
const secretPath = `${ssbFolder()}/identities/${filename}`;
|
||||
const secretFile = `
|
||||
# WARNING: Never show this to anyone.
|
||||
# WARNING: Never edit it or use it on multiple devices at once.
|
||||
#
|
||||
# This is your SECRET, it gives you magical powers. With your secret you can
|
||||
# sign your messages so that your friends can verify that the messages came
|
||||
# from you. If anyone learns your secret, they can use it to impersonate you.
|
||||
#
|
||||
# If you use this secret on more than one device you will create a fork and
|
||||
# your friends will stop replicating your content.
|
||||
#
|
||||
${JSON.stringify(req.context.profile.key)}
|
||||
#
|
||||
# The only part of this file that's safe to share is your public name:
|
||||
#
|
||||
# ${req.context.profile.id}
|
||||
`;
|
||||
|
||||
res.attachment("secret");
|
||||
res.sendFile(secretPath);
|
||||
res.contentType("text/plain");
|
||||
res.header("Content-Disposition", "attachment; filename=secret");
|
||||
res.send(secretFile);
|
||||
});
|
||||
|
||||
router.get(
|
||||
|
@ -367,7 +370,7 @@ router.post("/profile/:id(*)/add_friend", async (req, res) => {
|
|||
}
|
||||
|
||||
await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: false,
|
||||
content: {
|
||||
type: "contact",
|
||||
|
@ -386,7 +389,7 @@ router.post("/profile/:id(*)/reject_friend", async (req, res) => {
|
|||
}
|
||||
|
||||
await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: false,
|
||||
content: {
|
||||
type: "contact",
|
||||
|
@ -400,7 +403,7 @@ router.post("/profile/:id(*)/reject_friend", async (req, res) => {
|
|||
|
||||
router.post("/publish", async (req, res) => {
|
||||
await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: false,
|
||||
content: {
|
||||
type: "post",
|
||||
|
@ -416,7 +419,7 @@ router.post("/publish_secret", async (req, res) => {
|
|||
const recipients = req.body.recipients;
|
||||
|
||||
await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: true,
|
||||
content: {
|
||||
type: "post",
|
||||
|
@ -434,7 +437,7 @@ router.post("/vanish", async (req, res) => {
|
|||
|
||||
for (const key of keys) {
|
||||
await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: false,
|
||||
content: {
|
||||
type: "delete",
|
||||
|
@ -450,7 +453,7 @@ router.post("/profile/:id(*)/publish", async (req, res) => {
|
|||
const id = req.params.id;
|
||||
|
||||
await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: false,
|
||||
content: {
|
||||
type: "post",
|
||||
|
@ -466,7 +469,7 @@ router.post("/profile/:id(*)/publish_secret", async (req, res) => {
|
|||
const id = req.params.id;
|
||||
|
||||
await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: true,
|
||||
content: {
|
||||
type: "post",
|
||||
|
@ -519,7 +522,7 @@ router.post("/about", async (req, res) => {
|
|||
|
||||
if (update.name || update.image || update.description) {
|
||||
await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: false,
|
||||
content: update,
|
||||
});
|
||||
|
@ -562,7 +565,7 @@ router.post("/communities/new", async (req, res) => {
|
|||
const post = req.body.post;
|
||||
|
||||
await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: false,
|
||||
content: {
|
||||
type: "post",
|
||||
|
@ -573,7 +576,7 @@ router.post("/communities/new", async (req, res) => {
|
|||
});
|
||||
|
||||
await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: false,
|
||||
content: {
|
||||
type: "channel",
|
||||
|
@ -635,7 +638,7 @@ router.post("/communities/:name/new", async (req, res) => {
|
|||
const post = req.body.post;
|
||||
|
||||
const topic = await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: false,
|
||||
content: {
|
||||
type: "post",
|
||||
|
@ -652,7 +655,7 @@ router.post("/communities/:name/join", async (req, res) => {
|
|||
const name = req.params.name;
|
||||
|
||||
await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: false,
|
||||
content: {
|
||||
type: "channel",
|
||||
|
@ -668,7 +671,7 @@ router.post("/communities/:name/leave", async (req, res) => {
|
|||
const name = req.params.name;
|
||||
|
||||
await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: false,
|
||||
content: {
|
||||
type: "channel",
|
||||
|
@ -686,7 +689,7 @@ router.post("/communities/:name/:key(*)/publish", async (req, res) => {
|
|||
const reply = req.body.reply;
|
||||
|
||||
await ssb.client().identities.publishAs({
|
||||
id: req.context.profile.id,
|
||||
key: req.context.profile.key,
|
||||
private: false,
|
||||
content: {
|
||||
type: "post",
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
// 1) Monkeypatched to include the refresh function
|
||||
// 2) Monkeypatched to allow secret messages without published in the recps
|
||||
var leftpad = require("left-pad");
|
||||
var path = require("path");
|
||||
var mkdirp = require("mkdirp");
|
||||
var fs = require("fs");
|
||||
var ssbKeys = require("ssb-keys");
|
||||
var create = require("ssb-validate").create;
|
||||
|
||||
function toTarget(t) {
|
||||
return "object" === typeof t ? t && t.link : t;
|
||||
}
|
||||
|
||||
exports.name = "identities";
|
||||
exports.version = "1.0.0";
|
||||
exports.manifest = {
|
||||
main: "sync",
|
||||
list: "async",
|
||||
create: "async",
|
||||
publishAs: "async",
|
||||
help: "sync",
|
||||
refresh: "sync",
|
||||
};
|
||||
|
||||
exports.init = function (sbot, config) {
|
||||
var dir = path.join(config.path, "identities");
|
||||
console.log("identities directory", config.path);
|
||||
mkdirp.sync(dir);
|
||||
|
||||
function readKeys() {
|
||||
return fs
|
||||
.readdirSync(dir)
|
||||
.filter(function (name) {
|
||||
return /^secret_\d+\.butt$/.test(name);
|
||||
})
|
||||
.map(function (file) {
|
||||
return ssbKeys.loadSync(path.join(dir, file));
|
||||
});
|
||||
}
|
||||
|
||||
var keys = readKeys();
|
||||
|
||||
var locks = {};
|
||||
|
||||
sbot.addUnboxer({
|
||||
key: function (content) {
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = ssbKeys.unboxKey(content, keys[i]);
|
||||
if (key) return key;
|
||||
}
|
||||
},
|
||||
value: function (content, key) {
|
||||
return ssbKeys.unboxBody(content, key);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
main: function () {
|
||||
return sbot.id;
|
||||
},
|
||||
refresh: function () {
|
||||
keys = readKeys();
|
||||
},
|
||||
list: function (cb) {
|
||||
cb(
|
||||
null,
|
||||
[sbot.id].concat(
|
||||
keys.map(function (e) {
|
||||
return e.id;
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
create: function (cb) {
|
||||
var filename = "secret_" + leftpad(keys.length, 2, "0") + ".butt";
|
||||
ssbKeys.create(path.join(dir, filename), function (err, newKeys) {
|
||||
keys.push(newKeys);
|
||||
cb(err, newKeys.id);
|
||||
});
|
||||
},
|
||||
publishAs: function (opts, cb) {
|
||||
var id = opts.id;
|
||||
if (locks[id]) return cb(new Error("already writing"));
|
||||
var _keys =
|
||||
sbot.id === id
|
||||
? sbot.keys
|
||||
: keys.find(function (e) {
|
||||
return id === e.id;
|
||||
});
|
||||
if (!_keys) return cb(new Error("must provide id of listed identities"));
|
||||
var content = opts.content;
|
||||
|
||||
var recps = [].concat(content.recps).map(toTarget);
|
||||
|
||||
if (content.recps && !opts.private)
|
||||
return cb(new Error("recps set, but opts.private not set"));
|
||||
else if (!content.recps && opts.private)
|
||||
return cb(new Error("opts.private set, but content.recps not set"));
|
||||
else if (!!content.recps && opts.private) {
|
||||
if (!Array.isArray(content.recps) || !content.recps.length)
|
||||
return cb(
|
||||
new Error(
|
||||
"content.recps must be an array containing at least one id, was:" +
|
||||
JSON.stringify(recps)
|
||||
)
|
||||
);
|
||||
content = ssbKeys.box(content, recps);
|
||||
}
|
||||
|
||||
locks[id] = true;
|
||||
sbot.getLatest(id, function (err, data) {
|
||||
var state = data
|
||||
? {
|
||||
id: data.key,
|
||||
sequence: data.value.sequence,
|
||||
timestamp: data.value.timestamp,
|
||||
queue: [],
|
||||
}
|
||||
: { id: null, sequence: null, timestamp: null, queue: [] };
|
||||
sbot.add(
|
||||
create(
|
||||
state,
|
||||
_keys,
|
||||
config.caps && config.caps.sign,
|
||||
content,
|
||||
Date.now()
|
||||
),
|
||||
function (err, a, b) {
|
||||
delete locks[id];
|
||||
cb(err, a, b);
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
help: function () {
|
||||
return require("./help");
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,85 @@
|
|||
// Differently from ssb-identities, this plugin only keeps keys in memory, as we don't want to save them on the server
|
||||
|
||||
const ssbKeys = require("ssb-keys");
|
||||
const { create } = require("ssb-validate");
|
||||
|
||||
exports.name = "identities";
|
||||
exports.version = "1.0.0";
|
||||
exports.manifest = {
|
||||
create: "sync",
|
||||
addUnboxer: "sync",
|
||||
publishAs: "async",
|
||||
createNewKey: "sync",
|
||||
};
|
||||
|
||||
let unboxersAdded = [];
|
||||
let locks = {};
|
||||
|
||||
const toTarget = (t) => (typeof t == "object" ? t && t.link : t);
|
||||
|
||||
const addUnboxer = (ssb) => (key) => {
|
||||
if (unboxersAdded.includes(key.id)) return;
|
||||
|
||||
ssb.addUnboxer({
|
||||
key: (content) => {
|
||||
const unboxKey = ssbKeys.unboxKey(content, key);
|
||||
if (unboxKey) return unboxKey;
|
||||
},
|
||||
value: (content, key) => {
|
||||
return ssbKeys.unboxBody(content, key);
|
||||
},
|
||||
});
|
||||
unboxersAdded.push(key.id);
|
||||
};
|
||||
|
||||
const publishAs = (ssb, config) => ({ key, private, content }, cb) => {
|
||||
const id = key.id;
|
||||
if (locks[id]) throw new Error("already writing");
|
||||
|
||||
const recps = [].concat(content.recps).map(toTarget);
|
||||
|
||||
if (content.recps && !private) {
|
||||
return new Error("recps set, but private not set");
|
||||
} else if (!content.recps && private) {
|
||||
return new Error("private set, but content.recps not set");
|
||||
} else if (!!content.recps && private) {
|
||||
if (!Array.isArray(content.recps) || !~recps.indexOf(id))
|
||||
return new Error(
|
||||
"content.recps must be an array containing publisher id:" +
|
||||
id +
|
||||
" was:" +
|
||||
JSON.stringify(recps) +
|
||||
" indexOf:" +
|
||||
recps.indexOf(id)
|
||||
);
|
||||
content = ssbKeys.box(content, recps);
|
||||
}
|
||||
|
||||
locks[id] = true;
|
||||
ssb.getLatest(id, (_err, data) => {
|
||||
const state = data
|
||||
? {
|
||||
id: data.key,
|
||||
sequence: data.value.sequence,
|
||||
timestamp: data.value.timestamp,
|
||||
queue: [],
|
||||
}
|
||||
: { id: null, sequence: null, timestamp: null, queue: [] };
|
||||
|
||||
ssb.add(
|
||||
create(state, key, config.caps && config.caps.sign, content, Date.now()),
|
||||
(err, a, b) => {
|
||||
delete locks[id];
|
||||
cb(err, a, b);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
exports.init = function (ssb, config) {
|
||||
return {
|
||||
addUnboxer: addUnboxer(ssb),
|
||||
publishAs: publishAs(ssb, config),
|
||||
createNewKey: ssbKeys.generate,
|
||||
};
|
||||
};
|
|
@ -29,7 +29,7 @@ Server.use(require("ssb-master"))
|
|||
.use(require("./monkeypatch/ssb-friends"))
|
||||
.use(require("ssb-query"))
|
||||
.use(require("ssb-device-address"))
|
||||
.use(require("./monkeypatch/ssb-identities"))
|
||||
.use(require("./plugins/memory-identities"))
|
||||
.use(require("ssb-peer-invites"))
|
||||
.use(require("ssb-blobs"))
|
||||
.use(require("ssb-private"));
|
||||
|
|
|
@ -69,15 +69,6 @@ module.exports.writeKey = (key, path) => {
|
|||
fs.writeFileSync(secretPath, key, { mode: 0x100, flag: "wx" });
|
||||
};
|
||||
|
||||
module.exports.identityFilename = (index) => {
|
||||
return "secret_" + leftpad(index, 2, "0") + ".butt";
|
||||
};
|
||||
|
||||
module.exports.nextIdentityFilename = async (ssbClient) => {
|
||||
const identities = await ssbClient.identities.list();
|
||||
return module.exports.identityFilename(identities.length - 1);
|
||||
};
|
||||
|
||||
// From ssb-keys
|
||||
module.exports.reconstructKeys = (keyfile) => {
|
||||
var privateKey = keyfile
|
||||
|
@ -93,13 +84,6 @@ module.exports.reconstructKeys = (keyfile) => {
|
|||
return keys;
|
||||
};
|
||||
|
||||
module.exports.readKey = (path) => {
|
||||
let secretPath = `${ssbFolder()}${path}`;
|
||||
|
||||
let keyfile = fs.readFileSync(secretPath, "utf8");
|
||||
return module.exports.reconstructKeys(keyfile);
|
||||
};
|
||||
|
||||
module.exports.uploadPicture = async (ssbClient, picture) => {
|
||||
const maxSize = 5 * 1024 * 1024; // 5 MB
|
||||
if (picture.size > maxSize) throw "Max size exceeded";
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
"ssb-device-address": "^1.1.6",
|
||||
"ssb-friends": "^4.1.4",
|
||||
"ssb-gossip": "^1.1.1",
|
||||
"ssb-identities": "^2.1.1",
|
||||
"ssb-invite": "^2.1.4",
|
||||
"ssb-keys": "^7.2.2",
|
||||
"ssb-master": "^1.0.3",
|
||||
|
@ -57,4 +56,4 @@
|
|||
"devDependencies": {
|
||||
"electron": "^8.2.0"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue