diff --git a/.example.env b/.example.env index 811e22b..ff28532 100644 --- a/.example.env +++ b/.example.env @@ -61,9 +61,11 @@ ADMIN_EMAILS= # Get it from https://developers.google.com/safe-browsing/v4/get-started GOOGLE_SAFE_BROWSING_KEY= -# Your email host details to use to send verification emails. -# More info on http://nodemailer.com/ -# Mail from example "Kutt ". Leave empty to use MAIL_USER +# Optional - Email is used to verify or change email address, reset password, and send reports. +# If it's disabled, all the above functionality would be disabled as well. +# MAIL_FROM example: "Kutt ". Leave it empty to use MAIL_USER. +# More info on the configuration on http://nodemailer.com/. +MAIL_ENABLED=false MAIL_HOST= MAIL_PORT= MAIL_SECURE=true diff --git a/server/env.js b/server/env.js index 76368cb..4b7b335 100644 --- a/server/env.js +++ b/server/env.js @@ -28,12 +28,13 @@ const env = cleanEnv(process.env, { JWT_SECRET: str(), ADMIN_EMAILS: str({ default: "" }), GOOGLE_SAFE_BROWSING_KEY: str({ default: "" }), - MAIL_HOST: str(), - MAIL_PORT: num(), + MAIL_ENABLED: bool({ default: false }), + MAIL_HOST: str({ default: "" }), + MAIL_PORT: num({ default: 587 }), MAIL_SECURE: bool({ default: false }), - MAIL_USER: str(), + MAIL_USER: str({ default: "" }), MAIL_FROM: str({ default: "", example: "Kutt " }), - MAIL_PASSWORD: str(), + MAIL_PASSWORD: str({ default: "" }), REPORT_EMAIL: str({ default: "" }), CONTACT_EMAIL: str({ default: "" }) }); diff --git a/server/handlers/auth.handler.js b/server/handlers/auth.handler.js index e7b9c3e..83dce42 100644 --- a/server/handlers/auth.handler.js +++ b/server/handlers/auth.handler.js @@ -222,10 +222,11 @@ async function resetPasswordRequest(req, res) { reset_password_expires: addMinutes(new Date(), 30).toISOString() } ); - + if (user) { - // TODO: handle error - mail.resetPasswordToken(user).catch(() => null); + mail.resetPasswordToken(user).catch(error => { + console.error("Send reset-password token email error:\n", error); + }); } if (req.isHTML) { @@ -264,11 +265,6 @@ async function resetPassword(req, res, next) { next(); } -function signupAccess(req, res, next) { - if (!env.DISALLOW_REGISTRATION) return next(); - throw new CustomError("Registration is not allowed."); -} - async function changeEmailRequest(req, res) { const { email, password } = req.body; @@ -352,6 +348,25 @@ async function changeEmail(req, res, next) { return next(); } +function featureAccess(features, redirect) { + return function(req, res, next) { + for (let i = 0; i < features.length; ++i) { + if (!features[i]) { + if (redirect) { + return res.redirect("/"); + } else { + throw new CustomError("Request is not allowed.", 400); + } + } + } + next(); + } +} + +function featureAccessPage(features) { + return featureAccess(features, true); +} + module.exports = { admin, apikey, @@ -359,6 +374,8 @@ module.exports = { changeEmailRequest, changePassword, cooldown, + featureAccess, + featureAccessPage, generateApiKey, jwt, jwtLoose, @@ -369,6 +386,5 @@ module.exports = { resetPassword, resetPasswordRequest, signup, - signupAccess, verify, } diff --git a/server/handlers/locals.handler.js b/server/handlers/locals.handler.js index 0f5d1c2..711e19c 100644 --- a/server/handlers/locals.handler.js +++ b/server/handlers/locals.handler.js @@ -26,6 +26,7 @@ function config(req, res, next) { res.locals.contact_email = env.CONTACT_EMAIL; res.locals.server_ip_address = env.SERVER_IP_ADDRESS; res.locals.disallow_registration = env.DISALLOW_REGISTRATION; + res.locals.mail_enabled = env.MAIL_ENABLED; next(); } diff --git a/server/mail/mail.js b/server/mail/mail.js index 015a158..367215e 100644 --- a/server/mail/mail.js +++ b/server/mail/mail.js @@ -24,20 +24,33 @@ const transporter = nodemailer.createTransport(mailConfig); const resetEmailTemplatePath = path.join(__dirname, "template-reset.html"); const verifyEmailTemplatePath = path.join(__dirname, "template-verify.html"); const changeEmailTemplatePath = path.join(__dirname,"template-change-email.html"); -const resetEmailTemplate = fs - .readFileSync(resetEmailTemplatePath, { encoding: "utf-8" }) - .replace(/{{domain}}/gm, env.DEFAULT_DOMAIN) - .replace(/{{site_name}}/gm, env.SITE_NAME); -const verifyEmailTemplate = fs - .readFileSync(verifyEmailTemplatePath, { encoding: "utf-8" }) - .replace(/{{domain}}/gm, env.DEFAULT_DOMAIN) - .replace(/{{site_name}}/gm, env.SITE_NAME); -const changeEmailTemplate = fs - .readFileSync(changeEmailTemplatePath, { encoding: "utf-8" }) - .replace(/{{domain}}/gm, env.DEFAULT_DOMAIN) - .replace(/{{site_name}}/gm, env.SITE_NAME); + + +let resetEmailTemplate, + verifyEmailTemplate, + changeEmailTemplate; + +// only read email templates if email is enabled +if (env.MAIL_ENABLED) { + resetEmailTemplate = fs + .readFileSync(resetEmailTemplatePath, { encoding: "utf-8" }) + .replace(/{{domain}}/gm, env.DEFAULT_DOMAIN) + .replace(/{{site_name}}/gm, env.SITE_NAME); + verifyEmailTemplate = fs + .readFileSync(verifyEmailTemplatePath, { encoding: "utf-8" }) + .replace(/{{domain}}/gm, env.DEFAULT_DOMAIN) + .replace(/{{site_name}}/gm, env.SITE_NAME); + changeEmailTemplate = fs + .readFileSync(changeEmailTemplatePath, { encoding: "utf-8" }) + .replace(/{{domain}}/gm, env.DEFAULT_DOMAIN) + .replace(/{{site_name}}/gm, env.SITE_NAME); +} async function verification(user) { + if (!env.MAIL_ENABLED) { + throw new Error("Attempting to send verification email but email is not enabled."); + }; + const mail = await transporter.sendMail({ from: env.MAIL_FROM || env.MAIL_USER, to: user.email, @@ -58,6 +71,10 @@ async function verification(user) { } async function changeEmail(user) { + if (!env.MAIL_ENABLED) { + throw new Error("Attempting to send change email token but email is not enabled."); + }; + const mail = await transporter.sendMail({ from: env.MAIL_FROM || env.MAIL_USER, to: user.change_email_address, @@ -78,6 +95,10 @@ async function changeEmail(user) { } async function resetPasswordToken(user) { + if (!env.MAIL_ENABLED) { + throw new Error("Attempting to send reset password email but email is not enabled."); + }; + const mail = await transporter.sendMail({ from: env.MAIL_FROM || env.MAIL_USER, to: user.email, @@ -89,7 +110,7 @@ async function resetPasswordToken(user) { .replace(/{{resetpassword}}/gm, user.reset_password_token) .replace(/{{domain}}/gm, env.DEFAULT_DOMAIN) }); - + if (!mail.accepted.length) { throw new CustomError( "Couldn't send reset password email. Try again later." @@ -98,6 +119,10 @@ async function resetPasswordToken(user) { } async function sendReportEmail(link) { + if (!env.MAIL_ENABLED) { + throw new Error("Attempting to send report email but email is not enabled."); + }; + const mail = await transporter.sendMail({ from: env.MAIL_FROM || env.MAIL_USER, to: env.REPORT_EMAIL, diff --git a/server/routes/auth.routes.js b/server/routes/auth.routes.js index 073e67b..f9d22dc 100644 --- a/server/routes/auth.routes.js +++ b/server/routes/auth.routes.js @@ -6,6 +6,7 @@ const asyncHandler = require("../utils/asyncHandler"); const locals = require("../handlers/locals.handler"); const auth = require("../handlers/auth.handler"); const utils = require("../utils"); +const env = require("../env"); const router = Router(); @@ -21,7 +22,7 @@ router.post( router.post( "/signup", locals.viewTemplate("partials/auth/form"), - auth.signupAccess, + auth.featureAccess([!env.DISALLOW_REGISTRATION, env.MAIL_ENABLED]), validators.signup, asyncHandler(helpers.verify), asyncHandler(auth.signup) @@ -40,6 +41,7 @@ router.post( "/change-email", locals.viewTemplate("partials/settings/change_email"), asyncHandler(auth.jwt), + auth.featureAccess([env.MAIL_ENABLED]), validators.changeEmail, asyncHandler(helpers.verify), asyncHandler(auth.changeEmailRequest) @@ -55,6 +57,7 @@ router.post( router.post( "/reset-password", locals.viewTemplate("partials/reset_password/form"), + auth.featureAccess([env.MAIL_ENABLED]), validators.resetPassword, asyncHandler(helpers.verify), asyncHandler(auth.resetPasswordRequest) diff --git a/server/routes/link.routes.js b/server/routes/link.routes.js index 51c52d9..25d3f84 100644 --- a/server/routes/link.routes.js +++ b/server/routes/link.routes.js @@ -88,6 +88,7 @@ router.post( router.post( "/report", locals.viewTemplate("partials/report/form"), + auth.featureAccess([env.MAIL_ENABLED]), validators.reportLink, asyncHandler(helpers.verify), asyncHandler(link.report) diff --git a/server/routes/renders.routes.js b/server/routes/renders.routes.js index b0f59af..5f0179c 100644 --- a/server/routes/renders.routes.js +++ b/server/routes/renders.routes.js @@ -5,6 +5,7 @@ const renders = require("../handlers/renders.handler"); const asyncHandler = require("../utils/asyncHandler"); const locals = require("../handlers/locals.handler"); const auth = require("../handlers/auth.handler"); +const env = require("../env"); const router = Router(); @@ -64,6 +65,7 @@ router.get( router.get( "/reset-password", + auth.featureAccessPage([env.MAIL_ENABLED]), asyncHandler(auth.jwtLoosePage), asyncHandler(locals.user), asyncHandler(renders.resetPassword) diff --git a/server/views/partials/auth/form.hbs b/server/views/partials/auth/form.hbs index 856772a..d327a26 100644 --- a/server/views/partials/auth/form.hbs +++ b/server/views/partials/auth/form.hbs @@ -29,25 +29,29 @@ Log in {{#unless disallow_registration}} - + {{#if mail_enabled}} + + {{/if}} {{/unless}} - Forgot your password? + {{#if mail_enabled}} + Forgot your password? + {{/if}} {{#unless errors}} {{#if error}}

{{error}}

diff --git a/server/views/report.hbs b/server/views/report.hbs index 2e5da79..597626a 100644 --- a/server/views/report.hbs +++ b/server/views/report.hbs @@ -4,10 +4,12 @@ Report abuse.

- Report abuses, malware and phishing links to the email address below - or use the form. We will review as soon as we can. + Report abuses, malware and phishing links to the email address below {{#if mail_enabled}}or use the form{{/if}}. + We will review as soon as we can.

{{> report/email}} - {{> report/form}} + {{#if mail_enabled}} + {{> report/form}} + {{/if}} {{> footer}} \ No newline at end of file diff --git a/server/views/settings.hbs b/server/views/settings.hbs index 18597cb..198e3f9 100644 --- a/server/views/settings.hbs +++ b/server/views/settings.hbs @@ -10,8 +10,10 @@
{{> settings/change_password}}
- {{> settings/change_email}} -
+ {{#if mail_enabled}} + {{> settings/change_email}} +
+ {{/if}} {{> settings/delete_account}} {{> footer}} \ No newline at end of file