diff --git a/src/controllers/api/account/emailChangeRequestController.js b/src/controllers/api/account/emailChangeRequestController.js index 32c853c..f1f7181 100644 --- a/src/controllers/api/account/emailChangeRequestController.js +++ b/src/controllers/api/account/emailChangeRequestController.js @@ -62,27 +62,7 @@ module.exports.post = async function(req, res){ res.sendStatus(200); //Send the reset url via email - await mailUtils.mailem( - email, - `Email Change Request - ${userDB.user}`, - `

Email Change Request

-

A request to change the email associated with the ${config.instanceName} account '${userDB.user}' to this address has been requested.
- Click here to confirm this change.

- If you received this email without request, feel free to ignore and delete it! -Tokebot`, - true - ); - - //If the user has a pre-existing email address - if(userDB.email != null && userDB.email != ""){ - await mailUtils.mailem( - userDB.email, - `Email Change Request - ${userDB.user}`, - `

Email Change Request Notification

-

A request to change the email associated with the ${config.instanceName} account '${userDB.user}' to another address has been requested.
- If you received this email without request, you should immediately change your password and contact the server adminsitrator! -Tokebot`, - true - ); - } + await mailUtils.sendAddressVerification(requestDB, userDB, email); //Clean our hands of the operation return; @@ -91,6 +71,7 @@ module.exports.post = async function(req, res){ return res.send({errors: validResult.array()}); } }catch(err){ + console.log(err) return exceptionHandler(res, err); } } \ No newline at end of file diff --git a/src/controllers/api/account/registerController.js b/src/controllers/api/account/registerController.js index 4755ab5..f2b294e 100644 --- a/src/controllers/api/account/registerController.js +++ b/src/controllers/api/account/registerController.js @@ -59,6 +59,7 @@ module.exports.post = async function(req, res){ return res.send({errors: validResult.array()}); } }catch(err){ + console.log(err); return exceptionHandler(res, err); } } \ No newline at end of file diff --git a/src/controllers/profileController.js b/src/controllers/profileController.js index 99146c4..c37fdf9 100644 --- a/src/controllers/profileController.js +++ b/src/controllers/profileController.js @@ -28,13 +28,17 @@ module.exports.get = async function(req, res){ try{ var profileName = req.url.slice(1) == '' ? (req.session.user ? req.session.user.user : null) : req.url.slice(1); - const profile = await userModel.findProfile({user: profileName}); + const profile = await userModel.findProfile({user: profileName}, true); if(profile){ + //If we have a user, check if the is looking at their own profile + const selfProfile = req.session.user ? profile.user == req.session.user.user : false; + res.render('profile', { instance: config.instanceName, user: req.session.user, profile, + selfProfile, csrfToken: csrfUtils.generateToken(req) }); }else{ @@ -42,6 +46,7 @@ module.exports.get = async function(req, res){ instance: config.instanceName, user: req.session.user, profile: null, + selfProfile: false, csrfToken: csrfUtils.generateToken(req) }); } diff --git a/src/schemas/user/userSchema.js b/src/schemas/user/userSchema.js index fc5cd44..e6517f0 100644 --- a/src/schemas/user/userSchema.js +++ b/src/schemas/user/userSchema.js @@ -28,8 +28,10 @@ const statModel = require('../statSchema'); const flairModel = require('../flairSchema'); const permissionModel = require('../permissionSchema'); const emoteModel = require('../emoteSchema'); +const emailChangeModel = require('./emailChangeSchema'); //Utils const hashUtil = require('../../utils/hashUtils'); +const mailUtil = require('../../utils/mailUtils'); const userSchema = new mongoose.Schema({ @@ -214,13 +216,7 @@ userSchema.statics.register = async function(userObj, ip){ //Check password confirmation matches if(pass == passConfirm){ //Look for a user (case insensitive) - var userDB = await this.findOne({user: new RegExp(user, 'i')}); - - //If we didn't find a user and we submitted an email - if(userDB == null && email != null){ - //Check to make sure no one's used this email address - userDB = await this.findOne({email}); - } + var userDB = await this.findOne({user: new RegExp(user, 'i')}); //If the user is found or someones trying to impersonate tokeboi if(userDB || user.toLowerCase() == "tokebot"){ @@ -230,10 +226,17 @@ userSchema.statics.register = async function(userObj, ip){ const id = await statModel.incrementUserCount(); //Create user document in the database - const newUser = await this.create({id, user, pass, email}); + const newUser = await this.create({id, user, pass}); //Tattoo the hashed IP used to register to the new user await newUser.tattooIPRecord(ip); + + //if we submitted an email + if(email != null){ + const requestDB = await emailChangeModel.create({user: newUser._id, newEmail: email, ipHash: ip}); + + await mailUtil.sendAddressVerification(requestDB, newUser, email) + } } }else{ throw new Error("Confirmation password doesn't match!"); @@ -268,7 +271,7 @@ userSchema.statics.authenticate = async function(user, pass, failLine = "Bad Use } } -userSchema.statics.findProfile = async function(user){ +userSchema.statics.findProfile = async function(user, includeEmail = false){ //Catch null users if(user == null || user.user == null){ return null; @@ -298,7 +301,7 @@ userSchema.statics.findProfile = async function(user){ } //return the profile - return userDB.getProfile(); + return userDB.getProfile(includeEmail); } } @@ -425,7 +428,7 @@ userSchema.methods.getAuthenticatedSessions = async function(){ } -userSchema.methods.getProfile = function(){ +userSchema.methods.getProfile = function(includeEmail = false){ //Create profile hashtable const profile = { id: this.id, @@ -439,6 +442,11 @@ userSchema.methods.getProfile = function(){ bio: this.bio }; + //Include the email if we need to + if(includeEmail){ + profile.email = this.email; + } + //return profile hashtable return profile; } diff --git a/src/utils/mailUtils.js b/src/utils/mailUtils.js index 4636d71..29d744a 100644 --- a/src/utils/mailUtils.js +++ b/src/utils/mailUtils.js @@ -54,4 +54,29 @@ module.exports.mailem = async function(to, subject, body, htmlBody = false){ //return the mail info return sentMail; +} + +module.exports.sendAddressVerification = async function(requestDB, userDB, newEmail){ + //Send the reset url via email + await module.exports.mailem( + newEmail, + `Email Change Request - ${userDB.user}`, + `

Email Change Request

+

A request to change the email associated with the ${config.instanceName} account '${userDB.user}' to this address has been requested.
+ Click here to confirm this change.

+ If you received this email without request, feel free to ignore and delete it! -Tokebot`, + true + ); + + //If the user has a pre-existing email address + if(userDB.email != null && userDB.email != ""){ + await module.exports.mailem( + userDB.email, + `Email Change Request - ${userDB.user}`, + `

Email Change Request Notification

+

A request to change the email associated with the ${config.instanceName} account '${userDB.user}' to another address has been requested.
+ If you received this email without request, you should immediately change your password and contact the server adminsitrator! -Tokebot`, + true + ); + } } \ No newline at end of file diff --git a/src/validators/accountValidator.js b/src/validators/accountValidator.js index 777a9b5..377ecbf 100644 --- a/src/validators/accountValidator.js +++ b/src/validators/accountValidator.js @@ -34,7 +34,7 @@ module.exports = { pronouns: (field = 'pronouns') => body(field).optional().escape().trim().isLength({min: 0, max: 15}), - signature: (field = 'signature') => body(field).optional().escape().trim().isLength({min: 1, max: 150}), + signature: (field = 'signature') => body(field).optional().escape().trim().isLength({min: 1, max: 25}), bio: (field = 'bio') => body(field).optional().escape().trim().isLength({min: 1, max: 1000}), diff --git a/src/views/partial/popup/changeEmail.ejs b/src/views/partial/popup/changeEmail.ejs new file mode 100644 index 0000000..6aaa253 --- /dev/null +++ b/src/views/partial/popup/changeEmail.ejs @@ -0,0 +1,22 @@ +<%# Canopy - The next generation of stoner streaming software +Copyright (C) 2024-2025 Rainbownapkin and the TTN Community + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . %> + +

+
+

Enter new email and current password below:

+ + +
\ No newline at end of file diff --git a/src/views/partial/popup/changePassword.ejs b/src/views/partial/popup/changePassword.ejs new file mode 100644 index 0000000..76abca8 --- /dev/null +++ b/src/views/partial/popup/changePassword.ejs @@ -0,0 +1,23 @@ +<%# Canopy - The next generation of stoner streaming software +Copyright (C) 2024-2025 Rainbownapkin and the TTN Community + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . %> + + +
+

Enter new email and current password below:

+ + + +
\ No newline at end of file diff --git a/src/views/partial/profile/bio.ejs b/src/views/partial/profile/bio.ejs index e15910c..0cc620b 100644 --- a/src/views/partial/profile/bio.ejs +++ b/src/views/partial/profile/bio.ejs @@ -16,9 +16,10 @@ along with this program. If not, see . %>

Bio:

<% if(selfProfile){ %> -

<%- profile.bio %>

+ <%# Make sure to convert newlines to br so they display proepr %> +

<%- profile.bio.replaceAll('\n','
') %>

<% }else{ %> -

<%- profile.bio %>

+

<%- profile.bio.replaceAll('\n','
') %>

<% } %>
\ No newline at end of file diff --git a/src/views/partial/profile/settings.ejs b/src/views/partial/profile/settings.ejs index 4faea19..84a0ac8 100644 --- a/src/views/partial/profile/settings.ejs +++ b/src/views/partial/profile/settings.ejs @@ -15,6 +15,10 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . %>