diff --git a/src/controllers/api/account/migrationController.js b/src/controllers/api/account/migrationController.js
new file mode 100644
index 0000000..61762ed
--- /dev/null
+++ b/src/controllers/api/account/migrationController.js
@@ -0,0 +1,96 @@
+/*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
'), 'unauthorized');
+ }
+
+ //Find and consume migration document
+ await migrationModel.consumeByUsername(ip, migration);
+
+ //tell of our success
+ return res.sendStatus(200);
+ }else{
+ res.status(400);
+ return res.send({errors: validResult.array()});
+ }
+ }catch(err){
+ return exceptionHandler(res, err);
+ }
+}
\ No newline at end of file
diff --git a/src/routers/api/accountRouter.js b/src/routers/api/accountRouter.js
index 95aafd5..5975453 100644
--- a/src/routers/api/accountRouter.js
+++ b/src/routers/api/accountRouter.js
@@ -22,6 +22,7 @@ const accountValidator = require("../../validators/accountValidator");
const loginController = require("../../controllers/api/account/loginController");
const logoutController = require("../../controllers/api/account/logoutController");
const registerController = require("../../controllers/api/account/registerController");
+const migrationController = require("../../controllers/api/account/migrationController");
const updateController = require("../../controllers/api/account/updateController");
const rankEnumController = require("../../controllers/api/account/rankEnumController");
const passwordResetRequestController = require("../../controllers/api/account/passwordResetRequestController");
@@ -38,18 +39,32 @@ router.post('/login', accountValidator.user(), accountValidator.pass(), loginCon
//logout
router.post('/logout', logoutController.post);
//register
-router.post('/register', accountValidator.user(),
+router.post('/register',
+ accountValidator.user(),
accountValidator.securePass(),
accountValidator.pass('passConfirm'),
- accountValidator.email(), registerController.post);
+ accountValidator.email(),
+ registerController.post);
+
+//migrate legacy profile
+router.post('/migrate',
+ accountValidator.user(),
+ accountValidator.pass('oldPass'),
+ accountValidator.securePass('newPass'),
+ accountValidator.pass('passConfirm'),
+ migrationController.post);
+
//update profile
-router.post('/update', accountValidator.img(),
+router.post('/update',
+ accountValidator.img(),
accountValidator.bio(),
accountValidator.signature(),
accountValidator.pronouns(),
accountValidator.pass('passChange.oldPass'),
accountValidator.securePass('passChange.newPass'),
- accountValidator.pass('passChange.confirmPass'), updateController.post);
+ accountValidator.pass('passChange.confirmPass'),
+ updateController.post);
+
//rankEnum
//This might seem silly, but it allows us to cleanly get the current rank list to compare against, without storing it in multiple places
router.get('/rankEnum', rankEnumController.get);
diff --git a/src/schemas/user/migrationSchema.js b/src/schemas/user/migrationSchema.js
index 2856094..1cc3b1a 100644
--- a/src/schemas/user/migrationSchema.js
+++ b/src/schemas/user/migrationSchema.js
@@ -25,7 +25,11 @@ const config = require('../../../config.json');
const {userModel} = require('../user/userSchema');
const permissionModel = require('../permissionSchema');
const tokeModel = require('../tokebot/tokeSchema');
+const statModel = require('../statSchema');
+const emailChangeModel = require('../user/emailChangeSchema');
const loggerUtils = require('../../utils/loggerUtils');
+const hashUtils = require('../../utils/hashUtils');
+const mailUtils = require('../../utils/mailUtils');
/**
* DB Schema for documents representing legacy fore.st migration data for a single user account
@@ -307,15 +311,62 @@ migrationSchema.statics.buildMigrationCache = async function(){
}
}
+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')});
+
+ //Wait on the miration DB token to be consumed
+ await migrationDB.consume(ip, migration);
+}
+
//Methods
/**
* Consumes a migration profile and creates a new, modern canopy profile from the original.
* @param {String} oldPass - Original password to authenticate migration against
* @param {String} newPass - New password to re-hash with modern hashing algo
- * @param {String} confirmPass - Confirmation for the new pass
+ * @param {String} passConfirm - Confirmation for the new pass
*/
-migrationSchema.methods.consume = async function(oldPass, newPass, confirmPass){
+migrationSchema.methods.consume = async function(ip, migration){
+ //If we where handed a bad password
+ if(!hashUtils.compareLegacyPassword(migration.oldPass, this.pass)){
+ //Complain
+ throw loggerUtils.exceptionSmith("Incorrect username/password.", "migration");
+ }
+ //If we where handed a mismatched confirmation password
+ if(migration.newPass != migration.passConfirm){
+ //Complain
+ throw loggerUtils.exceptionSmith("New password does not match confirmation password.", "migration");
+ }
+
+ //Increment user count
+ const id = await statModel.incrementUserCount();
+
+ //Create new user from profile info
+ const newUser = await userModel.create({
+ id,
+ user: this.user,
+ pass: migration.newPass,
+ rank: permissionModel.rankEnum[this.rank],
+ bio: this.bio,
+ img: this.image,
+ date: this.date,
+ tokes: new Map([["Legacy Tokes", this.tokes]])
+ });
+
+ //Tattoo hashed IP use to migrate to the new user account
+ await newUser.tattooIPRecord(ip);
+
+ //if we submitted an email
+ if(this.email != null && this.email != ''){
+ //Generate new request
+ const requestDB = await emailChangeModel.create({user: newUser._id, newEmail: this.email, ipHash: ip});
+
+ //Send confirmation email
+ mailUtils.sendAddressVerification(requestDB, newUser, this.email, false, true);
+ }
+
+ await this.deleteOne();
}
module.exports = mongoose.model("migration", migrationSchema);
\ No newline at end of file
diff --git a/src/schemas/user/userSchema.js b/src/schemas/user/userSchema.js
index 003f2e6..bac2434 100644
--- a/src/schemas/user/userSchema.js
+++ b/src/schemas/user/userSchema.js
@@ -289,7 +289,7 @@ userSchema.statics.register = async function(userObj, ip){
if(email != null){
const requestDB = await emailChangeModel.create({user: newUser._id, newEmail: email, ipHash: ip});
- await mailUtil.sendAddressVerification(requestDB, newUser, email)
+ await mailUtil.sendAddressVerification(requestDB, newUser, email, true);
}
}
}else{
diff --git a/src/utils/csrfUtils.js b/src/utils/csrfUtils.js
index 5a898b3..457e48b 100644
--- a/src/utils/csrfUtils.js
+++ b/src/utils/csrfUtils.js
@@ -17,9 +17,6 @@ along with this program. If not, see
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.
a new ${config.instanceName} account '${userDB.user}' was created with this email address.
+ click here to confirm this change.
The ${config.instanceName} account '${userDB.user}' was successfully migrated to our fancy new codebase.
+ click here to confirm this change.
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.