Change the way standalone mode identity works, it does not use cookie anymore, only the local /secret file as other standard ssb clients, still if you have no .ssb folder locally it asks you to create or login, so new users have a more standard UX, with logout being possible as well
This commit is contained in:
parent
924e9a7ae0
commit
65578bf147
|
@ -0,0 +1,5 @@
|
||||||
|
PORT=3000
|
||||||
|
SSB_KEY=
|
||||||
|
COOKIES_SECRET=
|
||||||
|
SENTRY_DSN=
|
||||||
|
SENDGRID_API_KEY=
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
let server;
|
let server;
|
||||||
require("./lib/ssb");
|
require("./lib/ssb");
|
||||||
|
|
||||||
|
@ -6,8 +5,8 @@ setTimeout(() => {
|
||||||
server = require("./lib/express");
|
server = require("./lib/express");
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
let mode = process.env.MODE || "client";
|
let mode = process.env.MODE || "standalone";
|
||||||
if (mode == "client") {
|
if (mode == "standalone") {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
require("./lib/electron");
|
require("./lib/electron");
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
|
@ -8,6 +8,7 @@ const {
|
||||||
reconstructKeys,
|
reconstructKeys,
|
||||||
uploadPicture,
|
uploadPicture,
|
||||||
isPhone,
|
isPhone,
|
||||||
|
ssbFolder,
|
||||||
} = require("./utils");
|
} = require("./utils");
|
||||||
const queries = require("./queries");
|
const queries = require("./queries");
|
||||||
const serveBlobs = require("./serve-blobs");
|
const serveBlobs = require("./serve-blobs");
|
||||||
|
@ -22,10 +23,12 @@ const cookieEncrypter = require("cookie-encrypter");
|
||||||
const expressLayouts = require("express-ejs-layouts");
|
const expressLayouts = require("express-ejs-layouts");
|
||||||
const mobileRoutes = require("./mobile-routes");
|
const mobileRoutes = require("./mobile-routes");
|
||||||
const ejsUtils = require("ejs/lib/utils");
|
const ejsUtils = require("ejs/lib/utils");
|
||||||
|
const fs = require("fs");
|
||||||
|
const ssbKeys = require("ssb-keys");
|
||||||
|
|
||||||
let mode = process.env.MODE || "client";
|
const mode = process.env.MODE || "standalone";
|
||||||
|
|
||||||
let profileUrl = (id, path = "") => {
|
const profileUrl = (id, path = "") => {
|
||||||
return `/profile/${id}${path}`;
|
return `/profile/${id}${path}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,16 +46,18 @@ app.set("view engine", "ejs");
|
||||||
app.set("views", `${__dirname}/../views`);
|
app.set("views", `${__dirname}/../views`);
|
||||||
app.use(express.static(`${__dirname}/../public`));
|
app.use(express.static(`${__dirname}/../public`));
|
||||||
app.use(fileUpload());
|
app.use(fileUpload());
|
||||||
const cookieSecret =
|
|
||||||
process.env.COOKIES_SECRET || "set_cookie_secret_you_are_unsafe"; // has to be 32-bits
|
|
||||||
const cookieOptions = {
|
const cookieOptions = {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
signed: true,
|
signed: true,
|
||||||
expires: new Date(253402300000000), // Friday, 31 Dec 9999 23:59:59 GMT, nice date from stackoverflow
|
expires: new Date(253402300000000), // Friday, 31 Dec 9999 23:59:59 GMT, nice date from stackoverflow
|
||||||
sameSite: "Lax",
|
sameSite: "Lax",
|
||||||
};
|
};
|
||||||
app.use(cookieParser(cookieSecret));
|
if (mode != "standalone") {
|
||||||
if (mode != "client") {
|
const cookieSecret =
|
||||||
|
process.env.COOKIES_SECRET || "set_cookie_secret_you_are_unsafe"; // has to be 32-bits
|
||||||
|
|
||||||
|
app.use(cookieParser(cookieSecret));
|
||||||
app.use(cookieEncrypter(cookieSecret));
|
app.use(cookieEncrypter(cookieSecret));
|
||||||
}
|
}
|
||||||
app.use(expressLayouts);
|
app.use(expressLayouts);
|
||||||
|
@ -71,28 +76,32 @@ app.use(async (req, res, next) => {
|
||||||
syncing: ssb.isSyncing(),
|
syncing: ssb.isSyncing(),
|
||||||
};
|
};
|
||||||
res.locals.context = req.context;
|
res.locals.context = req.context;
|
||||||
|
|
||||||
|
let key;
|
||||||
try {
|
try {
|
||||||
const key = req.signedCookies["ssb_key"];
|
if (mode == "standalone") {
|
||||||
if (!key) return next();
|
const isLoggedOut = fs.existsSync(`${ssbFolder()}/logged-out`);
|
||||||
|
|
||||||
const parsedKey = JSON.parse(key);
|
key = !isLoggedOut && ssbKeys.loadSync(`${ssbFolder()}/secret`);
|
||||||
if (!parsedKey.id) return next();
|
} else {
|
||||||
|
key = req.signedCookies["ssb_key"];
|
||||||
|
if (key) key = JSON.parse(key);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
if (!key || !key.id) return next();
|
||||||
|
|
||||||
ssb.client().identities.addUnboxer(parsedKey);
|
ssb.client().identities.addUnboxer(key);
|
||||||
req.context.profile = await queries.getProfile(parsedKey.id);
|
req.context.profile = (await queries.getProfile(key.id)) || {};
|
||||||
req.context.profile.key = parsedKey;
|
req.context.profile.key = key;
|
||||||
|
|
||||||
const isRootUser =
|
const isRootUser =
|
||||||
req.context.profile.id == ssb.client().id ||
|
req.context.profile.id == ssb.client().id ||
|
||||||
process.env.NODE_ENV != "production";
|
process.env.NODE_ENV != "production";
|
||||||
|
|
||||||
req.context.profile.debug = isRootUser;
|
req.context.profile.debug = isRootUser;
|
||||||
req.context.profile.admin = isRootUser || mode == "client";
|
req.context.profile.admin = isRootUser || mode == "standalone";
|
||||||
|
|
||||||
next();
|
next();
|
||||||
} catch (e) {
|
|
||||||
next(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
app.use((_req, res, next) => {
|
app.use((_req, res, next) => {
|
||||||
res.locals.profileUrl = profileUrl;
|
res.locals.profileUrl = profileUrl;
|
||||||
|
@ -174,20 +183,30 @@ router.get(
|
||||||
);
|
);
|
||||||
|
|
||||||
const doLogin = async (submittedKey, res) => {
|
const doLogin = async (submittedKey, res) => {
|
||||||
|
let decodedKey;
|
||||||
try {
|
try {
|
||||||
const decodedKey = reconstructKeys(submittedKey);
|
decodedKey = reconstructKeys(submittedKey);
|
||||||
res.cookie("ssb_key", JSON.stringify(decodedKey), cookieOptions);
|
|
||||||
|
|
||||||
decodedKey.private = "[removed]";
|
|
||||||
debug("Login with key", decodedKey);
|
|
||||||
|
|
||||||
await queries.autofollow(decodedKey.id);
|
|
||||||
|
|
||||||
res.redirect("/");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debug("Error on login", e);
|
debug("Error on login", e);
|
||||||
res.send("Invalid key");
|
return res.send("Invalid key");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mode == "standalone") {
|
||||||
|
fs.unlinkSync(`${ssbFolder()}/secret`);
|
||||||
|
fs.writeFileSync(`${ssbFolder()}/secret`, submittedKey, {
|
||||||
|
mode: 0x100,
|
||||||
|
flag: "wx",
|
||||||
|
});
|
||||||
|
fs.unlinkSync(`${ssbFolder()}/logged-out`);
|
||||||
|
} else {
|
||||||
|
res.cookie("ssb_key", JSON.stringify(decodedKey), cookieOptions);
|
||||||
|
await queries.autofollow(decodedKey.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedKey.private = "[removed]";
|
||||||
|
debug("Login with key", decodedKey);
|
||||||
|
|
||||||
|
res.redirect("/");
|
||||||
};
|
};
|
||||||
|
|
||||||
router.get("/login", { public: true }, async (req, res) => {
|
router.get("/login", { public: true }, async (req, res) => {
|
||||||
|
@ -215,7 +234,11 @@ router.get("/download", { public: true }, (_req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/logout", async (_req, res) => {
|
router.get("/logout", async (_req, res) => {
|
||||||
res.clearCookie("ssb_key");
|
if (mode == "standalone") {
|
||||||
|
fs.writeFileSync(`${ssbFolder()}/logged-out`, "");
|
||||||
|
} else {
|
||||||
|
res.clearCookie("ssb_key");
|
||||||
|
}
|
||||||
res.redirect("/");
|
res.redirect("/");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -234,7 +257,13 @@ router.post("/signup", { public: true }, async (req, res) => {
|
||||||
const pictureLink = picture && (await uploadPicture(ssb.client(), picture));
|
const pictureLink = picture && (await uploadPicture(ssb.client(), picture));
|
||||||
|
|
||||||
const key = await ssb.client().identities.createNewKey();
|
const key = await ssb.client().identities.createNewKey();
|
||||||
res.cookie("ssb_key", JSON.stringify(key), cookieOptions);
|
if (mode == "standalone") {
|
||||||
|
fs.writeFileSync(`${ssbFolder()}/secret`, humanifyKey(key));
|
||||||
|
fs.unlinkSync(`${ssbFolder()}/logged-out`);
|
||||||
|
} else {
|
||||||
|
res.cookie("ssb_key", JSON.stringify(key), cookieOptions);
|
||||||
|
await queries.autofollow(key.id);
|
||||||
|
}
|
||||||
|
|
||||||
await ssb.client().identities.publishAs({
|
await ssb.client().identities.publishAs({
|
||||||
key,
|
key,
|
||||||
|
@ -252,8 +281,6 @@ router.post("/signup", { public: true }, async (req, res) => {
|
||||||
|
|
||||||
debug("Published about", { about: key.id, name, image: pictureLink });
|
debug("Published about", { about: key.id, name, image: pictureLink });
|
||||||
|
|
||||||
await queries.autofollow(key.id);
|
|
||||||
|
|
||||||
res.redirect("/keys");
|
res.redirect("/keys");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -305,24 +332,28 @@ router.get("/keys/copy", (req, res) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const humanifyKey = (key) => {
|
||||||
|
return `
|
||||||
|
# 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(key)}
|
||||||
|
#
|
||||||
|
# The only part of this file that's safe to share is your public name:
|
||||||
|
#
|
||||||
|
# ${key.id}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
router.get("/keys/download", async (req, res) => {
|
router.get("/keys/download", async (req, res) => {
|
||||||
const secretFile = `
|
const secretFile = humanifyKey(req.context.profile.key);
|
||||||
# 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.contentType("text/plain");
|
res.contentType("text/plain");
|
||||||
res.header("Content-Disposition", "attachment; filename=secret");
|
res.header("Content-Disposition", "attachment; filename=secret");
|
||||||
|
|
|
@ -14,27 +14,32 @@ let ssbSecret = ssbKeys.loadOrCreateSync(
|
||||||
);
|
);
|
||||||
let syncing = false;
|
let syncing = false;
|
||||||
|
|
||||||
Client(ssbSecret, ssbConfig, async (err, server) => {
|
const connectClient = (ssbSecret) => {
|
||||||
if (err) throw err;
|
Client(ssbSecret, ssbConfig, async (err, server) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
ssbClient = server;
|
ssbClient = server;
|
||||||
|
|
||||||
queries.progress(({ rate, feeds, incompleteFeeds, progress, total }) => {
|
queries.progress(({ rate, feeds, incompleteFeeds, progress, total }) => {
|
||||||
if (incompleteFeeds > 0) {
|
if (incompleteFeeds > 0) {
|
||||||
if (!syncing) debug("syncing");
|
if (!syncing) debug("syncing");
|
||||||
syncing = true;
|
syncing = true;
|
||||||
} else {
|
} else {
|
||||||
syncing = false;
|
syncing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics.ssbProgressRate.set(rate);
|
metrics.ssbProgressRate.set(rate);
|
||||||
metrics.ssbProgressFeeds.set(feeds);
|
metrics.ssbProgressFeeds.set(feeds);
|
||||||
metrics.ssbProgressIncompleteFeeds.set(incompleteFeeds);
|
metrics.ssbProgressIncompleteFeeds.set(incompleteFeeds);
|
||||||
metrics.ssbProgressProgress.set(progress);
|
metrics.ssbProgressProgress.set(progress);
|
||||||
metrics.ssbProgressTotal.set(total);
|
metrics.ssbProgressTotal.set(total);
|
||||||
|
});
|
||||||
|
console.log("SSB Client ready");
|
||||||
});
|
});
|
||||||
console.log("SSB Client ready");
|
};
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.client = () => ssbClient;
|
module.exports.client = () => ssbClient;
|
||||||
module.exports.isSyncing = () => syncing;
|
module.exports.isSyncing = () => syncing;
|
||||||
|
module.exports.reconnectWith = connectClient;
|
||||||
|
|
||||||
|
connectClient(ssbSecret);
|
||||||
|
|
|
@ -2,14 +2,14 @@ const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { writeKey, ssbFolder } = require("./utils");
|
const { writeKey, ssbFolder } = require("./utils");
|
||||||
|
|
||||||
let envKey =
|
const envKey =
|
||||||
process.env.SSB_KEY &&
|
process.env.SSB_KEY &&
|
||||||
Buffer.from(process.env.SSB_KEY, "base64").toString("utf8");
|
Buffer.from(process.env.SSB_KEY, "base64").toString("utf8");
|
||||||
if (envKey) {
|
const secretExists = fs.existsSync(`${ssbFolder()}/secret`);
|
||||||
try {
|
|
||||||
writeKey(envKey, "/secret");
|
if (!secretExists && envKey) {
|
||||||
console.log("Writing SSB_KEY from env");
|
writeKey(envKey, "/secret");
|
||||||
} catch (_) {}
|
console.log("Writing SSB_KEY from env");
|
||||||
if (!fs.existsSync(`${ssbFolder()}/gossip.json`)) {
|
if (!fs.existsSync(`${ssbFolder()}/gossip.json`)) {
|
||||||
fs.copyFileSync("gossip.json", `${ssbFolder()}/gossip.json`);
|
fs.copyFileSync("gossip.json", `${ssbFolder()}/gossip.json`);
|
||||||
}
|
}
|
||||||
|
@ -44,3 +44,9 @@ fs.writeFileSync(
|
||||||
path.join(config.path, "manifest.json"), // ~/.ssb/manifest.json
|
path.join(config.path, "manifest.json"), // ~/.ssb/manifest.json
|
||||||
JSON.stringify(manifest)
|
JSON.stringify(manifest)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// SSB server automatically creates a secret key, but we want the user flow where they choose to create a key or use an existing one
|
||||||
|
const mode = process.env.MODE || "standalone";
|
||||||
|
if (mode == "standalone" && !secretExists) {
|
||||||
|
fs.writeFileSync(`${ssbFolder()}/logged-out`, "");
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "MODE=server SSB_PORT=8009 node index.js",
|
"start": "MODE=server SSB_PORT=8009 node index.js",
|
||||||
"start:client": "electron .",
|
"start:standalone": "electron .",
|
||||||
"start:user-2": "SSB_PORT=8010 PORT=3001 CONFIG_FOLDER=feedless-user2 electron .",
|
"start:user-2": "SSB_PORT=8010 PORT=3001 CONFIG_FOLDER=feedless-user2 electron .",
|
||||||
"start:user-3": "SSB_PORT=8011 PORT=3002 CONFIG_FOLDER=feedless-user3 electron .",
|
"start:user-3": "SSB_PORT=8011 PORT=3002 CONFIG_FOLDER=feedless-user3 electron .",
|
||||||
"clear": "rm -rf ~/.feedless; rm -rf ~/.feedless-user2; rm -rf ~/.feedless-user3",
|
"clear": "rm -rf ~/.feedless; rm -rf ~/.feedless-user2; rm -rf ~/.feedless-user3",
|
||||||
|
@ -58,4 +58,4 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^8.2.0"
|
"electron": "^8.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue