Finished up with email change backend. Just need to make a prompt on the profile page for our AJAX call.
This commit is contained in:
parent
4a865e8aa8
commit
a51152a89d
|
|
@ -21,7 +21,7 @@ const config = require('../../config.json');
|
|||
const csrfUtils = require('../utils/csrfUtils');
|
||||
|
||||
//register page functions
|
||||
module.exports = async function(req, res, next){
|
||||
module.exports = async function(req, res){
|
||||
//set status
|
||||
res.status(404);
|
||||
|
||||
|
|
|
|||
96
src/controllers/api/account/emailChangeRequestController.js
Normal file
96
src/controllers/api/account/emailChangeRequestController.js
Normal file
|
|
@ -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 <https://www.gnu.org/licenses/>.*/
|
||||
|
||||
//Config
|
||||
const config = require('../../../../config.json');
|
||||
|
||||
//NPM Imports
|
||||
const {validationResult, matchedData} = require('express-validator');
|
||||
|
||||
//local imports
|
||||
const {userModel} = require('../../../schemas/user/userSchema');
|
||||
const emailChangeModel = require('../../../schemas/user/emailChangeSchema');
|
||||
const mailUtils = require('../../../utils/mailUtils');
|
||||
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
||||
|
||||
//Gateway for generating request token and having it emailed to the user
|
||||
module.exports.post = async function(req, res){
|
||||
try{
|
||||
//Check for validation errors
|
||||
const validResult = validationResult(req);
|
||||
|
||||
//If there are none
|
||||
if(validResult.isEmpty()){
|
||||
//Get sanatized/validated data
|
||||
const {email, pass} = matchedData(req);
|
||||
|
||||
//Check to make sure the user is logged in
|
||||
if(req.session.user == null){
|
||||
errorHandler(res, "Invalid user!");
|
||||
}
|
||||
|
||||
//Authenticate and find user model from DB
|
||||
const userDB = await userModel.authenticate(req.session.user.user, pass, "Bad password.");
|
||||
|
||||
//If we have an invalid user
|
||||
if(userDB == null){
|
||||
errorHandler(res, "Invalid user!");
|
||||
}
|
||||
|
||||
if(userDB.email == email){
|
||||
errorHandler(res, "Cannot set current email!");
|
||||
}
|
||||
|
||||
//Generate the password reset link
|
||||
const requestDB = await emailChangeModel.create({user: userDB._id, newEmail: email, ipHash: req.ip});
|
||||
|
||||
//Don't wait on mailer to get back to the browser
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
//Clean our hands of the operation
|
||||
return;
|
||||
}else{
|
||||
res.status(400);
|
||||
return res.send({errors: validResult.array()});
|
||||
}
|
||||
}catch(err){
|
||||
return exceptionHandler(res, err);
|
||||
}
|
||||
}
|
||||
57
src/controllers/emailChangeController.js
Normal file
57
src/controllers/emailChangeController.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*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');
|
||||
|
||||
//NPM Imports
|
||||
const {validationResult, matchedData} = require('express-validator');
|
||||
|
||||
//local imports
|
||||
const emailChangeModel = require('../schemas/user/emailChangeSchema');
|
||||
const csrfUtils = require('../utils/csrfUtils');
|
||||
|
||||
//gateway for resetting password
|
||||
module.exports.get = async function(req, res){
|
||||
try{
|
||||
//Check for validation errors
|
||||
const validResult = validationResult(req);
|
||||
|
||||
//If there are none
|
||||
if(validResult.isEmpty()){
|
||||
//Get sanatized/validated data
|
||||
const {token} = matchedData(req);
|
||||
|
||||
//Consume the password reset token using given input
|
||||
const requestDB = await emailChangeModel.findOne({token});
|
||||
|
||||
//If we have an invalid request
|
||||
if(requestDB == null){
|
||||
return res.render('emailChange', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false});
|
||||
}
|
||||
|
||||
//Speak of our success (don't wait for the emails to be sent)
|
||||
res.render('emailChange', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: true});
|
||||
|
||||
//Consume the request
|
||||
await requestDB.consume();
|
||||
}else{
|
||||
return res.render('emailChange', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false});
|
||||
}
|
||||
}catch(err){
|
||||
return res.render('emailChange', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false});
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ const updateController = require("../../controllers/api/account/updateController
|
|||
const rankEnumController = require("../../controllers/api/account/rankEnumController");
|
||||
const passwordResetRequestController = require("../../controllers/api/account/passwordResetRequestController");
|
||||
const passwordResetController = require("../../controllers/api/account/passwordResetController");
|
||||
const emailChangeRequestController = require('../../controllers/api/account/emailChangeRequestController');
|
||||
const deleteController = require("../../controllers/api/account/deleteController");
|
||||
|
||||
//globals
|
||||
|
|
@ -55,6 +56,8 @@ router.get('/rankEnum', rankEnumController.get);
|
|||
router.post('/passwordResetRequest', accountValidator.user(), passwordResetRequestController.post);
|
||||
//password reset
|
||||
router.post('/passwordReset', accountValidator.securityToken(), accountValidator.securePass(), accountValidator.pass('confirmPass'), passwordResetController.post);
|
||||
//email change request
|
||||
router.post('/emailChangeRequest', accountValidator.email(), accountValidator.pass(), emailChangeRequestController.post);
|
||||
//account deletion
|
||||
router.post('/delete', accountValidator.pass(), deleteController.post);
|
||||
|
||||
|
|
|
|||
31
src/routers/emailChangeController.js
Normal file
31
src/routers/emailChangeController.js
Normal 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/>.*/
|
||||
|
||||
//npm imports
|
||||
const { Router } = require('express');
|
||||
|
||||
|
||||
//local imports
|
||||
const accountValidator = require('../validators/accountValidator');
|
||||
const emailChangeController = require("../controllers/emailChangeController");
|
||||
|
||||
//globals
|
||||
const router = Router();
|
||||
|
||||
//routing functions
|
||||
router.get('/', accountValidator.securityToken(), emailChangeController.get);
|
||||
|
||||
module.exports = router;
|
||||
155
src/schemas/user/emailChangeSchema.js
Normal file
155
src/schemas/user/emailChangeSchema.js
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/*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/>.*/
|
||||
|
||||
//You could make an argument for making this part of the userModel
|
||||
//However, this is so rarely used the preformance benefits aren't worth the extra clutter
|
||||
|
||||
//Config
|
||||
const config = require('../../../config.json');
|
||||
|
||||
//Node Imports
|
||||
const crypto = require("node:crypto");
|
||||
|
||||
//NPM Imports
|
||||
const {mongoose} = require('mongoose');
|
||||
|
||||
//Local Imports
|
||||
const hashUtil = require('../../utils/hashUtils');
|
||||
const mailUtils = require('../../utils/mailUtils');
|
||||
|
||||
const daysToExpire = 7;
|
||||
|
||||
const emailChangeSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: mongoose.SchemaTypes.ObjectID,
|
||||
ref: "user",
|
||||
required: true
|
||||
},
|
||||
newEmail: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true
|
||||
},
|
||||
token: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
//Use a cryptographically secure algorythm to create a random hex string from 16 bytes as our change/cancel token
|
||||
default: ()=>{return crypto.randomBytes(16).toString('hex')}
|
||||
},
|
||||
ipHash: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true
|
||||
},
|
||||
date: {
|
||||
type: mongoose.SchemaTypes.Date,
|
||||
required: true,
|
||||
default: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//Presave function
|
||||
emailChangeSchema.pre('save', async function (next){
|
||||
//If we're saving an ip
|
||||
if(this.isModified('ipHash')){
|
||||
//Hash that sunnuvabitch
|
||||
this.ipHash = hashUtil.hashIP(this.ipHash);
|
||||
}
|
||||
|
||||
if(this.isModified('user')){
|
||||
//Delete previous requests for the given user
|
||||
const requests = await this.model().deleteMany({user: this.user._id});
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
//statics
|
||||
emailChangeSchema.statics.processExpiredRequests = async function(){
|
||||
//Pull all requests from the DB
|
||||
const requestDB = await this.find({});
|
||||
|
||||
//Fire em all off at once without waiting for the last one to complete since we don't fuckin' need to
|
||||
requestDB.forEach(async (request) => {
|
||||
//If the request hasn't been processed and it's been expired
|
||||
if(request.getDaysUntilExpiration() <= 0){
|
||||
//Delete the request
|
||||
await this.deleteOne({_id: request._id});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//methods
|
||||
emailChangeSchema.methods.consume = async function(){
|
||||
//Populate the user reference
|
||||
await this.populate('user');
|
||||
|
||||
const oldMail = this.user.email;
|
||||
|
||||
//Set the new email
|
||||
this.user.email = this.newEmail;
|
||||
|
||||
//Save the user
|
||||
await this.user.save();
|
||||
|
||||
//Delete the request token now that it has been consumed
|
||||
await this.deleteOne();
|
||||
|
||||
//If we had a previous email address
|
||||
if(oldMail != null && oldMail != ''){
|
||||
//Notify it of the change
|
||||
await mailUtils.mailem(
|
||||
oldMail,
|
||||
`Email Change Notification - ${this.user.user}`,
|
||||
`<h1>Email Change Notification</h1>
|
||||
<p>The ${config.instanceName} account '${this.user.user}' is no longer associated with this email address.<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
|
||||
);
|
||||
}
|
||||
|
||||
//Notify the new inbox of the change
|
||||
await mailUtils.mailem(
|
||||
this.newEmail,
|
||||
`Email Change Notification - ${this.user.user}`,
|
||||
`<h1>Email Change Notification</h1>
|
||||
<p>The ${config.instanceName} account '${this.user.user}' is now associated with this email address.<br>
|
||||
<sup>If you received this email without request, you should <strong>immediately</strong> check who's been inside your inbox! -Tokebot</sup>`,
|
||||
true
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
emailChangeSchema.methods.getChangeURL = function(){
|
||||
//Check for default port based on protocol
|
||||
if((config.protocol == 'http' && config.port == 80) || (config.protocol == 'https' && config.port == 443)){
|
||||
//Return path
|
||||
return `${config.protocol}://${config.domain}/emailChange?token=${this.token}`;
|
||||
}else{
|
||||
//Return path
|
||||
return `${config.protocol}://${config.domain}:${config.port}/emailChange?token=${this.token}`;
|
||||
}
|
||||
}
|
||||
|
||||
emailChangeSchema.methods.getDaysUntilExpiration = function(){
|
||||
//Get request date
|
||||
const expirationDate = new Date(this.date);
|
||||
//Get expiration days and calculate expiration date
|
||||
expirationDate.setDate(expirationDate.getDate() + daysToExpire);
|
||||
//Calculate and return days until request expiration
|
||||
return ((expirationDate - new Date()) / (1000 * 60 * 60 * 24)).toFixed(1);
|
||||
}
|
||||
|
||||
module.exports = mongoose.model("emailChange", emailChangeSchema);
|
||||
|
|
@ -240,7 +240,7 @@ userSchema.statics.register = async function(userObj, ip){
|
|||
}
|
||||
}
|
||||
|
||||
userSchema.statics.authenticate = async function(user, pass){
|
||||
userSchema.statics.authenticate = async function(user, pass, failLine = "Bad Username or Password."){
|
||||
//check for missing pass
|
||||
if(!user || !pass){
|
||||
throw new Error("Missing user/pass.");
|
||||
|
|
@ -264,7 +264,7 @@ userSchema.statics.authenticate = async function(user, pass){
|
|||
|
||||
//standardize bad login response so it's unknowin which is bad for security reasons.
|
||||
function badLogin(){
|
||||
throw new Error("Bad Username or Password.");
|
||||
throw new Error(failLine);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ const adminPanelRouter = require('./routers/adminPanelRouter');
|
|||
const channelRouter = require('./routers/channelRouter');
|
||||
const newChannelRouter = require('./routers/newChannelRouter');
|
||||
const passwordResetRouter = require('./routers/passwordResetRouter');
|
||||
const emailChangeRouter = require('./routers/emailChangeController');
|
||||
//Panel
|
||||
const panelRouter = require('./routers/panelRouter');
|
||||
//Popup
|
||||
|
|
@ -119,6 +120,7 @@ app.use('/adminPanel', adminPanelRouter);
|
|||
app.use('/c', channelRouter);
|
||||
app.use('/newChannel', newChannelRouter);
|
||||
app.use('/passwordReset', passwordResetRouter);
|
||||
app.use('/emailChange', emailChangeRouter);
|
||||
//Panel
|
||||
app.use('/panel', panelRouter);
|
||||
//Popup
|
||||
|
|
|
|||
|
|
@ -16,8 +16,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
|
||||
//At some point this will be a bit more advanced, right now it's just a placeholder :P
|
||||
module.exports.errorHandler = function(res, msg, type = "Generic", status = 400){
|
||||
//Some controllers do things after sending headers, for those, we should remain silent
|
||||
if(!res.headersSent){
|
||||
res.status(status);
|
||||
return res.send({errors: [{type, msg, date: new Date()}]});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.exceptionHandler = function(res, err){
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ const cron = require('node-cron');
|
|||
const {userModel} = require('../schemas/user/userSchema');
|
||||
const userBanModel = require('../schemas/user/userBanSchema');
|
||||
const passwordResetModel = require('../schemas/user/passwordResetSchema');
|
||||
const emailChangeModel = require('../schemas/user/emailChangeSchema');
|
||||
const channelModel = require('../schemas/channel/channelSchema');
|
||||
const sessionUtils = require('./sessionUtils');
|
||||
const { email } = require('../validators/accountValidator');
|
||||
|
||||
module.exports.schedule = function(){
|
||||
//Process hashed IP Records that haven't been recorded in a week or more
|
||||
|
|
@ -35,6 +37,8 @@ module.exports.schedule = function(){
|
|||
cron.schedule('0 0 * * *', ()=>{sessionUtils.processExpiredAttempts()},{scheduled: true, timezone: "UTC"});
|
||||
//Process expired password reset requests every night at midnight
|
||||
cron.schedule('0 0 * * *', ()=>{passwordResetModel.processExpiredRequests()},{scheduled: true, timezone: "UTC"});
|
||||
//Process expired email change requests every night at midnight
|
||||
cron.schedule('0 0 * * *', ()=>{emailChangeModel.processExpiredRequests()},{scheduled: true, timezone: "UTC"});
|
||||
}
|
||||
|
||||
module.exports.kickoff = function(){
|
||||
|
|
@ -46,7 +50,8 @@ module.exports.kickoff = function(){
|
|||
channelModel.processExpiredBans();
|
||||
//Process expired password reset requests that may have expired since last restart
|
||||
passwordResetModel.processExpiredRequests();
|
||||
|
||||
//Process expired email change requests that may have expired since last restart
|
||||
emailChangeModel.processExpiredRequests();
|
||||
|
||||
//Schedule jobs
|
||||
module.exports.schedule();
|
||||
|
|
|
|||
|
|
@ -23,9 +23,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
|||
</head>
|
||||
<body>
|
||||
<%- include('partial/navbar', {user}); %>
|
||||
<div class="error-body">
|
||||
<h1>404</h1>
|
||||
<h3>Congratulations, you've found a dead link!</h3>
|
||||
<h2>Congratulations, you've found a dead link!</h2>
|
||||
<img src="https://web.archive.org/web/20091027105418/http://geocities.com/cd_franks_elementary/graphics/cdcongratulationsspinning.gif">
|
||||
<a href="/">Return to Home</a>
|
||||
</div>
|
||||
</body>
|
||||
<footer>
|
||||
<%- include('partial/scripts', {user}); %>
|
||||
|
|
|
|||
34
src/views/emailChange.ejs
Normal file
34
src/views/emailChange.ejs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<%# 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}); %>
|
||||
<title><%= instance %></title>
|
||||
</head>
|
||||
<body>
|
||||
<%- include('partial/navbar', {user}); %>
|
||||
<% if(valid){ %>
|
||||
<h1>Email Change Successful!</h1>
|
||||
<% }else{ %>
|
||||
<h1>Invalid Email Change Token!</h1>
|
||||
<% } %>
|
||||
</body>
|
||||
<footer>
|
||||
<%- include('partial/scripts', {user}); %>
|
||||
</footer>
|
||||
</html>
|
||||
|
|
@ -13,11 +13,34 @@ 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/>.*/
|
||||
h1, h3{
|
||||
div.error-body{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.error-body h1,.error-body h2,.error-body a{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img{
|
||||
width: 50%;
|
||||
margin: 0 auto;
|
||||
.error-body h2,.error-body a{
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
.error-body h1{
|
||||
font-size: 3em;
|
||||
margin-top: 5%;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.error-body h2{
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.error-body a{
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.error-body img{
|
||||
width: 75%;
|
||||
margin: 3em auto;
|
||||
}
|
||||
|
|
@ -534,6 +534,25 @@ class canopyAjaxUtils{
|
|||
}
|
||||
}
|
||||
|
||||
async requestEmailChange(email, pass){
|
||||
const response = await fetch(`/api/account/emailChangeRequest`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-csrf-token": utils.ajax.getCSRFToken()
|
||||
},
|
||||
body: JSON.stringify({email, pass})
|
||||
});
|
||||
|
||||
//If we received a successful response
|
||||
if(response.status == 200){
|
||||
const popup = new canopyUXUtils.popup("A confirmation link has been sent to the new email address.");
|
||||
//Otherwise
|
||||
}else{
|
||||
utils.ux.displayResponseError(await response.json());
|
||||
}
|
||||
}
|
||||
|
||||
//Channel
|
||||
async newChannel(name, description, thumbnail, verification){
|
||||
var response = await fetch(`/api/channel/register`,{
|
||||
|
|
|
|||
Loading…
Reference in a new issue