Added 'altcha' captcha system for account and channel creation.
This commit is contained in:
parent
60801f0dc2
commit
e0f53df176
|
|
@ -2,6 +2,7 @@
|
||||||
"instanceName": "Canopy",
|
"instanceName": "Canopy",
|
||||||
"port": 8080,
|
"port": 8080,
|
||||||
"sessionSecret": "CHANGE_ME",
|
"sessionSecret": "CHANGE_ME",
|
||||||
|
"altchaSecret": "CHANGE_ME",
|
||||||
"db":{
|
"db":{
|
||||||
"address": "127.0.0.1",
|
"address": "127.0.0.1",
|
||||||
"port": "27017",
|
"port": "27017",
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
"version": "0.1",
|
"version": "0.1",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"altcha": "^1.0.7",
|
||||||
|
"altcha-lib": "^1.2.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
"connect-mongo": "^5.1.0",
|
"connect-mongo": "^5.1.0",
|
||||||
|
|
|
||||||
|
|
@ -14,20 +14,34 @@ GNU Affero General Public License for more details.
|
||||||
You should have received a copy of the GNU Affero General Public License
|
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/>.*/
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
|
//Config
|
||||||
|
const config = require('../../../../config.json');
|
||||||
|
|
||||||
//NPM Imports
|
//NPM Imports
|
||||||
const {validationResult, matchedData} = require('express-validator');
|
const {validationResult, matchedData} = require('express-validator');
|
||||||
|
|
||||||
//local imports
|
//local imports
|
||||||
const {userModel} = require('../../../schemas/userSchema');
|
const {userModel} = require('../../../schemas/userSchema');
|
||||||
const userBanModel = require('../../../schemas/userBanSchema');
|
const userBanModel = require('../../../schemas/userBanSchema');
|
||||||
|
const altchaUtils = require('../../../utils/altchaUtils');
|
||||||
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
||||||
|
|
||||||
module.exports.post = async function(req, res){
|
module.exports.post = async function(req, res){
|
||||||
try{
|
try{
|
||||||
|
//Check for validation errors
|
||||||
const validResult = validationResult(req);
|
const validResult = validationResult(req);
|
||||||
|
|
||||||
|
//If there are none
|
||||||
if(validResult.isEmpty()){
|
if(validResult.isEmpty()){
|
||||||
|
//Get sanatized/validated data
|
||||||
const user = matchedData(req);
|
const user = matchedData(req);
|
||||||
|
//Verify Altcha Payload
|
||||||
|
const verified = await altchaUtils.verify(req.body.verification);
|
||||||
|
|
||||||
|
//If altcha verification failed
|
||||||
|
if(!verified){
|
||||||
|
return errorHandler(res, 'Altcha verification failed, Please refresh the page!', 'unauthorized');
|
||||||
|
}
|
||||||
|
|
||||||
//Would prefer to stick this in userModel.statics.register() but we end up with circular dependencies >:(
|
//Would prefer to stick this in userModel.statics.register() but we end up with circular dependencies >:(
|
||||||
const nukedBans = await userBanModel.checkProcessedBans(user.user);
|
const nukedBans = await userBanModel.checkProcessedBans(user.user);
|
||||||
|
|
@ -42,9 +56,9 @@ module.exports.post = async function(req, res){
|
||||||
return res.sendStatus(200);
|
return res.sendStatus(200);
|
||||||
}else{
|
}else{
|
||||||
res.status(400);
|
res.status(400);
|
||||||
res.send({errors: validResult.array()})
|
return res.send({errors: validResult.array()});
|
||||||
}
|
}
|
||||||
}catch(err){
|
}catch(err){
|
||||||
exceptionHandler(res, err);
|
return exceptionHandler(res, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ const {validationResult, matchedData} = require('express-validator');
|
||||||
//local imports
|
//local imports
|
||||||
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
||||||
const {userModel} = require('../../../schemas/userSchema');
|
const {userModel} = require('../../../schemas/userSchema');
|
||||||
|
const altchaUtils = require('../../../utils/altchaUtils');
|
||||||
const channelModel = require('../../../schemas/channel/channelSchema');
|
const channelModel = require('../../../schemas/channel/channelSchema');
|
||||||
|
|
||||||
//api account functions
|
//api account functions
|
||||||
|
|
@ -32,6 +33,15 @@ module.exports.post = async function(req, res){
|
||||||
if(validResult.isEmpty()){
|
if(validResult.isEmpty()){
|
||||||
//Set channel object from sanatized/validated data, and get user document from session data
|
//Set channel object from sanatized/validated data, and get user document from session data
|
||||||
const channel = matchedData(req);
|
const channel = matchedData(req);
|
||||||
|
//Verify Altcha Payload
|
||||||
|
const verified = await altchaUtils.verify(req.body.verification);
|
||||||
|
|
||||||
|
//If altcha verification failed
|
||||||
|
if(!verified){
|
||||||
|
return errorHandler(res, 'Altcha verification failed, Please refresh the page!', 'unauthorized');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Find current user
|
||||||
const userDB = await userModel.findOne({user: req.session.user.user});
|
const userDB = await userModel.findOne({user: req.session.user.user});
|
||||||
|
|
||||||
//register new channel with requesting user as owner
|
//register new channel with requesting user as owner
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,17 @@ GNU Affero General Public License for more details.
|
||||||
You should have received a copy of the GNU Affero General Public License
|
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/>.*/
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
|
//Local Imports
|
||||||
|
const altchaUtils = require('../utils/altchaUtils');
|
||||||
|
|
||||||
//Config
|
//Config
|
||||||
const config = require('../../config.json');
|
const config = require('../../config.json');
|
||||||
|
|
||||||
//root index functions
|
//root index functions
|
||||||
module.exports.get = async function(req, res){
|
module.exports.get = async function(req, res){
|
||||||
res.render('newChannel', {instance: config.instanceName, user: req.session.user});
|
//Generate captcha
|
||||||
|
const challenge = await altchaUtils.genCaptcha();
|
||||||
|
|
||||||
|
//render the page
|
||||||
|
return res.render('newChannel', {instance: config.instanceName, user: req.session.user, challenge});
|
||||||
}
|
}
|
||||||
|
|
@ -17,7 +17,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
//Config
|
//Config
|
||||||
const config = require('../../config.json');
|
const config = require('../../config.json');
|
||||||
|
|
||||||
|
//Local Imports
|
||||||
|
const altchaUtils = require('../utils/altchaUtils');
|
||||||
|
|
||||||
//register page functions
|
//register page functions
|
||||||
module.exports.get = function(req, res){
|
module.exports.get = async function(req, res){
|
||||||
res.render('register', {instance: config.instanceName, user: req.session.user});
|
//Generate captcha
|
||||||
|
const challenge = await altchaUtils.genCaptcha();
|
||||||
|
|
||||||
|
//Render page
|
||||||
|
return res.render('register', {instance: config.instanceName, user: req.session.user, challenge});
|
||||||
}
|
}
|
||||||
|
|
@ -34,8 +34,9 @@ router.post('/login', accountValidator.user(), accountValidator.pass(), loginCon
|
||||||
|
|
||||||
router.get('/logout', logoutController.get);
|
router.get('/logout', logoutController.get);
|
||||||
|
|
||||||
|
|
||||||
router.post('/register', accountValidator.user(),
|
router.post('/register', accountValidator.user(),
|
||||||
accountValidator.pass(),
|
accountValidator.securePass(),
|
||||||
accountValidator.pass('passConfirm'),
|
accountValidator.pass('passConfirm'),
|
||||||
accountValidator.email(), registerController.post);
|
accountValidator.email(), registerController.post);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,9 @@ const path = require('path');
|
||||||
const mongoStore = require('connect-mongo');
|
const mongoStore = require('connect-mongo');
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
//Define global crypto variable for altcha
|
||||||
|
globalThis.crypto = require('node:crypto').webcrypto;
|
||||||
|
|
||||||
//Define Local Imports
|
//Define Local Imports
|
||||||
const channelManager = require('./app/channel/channelManager');
|
const channelManager = require('./app/channel/channelManager');
|
||||||
const scheduler = require('./utils/scheduler');
|
const scheduler = require('./utils/scheduler');
|
||||||
|
|
@ -80,6 +83,7 @@ app.set('views', __dirname + '/views');
|
||||||
//Middlware
|
//Middlware
|
||||||
//Enable Express
|
//Enable Express
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
//app.use(express.urlencoded());
|
||||||
|
|
||||||
//Enable Express-Sessions
|
//Enable Express-Sessions
|
||||||
app.use(sessionMiddleware);
|
app.use(sessionMiddleware);
|
||||||
|
|
@ -104,12 +108,11 @@ app.use('/tooltip', tooltipRouter);
|
||||||
//Bot-Ready
|
//Bot-Ready
|
||||||
app.use('/api', apiRouter);
|
app.use('/api', apiRouter);
|
||||||
|
|
||||||
//3rd-Party Browser-Side Libraries
|
|
||||||
app.use('/lib/bootstrap-icons',express.static(path.join(__dirname, '../node_modules/bootstrap-icons')));
|
|
||||||
app.use('/lib/socket.io',express.static(path.join(__dirname, '../node_modules/socket.io/client-dist')));
|
|
||||||
app.use('/lib/validator',express.static(path.join(__dirname, '../node_modules/validator')));
|
|
||||||
|
|
||||||
//Static File Server
|
//Static File Server
|
||||||
|
//Serve bootstrap icons
|
||||||
|
app.use('/lib/bootstrap-icons',express.static(path.join(__dirname, '../node_modules/bootstrap-icons')));
|
||||||
|
app.use('/lib/altcha',express.static(path.join(__dirname, '../node_modules/altcha/dist_external')));
|
||||||
|
//Server public 'www' folder
|
||||||
app.use(express.static(path.join(__dirname, '../www')));
|
app.use(express.static(path.join(__dirname, '../www')));
|
||||||
|
|
||||||
//Increment launch counter
|
//Increment launch counter
|
||||||
|
|
|
||||||
61
src/utils/altchaUtils.js
Normal file
61
src/utils/altchaUtils.js
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*Canopy - The next generation of stoner streaming software
|
||||||
|
Copyright (C) 2024 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 { createChallenge, verifySolution } = require('altcha-lib');
|
||||||
|
|
||||||
|
//Create empty array to hold cache of spent payloades to protect against replay attacks
|
||||||
|
const spent = [];
|
||||||
|
//Captcha lifetime in minutes
|
||||||
|
const lifetime = 2;
|
||||||
|
|
||||||
|
module.exports.genCaptcha = async function(){
|
||||||
|
//Set altcha expiration date
|
||||||
|
const expiration = new Date();
|
||||||
|
|
||||||
|
//Add four minutes
|
||||||
|
expiration.setMinutes(expiration.getMinutes() + lifetime);
|
||||||
|
|
||||||
|
//Generate Altcha Challenge
|
||||||
|
return await createChallenge({
|
||||||
|
hmacKey: config.altchaSecret,
|
||||||
|
maxNumber: 200000,
|
||||||
|
expires: expiration
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.verify = async function(payload){
|
||||||
|
//If we already checked this payload
|
||||||
|
if(spent.indexOf(payload) != -1){
|
||||||
|
//Fuck off and die
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get length before pushing payload to get index of next item
|
||||||
|
const payloadIndex = spent.length;
|
||||||
|
|
||||||
|
//Add payload to cache of spent payloades
|
||||||
|
spent.push(payload);
|
||||||
|
|
||||||
|
//Set timeout to splice out the used payload after its expired so we're not filling RAM with expired payloads that aren't going to resolve true anyways
|
||||||
|
setTimeout(() => {spent.splice(payloadIndex,1);}, lifetime * 60 * 1000);
|
||||||
|
|
||||||
|
//Return verification results
|
||||||
|
return await verifySolution(payload, config.altchaSecret);
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ const { check, body, checkSchema, checkExact} = require('express-validator');
|
||||||
const {isRank} = require('./permissionsValidator');
|
const {isRank} = require('./permissionsValidator');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
user: (field = 'user') => check(field).escape().trim().isLength({min: 1, max: 22}),
|
user: (field = 'user') => check(field).escape().trim().isAlphanumeric().isLength({min: 1, max: 22}),
|
||||||
|
|
||||||
//Password security requirements may change over time, therefore we should only validate against strongPassword() when creating new accounts
|
//Password security requirements may change over time, therefore we should only validate against strongPassword() when creating new accounts
|
||||||
//that way we don't break old ones upon change
|
//that way we don't break old ones upon change
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ const { check, body, checkSchema, checkExact} = require('express-validator');
|
||||||
const accountValidator = require('./accountValidator');
|
const accountValidator = require('./accountValidator');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: (field = 'name') => check(field).escape().trim().isLength({min: 1, max: 50}),
|
name: (field = 'name') => check(field).escape().trim().isAlphanumeric().isLength({min: 1, max: 50}),
|
||||||
|
|
||||||
description: (field = 'description') => body(field).escape().trim().isLength({min: 1, max: 1000}),
|
description: (field = 'description') => body(field).escape().trim().isLength({min: 1, max: 1000}),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.-->
|
||||||
</body>
|
</body>
|
||||||
<footer>
|
<footer>
|
||||||
<%- include('partial/scripts', {user}); %>
|
<%- include('partial/scripts', {user}); %>
|
||||||
<script src="/lib/socket.io/socket.io.min.js"></script>
|
<script src="/socket.io/socket.io.min.js"></script>
|
||||||
<script src="/js/channel/commandPreprocessor.js"></script>
|
<script src="/js/channel/commandPreprocessor.js"></script>
|
||||||
<script src="/js/channel/chatPostprocessor.js"></script>
|
<script src="/js/channel/chatPostprocessor.js"></script>
|
||||||
<script src="/js/channel/chat.js"></script>
|
<script src="/js/channel/chat.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -18,21 +18,25 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.-->
|
||||||
<head>
|
<head>
|
||||||
<%- include('partial/styles', {instance, user}); %>
|
<%- include('partial/styles', {instance, user}); %>
|
||||||
<link rel="stylesheet" type="text/css" href="/css/newChannel.css">
|
<link rel="stylesheet" type="text/css" href="/css/newChannel.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/lib/altcha/altcha.css">
|
||||||
<title><%= instance %> - New Channel</title>
|
<title><%= instance %> - New Channel</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%- include('partial/navbar', {user}); %>
|
<%- include('partial/navbar', {user}); %>
|
||||||
<form action="javascript:">
|
<form action="javascript:">
|
||||||
<label>Channel Name:</label>
|
<label>Channel Name:</label>
|
||||||
<input id="register-channel-name" placeholder="Required">
|
<input class="register-prompt" id="register-channel-name" placeholder="Required">
|
||||||
<label>Description:</label>
|
<label>Description:</label>
|
||||||
<input id="register-description" placeholder="Required">
|
<input class="register-prompt" id="register-description" placeholder="Required">
|
||||||
<label>Thumbnail:</label>
|
<label>Thumbnail:</label>
|
||||||
<input id="register-thumbnail" placeholder="Required">
|
<input class="register-prompt" id="register-thumbnail" placeholder="Required">
|
||||||
|
<altcha-widget floating challengejson="<%= JSON.stringify(challenge) %>"></altcha-widget>
|
||||||
|
<button id="register-button" class='positive-button'>Register</button>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
<footer>
|
<footer>
|
||||||
<%- include('partial/scripts', {user}); %>
|
<%- include('partial/scripts', {user}); %>
|
||||||
<script src="js/newChannel.js"></script>
|
<script src="js/newChannel.js"></script>
|
||||||
|
<script src="/lib/altcha/altcha.js" type="module"></script>
|
||||||
</footer>
|
</footer>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -19,23 +19,27 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.-->
|
||||||
<head>
|
<head>
|
||||||
<%- include('partial/styles', {instance, user}); %>
|
<%- include('partial/styles', {instance, user}); %>
|
||||||
<link rel="stylesheet" type="text/css" href="/css/register.css">
|
<link rel="stylesheet" type="text/css" href="/css/register.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/lib/altcha/altcha.css">
|
||||||
<title><%= instance %> - Account Registration</title>
|
<title><%= instance %> - Account Registration</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%- include('partial/navbar', {user}); %>
|
<%- include('partial/navbar', {user}); %>
|
||||||
<form action="javascript:">
|
<form action="javascript:">
|
||||||
<label>Username:</label>
|
<label>Username:</label>
|
||||||
<input id="register-username" placeholder="Required">
|
<input class="register-prompt" id="register-username" placeholder="Required">
|
||||||
<label>Password:</label>
|
<label>Password:</label>
|
||||||
<input id="register-password" placeholder="Required" type="password">
|
<input class="register-prompt" id="register-password" placeholder="Required" type="password">
|
||||||
<label>Confirm Password:</label>
|
<label>Confirm Password:</label>
|
||||||
<input id="register-password-confirm" placeholder="Required" type="password">
|
<input class="register-prompt" id="register-password-confirm" placeholder="Required" type="password">
|
||||||
<label>Account Recovery Email:</label>
|
<label>Account Recovery Email:</label>
|
||||||
<input id="register-email" placeholder="Optional">
|
<input class="register-prompt" id="register-email" placeholder="Optional">
|
||||||
|
<altcha-widget floating challengejson="<%= JSON.stringify(challenge) %>"></altcha-widget>
|
||||||
|
<button id="register-button" class='positive-button'>Register</button>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
<footer>
|
<footer>
|
||||||
<%- include('partial/scripts', {user}); %>
|
<%- include('partial/scripts', {user}); %>
|
||||||
<script src="js/register.js"></script>
|
<script src="/js/register.js"></script>
|
||||||
|
<script src="/lib/altcha/altcha.js" type="module"></script>
|
||||||
</footer>
|
</footer>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,19 @@ GNU Affero General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
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/>.*/
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
form{
|
form{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
margin: 5% 17%;
|
margin: 5% 17%;
|
||||||
}
|
}
|
||||||
|
|
||||||
input{
|
.register-prompt{
|
||||||
margin: 0 0 2em;
|
width: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
#register-button{
|
||||||
|
width: 6em;
|
||||||
|
height: 2em;
|
||||||
}
|
}
|
||||||
|
|
@ -16,9 +16,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
form{
|
form{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
margin: 5% 17%;
|
margin: 5% 17%;
|
||||||
}
|
}
|
||||||
|
|
||||||
input{
|
.register-prompt{
|
||||||
margin: 0 0 2em;
|
width: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
#register-button{
|
||||||
|
width: 6em;
|
||||||
|
height: 2em;
|
||||||
}
|
}
|
||||||
|
|
@ -59,6 +59,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
--background-panel-effect-pretty: blur(4px);
|
--background-panel-effect-pretty: blur(4px);
|
||||||
--background-panel-effect-fast: none;
|
--background-panel-effect-fast: none;
|
||||||
|
|
||||||
|
/* altcha theming */
|
||||||
|
--altcha-border-width: 1px;
|
||||||
|
--altcha-border-radius: 1em;
|
||||||
|
--altcha-color-base: var(--bg1);
|
||||||
|
--altcha-color-border: var(--accent1);
|
||||||
|
--altcha-color-text: var(--accent1);
|
||||||
|
--altcha-color-border-focus: currentColor;
|
||||||
|
--altcha-color-error-text: #f23939;
|
||||||
|
--altcha-max-width: 260px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* global */
|
/* global */
|
||||||
|
|
@ -108,6 +118,10 @@ button:active{
|
||||||
box-shadow: var(--focus-glow0-alt0);
|
box-shadow: var(--focus-glow0-alt0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input{
|
||||||
|
accent-color: var(--focus0);
|
||||||
|
}
|
||||||
|
|
||||||
.danger-button{
|
.danger-button{
|
||||||
background-color: var(--danger0);
|
background-color: var(--danger0);
|
||||||
color: var(--accent1);
|
color: var(--accent1);
|
||||||
|
|
@ -384,4 +398,13 @@ span.emote-panel-list-emote:active, span.emote-list-trash-icon:active{
|
||||||
span.emote-list-trash-icon{
|
span.emote-list-trash-icon{
|
||||||
background-color: var(--bg2);
|
background-color: var(--bg2);
|
||||||
border: 1px solid var(--accent0)
|
border: 1px solid var(--accent0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* altcha theming*/
|
||||||
|
div.altcha{
|
||||||
|
box-shadow: 4px 4px 1px var(--bg1-alt0) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
altcha-widget a{
|
||||||
|
color: var(--accent1);
|
||||||
}
|
}
|
||||||
|
|
@ -15,17 +15,60 @@ 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/>.*/
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
//I could make a class like the others but it's so god-damned basic why bother?
|
//I could make a class like the others but it's so god-damned basic why bother?
|
||||||
async function registerPrompt(event){
|
class registerPrompt{
|
||||||
if(!event || event.key == "Enter"){
|
constructor(){
|
||||||
var name = document.querySelector("#register-channel-name").value;
|
//Grab input prompts
|
||||||
var description = document.querySelector("#register-description").value;
|
this.name = document.querySelector("#register-channel-name");
|
||||||
var thumbnail = document.querySelector("#register-thumbnail").value;
|
this.description = document.querySelector("#register-description");
|
||||||
|
this.thumbnail = document.querySelector("#register-thumbnail");
|
||||||
|
//Grab register button
|
||||||
|
this.button = document.querySelector("#register-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 register event listener to register button
|
||||||
|
this.button.addEventListener("click", this.register.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(event){
|
||||||
|
//pull verification payload from event
|
||||||
|
this.verification = event.detail.payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
register(){
|
||||||
|
//If altcha verification isn't complete
|
||||||
|
if(this.verification == null){
|
||||||
|
//don't bother
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Send the registration informaiton off to the server
|
||||||
|
utils.ajax.newChannel(this.name.value, this.description.value, this.thumbnail.value, this.verification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerForm = new registerPrompt();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*async function registerPrompt(event){
|
||||||
|
if(!event || event.key == "Enter"){
|
||||||
|
|
||||||
|
|
||||||
utils.ajax.newChannel(name, description, thumbnail);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//assign events
|
//assign events
|
||||||
document.querySelector("#register-channel-name").addEventListener("keydown", registerPrompt)
|
document.querySelector("#register-channel-name").addEventListener("keydown", registerPrompt)
|
||||||
document.querySelector("#register-description").addEventListener("keydown", registerPrompt)
|
document.querySelector("#register-description").addEventListener("keydown", registerPrompt)
|
||||||
document.querySelector("#register-thumbnail").addEventListener("keydown", registerPrompt)
|
document.querySelector("#register-thumbnail").addEventListener("keydown", registerPrompt)*/
|
||||||
|
|
@ -14,20 +14,56 @@ GNU Affero General Public License for more details.
|
||||||
You should have received a copy of the GNU Affero General Public License
|
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/>.*/
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
//I could make a class like the others but it's so god-damned basic why bother?
|
class registerPrompt{
|
||||||
async function registerPrompt(event){
|
constructor(){
|
||||||
if(!event || event.key == "Enter"){
|
//Grab user prompt
|
||||||
var user = document.querySelector("#register-username").value;
|
this.user = document.querySelector("#register-username");
|
||||||
var pass = document.querySelector("#register-password").value;
|
//Grab pass prompts
|
||||||
var passConfirm = document.querySelector("#register-password-confirm").value;
|
this.pass = document.querySelector("#register-password");
|
||||||
var email = document.querySelector("#register-email").value;
|
this.passConfirm = document.querySelector("#register-password-confirm");
|
||||||
|
//Grab email prompt
|
||||||
|
this.email = document.querySelector("#register-email");
|
||||||
|
//Grab register button
|
||||||
|
this.button = document.querySelector("#register-button");
|
||||||
|
//Grab altcha widget
|
||||||
|
this.altcha = document.querySelector("altcha-widget");
|
||||||
|
//Setup null property to hold verification payload from altcha widget
|
||||||
|
this.verification = null
|
||||||
|
|
||||||
utils.ajax.register(user, pass, passConfirm, email);
|
//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 register event listener to register button
|
||||||
|
this.button.addEventListener("click", this.register.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(event){
|
||||||
|
//pull verification payload from event
|
||||||
|
this.verification = event.detail.payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
register(){
|
||||||
|
//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.register(this.user.value , this.pass.value , this.passConfirm.value , this.email.value, this.verification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//assign events
|
const registerForm = new registerPrompt();
|
||||||
document.querySelector("#register-username").addEventListener("keydown", registerPrompt)
|
|
||||||
document.querySelector("#register-password").addEventListener("keydown", registerPrompt)
|
|
||||||
document.querySelector("#register-password-confirm").addEventListener("keydown", registerPrompt)
|
|
||||||
document.querySelector("#register-email").addEventListener("keydown", registerPrompt)
|
|
||||||
|
|
@ -187,21 +187,45 @@ class canopyUXUtils{
|
||||||
}
|
}
|
||||||
|
|
||||||
createPopup(){
|
createPopup(){
|
||||||
this.popupBacker = document.createElement('div');
|
//Check if another popup has already thrown a backer up
|
||||||
this.popupBacker.classList.add('popup-backer');
|
if(document.querySelector('.popup-backer') == null){
|
||||||
|
//Create popup backer
|
||||||
|
this.popupBacker = document.createElement('div');
|
||||||
|
this.popupBacker.classList.add('popup-backer');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create popup
|
||||||
this.popupDiv = document.createElement('div');
|
this.popupDiv = document.createElement('div');
|
||||||
this.popupDiv.classList.add('popup-div');
|
this.popupDiv.classList.add('popup-div');
|
||||||
|
|
||||||
|
//Create close icon
|
||||||
this.closeIcon = document.createElement('i');
|
this.closeIcon = document.createElement('i');
|
||||||
this.closeIcon.classList.add('bi-x','popup-close-icon');
|
this.closeIcon.classList.add('bi-x','popup-close-icon');
|
||||||
this.closeIcon.addEventListener("click", this.closePopup.bind(this));
|
this.closeIcon.addEventListener("click", this.closePopup.bind(this));
|
||||||
|
|
||||||
|
//Create content div
|
||||||
this.contentDiv = document.createElement('div');
|
this.contentDiv = document.createElement('div');
|
||||||
this.contentDiv.classList.add('popup-content-div');
|
this.contentDiv.classList.add('popup-content-div');
|
||||||
|
|
||||||
|
//Append popup innards
|
||||||
this.popupDiv.appendChild(this.closeIcon);
|
this.popupDiv.appendChild(this.closeIcon);
|
||||||
this.popupDiv.appendChild(this.contentDiv);
|
this.popupDiv.appendChild(this.contentDiv);
|
||||||
|
|
||||||
|
|
||||||
|
//Bit hacky but the only way to remove an event listener while keeping the function bound to this
|
||||||
|
//Isn't javascript precious?
|
||||||
|
this.keyClose = ((event)=>{
|
||||||
|
//If we hit enter
|
||||||
|
if(event.key == "Enter"){
|
||||||
|
//Close the pop-up
|
||||||
|
this.closePopup();
|
||||||
|
//Remove this event listener
|
||||||
|
document.removeEventListener('keydown', this.keyClose);
|
||||||
|
}
|
||||||
|
}).bind(this);
|
||||||
|
|
||||||
|
//Add event listener to close popup when enter is hit
|
||||||
|
document.addEventListener('keydown', this.keyClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fillPopupContent(){
|
async fillPopupContent(){
|
||||||
|
|
@ -221,13 +245,31 @@ class canopyUXUtils{
|
||||||
}
|
}
|
||||||
|
|
||||||
displayPopup(){
|
displayPopup(){
|
||||||
|
//Blur active element that probably caused the popup
|
||||||
|
document.activeElement.blur();
|
||||||
|
|
||||||
|
//display the popup
|
||||||
document.body.prepend(this.popupDiv);
|
document.body.prepend(this.popupDiv);
|
||||||
document.body.prepend(this.popupBacker);
|
|
||||||
|
//if we created a popup backer
|
||||||
|
if(this.popupBacker != null){
|
||||||
|
//display the popup backer
|
||||||
|
document.body.prepend(this.popupBacker);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
closePopup(){
|
closePopup(){
|
||||||
|
//Take out the popup
|
||||||
this.popupDiv.remove();
|
this.popupDiv.remove();
|
||||||
this.popupBacker.remove();
|
|
||||||
|
//Look for the backer instead of using the object property since the bitch mighta been created by someone else
|
||||||
|
const foundBacker = document.querySelector('.popup-backer');
|
||||||
|
|
||||||
|
//if there aren't any more popups
|
||||||
|
if(document.querySelector('.popup-div') == null && foundBacker != null){
|
||||||
|
//Take out the backer
|
||||||
|
foundBacker.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -346,13 +388,13 @@ class canopyAjaxUtils{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async register(user, pass, passConfirm, email){
|
async register(user, pass, passConfirm, email, verification){
|
||||||
var response = await fetch(`/api/account/register`,{
|
var response = await fetch(`/api/account/register`,{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
body: JSON.stringify(email ? {user, pass, passConfirm, email} : {user, pass, passConfirm})
|
body: JSON.stringify(email ? {user, pass, passConfirm, email, verification} : {user, pass, passConfirm, verification})
|
||||||
});
|
});
|
||||||
|
|
||||||
if(response.status == 200){
|
if(response.status == 200){
|
||||||
|
|
@ -435,13 +477,13 @@ class canopyAjaxUtils{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async newChannel(name, description, thumbnail){
|
async newChannel(name, description, thumbnail, verification){
|
||||||
var response = await fetch(`/api/channel/register`,{
|
var response = await fetch(`/api/channel/register`,{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
body: JSON.stringify(thumbnail ? {name, description, thumbnail} : {name, description})
|
body: JSON.stringify(thumbnail ? {name, description, thumbnail, verification} : {name, description, verification})
|
||||||
});
|
});
|
||||||
|
|
||||||
if(response.status == 200){
|
if(response.status == 200){
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue