Added 'altcha' captcha system for account and channel creation.
This commit is contained in:
parent
60801f0dc2
commit
e0f53df176
20 changed files with 326 additions and 55 deletions
|
|
@ -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
|
||||
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/userSchema');
|
||||
const userBanModel = require('../../../schemas/userBanSchema');
|
||||
const altchaUtils = require('../../../utils/altchaUtils');
|
||||
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
||||
|
||||
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 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 >:(
|
||||
const nukedBans = await userBanModel.checkProcessedBans(user.user);
|
||||
|
|
@ -42,9 +56,9 @@ module.exports.post = async function(req, res){
|
|||
return res.sendStatus(200);
|
||||
}else{
|
||||
res.status(400);
|
||||
res.send({errors: validResult.array()})
|
||||
return res.send({errors: validResult.array()});
|
||||
}
|
||||
}catch(err){
|
||||
exceptionHandler(res, err);
|
||||
return exceptionHandler(res, err);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ const {validationResult, matchedData} = require('express-validator');
|
|||
//local imports
|
||||
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
||||
const {userModel} = require('../../../schemas/userSchema');
|
||||
const altchaUtils = require('../../../utils/altchaUtils');
|
||||
const channelModel = require('../../../schemas/channel/channelSchema');
|
||||
|
||||
//api account functions
|
||||
|
|
@ -32,6 +33,15 @@ module.exports.post = async function(req, res){
|
|||
if(validResult.isEmpty()){
|
||||
//Set channel object from sanatized/validated data, and get user document from session data
|
||||
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});
|
||||
|
||||
//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
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||
|
||||
//Local Imports
|
||||
const altchaUtils = require('../utils/altchaUtils');
|
||||
|
||||
//Config
|
||||
const config = require('../../config.json');
|
||||
|
||||
//root index functions
|
||||
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
|
||||
const config = require('../../config.json');
|
||||
|
||||
//Local Imports
|
||||
const altchaUtils = require('../utils/altchaUtils');
|
||||
|
||||
//register page functions
|
||||
module.exports.get = function(req, res){
|
||||
res.render('register', {instance: config.instanceName, user: req.session.user});
|
||||
module.exports.get = async function(req, res){
|
||||
//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.post('/register', accountValidator.user(),
|
||||
accountValidator.pass(),
|
||||
accountValidator.securePass(),
|
||||
accountValidator.pass('passConfirm'),
|
||||
accountValidator.email(), registerController.post);
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ const path = require('path');
|
|||
const mongoStore = require('connect-mongo');
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
//Define global crypto variable for altcha
|
||||
globalThis.crypto = require('node:crypto').webcrypto;
|
||||
|
||||
//Define Local Imports
|
||||
const channelManager = require('./app/channel/channelManager');
|
||||
const scheduler = require('./utils/scheduler');
|
||||
|
|
@ -80,6 +83,7 @@ app.set('views', __dirname + '/views');
|
|||
//Middlware
|
||||
//Enable Express
|
||||
app.use(express.json());
|
||||
//app.use(express.urlencoded());
|
||||
|
||||
//Enable Express-Sessions
|
||||
app.use(sessionMiddleware);
|
||||
|
|
@ -104,12 +108,11 @@ app.use('/tooltip', tooltipRouter);
|
|||
//Bot-Ready
|
||||
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
|
||||
//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')));
|
||||
|
||||
//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');
|
||||
|
||||
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
|
||||
//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');
|
||||
|
||||
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}),
|
||||
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.-->
|
|||
</body>
|
||||
<footer>
|
||||
<%- 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/chatPostprocessor.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>
|
||||
<%- include('partial/styles', {instance, user}); %>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<%- include('partial/navbar', {user}); %>
|
||||
<form action="javascript:">
|
||||
<label>Channel Name:</label>
|
||||
<input id="register-channel-name" placeholder="Required">
|
||||
<input class="register-prompt" id="register-channel-name" placeholder="Required">
|
||||
<label>Description:</label>
|
||||
<input id="register-description" placeholder="Required">
|
||||
<input class="register-prompt" id="register-description" placeholder="Required">
|
||||
<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>
|
||||
</body>
|
||||
<footer>
|
||||
<%- include('partial/scripts', {user}); %>
|
||||
<script src="js/newChannel.js"></script>
|
||||
<script src="/lib/altcha/altcha.js" type="module"></script>
|
||||
</footer>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -19,23 +19,27 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.-->
|
|||
<head>
|
||||
<%- include('partial/styles', {instance, user}); %>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<%- include('partial/navbar', {user}); %>
|
||||
<form action="javascript:">
|
||||
<label>Username:</label>
|
||||
<input id="register-username" placeholder="Required">
|
||||
<input class="register-prompt" id="register-username" placeholder="Required">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</body>
|
||||
<footer>
|
||||
<%- 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>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue