diff --git a/README.md b/README.md index 2c13366..2ce3d9d 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,10 @@ Colubrina is a simple self-hostable chatting platform written in Vue, and Vuetif - [x] Embeds & MediaProxy - [ ] Clean-up/refactor code - [x] Mobile responsiveness/compatibility +- [x] Email verification +- [ ] Password resetting +- [ ] Channel message pins +- [ ] Read receipts Chat Friends diff --git a/backend/.env.example b/backend/.env.example index ecd5e9d..fc36907 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -5,4 +5,11 @@ NOTIFICATION= NOTIFICATION_TYPE=info SITE_NAME=Colubrina ALLOW_REGISTRATIONS=true -PUBLIC_USERS=false \ No newline at end of file +PUBLIC_USERS=false +EMAIL_VERIFICATION=false +EMAIL_SMTP_HOST=smtp.myhost.com +EMAIL_SMTP_PORT=587 +EMAIL_SMTP_USER=colubrina@myhost.com +EMAIL_SMTP_FROM=colubrina@myhost.com +EMAIL_SMTP_PASSWORD=myPassword +EMAIL_SMTP_SECURE=false \ No newline at end of file diff --git a/backend/index.js b/backend/index.js index dcb9a74..ed37fc9 100644 --- a/backend/index.js +++ b/backend/index.js @@ -31,7 +31,8 @@ app.get("/api/v1/state", async (req, res) => { latestVersion: require("../frontend/package.json").version, name: process.env.SITE_NAME, allowRegistrations: process.env.ALLOW_REGISTRATIONS === "true", - publicUsers: process.env.PUBLIC_USERS === "true" + publicUsers: process.env.PUBLIC_USERS === "true", + emailVerification: process.env.EMAIL_VERIFICATION === "true" }) }) diff --git a/backend/lib/authorize.js b/backend/lib/authorize.js index 9ef0f4f..611620c 100644 --- a/backend/lib/authorize.js +++ b/backend/lib/authorize.js @@ -9,7 +9,7 @@ module.exports = async function (req, res, next) { const user = await User.findOne({ where: { id: session.userId }, attributes: { - exclude: ["totp", "password"] + exclude: ["totp", "password", "emailToken"] }, include: [ { diff --git a/backend/lib/authorize_socket.js b/backend/lib/authorize_socket.js index ce48ac9..e700453 100644 --- a/backend/lib/authorize_socket.js +++ b/backend/lib/authorize_socket.js @@ -19,7 +19,7 @@ module.exports = async function (socket, next) { const user = await User.findOne({ where: { id: session.userId }, attributes: { - exclude: ["totp", "password"] + exclude: ["totp", "password", "emailToken"] }, include: [ { diff --git a/backend/lib/errors.js b/backend/lib/errors.js index 3b61afc..e2b75ac 100644 --- a/backend/lib/errors.js +++ b/backend/lib/errors.js @@ -39,7 +39,13 @@ let Errors = { 400 ], banned: ["You are banned from this instance.", 400], - leavingDirectChat: ["You cannot leave a direct message.", 400] + leavingDirectChat: ["You cannot leave a direct message.", 400], + emailVerificationRequired: [ + "You must verify your email before you can do this action.", + 401 + ], + mailFail: ["There was an error sending the verification email.", 400], + invalidToken: ["The token you provided is invalid.", 400] } function processErrors(errorName) { diff --git a/backend/migrations/20220731032753-emailVerified.js b/backend/migrations/20220731032753-emailVerified.js new file mode 100644 index 0000000..0ff4faa --- /dev/null +++ b/backend/migrations/20220731032753-emailVerified.js @@ -0,0 +1,23 @@ +"use strict" + +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.addColumn("Users", "emailVerified", { + type: Sequelize.BOOLEAN, + defaultValue: false + }) + await queryInterface.addColumn("Users", "emailToken", { + type: Sequelize.STRING, + defaultValue: null + }) + }, + + async down(queryInterface, Sequelize) { + /** + * Add reverting commands here. + * + * Example: + * await queryInterface.dropTable('users'); + */ + } +} diff --git a/backend/models/users.js b/backend/models/users.js index 8b894d9..8ec451b 100644 --- a/backend/models/users.js +++ b/backend/models/users.js @@ -41,8 +41,8 @@ module.exports = (sequelize, DataTypes) => { msg: "Username is required" }, len: { - args: [2, 16], - msg: "Username must be between 2 and 16 characters" + args: [2, 24], + msg: "Username must be between 2 and 24 characters" }, regex: { args: /^[a-z0-9_-]+$/i, @@ -158,6 +158,15 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false + }, + emailVerified: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + emailToken: { + type: DataTypes.STRING, + allowNull: true } }, { diff --git a/backend/package.json b/backend/package.json index 8f4b763..ab143d2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -25,9 +25,11 @@ "jade": "~1.11.0", "jw-paginate": "^1.0.4", "local-cors-proxy": "^1.1.0", + "mailgen": "^2.0.27", "mariadb": "^3.0.1", "multer": "^1.4.4", "node-xwhois": "^2.0.10", + "nodemailer": "^6.7.7", "open-graph-scraper": "^4.11.0", "patch-package": "^6.4.7", "pg": "^8.7.3", diff --git a/backend/routes/communications.js b/backend/routes/communications.js index fb6c5b1..e4b416d 100644 --- a/backend/routes/communications.js +++ b/backend/routes/communications.js @@ -135,6 +135,18 @@ async function createMessage(req, type, content, association, userId) { }) } +router.all("*", auth, async (req, res, next) => { + try { + if (!req.user.emailVerified && process.env.EMAIL_VERIFICATION === "true") { + throw Errors.emailVerificationRequired + } else { + next() + } + } catch (e) { + next(e) + } +}) + router.get("/", auth, async (req, res, next) => { try { let chats = await ChatAssociation.findAll({ diff --git a/backend/routes/user.js b/backend/routes/user.js index cf2c58c..4192a67 100644 --- a/backend/routes/user.js +++ b/backend/routes/user.js @@ -15,6 +15,8 @@ const semver = require("semver") const multer = require("multer") const FileType = require("file-type") const rateLimit = require("express-rate-limit") +const Mailgen = require("mailgen") +const nodemailer = require("nodemailer") const storage = multer.diskStorage({ destination: function (req, file, cb) { @@ -38,6 +40,16 @@ const limiter = rateLimit({ keyGenerator: (req, res) => req.user?.id || req.ip }) +const mailLimiter = rateLimit({ + windowMs: 60 * 1000, + max: 1, + message: Errors.rateLimit, + standardHeaders: true, + legacyHeaders: false, + skipFailedRequests: true, + keyGenerator: (req, res) => req.user?.id || req.ip +}) + const whitelist = [ "image/png", "image/jpeg", @@ -58,6 +70,92 @@ const upload = multer({ } }) +router.post("/verify/resend", auth, mailLimiter, async (req, res, next) => { + try { + if (process.env.EMAIL_VERIFICATION !== "true") { + throw Errors.invalidParameter("Email verification is disabled") + } + const token = "COLUBRINA-VERIFY-" + cryptoRandomString({ length: 64 }) + await req.user.update({ + emailToken: token + }) + const mailGenerator = new Mailgen({ + theme: "default", + product: { + name: process.env.SITE_NAME, + link: process.env.CORS_HOSTNAME + } + }) + const email = { + body: { + name: req.user.username, + intro: `${process.env.SITE_NAME} Account Verification`, + action: { + instructions: `You are receiving this email because you registered on ${process.env.SITE_NAME}, please use the link below to verify your account.`, + button: { + color: "#1A97FF", + text: "Account Verification", + link: process.env.CORS_HOSTNAME + "/email/confirm/" + token + } + }, + outro: "If you did not register, please disregard this email." + } + } + const emailBody = mailGenerator.generate(email) + + const emailText = mailGenerator.generatePlaintext(email) + const transporter = nodemailer.createTransport({ + host: process.env.EMAIL_SMTP_HOST, + port: process.env.EMAIL_SMTP_PORT, + secure: JSON.parse(process.env.EMAIL_SMTP_SECURE.toLowerCase()), + auth: { + user: process.env.EMAIL_SMTP_USER, + pass: process.env.EMAIL_SMTP_PASSWORD + } + }) + let info = await transporter.sendMail({ + from: process.env.EMAIL_SMTP_FROM, + to: req.user.email, + subject: "Email Verification - " + process.env.SITE_NAME, + text: emailText, + html: emailBody + }) + if (info) { + res.json({ success: true }) + } else { + throw Errors.mailFail + } + } catch (e) { + next(e) + } +}) + +router.post("/verify/confirm/:token", auth, async (req, res, next) => { + try { + const user = await User.findOne({ + where: { + id: req.user.id + } + }) + if (process.env.EMAIL_VERIFICATION !== "true") { + throw Errors.invalidParameter("Email verification is disabled") + } + if (!req.params.token) { + throw Errors.invalidToken + } + if (req.params.token !== user.emailToken) { + throw Errors.invalidToken + } + await user.update({ + emailVerified: true, + emailToken: null + }) + res.json({ success: true }) + } catch (e) { + next(e) + } +}) + router.post("/login", async (req, res, next) => { async function checkPassword(password, hash) { try { diff --git a/backend/yarn.lock b/backend/yarn.lock index 084892f..5660070 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -266,6 +266,11 @@ align-text@^0.1.1, align-text@^0.1.3: longest "^1.0.1" repeat-string "^1.5.2" +ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + ansi-escapes@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" @@ -293,7 +298,7 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -369,6 +374,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== +async@^3.2.3: + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -506,6 +516,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -652,6 +669,14 @@ chalk@^2.3.2, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + character-parser@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/character-parser/-/character-parser-1.2.1.tgz#c0dde4ab182713b919b970959a123ecc1a30fcd6" @@ -674,7 +699,7 @@ cheerio-select@^2.1.0: domhandler "^5.0.3" domutils "^3.0.1" -cheerio@^1.0.0-rc.11: +cheerio@^1.0.0-rc.11, cheerio@^1.0.0-rc.3: version "1.0.0-rc.12" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683" integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q== @@ -827,6 +852,11 @@ commander@^2.19.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + commander@~2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" @@ -1102,6 +1132,15 @@ dicer@0.2.5: readable-stream "1.1.x" streamsearch "0.1.2" +dom-serializer@^1.0.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + dom-serializer@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" @@ -1111,11 +1150,25 @@ dom-serializer@^2.0.0: domhandler "^5.0.2" entities "^4.2.0" -domelementtype@^2.3.0: +domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== +domhandler@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a" + integrity sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA== + dependencies: + domelementtype "^2.0.1" + +domhandler@^4.2.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" @@ -1123,6 +1176,15 @@ domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" +domutils@^2.0.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + domutils@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c" @@ -1165,6 +1227,13 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== +ejs@^3.1.6: + version "3.1.8" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" + integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== + dependencies: + jake "^10.8.5" + emittery@^0.10.2: version "0.10.2" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" @@ -1215,6 +1284,11 @@ engine.io@~6.2.0: engine.io-parser "~5.0.3" ws "~8.2.3" +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + entities@^4.2.0, entities@^4.3.0: version "4.3.1" resolved "https://registry.yarnpkg.com/entities/-/entities-4.3.1.tgz#c34062a94c865c322f9d67b4384e4169bcede6a4" @@ -1271,6 +1345,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-goat@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-3.0.0.tgz#e8b5fb658553fe8a3c4959c316c6ebb8c842b19c" + integrity sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -1395,6 +1474,13 @@ file-type@16.5.4: strtok3 "^6.2.4" token-types "^4.1.1" +filelist@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -1640,6 +1726,11 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" @@ -1657,6 +1748,21 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +htmlparser2@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78" + integrity sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q== + dependencies: + domelementtype "^2.0.1" + domhandler "^3.0.0" + domutils "^2.0.0" + entities "^2.0.0" + htmlparser2@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.1.tgz#abaa985474fcefe269bc761a779b544d7196d010" @@ -1966,6 +2072,16 @@ jade@~1.11.0: void-elements "~2.0.1" with "~4.0.0" +jake@^10.8.5: + version "10.8.5" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" + integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.1" + minimatch "^3.0.4" + js-beautify@^1.14.0: version "1.14.4" resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.14.4.tgz#187d600a835f84de67a6d09ceaf3f199b7284c82" @@ -2055,6 +2171,17 @@ jszip@latest: readable-stream "~2.3.6" setimmediate "^1.0.5" +juice@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/juice/-/juice-8.0.0.tgz#ac77d3372373409b06a875aee425b9d381f645fe" + integrity sha512-LRCfXBOqI1wt+zYR/5xwDnf+ZyiJiDt44DGZaBSAVwZWyWv3BliaiGTLS6KCvadv3uw6XGiPPFcTfY7CdF7Z/Q== + dependencies: + cheerio "^1.0.0-rc.3" + commander "^6.1.0" + mensch "^0.3.4" + slick "^1.12.2" + web-resource-inliner "^5.0.0" + jw-paginate@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/jw-paginate/-/jw-paginate-1.0.4.tgz#c6e7dbb4a6e9d62e501c7f9562a3a2a33d77c9b9" @@ -2164,6 +2291,15 @@ lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" +mailgen@^2.0.27: + version "2.0.27" + resolved "https://registry.yarnpkg.com/mailgen/-/mailgen-2.0.27.tgz#aa4a6eb67bc97568288286025f535bef394f23aa" + integrity sha512-k+Q02hK/gI224JRw9FFW/yKgo3SbYOCTbfE0MsXx0YFOR9XMsdOPBhAbHH6fKvyDX6y1r4zvw9QIponQOippFQ== + dependencies: + ejs "^3.1.6" + he "^1.2.0" + juice "^8.0.0" + make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -2238,6 +2374,11 @@ memoizee@^0.4.15: next-tick "^1.1.0" timers-ext "^0.1.7" +mensch@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/mensch/-/mensch-0.3.4.tgz#770f91b46cb16ea5b204ee735768c3f0c491fecd" + integrity sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g== + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -2273,6 +2414,11 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^2.4.6: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + mimic-response@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -2283,13 +2429,20 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -minimatch@^3.0.2, minimatch@^3.1.1: +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" + integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" @@ -2437,7 +2590,7 @@ node-addon-api@^5.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501" integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== -node-fetch@^2.6.7: +node-fetch@^2.6.0, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -2472,6 +2625,11 @@ node-xwhois@^2.0.10: tar-stream latest whois latest +nodemailer@^6.7.7: + version "6.7.7" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.7.tgz#e522fbd7507b81c51446d3f79c4603bf00083ddd" + integrity sha512-pOLC/s+2I1EXuSqO5Wa34i3kXZG3gugDssH+ZNCevHad65tc8vQlCQpOLaUjopvkRQKm2Cki2aME7fEOPRy3bA== + nopt@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" @@ -3214,6 +3372,11 @@ slash@^2.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== +slick@^1.12.2: + version "1.12.2" + resolved "https://registry.yarnpkg.com/slick/-/slick-1.12.2.tgz#bd048ddb74de7d1ca6915faa4a57570b3550c2d7" + integrity sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A== + smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" @@ -3413,6 +3576,13 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -3673,6 +3843,11 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +valid-data-url@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/valid-data-url/-/valid-data-url-3.0.1.tgz#826c1744e71b5632e847dd15dbd45b9fb38aa34f" + integrity sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA== + validator@^13.7.0: version "13.7.0" resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" @@ -3697,6 +3872,18 @@ void-elements@~2.0.1: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung== +web-resource-inliner@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/web-resource-inliner/-/web-resource-inliner-5.0.0.tgz#ac30db8096931f20a7c1b3ade54ff444e2e20f7b" + integrity sha512-AIihwH+ZmdHfkJm7BjSXiEClVt4zUFqX4YlFAzjL13wLtDuUneSaFvDBTbdYRecs35SiU7iNKbMnN+++wVfb6A== + dependencies: + ansi-colors "^4.1.1" + escape-goat "^3.0.0" + htmlparser2 "^4.0.0" + mime "^2.4.6" + node-fetch "^2.6.0" + valid-data-url "^3.0.0" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" diff --git a/cli/index.js b/cli/index.js index 0f4921b..1019575 100644 --- a/cli/index.js +++ b/cli/index.js @@ -159,11 +159,12 @@ async function createUser() { email: await input.text("Email", { default: "troplo@troplo.com" }), - admin: JSON.parse( - await input.confirm("Admin (true/false)", { - default: false - }) - ) + admin: await input.confirm("Admin (true/false)", { + default: false + }), + emailVerified: await input.confirm("Email verified (true/false)", { + default: true + }) } const { User } = require("../backend/models") await User.create(user) @@ -231,6 +232,52 @@ async function configureDotEnv() { default: false }) ) + const emailVerify = await input.text("Enforce email verification?", { + default: false + }) + if (emailVerify) { + setEnvValue("EMAIL_VERIFICATION", true) + setEnvValue( + "EMAIL_SMTP_HOST", + await input.text("SMTP Host", { + default: "smtp.myhost.com" + }) + ) + setEnvValue( + "EMAIL_SMTP_PORT", + await input.text("SMTP Port", { + default: 587 + }) + ) + setEnvValue( + "EMAIL_SMTP_USER", + await input.text("SMTP User", { + default: "colubrina@myhost.com" + }) + ) + setEnvValue( + "EMAIL_SMTP_FROM", + await input.text("SMTP From Address", { + default: "colubrina@myhost.com" + }) + ) + setEnvValue("EMAIL_SMTP_PASSWORD", await input.text("SMTP Password", {})) + setEnvValue( + "EMAIL_SMTP_SECURE", + await input.text("SMTP Secure", { + default: false + }) + ) + } else { + setEnvValue("EMAIL_VERIFICATION", false) + setEnvValue("EMAIL_SMTP_HOST", "smtp.myhost.com") + setEnvValue("EMAIL_SMTP_PORT", "587") + setEnvValue("EMAIL_SMTP_USER", "colubrina@myhost.com") + setEnvValue("EMAIL_SMTP_FROM", "colubrina@myhost.com") + setEnvValue("EMAIL_SMTP_PASSWORD", "myPassword") + setEnvValue("EMAIL_SMTP_SECURE", false) + } + setEnvValue("PUBLIC_USERS") setEnvValue("NOTIFICATION", "") setEnvValue("NOTIFICATION_TYPE", "info") setEnvValue("RELEASE", "stable") diff --git a/frontend/package.json b/frontend/package.json index a5ea34d..77e7571 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "colubrina-chat", - "version": "1.0.7", + "version": "1.0.8", "private": true, "author": "Troplo ", "license": "GPL-3.0", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index cfbe0e3..8fb5f45 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -765,11 +765,25 @@ export default { this.$store.dispatch("getState") this.getThemes() this.communicationsIdleCheck() - this.$store.dispatch("getUserInfo").catch(() => { - if (!["/login", "/register"].includes(this.$route.path)) { - this.$router.push("/login") - } - }) + this.$store + .dispatch("getUserInfo") + .then(() => { + console.log(window.location.pathname) + // check if its /email/confirm/ + if ( + !window.location.pathname.includes("/email/confirm/") && + !window.location.pathname.includes("/email/verify") && + !this.$store.state.user.emailVerified && + this.$store.state.site.emailVerification + ) { + this.$router.push("/email/verify") + } + }) + .catch(() => { + if (!["/login", "/register"].includes(this.$route.path)) { + this.$router.push("/login") + } + }) this.registerSocket() }, watch: { diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 6368ea5..59bd29e 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -150,6 +150,22 @@ const routes = [ component: () => import(/* webpackChunkName: "about" */ "../views/About.vue") }, + { + path: "/email/verify", + name: "Email Verify", + component: () => + import( + /* webpackChunkName: "emailVerify" */ "../views/Email/EmailVerify.vue" + ) + }, + { + path: "/email/confirm/:token", + name: "Email Confirm", + component: () => + import( + /* webpackChunkName: "emailConfirm" */ "../views/Email/EmailConfirm.vue" + ) + }, { path: "*", name: "Not Found", diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index 9423848..d9f631c 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -39,7 +39,8 @@ export default new Vuex.Store({ site: { release: "stable", loading: true, - name: "Colubrina" + name: "Colubrina", + emailVerification: false }, user: { bcUser: null, @@ -135,11 +136,14 @@ export default new Vuex.Store({ getChats(context) { Vue.axios.defaults.headers.common["Authorization"] = localStorage.getItem("session") - Vue.axios.get("/api/v1/communications").then((res) => { - context.commit("setChats", res.data) - context.dispatch("getCommunicationsUnread") - context.dispatch("updateQuickSwitch") - }) + Vue.axios + .get("/api/v1/communications") + .then((res) => { + context.commit("setChats", res.data) + context.dispatch("getCommunicationsUnread") + context.dispatch("updateQuickSwitch") + }) + .catch(() => {}) }, getCommunicationsUnread(context) { Vue.axios @@ -152,6 +156,7 @@ export default new Vuex.Store({ context.state.communicationNotifications += item.unread }) }) + .catch(() => {}) }, discardTheme(context) { context.state.themeEngine.theme = { diff --git a/frontend/src/views/Email/EmailConfirm.vue b/frontend/src/views/Email/EmailConfirm.vue new file mode 100644 index 0000000..f64ba93 --- /dev/null +++ b/frontend/src/views/Email/EmailConfirm.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/frontend/src/views/Email/EmailVerify.vue b/frontend/src/views/Email/EmailVerify.vue new file mode 100644 index 0000000..87551bc --- /dev/null +++ b/frontend/src/views/Email/EmailVerify.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index 8d263f9..7502f2d 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -123,7 +123,14 @@ export default { this.loading = false this.$socket.disconnect() this.$socket.connect() - this.$router.push("/") + if ( + this.$store.state.site.emailVerification && + !this.$store.state.user.emailVerified + ) { + this.$router.push("/email/verify") + } else { + this.$router.push("/") + } }) .catch((e) => { if ( diff --git a/frontend/src/views/Register.vue b/frontend/src/views/Register.vue index 835038f..2d1a4d4 100644 --- a/frontend/src/views/Register.vue +++ b/frontend/src/views/Register.vue @@ -35,6 +35,10 @@ label="Password" type="password" > + This instance has email verification enforced, ensure your + email is correct. { if (