make sending emails optional
This commit is contained in:
parent
ae98343cd3
commit
ea888dcb1d
@ -61,9 +61,11 @@ ADMIN_EMAILS=
|
|||||||
# Get it from https://developers.google.com/safe-browsing/v4/get-started
|
# Get it from https://developers.google.com/safe-browsing/v4/get-started
|
||||||
GOOGLE_SAFE_BROWSING_KEY=
|
GOOGLE_SAFE_BROWSING_KEY=
|
||||||
|
|
||||||
# Your email host details to use to send verification emails.
|
# Optional - Email is used to verify or change email address, reset password, and send reports.
|
||||||
# More info on http://nodemailer.com/
|
# If it's disabled, all the above functionality would be disabled as well.
|
||||||
# Mail from example "Kutt <support@kutt.it>". Leave empty to use MAIL_USER
|
# MAIL_FROM example: "Kutt <support@kutt.it>". Leave it empty to use MAIL_USER.
|
||||||
|
# More info on the configuration on http://nodemailer.com/.
|
||||||
|
MAIL_ENABLED=false
|
||||||
MAIL_HOST=
|
MAIL_HOST=
|
||||||
MAIL_PORT=
|
MAIL_PORT=
|
||||||
MAIL_SECURE=true
|
MAIL_SECURE=true
|
||||||
|
@ -28,12 +28,13 @@ const env = cleanEnv(process.env, {
|
|||||||
JWT_SECRET: str(),
|
JWT_SECRET: str(),
|
||||||
ADMIN_EMAILS: str({ default: "" }),
|
ADMIN_EMAILS: str({ default: "" }),
|
||||||
GOOGLE_SAFE_BROWSING_KEY: str({ default: "" }),
|
GOOGLE_SAFE_BROWSING_KEY: str({ default: "" }),
|
||||||
MAIL_HOST: str(),
|
MAIL_ENABLED: bool({ default: false }),
|
||||||
MAIL_PORT: num(),
|
MAIL_HOST: str({ default: "" }),
|
||||||
|
MAIL_PORT: num({ default: 587 }),
|
||||||
MAIL_SECURE: bool({ default: false }),
|
MAIL_SECURE: bool({ default: false }),
|
||||||
MAIL_USER: str(),
|
MAIL_USER: str({ default: "" }),
|
||||||
MAIL_FROM: str({ default: "", example: "Kutt <support@kutt.it>" }),
|
MAIL_FROM: str({ default: "", example: "Kutt <support@kutt.it>" }),
|
||||||
MAIL_PASSWORD: str(),
|
MAIL_PASSWORD: str({ default: "" }),
|
||||||
REPORT_EMAIL: str({ default: "" }),
|
REPORT_EMAIL: str({ default: "" }),
|
||||||
CONTACT_EMAIL: str({ default: "" })
|
CONTACT_EMAIL: str({ default: "" })
|
||||||
});
|
});
|
||||||
|
@ -222,10 +222,11 @@ async function resetPasswordRequest(req, res) {
|
|||||||
reset_password_expires: addMinutes(new Date(), 30).toISOString()
|
reset_password_expires: addMinutes(new Date(), 30).toISOString()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
// TODO: handle error
|
mail.resetPasswordToken(user).catch(error => {
|
||||||
mail.resetPasswordToken(user).catch(() => null);
|
console.error("Send reset-password token email error:\n", error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.isHTML) {
|
if (req.isHTML) {
|
||||||
@ -264,11 +265,6 @@ async function resetPassword(req, res, next) {
|
|||||||
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) {
|
async function changeEmailRequest(req, res) {
|
||||||
const { email, password } = req.body;
|
const { email, password } = req.body;
|
||||||
|
|
||||||
@ -352,6 +348,25 @@ async function changeEmail(req, res, next) {
|
|||||||
return 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 = {
|
module.exports = {
|
||||||
admin,
|
admin,
|
||||||
apikey,
|
apikey,
|
||||||
@ -359,6 +374,8 @@ module.exports = {
|
|||||||
changeEmailRequest,
|
changeEmailRequest,
|
||||||
changePassword,
|
changePassword,
|
||||||
cooldown,
|
cooldown,
|
||||||
|
featureAccess,
|
||||||
|
featureAccessPage,
|
||||||
generateApiKey,
|
generateApiKey,
|
||||||
jwt,
|
jwt,
|
||||||
jwtLoose,
|
jwtLoose,
|
||||||
@ -369,6 +386,5 @@ module.exports = {
|
|||||||
resetPassword,
|
resetPassword,
|
||||||
resetPasswordRequest,
|
resetPasswordRequest,
|
||||||
signup,
|
signup,
|
||||||
signupAccess,
|
|
||||||
verify,
|
verify,
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ function config(req, res, next) {
|
|||||||
res.locals.contact_email = env.CONTACT_EMAIL;
|
res.locals.contact_email = env.CONTACT_EMAIL;
|
||||||
res.locals.server_ip_address = env.SERVER_IP_ADDRESS;
|
res.locals.server_ip_address = env.SERVER_IP_ADDRESS;
|
||||||
res.locals.disallow_registration = env.DISALLOW_REGISTRATION;
|
res.locals.disallow_registration = env.DISALLOW_REGISTRATION;
|
||||||
|
res.locals.mail_enabled = env.MAIL_ENABLED;
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,20 +24,33 @@ const transporter = nodemailer.createTransport(mailConfig);
|
|||||||
const resetEmailTemplatePath = path.join(__dirname, "template-reset.html");
|
const resetEmailTemplatePath = path.join(__dirname, "template-reset.html");
|
||||||
const verifyEmailTemplatePath = path.join(__dirname, "template-verify.html");
|
const verifyEmailTemplatePath = path.join(__dirname, "template-verify.html");
|
||||||
const changeEmailTemplatePath = path.join(__dirname,"template-change-email.html");
|
const changeEmailTemplatePath = path.join(__dirname,"template-change-email.html");
|
||||||
const resetEmailTemplate = fs
|
|
||||||
.readFileSync(resetEmailTemplatePath, { encoding: "utf-8" })
|
|
||||||
.replace(/{{domain}}/gm, env.DEFAULT_DOMAIN)
|
let resetEmailTemplate,
|
||||||
.replace(/{{site_name}}/gm, env.SITE_NAME);
|
verifyEmailTemplate,
|
||||||
const verifyEmailTemplate = fs
|
changeEmailTemplate;
|
||||||
.readFileSync(verifyEmailTemplatePath, { encoding: "utf-8" })
|
|
||||||
.replace(/{{domain}}/gm, env.DEFAULT_DOMAIN)
|
// only read email templates if email is enabled
|
||||||
.replace(/{{site_name}}/gm, env.SITE_NAME);
|
if (env.MAIL_ENABLED) {
|
||||||
const changeEmailTemplate = fs
|
resetEmailTemplate = fs
|
||||||
.readFileSync(changeEmailTemplatePath, { encoding: "utf-8" })
|
.readFileSync(resetEmailTemplatePath, { encoding: "utf-8" })
|
||||||
.replace(/{{domain}}/gm, env.DEFAULT_DOMAIN)
|
.replace(/{{domain}}/gm, env.DEFAULT_DOMAIN)
|
||||||
.replace(/{{site_name}}/gm, env.SITE_NAME);
|
.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) {
|
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({
|
const mail = await transporter.sendMail({
|
||||||
from: env.MAIL_FROM || env.MAIL_USER,
|
from: env.MAIL_FROM || env.MAIL_USER,
|
||||||
to: user.email,
|
to: user.email,
|
||||||
@ -58,6 +71,10 @@ async function verification(user) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function changeEmail(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({
|
const mail = await transporter.sendMail({
|
||||||
from: env.MAIL_FROM || env.MAIL_USER,
|
from: env.MAIL_FROM || env.MAIL_USER,
|
||||||
to: user.change_email_address,
|
to: user.change_email_address,
|
||||||
@ -78,6 +95,10 @@ async function changeEmail(user) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function resetPasswordToken(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({
|
const mail = await transporter.sendMail({
|
||||||
from: env.MAIL_FROM || env.MAIL_USER,
|
from: env.MAIL_FROM || env.MAIL_USER,
|
||||||
to: user.email,
|
to: user.email,
|
||||||
@ -89,7 +110,7 @@ async function resetPasswordToken(user) {
|
|||||||
.replace(/{{resetpassword}}/gm, user.reset_password_token)
|
.replace(/{{resetpassword}}/gm, user.reset_password_token)
|
||||||
.replace(/{{domain}}/gm, env.DEFAULT_DOMAIN)
|
.replace(/{{domain}}/gm, env.DEFAULT_DOMAIN)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!mail.accepted.length) {
|
if (!mail.accepted.length) {
|
||||||
throw new CustomError(
|
throw new CustomError(
|
||||||
"Couldn't send reset password email. Try again later."
|
"Couldn't send reset password email. Try again later."
|
||||||
@ -98,6 +119,10 @@ async function resetPasswordToken(user) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function sendReportEmail(link) {
|
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({
|
const mail = await transporter.sendMail({
|
||||||
from: env.MAIL_FROM || env.MAIL_USER,
|
from: env.MAIL_FROM || env.MAIL_USER,
|
||||||
to: env.REPORT_EMAIL,
|
to: env.REPORT_EMAIL,
|
||||||
|
@ -6,6 +6,7 @@ const asyncHandler = require("../utils/asyncHandler");
|
|||||||
const locals = require("../handlers/locals.handler");
|
const locals = require("../handlers/locals.handler");
|
||||||
const auth = require("../handlers/auth.handler");
|
const auth = require("../handlers/auth.handler");
|
||||||
const utils = require("../utils");
|
const utils = require("../utils");
|
||||||
|
const env = require("../env");
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ router.post(
|
|||||||
router.post(
|
router.post(
|
||||||
"/signup",
|
"/signup",
|
||||||
locals.viewTemplate("partials/auth/form"),
|
locals.viewTemplate("partials/auth/form"),
|
||||||
auth.signupAccess,
|
auth.featureAccess([!env.DISALLOW_REGISTRATION, env.MAIL_ENABLED]),
|
||||||
validators.signup,
|
validators.signup,
|
||||||
asyncHandler(helpers.verify),
|
asyncHandler(helpers.verify),
|
||||||
asyncHandler(auth.signup)
|
asyncHandler(auth.signup)
|
||||||
@ -40,6 +41,7 @@ router.post(
|
|||||||
"/change-email",
|
"/change-email",
|
||||||
locals.viewTemplate("partials/settings/change_email"),
|
locals.viewTemplate("partials/settings/change_email"),
|
||||||
asyncHandler(auth.jwt),
|
asyncHandler(auth.jwt),
|
||||||
|
auth.featureAccess([env.MAIL_ENABLED]),
|
||||||
validators.changeEmail,
|
validators.changeEmail,
|
||||||
asyncHandler(helpers.verify),
|
asyncHandler(helpers.verify),
|
||||||
asyncHandler(auth.changeEmailRequest)
|
asyncHandler(auth.changeEmailRequest)
|
||||||
@ -55,6 +57,7 @@ router.post(
|
|||||||
router.post(
|
router.post(
|
||||||
"/reset-password",
|
"/reset-password",
|
||||||
locals.viewTemplate("partials/reset_password/form"),
|
locals.viewTemplate("partials/reset_password/form"),
|
||||||
|
auth.featureAccess([env.MAIL_ENABLED]),
|
||||||
validators.resetPassword,
|
validators.resetPassword,
|
||||||
asyncHandler(helpers.verify),
|
asyncHandler(helpers.verify),
|
||||||
asyncHandler(auth.resetPasswordRequest)
|
asyncHandler(auth.resetPasswordRequest)
|
||||||
|
@ -88,6 +88,7 @@ router.post(
|
|||||||
router.post(
|
router.post(
|
||||||
"/report",
|
"/report",
|
||||||
locals.viewTemplate("partials/report/form"),
|
locals.viewTemplate("partials/report/form"),
|
||||||
|
auth.featureAccess([env.MAIL_ENABLED]),
|
||||||
validators.reportLink,
|
validators.reportLink,
|
||||||
asyncHandler(helpers.verify),
|
asyncHandler(helpers.verify),
|
||||||
asyncHandler(link.report)
|
asyncHandler(link.report)
|
||||||
|
@ -5,6 +5,7 @@ const renders = require("../handlers/renders.handler");
|
|||||||
const asyncHandler = require("../utils/asyncHandler");
|
const asyncHandler = require("../utils/asyncHandler");
|
||||||
const locals = require("../handlers/locals.handler");
|
const locals = require("../handlers/locals.handler");
|
||||||
const auth = require("../handlers/auth.handler");
|
const auth = require("../handlers/auth.handler");
|
||||||
|
const env = require("../env");
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@ -64,6 +65,7 @@ router.get(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/reset-password",
|
"/reset-password",
|
||||||
|
auth.featureAccessPage([env.MAIL_ENABLED]),
|
||||||
asyncHandler(auth.jwtLoosePage),
|
asyncHandler(auth.jwtLoosePage),
|
||||||
asyncHandler(locals.user),
|
asyncHandler(locals.user),
|
||||||
asyncHandler(renders.resetPassword)
|
asyncHandler(renders.resetPassword)
|
||||||
|
@ -29,25 +29,29 @@
|
|||||||
Log in
|
Log in
|
||||||
</button>
|
</button>
|
||||||
{{#unless disallow_registration}}
|
{{#unless disallow_registration}}
|
||||||
<button
|
{{#if mail_enabled}}
|
||||||
type="button"
|
<button
|
||||||
class="secondary signup"
|
type="button"
|
||||||
hx-post="/api/auth/signup"
|
class="secondary signup"
|
||||||
hx-target="#login-signup"
|
hx-post="/api/auth/signup"
|
||||||
hx-trigger="click"
|
hx-target="#login-signup"
|
||||||
hx-indicator="#login-signup"
|
hx-trigger="click"
|
||||||
hx-swap="outerHTML"
|
hx-indicator="#login-signup"
|
||||||
hx-sync="closest form"
|
hx-swap="outerHTML"
|
||||||
hx-on:htmx:before-request="htmx.addClass('#login-signup', 'signup')"
|
hx-sync="closest form"
|
||||||
hx-on:htmx:after-request="htmx.removeClass('#login-signup', 'signup')"
|
hx-on:htmx:before-request="htmx.addClass('#login-signup', 'signup')"
|
||||||
>
|
hx-on:htmx:after-request="htmx.removeClass('#login-signup', 'signup')"
|
||||||
<span>{{> icons/new_user}}</span>
|
>
|
||||||
<span>{{> icons/spinner}}</span>
|
<span>{{> icons/new_user}}</span>
|
||||||
Sign up
|
<span>{{> icons/spinner}}</span>
|
||||||
</button>
|
Sign up
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
<a class="forgot-password" href="/reset-password" title="Reset password">Forgot your password?</a>
|
{{#if mail_enabled}}
|
||||||
|
<a class="forgot-password" href="/reset-password" title="Reset password">Forgot your password?</a>
|
||||||
|
{{/if}}
|
||||||
{{#unless errors}}
|
{{#unless errors}}
|
||||||
{{#if error}}
|
{{#if error}}
|
||||||
<p class="error">{{error}}</p>
|
<p class="error">{{error}}</p>
|
||||||
|
@ -4,10 +4,12 @@
|
|||||||
Report abuse.
|
Report abuse.
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
Report abuses, malware and phishing links to the email address below
|
Report abuses, malware and phishing links to the email address below {{#if mail_enabled}}or use the form{{/if}}.
|
||||||
or use the form. We will review as soon as we can.
|
We will review as soon as we can.
|
||||||
</p>
|
</p>
|
||||||
{{> report/email}}
|
{{> report/email}}
|
||||||
{{> report/form}}
|
{{#if mail_enabled}}
|
||||||
|
{{> report/form}}
|
||||||
|
{{/if}}
|
||||||
</section>
|
</section>
|
||||||
{{> footer}}
|
{{> footer}}
|
@ -10,8 +10,10 @@
|
|||||||
<hr />
|
<hr />
|
||||||
{{> settings/change_password}}
|
{{> settings/change_password}}
|
||||||
<hr />
|
<hr />
|
||||||
{{> settings/change_email}}
|
{{#if mail_enabled}}
|
||||||
<hr />
|
{{> settings/change_email}}
|
||||||
|
<hr />
|
||||||
|
{{/if}}
|
||||||
{{> settings/delete_account}}
|
{{> settings/delete_account}}
|
||||||
</section>
|
</section>
|
||||||
{{> footer}}
|
{{> footer}}
|
Loading…
x
Reference in New Issue
Block a user