Add singup process including picture

This commit is contained in:
Rogerio Chaves 2020-04-11 13:02:05 +02:00
parent c8b0de7d77
commit 1b5cadbcf5
No known key found for this signature in database
GPG Key ID: E6AF5440509B1D94
9 changed files with 177 additions and 57 deletions

View File

@ -5,12 +5,20 @@ const bodyParser = require("body-parser");
const Client = require("ssb-client");
const ssbKeys = require("ssb-keys");
const ssbConfig = require("./ssb-config");
const { asyncRouter, writeKey } = require("./utils");
const {
asyncRouter,
writeKey,
nextIdentityFilename,
reconstructKeys,
readKey,
} = require("./utils");
const queries = require("./queries");
const serveBlobs = require("./serve-blobs");
const cookieParser = require("cookie-parser");
const leftpad = require("left-pad"); // I don't believe I'm depending on this
const debug = require("debug")("express");
const fileUpload = require("express-fileupload");
const pull = require("pull-stream");
const split = require("split-buffer");
let ssbServer;
let mode = process.env.MODE || "server";
@ -39,6 +47,7 @@ app.use(bodyParser.urlencoded({ extended: true }));
app.set("view engine", "ejs");
app.use(express.static("public"));
app.use(cookieParser());
app.use(fileUpload());
app.use(async (req, res, next) => {
if (!ssbServer) {
setTimeout(() => {
@ -64,8 +73,7 @@ app.use(async (req, res, next) => {
const parsedKey = JSON.parse(key);
if (!identities.includes(parsedKey.id)) {
const filename =
"secret_" + leftpad(identities.length - 1, 2, "0") + ".butt";
const filename = await nextIdentityFilename(ssbServer);
writeKey(key, `/identities/${filename}`);
ssbServer.identities.refresh();
@ -78,7 +86,7 @@ app.use(async (req, res, next) => {
next(e);
}
});
app.use((req, res, next) => {
app.use((_req, res, next) => {
res.locals.profileUrl = profileUrl;
res.locals.imageUrl = (blob) => {
const imageHash = blob && typeof blob == "object" ? blob.link : blob;
@ -124,21 +132,6 @@ router.get("/login", (_req, res) => {
router.post("/login", async (req, res) => {
const submittedKey = req.body.ssb_key;
// From ssb-keys
const reconstructKeys = (keyfile) => {
var privateKey = keyfile
.replace(/\s*\#[^\n]*/g, "")
.split("\n")
.filter((x) => x)
.join("");
var keys = JSON.parse(privateKey);
const hasSigil = (x) => /^(@|%|&)/.test(x);
if (!hasSigil(keys.id)) keys.id = "@" + keys.public;
return keys;
};
try {
const decodedKey = reconstructKeys(submittedKey);
res.cookie("ssb_key", JSON.stringify(decodedKey));
@ -158,10 +151,61 @@ router.get("/logout", async (_req, res) => {
res.redirect("/");
});
router.get("/signup", (_req, res) => {
router.get("/signup", (req, res) => {
if (req.context.profile) {
return res.redirect("/");
}
res.render("signup");
});
router.post("/signup", async (req, res) => {
const name = req.body.name;
const picture = req.files && req.files.pic;
let pictureLink;
if (picture) {
const maxSize = 5 * 1024 * 1024; // 5 MB
if (picture.size > maxSize) throw "Max size exceeded";
pictureLink = await new Promise((resolve, reject) =>
pull(
pull.values(split(picture.data, 64 * 1024)),
ssbServer.blobs.add((err, result) => {
if (err) return reject(err);
return resolve(result);
})
)
);
}
const filename = await nextIdentityFilename(ssbServer);
const profileId = await ssbServer.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);
res.cookie("ssb_key", JSON.stringify(key));
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 });
res.redirect("/");
});
router.get("/profile/:id", async (req, res) => {
const id = req.params.id;
@ -179,10 +223,14 @@ router.get("/profile/:id", async (req, res) => {
});
router.post("/publish", async (req, res) => {
await ssbServer.publish({
type: "post",
text: req.body.message,
root: req.context.profile.id,
await ssbServer.identities.publishAs({
id: req.context.profile.id,
private: false,
content: {
type: "post",
text: req.body.message,
root: req.context.profile.id,
},
});
res.redirect("/");
@ -192,9 +240,13 @@ router.post("/publish", async (req, res) => {
router.post("/vanish", async (req, res) => {
const key = req.body.key;
await ssbServer.publish({
type: "delete",
dest: key,
await ssbServer.identities.publishAs({
id: req.context.profile.id,
private: false,
content: {
type: "delete",
dest: key,
},
});
res.send("ok");
@ -205,19 +257,25 @@ router.post("/profile/:id/publish", async (req, res) => {
const visibility = req.body.visibility;
if (visibility == "vanishing") {
await ssbServer.private.publish(
{
await ssbServer.identities.publishAs({
id: req.context.profile.id,
private: true,
content: {
type: "post",
text: req.body.message,
root: id,
recps: [id],
},
});
} else {
await ssbServer.identities.publishAs({
id: req.context.profile.id,
private: false,
content: {
type: "post",
text: req.body.message,
root: id,
},
[id]
);
} else {
await ssbServer.publish({
type: "post",
text: req.body.message,
root: id,
});
}
@ -247,10 +305,14 @@ router.post("/about", async (req, res) => {
const name = req.body.name;
if (name != req.context.profile.name) {
await ssbServer.publish({
type: "about",
about: req.context.profile.id,
name: name,
await ssbServer.identities.publishAs({
id: req.context.profile.id,
private: false,
content: {
type: "about",
about: req.context.profile.id,
name: name,
},
});
req.context.profile.name = name;
}

View File

@ -1,4 +1,5 @@
// Monkeypatched to include the refresh function
// 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");
@ -94,15 +95,11 @@ exports.init = function (sbot, config) {
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) || !~recps.indexOf(id))
if (!Array.isArray(content.recps) || !content.recps.length)
return cb(
new Error(
"content.recps must be an array containing publisher id:" +
id +
" was:" +
JSON.stringify(recps) +
" indexOf:" +
recps.indexOf(id)
"content.recps must be an array containing at least one id, was:" +
JSON.stringify(recps)
)
);
content = ssbKeys.box(content, recps);

View File

@ -1,4 +1,5 @@
const fs = require("fs");
const leftpad = require("left-pad"); // I don't believe I'm depending on this
module.exports.asyncRouter = (app) => {
const debug = require("debug")("router");
@ -22,13 +23,43 @@ module.exports.asyncRouter = (app) => {
};
};
module.exports.writeKey = (key, path) => {
const ssbFolder = () => {
let homeFolder =
process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
let ssbFolder = `${homeFolder}/.${process.env.CONFIG_FOLDER || "social"}`;
let secretPath = `${ssbFolder}${path}`;
return `${homeFolder}/.${process.env.CONFIG_FOLDER || "social"}`;
};
module.exports.writeKey = (key, path) => {
let secretPath = `${ssbFolder()}${path}`;
// Same options ssb-keys use
fs.mkdirSync(ssbFolder, { recursive: true });
fs.mkdirSync(ssbFolder(), { recursive: true });
fs.writeFileSync(secretPath, key, { mode: 0x100, flag: "wx" });
};
module.exports.nextIdentityFilename = async (ssbServer) => {
const identities = await ssbServer.identities.list();
return "secret_" + leftpad(identities.length - 1, 2, "0") + ".butt";
};
// From ssb-keys
module.exports.reconstructKeys = (keyfile) => {
var privateKey = keyfile
.replace(/\s*\#[^\n]*/g, "")
.split("\n")
.filter((x) => x)
.join("");
var keys = JSON.parse(privateKey);
const hasSigil = (x) => /^(@|%|&)/.test(x);
if (!hasSigil(keys.id)) keys.id = "@" + keys.public;
return keys;
};
module.exports.readKey = (path) => {
let secretPath = `${ssbFolder()}${path}`;
let keyfile = fs.readFileSync(secretPath, "utf8");
return module.exports.reconstructKeys(keyfile);
};

29
app/package-lock.json generated
View File

@ -191,6 +191,14 @@
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
},
"busboy": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz",
"integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==",
"requires": {
"dicer": "0.3.0"
}
},
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@ -557,6 +565,14 @@
"dev": true,
"optional": true
},
"dicer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz",
"integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==",
"requires": {
"streamsearch": "0.1.2"
}
},
"discontinuous-range": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
@ -781,6 +797,14 @@
}
}
},
"express-fileupload": {
"version": "1.1.7-alpha.3",
"resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.1.7-alpha.3.tgz",
"integrity": "sha512-2YRJQqjgfFcYiMr8inico+UQ0UsxuOUyO9wkWkx+vjsEcUI7c1ae38Nv5NKdGjHqL5+J01P6StT9mjZTI7Qzjg==",
"requires": {
"busboy": "^0.3.1"
}
},
"extract-zip": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz",
@ -9637,6 +9661,11 @@
}
}
},
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
},
"string.prototype.trim": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz",

View File

@ -17,6 +17,7 @@
"debug": "^4.1.1",
"ejs": "^3.0.2",
"express": "^4.17.1",
"express-fileupload": "^1.1.7-alpha.3",
"pull-stream": "^3.6.14",
"ssb-about": "^2.0.1",
"ssb-backlinks": "^1.0.0",

View File

@ -48,7 +48,7 @@ input.button.button-primary:hover {
color: #fff;
}
input:not([type="submit"]),
input[type="text"],
textarea {
line-height: 32px;
padding: 0 6px;

View File

@ -54,7 +54,7 @@
<form action="/publish" method="POST">
<textarea name="message"></textarea>
<input type="submit" value="Send" />
<input type="submit" class="button button-primary" value="Send" />
</form>
<% posts.map(post => { %>

View File

@ -36,7 +36,7 @@
</label>
</div>
<textarea name="message"></textarea>
<input type="submit" value="Send" />
<input type="submit" class="button button-primary" value="Send" />
</form>
<% posts.map(post => { %>

View File

@ -3,7 +3,7 @@
<div style="max-width: 800px; margin: 0 auto">
<h1 style="padding-top: 50px">Create account</h1>
<form method="POST" action="/signup">
<form method="POST" action="/signup" enctype="multipart/form-data">
<div style="padding-top: 30px">
<label>
Profile picture: