Started work on migration UI. Improved email handling.

This commit is contained in:
rainbow napkin 2025-10-16 08:25:13 -04:00
parent bddbd9cd36
commit eb48b92551
9 changed files with 252 additions and 23 deletions

View file

@ -0,0 +1,31 @@
/*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/>.*/
//Config
const config = require('../../config.json');
//Local Imports
const altchaUtils = require('../utils/altchaUtils');
const csrfUtils = require('../utils/csrfUtils');
//register page functions
module.exports.get = async function(req, res){
//Generate captcha
const challenge = await altchaUtils.genCaptcha();
//Render page
return res.render('migrate', {instance: config.instanceName, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)});
}

View file

@ -0,0 +1,30 @@
/*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/>.*/
//npm imports
const { Router } = require('express');
//local imports
const migrateController = require("../controllers/migrateController");
//globals
const router = Router();
//routing functions
router.get('/', migrateController.get);
module.exports = router;

View file

@ -316,6 +316,12 @@ migrationSchema.statics.consumeByUsername = async function(ip, migration){
//Pull migration doc by case-insensitive username //Pull migration doc by case-insensitive username
const migrationDB = await this.findOne({user: new RegExp(migration.user, 'i')}); const migrationDB = await this.findOne({user: new RegExp(migration.user, 'i')});
//If we have no migration document
if(migrationDB == null){
//Bitch and moan
throw loggerUtils.exceptionSmith("Incorrect username/password.", "migration");
}
//Wait on the miration DB token to be consumed //Wait on the miration DB token to be consumed
await migrationDB.consume(ip, migration); await migrationDB.consume(ip, migration);
} }
@ -359,7 +365,7 @@ migrationSchema.methods.consume = async function(ip, migration){
await newUser.tattooIPRecord(ip); await newUser.tattooIPRecord(ip);
//if we submitted an email //if we submitted an email
if(this.email != null && this.email != ''){ if(this.email != null && validator.isEmail(this.email)){
//Generate new email change request //Generate new email change request
const requestDB = await emailChangeModel.create({user: newUser._id, newEmail: this.email, ipHash: ip}); const requestDB = await emailChangeModel.create({user: newUser._id, newEmail: this.email, ipHash: ip});

View file

@ -58,6 +58,7 @@ const channelRouter = require('./routers/channelRouter');
const newChannelRouter = require('./routers/newChannelRouter'); const newChannelRouter = require('./routers/newChannelRouter');
const passwordResetRouter = require('./routers/passwordResetRouter'); const passwordResetRouter = require('./routers/passwordResetRouter');
const emailChangeRouter = require('./routers/emailChangeController'); const emailChangeRouter = require('./routers/emailChangeController');
const migrateRouter = require('./routers/migrateRouter');
//Panel //Panel
const panelRouter = require('./routers/panelRouter'); const panelRouter = require('./routers/panelRouter');
//Popup //Popup
@ -161,6 +162,7 @@ app.use('/c', channelRouter);
app.use('/newChannel', newChannelRouter); app.use('/newChannel', newChannelRouter);
app.use('/passwordReset', passwordResetRouter); app.use('/passwordReset', passwordResetRouter);
app.use('/emailChange', emailChangeRouter); app.use('/emailChange', emailChangeRouter);
app.use('/migrate', migrateRouter);
//Panel //Panel
app.use('/panel', panelRouter); app.use('/panel', panelRouter);
//tooltip //tooltip

View file

@ -173,10 +173,10 @@ module.exports.errorMiddleware = function(err, req, res, next){
* @param {Error} err - error to dump to file * @param {Error} err - error to dump to file
* @param {Date} date - Date of error, defaults to now * @param {Date} date - Date of error, defaults to now
*/ */
module.exports.dumpError = async function(err, date = new Date()){ module.exports.dumpError = async function(err, date = new Date(), subDir){
try{ try{
//Crash directory //Crash directory
const dir = "./log/crash/" const dir = `./log/crash/${subDir}`
//Double check crash folder exists //Double check crash folder exists
try{ try{

View file

@ -19,6 +19,11 @@ const config = require('../../config.json');
//NPM imports //NPM imports
const nodeMailer = require("nodemailer"); const nodeMailer = require("nodemailer");
const validator = require('validator');
//local imports
const loggerUtils = require('./loggerUtils');
//Setup mail transport //Setup mail transport
/** /**
@ -43,6 +48,13 @@ const transporter = nodeMailer.createTransport({
* @returns {Object} Sent mail info * @returns {Object} Sent mail info
*/ */
module.exports.mailem = async function(to, subject, body, htmlBody = false){ module.exports.mailem = async function(to, subject, body, htmlBody = false){
try{
//If we have a bad email address
if(!validator.isEmail(to)){
//fuck off
return;
}
//Create mail object //Create mail object
const mailObj = { const mailObj = {
from: `"Tokebot🤖💨"<${config.mail.address}>`, from: `"Tokebot🤖💨"<${config.mail.address}>`,
@ -65,6 +77,9 @@ module.exports.mailem = async function(to, subject, body, htmlBody = false){
//return the mail info //return the mail info
return sentMail; return sentMail;
}catch(err){
loggerUtils.dumpError(err, new Date(), 'mail/');
}
} }
/** /**

46
src/views/migrate.ejs Normal file
View file

@ -0,0 +1,46 @@
<%# 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/>. %>
<!DOCTYPE html>
<html>
<head>
<%- include('partial/styles', {instance, user}); %>
<%- include('partial/csrfToken', {csrfToken}); %>
<link rel="stylesheet" type="text/css" href="/css/migrate.css">
<link rel="stylesheet" type="text/css" href="/lib/altcha/altcha.css">
<title><%= instance %> - Account Migration</title>
</head>
<body>
<%- include('partial/navbar', {user}); %>
<form action="javascript:">
<label>Username:</label>
<input class="migrate-prompt" id="migrate-username" placeholder="Required">
<label>Old Password:</label>
<input class="migrate-prompt" id="migrate-password-old" placeholder="Required" type="password">
<label>Password:</label>
<input class="migrate-prompt" id="migrate-password" placeholder="Required" type="password">
<label>Confirm Password:</label>
<input class="migrate-prompt" id="migrate-password-confirm" placeholder="Required" type="password">
<altcha-widget floating challengejson="<%= JSON.stringify(challenge) %>"></altcha-widget>
<button id="migrate-button" class='positive-button'>migrate</button>
</form>
</body>
<footer>
<%- include('partial/scripts', {user}); %>
<script src="/js/migrate.js"></script>
<script src="/lib/altcha/altcha.js" type="module"></script>
</footer>
</html>

31
www/css/migrate.css Normal file
View file

@ -0,0 +1,31 @@
/*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/>.*/
form{
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5em;
margin: 5% 17%;
}
.migrate-prompt{
width: 100%
}
#migrate-button{
width: 6em;
height: 2em;
}

68
www/js/migrate.js Normal file
View file

@ -0,0 +1,68 @@
/*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/>.*/
class migratePrompt{
constructor(){
//Grab user prompt
this.user = document.querySelector("#migrate-username");
//Grab pass prompts
this.oldPass = document.querySelector("#migrate-password-old");
this.pass = document.querySelector("#migrate-password");
this.passConfirm = document.querySelector("#migrate-password-confirm");
//Grab migrate button
this.button = document.querySelector("#migrate-button");
//Grab altcha widget
this.altcha = document.querySelector("altcha-widget");
//Setup null property to hold verification payload from altcha widget
this.verification = null
//Run input setup after DOM content has completely loaded to ensure altcha event listeners work
document.addEventListener('DOMContentLoaded', this.setupInput.bind(this));
}
setupInput(){
//Add verification event listener to altcha widget
this.altcha.addEventListener("verified", this.verify.bind(this));
//Add migrate event listener to migrate button
this.button.addEventListener("click", this.migrate.bind(this));
}
verify(event){
//pull verification payload from event
this.verification = event.detail.payload;
}
migrate(){
//If altcha verification isn't complete
if(this.verification == null){
//don't bother
return;
}
//if the confirmation password doesn't match
if(this.pass.value != this.passConfirm.value){
//Scream and shout
new canopyUXUtils.popup(`<p>Confirmation password does not match!</p>`);
return;
}
//Send the registration informaiton off to the server
utils.ajax.migrate(this.user.value , this.oldPass.value, this.pass.value , this.passConfirm.value , this.verification);
}
}
const migrateForm = new migratePrompt();