Finished up with email verification system and profile page redux.

This commit is contained in:
rainbow napkin 2024-12-31 14:28:12 -05:00
parent c32f3df3f3
commit 40c004795b
15 changed files with 275 additions and 58 deletions

View file

@ -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}`,
`<h1>Email Change Request</h1>
<p>A request to change the email associated with the ${config.instanceName} account '${userDB.user}' to this address has been requested.<br>
<a href="${requestDB.getChangeURL()}">Click here</a> to confirm this change.</p>
<sup>If you received this email without request, feel free to ignore and delete it! -Tokebot</sup>`,
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}`,
`<h1>Email Change Request Notification</h1>
<p>A request to change the email associated with the ${config.instanceName} account '${userDB.user}' to another address has been requested.<br>
<sup>If you received this email without request, you should <strong>immediately</strong> change your password and contact the server adminsitrator! -Tokebot</sup>`,
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);
}
}

View file

@ -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);
}
}

View file

@ -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)
});
}

View file

@ -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;
}

View file

@ -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}`,
`<h1>Email Change Request</h1>
<p>A request to change the email associated with the ${config.instanceName} account '${userDB.user}' to this address has been requested.<br>
<a href="${requestDB.getChangeURL()}">Click here</a> to confirm this change.</p>
<sup>If you received this email without request, feel free to ignore and delete it! -Tokebot</sup>`,
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}`,
`<h1>Email Change Request Notification</h1>
<p>A request to change the email associated with the ${config.instanceName} account '${userDB.user}' to another address has been requested.<br>
<sup>If you received this email without request, you should <strong>immediately</strong> change your password and contact the server adminsitrator! -Tokebot</sup>`,
true
);
}
}

View file

@ -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}),

View file

@ -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 <https://www.gnu.org/licenses/>. %>
<link rel="stylesheet" type="text/css" href="/css/popup/changeEmail.css">
<h3 id="email-change-popup-title" class="popup-title">Update Email</h3>
<div id="email-change-popup-content">
<p id="email-change-popup-caption">Enter new email and current password below:</p>
<input type="email" placeholder="email" id="email-change-popup-email">
<input type="password" placeholder="password" id="email-change-popup-password">
</div>

View file

@ -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 <https://www.gnu.org/licenses/>. %>
<link rel="stylesheet" type="text/css" href="/css/popup/changePassword.css">
<h3 id="password-change-popup-title" class="popup-title">Update Password</h3>
<div id="password-change-popup-content">
<p id="password-change-popup-caption">Enter new email and current password below:</p>
<input type="password" placeholder="password" id="password-change-popup-old-password">
<input type="password" placeholder="new password" id="password-change-popup-new-password">
<input type="password" placeholder="confirm new password" id="password-change-popup-confirm-new-password">
</div>

View file

@ -16,9 +16,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<span class="profile-bio-span">
<h4 id="profile-bio-label" class="profile-item-label">Bio:</h4>
<% if(selfProfile){ %>
<p class="profile-item interactive" id="profile-bio-content"><%- profile.bio %></p>
<%# Make sure to convert newlines to br so they display proepr %>
<p class="profile-item interactive" id="profile-bio-content"><%- profile.bio.replaceAll('\n','<br>') %></p>
<textarea class="profile-item-prompt" id="profile-bio-prompt"></textarea>
<% }else{ %>
<p class="profile-item" id="profile-bio-content"><%- profile.bio %></p>
<p class="profile-item" id="profile-bio-content"><%- profile.bio.replaceAll('\n','<br>') %></p>
<% } %>
</span>

View file

@ -15,6 +15,10 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<div class="account-settings dynamic-container" id="account-settings-div">
<h3 class="account-settings" id="account-settings-label">Account Settings</h3>
<% if(profile.email){ %>
<h4 class="account-settings" id="account-email-label">Email Address:</h3>
<h4 class="account-settings" id="account-email-address"><%= profile.email %></h4>
<% } %>
<span class="account-settings" id="account-settings-buttons">
<button href="javascript:" class="account-settings positive-button" id="account-settings-update-email-button">Update Email</button>
<button href="javascript:" class="account-settings positive-button" id="account-settings-change-password-button">Change Password</button>

View file

@ -18,7 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<head>
<%- include('partial/styles', {instance, user}); %>
<%- include('partial/csrfToken', {csrfToken}); %>
<% var selfProfile = user ? profile ? profile.user == user.user : false : false %>
<% %>
<link rel="stylesheet" type="text/css" href="/css/profile.css">
<% if(profile){ %>
<title><%= instance %> - Profile: <%= profile.user %></title>