diff --git a/src/controllers/migrateController.js b/src/controllers/migrateController.js
new file mode 100644
index 0000000..ea82e5e
--- /dev/null
+++ b/src/controllers/migrateController.js
@@ -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 .*/
+
+//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)});
+}
\ No newline at end of file
diff --git a/src/routers/migrateRouter.js b/src/routers/migrateRouter.js
new file mode 100644
index 0000000..8ee67be
--- /dev/null
+++ b/src/routers/migrateRouter.js
@@ -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 .*/
+
+//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;
diff --git a/src/schemas/user/migrationSchema.js b/src/schemas/user/migrationSchema.js
index 3cdad49..c17ed51 100644
--- a/src/schemas/user/migrationSchema.js
+++ b/src/schemas/user/migrationSchema.js
@@ -316,6 +316,12 @@ migrationSchema.statics.consumeByUsername = async function(ip, migration){
//Pull migration doc by case-insensitive username
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
await migrationDB.consume(ip, migration);
}
@@ -359,7 +365,7 @@ migrationSchema.methods.consume = async function(ip, migration){
await newUser.tattooIPRecord(ip);
//if we submitted an email
- if(this.email != null && this.email != ''){
+ if(this.email != null && validator.isEmail(this.email)){
//Generate new email change request
const requestDB = await emailChangeModel.create({user: newUser._id, newEmail: this.email, ipHash: ip});
diff --git a/src/server.js b/src/server.js
index 43a1f57..336071c 100644
--- a/src/server.js
+++ b/src/server.js
@@ -58,6 +58,7 @@ const channelRouter = require('./routers/channelRouter');
const newChannelRouter = require('./routers/newChannelRouter');
const passwordResetRouter = require('./routers/passwordResetRouter');
const emailChangeRouter = require('./routers/emailChangeController');
+const migrateRouter = require('./routers/migrateRouter');
//Panel
const panelRouter = require('./routers/panelRouter');
//Popup
@@ -161,6 +162,7 @@ app.use('/c', channelRouter);
app.use('/newChannel', newChannelRouter);
app.use('/passwordReset', passwordResetRouter);
app.use('/emailChange', emailChangeRouter);
+app.use('/migrate', migrateRouter);
//Panel
app.use('/panel', panelRouter);
//tooltip
diff --git a/src/utils/loggerUtils.js b/src/utils/loggerUtils.js
index eb51977..12e5ad8 100644
--- a/src/utils/loggerUtils.js
+++ b/src/utils/loggerUtils.js
@@ -173,10 +173,10 @@ module.exports.errorMiddleware = function(err, req, res, next){
* @param {Error} err - error to dump to file
* @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{
//Crash directory
- const dir = "./log/crash/"
+ const dir = `./log/crash/${subDir}`
//Double check crash folder exists
try{
diff --git a/src/utils/mailUtils.js b/src/utils/mailUtils.js
index ec825a1..fce92ca 100644
--- a/src/utils/mailUtils.js
+++ b/src/utils/mailUtils.js
@@ -19,6 +19,11 @@ const config = require('../../config.json');
//NPM imports
const nodeMailer = require("nodemailer");
+const validator = require('validator');
+
+//local imports
+const loggerUtils = require('./loggerUtils');
+
//Setup mail transport
/**
@@ -43,28 +48,38 @@ const transporter = nodeMailer.createTransport({
* @returns {Object} Sent mail info
*/
module.exports.mailem = async function(to, subject, body, htmlBody = false){
- //Create mail object
- const mailObj = {
- from: `"Tokebot🤖💨"<${config.mail.address}>`,
- to,
- subject
- };
+ try{
+ //If we have a bad email address
+ if(!validator.isEmail(to)){
+ //fuck off
+ return;
+ }
- //If we're sending HTML
- if(htmlBody){
- //set body as html
- mailObj.html = body;
- //If we're sending plaintext
- }else{
- //Set body as plaintext
- mailObj.text = body
+ //Create mail object
+ const mailObj = {
+ from: `"Tokebot🤖💨"<${config.mail.address}>`,
+ to,
+ subject
+ };
+
+ //If we're sending HTML
+ if(htmlBody){
+ //set body as html
+ mailObj.html = body;
+ //If we're sending plaintext
+ }else{
+ //Set body as plaintext
+ mailObj.text = body
+ }
+
+ //Send mail based on mail object
+ const sentMail = await transporter.sendMail(mailObj);
+
+ //return the mail info
+ return sentMail;
+ }catch(err){
+ loggerUtils.dumpError(err, new Date(), 'mail/');
}
-
- //Send mail based on mail object
- const sentMail = await transporter.sendMail(mailObj);
-
- //return the mail info
- return sentMail;
}
/**
diff --git a/src/views/migrate.ejs b/src/views/migrate.ejs
new file mode 100644
index 0000000..3744efb
--- /dev/null
+++ b/src/views/migrate.ejs
@@ -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 . %>
+
+
+
+
+ <%- include('partial/styles', {instance, user}); %>
+ <%- include('partial/csrfToken', {csrfToken}); %>
+
+
+ <%= instance %> - Account Migration
+
+
+ <%- include('partial/navbar', {user}); %>
+
+
+
+
diff --git a/www/css/migrate.css b/www/css/migrate.css
new file mode 100644
index 0000000..02ef485
--- /dev/null
+++ b/www/css/migrate.css
@@ -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 .*/
+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;
+}
\ No newline at end of file
diff --git a/www/js/migrate.js b/www/js/migrate.js
new file mode 100644
index 0000000..ed4a1ee
--- /dev/null
+++ b/www/js/migrate.js
@@ -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 .*/
+
+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(`
Confirmation password does not match!
`);
+ 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();
\ No newline at end of file