diff --git a/app/lib/express.js b/app/lib/express.js index 51052e9..5ff7e88 100644 --- a/app/lib/express.js +++ b/app/lib/express.js @@ -12,6 +12,8 @@ const { reconstructKeys, readKey, uploadPicture, + identityFilename, + ssbFolder, } = require("./utils"); const queries = require("./queries"); const serveBlobs = require("./serve-blobs"); @@ -20,6 +22,8 @@ const debug = require("debug")("express"); const fileUpload = require("express-fileupload"); const Sentry = require("@sentry/node"); const metrics = require("./metrics"); +const sgMail = require("@sendgrid/mail"); +const ejs = require("ejs"); let ssbServer; let mode = process.env.MODE || "client"; @@ -60,7 +64,7 @@ let profileUrl = (id, path = "") => { }; const SENTRY_DSN = process.env.SENTRY_DSN; -if (SENTRY_DSN) { +if (SENTRY_DSN && process.env.NODE_ENV == "production") { Sentry.init({ dsn: SENTRY_DSN, }); @@ -128,9 +132,6 @@ router.get("/", async (req, res) => { if (!req.context.profile) { return res.render("index"); } - if (!req.context.profile.name) { - return res.redirect("/about"); - } const [posts, friends, vanishingMessages] = await Promise.all([ queries.getPosts(ssbServer, req.context.profile), @@ -162,6 +163,8 @@ router.post("/login", async (req, res) => { decodedKey.private = "[removed]"; debug("Login with key", decodedKey); + await queries.autofollow(ssbServer, decodedKey.id); + res.redirect("/"); } catch (e) { debug("Error on login", e); @@ -204,21 +207,54 @@ router.post("/signup", async (req, res) => { key.private = "[removed]"; debug("Generated key", key); - await ssbServer.identities.publishAs({ - id: profileId, - private: false, - content: { - type: "about", - about: profileId, - name: name, - ...(pictureLink ? { image: pictureLink } : {}), - }, - }); debug("Published about", { about: profileId, name, image: pictureLink }); + await queries.autofollow(ssbServer, profileId); + res.redirect("/"); }); +router.get("/keys", (req, res) => { + res.render("keys", { + useEmail: process.env.SENDGRID_API_KEY, + key: req.cookies["ssb_key"], + }); +}); + +router.post("/keys/email", async (req, res) => { + const email = req.body.email; + + let html = await ejs.renderFile("views/email_sign_in.ejs", { + host: `http://${req.headers.host}`, + ssb_key: req.cookies["ssb_key"], + }); + + sgMail.setApiKey(process.env.SENDGRID_API_KEY); + const msg = { + to: email, + from: "nobody@social.com", + subject: `Login button for ${req.context.profile.name}`, + html, + }; + await sgMail.send(msg); + + res.render("keys_sent"); +}); + +router.get("/keys/copy", (req, res) => { + res.render("keys_copy", { key: req.cookies["ssb_key"] }); +}); + +router.get("/keys/download", async (req, res) => { + const identities = await ssbServer.identities.list(); + const index = identities.indexOf(req.context.profile.id) - 1; + const filename = identityFilename(index); + const secretPath = `${ssbFolder()}/identities/${filename}`; + + res.attachment("secret"); + res.sendFile(secretPath); +}); + router.get("/profile/:id(*)", async (req, res) => { const id = req.params.id; @@ -423,7 +459,7 @@ router.get("/metrics", (_req, res) => { res.end(metrics.register.metrics()); }); -if (SENTRY_DSN) { +if (SENTRY_DSN && process.env.NODE_ENV == "production") { // The error handler must be before any other error middleware and after all controllers app.use(Sentry.Handlers.errorHandler()); } diff --git a/app/lib/queries.js b/app/lib/queries.js index 8eb0fbe..b1296bb 100644 --- a/app/lib/queries.js +++ b/app/lib/queries.js @@ -305,15 +305,30 @@ const getProfile = async (ssbServer, id) => { const progress = (ssbServer, callback) => { pull( ssbServer.replicate.changes(), - pull.drain( - callback, - (err) => { - console.error("Progress drain error", err); - } - ) + pull.drain(callback, (err) => { + console.error("Progress drain error", err); + }) ); }; +const autofollow = async (ssbServer, id) => { + console.log("ssbServer.id", ssbServer.id); + + const isFollowing = await ssbServer.friends.isFollowing({ + source: ssbServer.id, + dest: id, + }); + + if (!isFollowing) { + await ssbServer.publish({ + type: "contact", + contact: id, + following: true, + autofollow: true, + }); + } +}; + setInterval(() => { debugProfile("Clearing profile cache"); profileCache = {}; @@ -330,4 +345,5 @@ module.exports = { profileCache, getFriendshipStatus, progress, + autofollow, }; diff --git a/app/lib/utils.js b/app/lib/utils.js index f4d8ec7..9060360 100644 --- a/app/lib/utils.js +++ b/app/lib/utils.js @@ -44,9 +44,13 @@ 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 (ssbServer) => { const identities = await ssbServer.identities.list(); - return "secret_" + leftpad(identities.length - 1, 2, "0") + ".butt"; + return module.exports.identityFilename(identities.length - 1); }; // From ssb-keys diff --git a/app/package-lock.json b/app/package-lock.json index 304870e..a066249 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -20,6 +20,33 @@ "sumchecker": "^3.0.1" } }, + "@sendgrid/client": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-7.0.1.tgz", + "integrity": "sha512-HZhDD1bctv5rM0wqAz9LhJC1IL9YHn5jJvxPqiK/3f3WCQjRvraJ1AkqkFFNFd9lPBVLmcrORX04lojwl+5ZaA==", + "requires": { + "@sendgrid/helpers": "^7.0.1", + "axios": "^0.19.2" + } + }, + "@sendgrid/helpers": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-7.0.1.tgz", + "integrity": "sha512-i/zsissq1upgdywtuJKysaplJJZC24GdtEKiJC1IRlXvBHzIjH4eU+rqUFO8h+hGji3UMURGgMFuLUXTUYvZ9w==", + "requires": { + "chalk": "^2.0.1", + "deepmerge": "^4.2.2" + } + }, + "@sendgrid/mail": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-7.0.1.tgz", + "integrity": "sha512-yFkhjrYQvwpdy8eUiDxLLgPp9o5jHQzjJ5qUkgMr2fuPuYSKKqbpPir1PXIHx0ek2VKkTsvj/7Z5UQk6hPZcrQ==", + "requires": { + "@sendgrid/client": "^7.0.1", + "@sendgrid/helpers": "^7.0.1" + } + }, "@sentry/apm": { "version": "5.15.4", "resolved": "https://registry.npmjs.org/@sentry/apm/-/apm-5.15.4.tgz", @@ -177,6 +204,14 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -221,6 +256,14 @@ "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz", "integrity": "sha1-0IiFvmubv5Q5/gh8dihyRfCoFFA=" }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -451,6 +494,23 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + } + } + }, "charwise": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/charwise/-/charwise-3.0.1.tgz", @@ -504,6 +564,19 @@ "mimic-response": "^1.0.0" } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -754,6 +827,11 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + }, "defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", @@ -1312,6 +1390,29 @@ } } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -1516,6 +1617,11 @@ "function-bind": "^1.1.1" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, "has-network": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/has-network/-/has-network-0.0.1.tgz", @@ -10459,6 +10565,14 @@ "debug": "^4.1.0" } }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, "tape": { "version": "4.13.2", "resolved": "https://registry.npmjs.org/tape/-/tape-4.13.2.tgz", diff --git a/app/package.json b/app/package.json index 5eb54a8..3d132d9 100644 --- a/app/package.json +++ b/app/package.json @@ -14,6 +14,7 @@ "author": "", "license": "ISC", "dependencies": { + "@sendgrid/mail": "^7.0.1", "@sentry/node": "^5.15.4", "chokidar": "^3.3.1", "cookie-parser": "^1.4.5", diff --git a/app/public/style.css b/app/public/style.css index eb20ec7..15e4894 100644 --- a/app/public/style.css +++ b/app/public/style.css @@ -23,6 +23,7 @@ input[type="submit"] { border: none; cursor: pointer; text-decoration: none; + display: inline-block; } button.button-big, a.button.button-big, @@ -44,6 +45,7 @@ input[type="submit"].button-secondary:hover { } input[type="text"], +input[type="email"], textarea { line-height: 32px; padding: 0 6px; @@ -304,3 +306,11 @@ button.notification-box:hover { .undo-request:hover:after { content: " (undo)"; } + +.key-block { + white-space: pre-wrap; + background: #f5f5f5; + border: 1px solid #ccc; + border-radius: 3px; + padding: 10px; +} diff --git a/app/views/_header.ejs b/app/views/_header.ejs index 4cdb4e3..6767815 100644 --- a/app/views/_header.ejs +++ b/app/views/_header.ejs @@ -7,13 +7,13 @@ > - <% if (context.profile) { %> + <% if (context.profile && typeof hideHeader == "undefined") { %>