Restructuring for new way of installing libreevent

This commit is contained in:
2024-08-26 11:16:28 +02:00
parent 4d0b8eb1cb
commit 688b0616cc
223 changed files with 11 additions and 58 deletions

View File

@@ -1,3 +0,0 @@
# ignore node_modules folder for eslint
node_modules
webapp

View File

@@ -1,84 +0,0 @@
/*
* libreevent - 2fa.js
*
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const token = require( '../backend/token.js' );
let createSSRApp = require( 'vue' ).createSSRApp;
let renderToString = require( 'vue/server-renderer' ).renderToString;
const fs = require( 'fs' );
const path = require( 'path' );
class TwoFA {
constructor () {
this.tokenStore = {};
this.references = {};
}
registerStandardAuthentication () {
let tok = token.generateToken( 60 );
while ( this.tokenStore[ tok ] ) {
tok = token.generateToken( 60 );
}
this.tokenStore[ tok ] = { 'mode': 'standard' };
return { 'token': tok };
}
registerEnhancedAuthentication () {
let tok = token.generateToken( 60 );
while ( this.tokenStore[ tok ] ) {
tok = token.generateToken( 60 );
}
let code = token.generateNumber( 6 );
this.tokenStore[ tok ] = { 'mode': 'enhanced', 'code': code };
return { 'code': code, 'token': tok };
}
storeTokenReference ( token, sessionID ) {
this.references[ token ] = sessionID;
}
getTokenReference ( token ) {
return this.references[ token ];
}
verifyEnhanced ( token, number = '' ) {
if ( this.tokenStore[ token ]?.mode === 'standard' ) return true;
else if ( this.tokenStore[ token ]?.mode === 'enhanced' ) {
if ( this.tokenStore[ token ].code == number ) {
delete this.tokenStore[ token ];
return true;
} else return false;
} else return false;
}
verifySimple ( token ) {
if ( this.tokenStore[ token ]?.mode === 'standard' ) {
delete this.tokenStore[ token ];
return 'standard';
} else if ( this.tokenStore[ token ]?.mode === 'enhanced' ) return 'enhanced';
else return 'invalid';
}
async generateTwoFAMail ( token, ip, domain, pageName ) {
const app = createSSRApp( {
data() {
return {
token: token,
ip: ip,
host: domain,
pageName: pageName,
};
},
template: '' + fs.readFileSync( path.join( __dirname + '/twoFAMail.html' ) )
} );
return await renderToString( app );
}
}
module.exports = TwoFA;

View File

@@ -1,98 +0,0 @@
/*
* libreevent - adminAPIRoutes.js
*
* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const posth = require( './api/postHandler.js' );
const geth = require( './api/getHandler.js' );
const path = require( 'path' );
const bodyParser = require( 'body-parser' );
const mlt = require( 'multer' );
const pngToIco = require( 'png-to-ico' );
const multer = mlt();
const fs = require( 'fs' );
const settings = JSON.parse( fs.readFileSync( path.join( __dirname + '/../config/settings.config.json' ) ) );
const getHandler = new geth( settings );
const postHandler = new posth( settings );
// settings is missing in arguments which shouldn't pose any problem
module.exports = ( app ) => {
// Add specific routes here to have them be checked first to not get general handling
app.get( '/admin/getAPI/:call', ( req, res ) => {
if ( req.session.loggedInAdmin ) {
getHandler.handleCall( req.params.call, req.query ).then( data => {
res.send( data );
} ).catch( error => {
res.status( error.code ?? 500 ).send( error.error );
} );
} else {
res.status( 403 ).sendFile( path.join( __dirname + '/../ui/' + ( req.query.lang ?? 'en' ) + '/errors/403.html' ) );
}
} );
app.post( '/admin/API/:call', bodyParser.json( { limit: '20mb' } ), ( req, res ) => {
if ( req.session.loggedInAdmin ) {
postHandler.handleCall( req.params.call, req.body, req.query.lang ).then( data => {
res.send( data );
} ).catch( error => {
console.error( error );
res.status( error.code ?? 500 ).send( error.error );
} );
} else {
res.status( 403 ).sendFile( path.join( __dirname + '/../ui/' + ( req.query.lang ?? 'en' ) + '/errors/403.html' ) );
}
} );
app.post( '/admin/events/uploadImages', multer.array( 'image', 2 ), ( req, res ) => {
if ( req.session.loggedInAdmin ) {
if ( req.query.event.includes( '/' ) || req.query.event.includes( '.' ) ) {
res.status( 400 ).send( 'fp_wrong' );
} else {
for ( let file in req.files ) {
if ( req.files[ file ].originalname === req.body.logo ) {
fs.writeFileSync( path.join( __dirname + '/../assets/events/' + req.query.event + 'Logo.jpg' ), req.files[ file ].buffer );
} else {
fs.writeFileSync( path.join( __dirname + '/../assets/events/' + req.query.event + 'Banner.jpg' ), req.files[ file ].buffer );
}
}
res.send( 'ok' );
}
} else {
res.status( 403 ).send( 'unauthorized' );
}
} );
app.post( '/admin/pages/uploadImages', multer.array( 'image', 1 ), ( req, res ) => {
if ( req.session.loggedInAdmin ) {
if ( req.query.image.includes( '/' ) || req.query.image.includes( '.' ) || req.query.template.includes( '/' ) || req.query.template.includes( '.' ) ) {
res.status( 400 ).send( 'fp_wrong' );
} else {
fs.writeFileSync( path.join( __dirname + '/../ui/home/templates/' + req.query.template + '/assets/' + req.query.image + '.jpg' ), req.files[ 0 ].buffer );
res.send( 'ok' );
}
} else {
res.status( 403 ).send( 'unauthorized' );
}
} );
app.post( '/admin/logo/upload', multer.array( 'image', 1 ), ( req, res ) => {
if ( req.session.loggedInAdmin ) {
fs.writeFileSync( path.join( __dirname + '/../assets/logo.png' ), req.files[ 0 ].buffer );
pngToIco( path.join( __dirname + '/../assets/logo.png' ) ).then( buf => {
fs.writeFileSync( path.join( __dirname + '/../webapp/main/dist/favicon.ico' ), buf );
} ).catch( () => {
console.error( '[ ICON CONVERTER ] Failed to convert png to ico file' );
} );
res.send( 'ok' );
} else {
res.status( 403 ).send( 'unauthorized' );
}
} );
};

View File

@@ -1,128 +0,0 @@
/*
* libreevent - routes.js (admin)
*
* Created by Janis Hutz 03/11/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
// const db = require( './db/db.js' );
const pwdmanager = require( './pwdmanager.js' );
const db = require( '../backend/db/db.js' );
const auth = require( './2fa.js' );
const twoFA = new auth();
const path = require( 'path' );
const mail = require( '../backend/mail/mailSender.js' );
const mailManager = new mail();
const bodyParser = require( 'body-parser' );
let responseObjects = {};
let authOk = {};
module.exports = ( app, settings ) => {
/*
Admin login route that checks the password
*/
app.post( '/admin/auth', bodyParser.json(), ( request, response ) => {
if ( request.body.mail && request.body.password ) {
pwdmanager.checkpassword( request.body.mail, request.body.password ).then( data => {
request.session.username = request.body.mail;
if ( data.status ) {
request.session.username = request.body.mail;
if ( data.twoFA === 'simple' ) {
( async () => {
let tok = twoFA.registerStandardAuthentication()[ 'token' ];
let ipRetrieved = request.headers[ 'x-forwarded-for' ];
let ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : request.connection.remoteAddress;
mailManager.sendMail( request.body.mail, await twoFA.generateTwoFAMail( tok, ip, settings.yourDomain, settings.name ), 'Verify admin account login', settings.mailSender );
request.session.token = tok;
response.send( { 'status': '2fa' } );
} )();
} else if ( data.twoFA === 'enhanced' ) {
( async () => {
let res = twoFA.registerEnhancedAuthentication();
let ipRetrieved = request.headers[ 'x-forwarded-for' ];
let ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : request.connection.remoteAddress;
if ( request.body.mail === 'root' ) {
db.getJSONDataSimple( 'rootAccount', 'email' ).then( email => {
( async () => {
mailManager.sendMail( email, await twoFA.generateTwoFAMail( res.token, ip, settings.yourDomain, settings.name ), 'Verify admin account login', settings.mailSender );
} )();
} );
} else {
mailManager.sendMail( request.body.mail, await twoFA.generateTwoFAMail( res.token, ip, settings.yourDomain, settings.name ), 'Verify admin account login', settings.mailSender );
}
request.session.token = res.token;
response.send( { 'status': '2fa+', 'code': res.code } );
} )();
} else {
request.session.loggedInAdmin = true;
response.send( { 'status': 'ok' } );
}
} else {
response.send( { 'status': 'pwErr' } );
}
} );
} else {
response.send( 'missingCredentials' );
}
} );
app.get( '/admin/2fa', ( request, response ) => {
let tokType = twoFA.verifySimple( request.query.token );
if ( tokType === 'standard' ) {
request.session.loggedInAdmin = true;
responseObjects[ request.query.token ].write( 'data: authenticated\n\n' );
response.sendFile( path.join( __dirname + '/../ui/en/2fa/2faSimple.html' ) );
} else if ( tokType === 'enhanced' ) {
response.sendFile( path.join( __dirname + '/../ui/en/2fa/2faEnhancedAdmin.html' ) );
} else {
response.sendFile( path.join( __dirname + '/../ui/en/2fa/2faInvalid.html' ) );
}
} );
app.post( '/admin/2fa/verify', bodyParser.json(), ( request, response ) => {
let verified = twoFA.verifyEnhanced( request.body.token, request.body.code );
if ( verified ) {
request.session.loggedInAdmin = true;
responseObjects[ request.body.token ].write( 'data: authenticated\n\n' );
response.send( 'ok' );
} else response.send( 'wrong' );
} );
app.get( '/admin/2fa/check', ( request, response ) => {
response.writeHead( 200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
} );
response.status( 200 );
response.flushHeaders();
response.write( 'data: connected\n\n' );
responseObjects[ request.session.token ] = response;
} );
app.get( '/admin/2fa/ping', ( request, response ) => {
if ( authOk[ request.session.token ] === 'ok' ) {
response.send( { 'status': 'ok' } );
} else {
response.send( '' );
}
} );
// app.get( '/test/login', ( request, response ) => {
// request.session.loggedInAdmin = true;
// response.send( 'Logged in' );
// } );
app.get( '/admin/logout', ( request, response ) => {
request.session.loggedInAdmin = false;
response.send( 'logged out' );
} );
app.get( '/api/getAuth', ( request, response ) => {
response.send( { 'admin': request.session.loggedInAdmin ? true : false, 'user': request.session.loggedInUser ? true : false } );
} );
};

View File

@@ -1,136 +0,0 @@
/*
* libreevent - getHandler.js
*
* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const db = require( '../../backend/db/db.js' );
const pm = require( '../../backend/plugins/manager.js' );
const spm = require( '../startPageManager.js' );
class GETHandler {
constructor ( settings ) {
this.pluginManager = new pm( settings );
this.settings = settings;
this.startPageManager = new spm( settings );
}
handleCall ( call, query ) {
return new Promise( ( resolve, reject ) => {
if ( call === 'getSeatplan' ) {
db.getJSONDataSimple( 'seatplan', query.location ).then( data => {
if ( Object.keys( data ).length > 0 ) {
resolve( data[ 'save' ] );
} else {
reject( { 'code': 400, 'error': 'No data found for this location' } );
}
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'getSeatplanDraft' ) {
db.getJSONDataSimple( 'seatplan', query.location ).then( data => {
if ( Object.keys( data ).length > 0 ) {
if ( Object.keys( data[ 'draft' ] ).length > 0 ) {
resolve( data[ 'draft' ] );
} else {
resolve( data[ 'save' ] );
}
} else {
reject( { 'code': 400, 'error': 'No data found for this location' } );
}
} ).catch( error => {
reject( error );
} );
} else if ( call === 'getLocations' ) {
db.getJSONData( 'locations' ).then( data => {
resolve( data ?? {} );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'getTicketTemplate' ) {
db.getJSONDataSimple( 'tickets', query.ticket ).then( data => {
resolve( data ?? {} );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'getEvent' ) {
db.getJSONDataSimple( 'eventDrafts', query.event ).then( data => {
if ( Object.keys( data ).length > 1 ) {
resolve( data );
} else {
reject( { 'code': 404, 'error': 'EventNotFound' } );
}
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'getEventStatus' ) {
db.getJSONDataSimple( 'events', query.event ).then( data => {
if ( Object.keys( data ) ) {
resolve( true );
} else {
resolve( false );
}
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'getAllEvents' ) {
db.getJSONData( 'eventDrafts' ).then( data => {
db.getJSONData( 'events' ).then( dat => {
resolve( { 'live': dat ?? {}, 'drafts': data ?? {} } );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'getCurrency' ) {
resolve( this.settings.currency );
} else if ( call === 'getAdminAccounts' ) {
db.getData( 'admin' ).then( data => {
if ( data[ 0 ] ) {
resolve( { 'data': data, 'status': 'ok' } );
} else {
resolve( { 'data': {}, 'status': 'empty' } );
}
} ).catch( err => {
reject( { 'code': 500, 'message': 'ERR_DB: ' + err } );
} );
} else if ( call === 'getRootAccountDetails' ) {
db.getJSONData( 'rootAccount' ).then( data => {
resolve( data );
} ).catch( err => {
reject( { 'code': 500, 'message': 'ERR_DB: ' + err } );
} );
} else if ( call === 'getPaymentGatewaySettings' ) {
this.pluginManager.loadPaymentGatewaySettings().then( dat => {
resolve( dat );
} ).catch( err => {
reject( { 'code': 500, 'error': err } );
} );
} else if ( call === 'getSettings' ) {
resolve( this.settings );
} else if ( call === 'getAllPlugins' ) {
resolve( this.pluginManager.getPlugins() );
} else if ( call === 'getStartPageSettings' ) {
resolve( this.startPageManager.loadStartPagePreferences( query.name ) );
} else if ( call === 'getAllStartPages' ) {
resolve( this.startPageManager.findAllStartPageTemplates() );
} else if ( call === 'buildStartPage' ) {
( async() => {
if ( await this.startPageManager.renderStartPage( query.page ) ) {
resolve( 'ok' );
} else {
reject( { 'code': 412, 'error': 'Missing entries' } );
}
} )();
} else {
reject( { 'code': 404, 'error': 'Route not found' } );
}
} );
}
}
module.exports = GETHandler;

View File

@@ -1,192 +0,0 @@
/*
* libreevent - postHandler.js
*
* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const db = require( '../../backend/db/db.js' );
const pwdmanager = require( '../pwdmanager.js' );
const pm = require( '../../backend/plugins/manager.js' );
const spm = require( '../startPageManager.js' );
const startPageManager = new spm();
class POSTHandler {
constructor ( settings ) {
this.pluginManager = new pm( settings );
this.settings = settings;
}
handleCall ( call, data, lang ) {
return new Promise( ( resolve, reject ) => {
console.log( lang );
if ( call === 'saveSeatplanDraft' ) {
db.getJSONDataSimple( 'seatplan', data.location ).then( res => {
let dat = res;
dat[ 'draft' ] = data.data;
db.writeJSONDataSimple( 'seatplan', data.location, dat ).then( resp => {
db.getJSONDataSimple( 'locations', data.location ).then( dat => {
let s = dat;
s[ 'totalSeats' ] = data.data.seatInfo.count;
db.writeJSONDataSimple( 'locations', data.location, s ).then( () => {
resolve( resp );
} );
} );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} );
} else if ( call === 'saveSeatplan' ) {
db.writeJSONDataSimple( 'seatplan', data.location, { 'draft': {}, 'save': data.data } ).then( resp => {
db.getJSONDataSimple( 'locations', data.location ).then( dat => {
let s = dat;
s[ 'totalSeats' ] = data.data.seatInfo.count;
db.writeJSONDataSimple( 'locations', data.location, s ).then( () => {
resolve( resp );
} );
} );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'saveLocations' ) {
db.getJSONData( 'seatplan' ).then( res => {
let dat = res;
for ( let loc in data.updated ) {
if ( res[ loc ] ) {
dat[ data.updated[ loc ] ] = res[ loc ];
delete dat[ loc ];
}
}
db.writeJSONData( 'seatplan', dat ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
db.writeJSONData( 'locations', data.data ).then( resp => {
resolve( resp );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'deleteLocation' ) {
db.deleteJSONDataSimple( 'locations', data.location ).then( () => {
resolve( 'ok' );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'createEvent' ) {
db.getJSONDataSimple( 'eventDrafts', data.event ).then( dat => {
if ( Object.keys( dat ).length < 1 ) {
db.writeJSONDataSimple( 'eventDrafts', data.event, { 'name': 'Unnamed event', 'description': '', 'location': '', 'date': '', 'categories': {}, 'ageGroups': { '1': { 'id': 1, 'name': 'Child', 'age': '0 - 15.99' }, '2': { 'id': 2, 'name': 'Adult' } }, 'maxTickets': 2, 'eventID': data.event } ).then( () => {
resolve( 'ok' );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else {
reject( { 'code': 409, 'error': 'ExistsAlready' } );
}
} );
} else if ( call === 'saveEvent' ) {
db.writeJSONDataSimple( 'eventDrafts', data.event, data.eventData ).then( () => {
resolve( 'ok' );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'deployEvent' ) {
db.writeJSONDataSimple( 'events', data.event, data.eventData ).then( () => {
resolve( 'ok' );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'deleteEvent' ) {
db.deleteJSONDataSimple( 'eventDrafts', data.event ).then( () => {
db.deleteJSONDataSimple( 'events', data.event ).then( () => {
resolve( 'ok' );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'undeployEvent' ) {
db.deleteJSONDataSimple( 'events', data.event ).then( () => {
resolve( 'ok' );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'saveTickets' ) {
db.writeJSONDataSimple( 'tickets', data.location, data.data ).then( resp => {
resolve( resp );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'createAdminAccount' ) {
let dat = data;
pwdmanager.hashPassword( dat.pass ).then( hash => {
dat[ 'pass' ] = hash;
db.writeDataSimple( 'admin', 'email', data.email, dat ).then( resp => {
resolve( resp );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} );
} else if ( call === 'updateAdminAccount' ) {
if ( data.pass ) {
let dat = data;
pwdmanager.hashPassword( data.pass ).then( hash => {
dat[ 'pass' ] = hash;
db.writeDataSimple( 'admin', 'email', data.email, dat ).then( resp => {
resolve( resp );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} );
} else {
db.writeDataSimple( 'admin', 'email', data.email, data ).then( resp => {
resolve( resp );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
}
} else if ( call === 'deleteAdminAccount' ) {
db.deleteDataSimple( 'admin', 'email', data.email ).then( resp => {
resolve( resp );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'updateSettings' ) {
this.settings[ 'twoFA' ] = data.twoFA;
this.settings[ 'currency' ] = data.currency;
this.settings[ 'payments' ] = data.payments;
this.settings[ 'ticketTimeout' ] = data.ticketTimeout;
db.saveSettings( this.settings );
db.getJSONData( 'events' ).then( dat => {
let updated = dat;
for ( let event in updated ) {
updated[ event ][ 'currency' ] = data.currency;
}
db.writeJSONData( 'events', updated );
} );
resolve( 'ok' );
} else if ( call === 'updatePaymentGatewaySettings' ) {
this.pluginManager.savePaymentGatewaySettings( data ).then( () => {
resolve( 'ok' );
} ).catch( err => {
reject( { 'code': 500, 'message': err } );
} );
} else if ( call === 'savePageSettings' ) {
startPageManager.saveStartPagePreferences( data.page, data.preferences );
resolve( 'ok' );
} else {
reject( { 'code': 404, 'error': 'Route not found' } );
}
} );
}
}
module.exports = POSTHandler;

View File

@@ -1,84 +0,0 @@
/*
* libreevent - appApiRoutes.js
*
* Created by Janis Hutz 08/19/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const bodyParser = require( 'body-parser' );
const db = require( '../backend/db/db.js' );
const pwHandler = require( './pwdmanager.js' );
module.exports = ( app ) => {
console.log( '[ APP API ] Loaded!' );
app.post( '/app/authenticate', bodyParser.json(), ( req, res ) => {
pwHandler.checkpassword( req.body.email, req.body.password ).then( status => {
if ( status ) {
if ( status.status ) {
res.send( 'authOk' );
} else {
res.send( 'wrong' );
}
} else {
res.send( 'wrong' );
}
} );
} );
app.post( '/app/ticketLookup', bodyParser.json(), ( req, res ) => {
pwHandler.checkpassword( req.body.email, req.body.password ).then( status => {
if ( status ) {
if ( status.status ) {
// extract order name
let indexOfOrderNameEnd = req.body.ticketID.lastIndexOf( '_' );
if ( indexOfOrderNameEnd > req.body.ticketID.length - 5 ) {
indexOfOrderNameEnd = req.body.ticketID.slice( 0, req.body.ticketID.length - 5 ).lastIndexOf( '_' );
}
db.getDataSimple( 'orders', 'order_name', req.body.ticketID.slice( 0, indexOfOrderNameEnd ) ).then( dat => {
if ( dat[ 0 ] ) {
let tickets = JSON.parse( dat[ 0 ][ 'tickets' ] );
const event = req.body.ticketID.slice( indexOfOrderNameEnd + 1, req.body.ticketID.lastIndexOf( '-' ) );
const ticket = req.body.ticketID.slice( req.body.ticketID.lastIndexOf( '-' ) + 1, req.body.ticketID.length );
if ( tickets[ event ] ) {
if ( tickets[ event ][ ticket ] ) {
if ( tickets[ event ][ ticket ][ 'count' ] ) {
if ( !tickets[ event ][ ticket ][ 'used' ] ) {
tickets[ event ][ ticket ][ 'used' ] = 0;
}
if ( tickets[ event ][ ticket ][ 'used' ] == tickets[ event ][ ticket ][ 'count' ] ) {
tickets[ event ][ ticket ][ 'used' ] += 1;
db.writeDataSimple( 'orders', 'order_name', req.body.ticketID.slice( 0, req.body.ticketID.lastIndexOf( '_' ) ), { 'tickets': JSON.stringify( tickets ) } );
res.send( 'ticketValid' );
} else {
res.send( 'ticketInvalid' );
}
} else {
if ( !tickets[ event ][ ticket ][ 'invalidated' ] ) {
tickets[ event ][ ticket ][ 'invalidated' ] = true;
db.writeDataSimple( 'orders', 'order_name', req.body.ticketID.slice( 0, req.body.ticketID.lastIndexOf( '_' ) ), { 'tickets': JSON.stringify( tickets ) } );
res.send( 'ticketValid' );
} else {
res.send( 'ticketInvalid' );
}
}
} else {
res.send( 'ticketInvalid' );
}
} else {
res.send( 'ticketInvalid' );
}
} else {
res.send( 'ticketInvalid' );
}
} );
} else {
res.send( 'wrong' );
}
} else {
res.send( 'wrong' );
}
} );
} );
};

View File

@@ -1,51 +0,0 @@
/*
* libreevent - pwdmanager.js
*
* Created by Janis Hutz 03/26/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
/*
These functions are required to verify user login and to create new users
and to hash new passwords (if user changes password.) This here is only
used for the admin panel, another one is used for the normal user accounts
to separate the two for additional security.
*/
// import and init
const bcrypt = require( 'bcrypt' );
const db = require( '../backend/db/db.js' );
module.exports.checkpassword = ( username, password ) => {
return new Promise( resolve => {
if ( username === 'root' ) {
db.getJSONData( 'rootAccount' ).then( account => {
bcrypt.compare( password, account.pass ).then( res => {
resolve( { 'status': res, 'twoFA': 'enhanced' } );
} );
} );
} else {
db.getDataSimple( 'admin', 'email', username ).then( data => {
if ( data ) {
if ( data[ 0 ] ) {
bcrypt.compare( password, data[ 0 ].pass ).then( res => {
resolve( { 'status': res, 'twoFA': data[ 0 ].two_fa } );
} );
} else {
resolve( false );
}
} else {
resolve( false );
}
} );
}
} );
};
module.exports.hashPassword = ( password ) => {
return new Promise( resolve => {
resolve( bcrypt.hashSync( password, 10 ) );
} );
};

View File

@@ -1,67 +0,0 @@
/*
* libreevent - startPageManager.js
*
* Created by Janis Hutz 09/04/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
let createSSRApp = require( 'vue' ).createSSRApp;
let renderToString = require( 'vue/server-renderer' ).renderToString;
const fs = require( 'fs' );
const path = require( 'path' );
const db = require( '../backend/db/db.js' );
class StartPageManager {
constructor ( settings ) {
this.settings = settings;
}
saveStartPagePreferences( startPageName, preferences ) {
let conf = {};
for ( let setting in preferences ) {
conf[ setting ] = preferences[ setting ][ 'value' ];
}
fs.writeFileSync( path.join( __dirname + '/../ui/home/templates/' + startPageName + '/startPage.config.json' ), JSON.stringify( conf ) );
}
loadStartPagePreferences( startPageName ) {
let conf, options;
try {
options = JSON.parse( fs.readFileSync( path.join( __dirname + '/../ui/home/templates/' + startPageName + '/startPage.json' ) ) );
conf = JSON.parse( fs.readFileSync( path.join( __dirname + '/../ui/home/templates/' + startPageName + '/startPage.config.json' ) ) );
return { 'conf': conf, 'options': options };
} catch ( err ) {
return {};
}
}
findAllStartPageTemplates() {
return fs.readdirSync( path.join( __dirname + '/../ui/home/templates/' ) );
}
setActiveStartPage( startPageName ) {
this.settings[ 'startPage' ] = startPageName;
db.saveSettings( this.settings );
}
async renderStartPage( startPageName ) {
this.setActiveStartPage( startPageName );
let self = this;
const app = createSSRApp( {
data() {
return {
'data': self.loadStartPagePreferences( startPageName ),
'pageName': self.settings.pageName,
};
},
template: '' + fs.readFileSync( path.join( __dirname + '/../ui/home/templates/' + startPageName + '/index.html' ) )
} );
fs.writeFileSync( path.join( __dirname + '/../ui/home/active/en/index.html' ), await renderToString( app ) );
return true;
}
}
module.exports = StartPageManager;

View File

@@ -1,70 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Two-Factor Authentication</title>
<style>
body {
font-family: sans-serif;
width: 100%;
height: 800px;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.content {
width: 80%;
height: 90%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.ip {
color: rgb(94, 94, 94);
}
.logo {
width: 70vw;
}
.verify {
padding: 20px 30px;
background-color: rgb(0, 7, 87);
text-decoration: none;
color: white;
transition: 0.5s all;
border-radius: 5px;
margin-bottom: 20px;
}
.verify:hover {
background-color: rgb(0, 12, 139);
}
@media only screen and (min-width: 999px) {
.logo {
width: 20vw;
}
.content {
width: 40vw;
}
}
</style>
</head>
<body>
<div class="content">
<img :src="host + '/otherAssets/logo.png'" alt="Logo" class="logo">
<h1>Welcome back!</h1>
<p>It looks like someone is trying to sign in to your admin account at {{ pageName }}. If it was you, please click the button below to confirm the login. If not, please <a :href="host + '/admin/settings'">change</a> your password immediately or have it changed by the root account!</p>
<p class="ip">Logging in from IP {{ ip }}.</p>
<a :href="host + '/admin/2fa?token=' + token" class="verify">Verify</a>
</div>
</body>
</html>

View File

@@ -1,114 +0,0 @@
/*
* libreevent - app.js
*
* Created by Janis Hutz 02/26/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const express = require( 'express' );
let app = express();
const path = require( 'path' );
const expressSession = require( 'express-session' );
const cookieParser = require( 'cookie-parser' );
const http = require( 'http' );
const fs = require( 'fs' );
const token = require( './backend/token.js' );
console.log( `
_ _ _ _
| (_) | | |
| |_| |__ _ __ ___ _____ _____ _ __ | |_
| | | '_ \\| '__/ _ \\/ _ \\ \\ / / _ \\ '_ \\| __|
| | | |_) | | | __/ __/\\ V / __/ | | | |_
|_|_|_.__/|_| \\___|\\___| \\_/ \\___|_| |_|\\__|
-------------------------------
==> Welcome to libreevent!
==> You are running Version V1.0.0
Below you can see all important things that happen during operation.
libreevent logs all errors in the console such that they appear in the
log files when running it with an output pipe (which you should definitely do)
To do this run the following command when starting libreevent:
'node app.js > libreevent_log.txt'
` );
console.log( '[ Server ] loading settings' );
const settings = JSON.parse( fs.readFileSync( path.join( __dirname + '/config/settings.config.json' ) ) );
// Route for static html file for start page (page is compiled using
// Vue SSR and gets its support files (e.g. CSS and JS files) from
// the /home/supportFiles/:file route plus its assets from the /otherAssets/:file
// route).
if ( settings.setupDone ) {
app.get( '/', ( req, res ) => {
res.sendFile( path.join( __dirname + '/ui/home/active/en/index.html' ) );
} );
}
// Set up static routes for static file serving (performance wise not
// that good, but way easier to set up)
console.log( '[ Server ] Setting up static routes' );
if ( settings.setupDone ) {
app.use( express.static( 'webapp/main/dist' ) );
} else {
app.use( express.static( 'webapp/setup/dist' ) );
}
// initialise express with middlewares
console.log( '[ Server ] loading and initializing middlewares' );
app.use( expressSession( {
secret: token.generateToken( 60 ),
resave: false,
saveUninitialized: true,
cookie: {
sameSite: 'none',
httpOnly: true,
secure: false,
}
} ) );
app.use( cookieParser() );
let file = path.join( __dirname + '/webapp/main/dist/index.html' );
if ( settings.setupDone ) {
console.log( '[ Server ] loading backend components' );
require( './backend/helperRoutes.js' )( app, settings ); // Helper routes
require( './admin/adminRoutes.js' )( app, settings ); // admin routes
require( './admin/adminAPIRoutes.js' )( app, settings ); // admin api routes
require( './admin/appApiRoutes.js' )( app, settings ); // app api routes
require( './backend/userAPIRoutes.js' )( app, settings ); // user api routes
require( './backend/userRoutes.js' )( app, settings ); // user routes
require( './backend/payments/paymentRoutes.js' )( app, settings ); // payment routes
require( './backend/plugins/pluginLoader.js' )( app, settings ); // plugin loader
} else {
console.log( '[ Setup ] Loading setup routes' );
require( './setup/setupRoutes.js' )( app, settings ); // setup routes
file = path.join( __dirname + '/webapp/setup/dist/index.html' );
}
// handling of any unknown route. Returns the SPA index.html file which
// initiates loading of the SPA
app.use( ( request, response ) => {
response.sendFile( file );
} );
console.log( '\n\n[ Server ] loading complete!\n\n' );
const PORT = process.env.PORT || 8080;
console.log( '[ Server ] listening on port ' + PORT );
http.createServer( app ).listen( PORT );

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -1 +0,0 @@
*.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 698 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 698 KiB

View File

@@ -1,69 +0,0 @@
/*
* libreevent - getHandler.js
*
* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const db = require( '../db/db.js' );
class GETHandler {
constructor () {
}
handleCall ( call, query, session, settings ) {
return new Promise( ( resolve, reject ) => {
if ( call === 'getSeatplan' ) {
db.getJSONDataSimple( 'seatplan', query.location ).then( data => {
if ( Object.keys( data ).length > 0 ) {
resolve( data[ 'save' ] );
} else {
reject( { 'code': 404, 'message': 'No data found for this location' } );
}
} ).catch( error => {
reject( { 'code': 500, 'message': error } );
} );
} else if ( call === 'getReservedSeats' ) {
if ( query.event ) {
db.getDataSimple( 'temp', 'user_id', session.id ).then( dat => {
resolve( { 'user': dat[ 0 ] ? JSON.parse( dat[ 0 ].data )[ query.event ] ?? {} : {} } );
} ).catch( () => {
reject( { 'code': 500, 'message': 'ERR_DB' } );
} );
} else {
reject( { 'code': 400, 'message': 'Bad request, missing event query' } );
}
} else if ( call === 'getEvent' ) {
db.getJSONDataSimple( 'events', query.event ).then( data => {
if ( Object.keys( data ) ) {
resolve( data );
} else {
reject( { 'code': 404, 'error': 'EventNotFound' } );
}
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'getAllEvents' ) {
db.getJSONData( 'events' ).then( data => {
resolve( data ?? {} );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'extendTicketDeletion' ) {
db.writeDataSimple( 'temp', 'user_id', session.id, { 'timestamp': new Date().toString() } );
resolve( 'extended' );
} else if ( call === 'getName' ) {
resolve( { 'name': settings.name } );
} else if ( call === 'reloadData' ) {
resolve( 'ok' );
} else {
reject( { 'code': 404, 'message': 'Route not found' } );
}
} );
}
}
module.exports = GETHandler;

View File

@@ -1,334 +0,0 @@
/*
* libreevent - postHandler.js
*
* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const path = require( 'path' );
const db = require( '../db/db.js' );
const fs = require( 'fs' );
const pwHandler = require( '../credentials/pwdmanager.js' );
class POSTHandler {
constructor () {
this.loadData();
this.temporarilySelected = {};
this.temporarilySelectedTotals = {};
this.temporaryTotals = {};
this.freeSeats = {};
this.settings = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../config/settings.config.json' ) ) );
/*
Here, GC-Duty is scheduled to run every so often (defined in settings.config.json file, no GUI setting available.
If you are a developer and are thinking about adding a GUI setting for this, please consider that this is a
advanced setting and many people might not understand what it changes. The config file is mentioned in the
"advanced settings" section of the admin panel documentation where all the available settings in the config file
are explained in some detail.)
*/
setInterval( () => {
this.gc();
}, parseInt( this.settings.gcInterval ) * 1000 );
}
loadData () {
db.getJSONData( 'booked' ).then( dat => {
this.allSelectedSeats = dat;
db.getJSONData( 'events' ).then( dat => {
this.events = dat;
this.ticketTotals = {};
for ( let event in this.events ) {
this.ticketTotals[ event ] = this.events[ event ][ 'totalSeats' ];
}
for ( let event in this.allSelectedSeats ) {
for ( let t in this.allSelectedSeats[ event ] ) {
if ( this.allSelectedSeats[ event ][ t ][ 'count' ] ) {
this.ticketTotals[ event ] -= parseInt( this.allSelectedSeats[ event ][ t ][ 'count' ] );
} else {
this.ticketTotals[ event ] -= 1;
}
}
}
this.countFreeSeats();
} );
} );
}
gc() {
// this function acts as the database garbage collector. TicketTimeout can be changed from the GUI.
db.getData( 'temp' ).then( tempData => {
let data = tempData;
console.info( '[ DB ] Garbage Collecting' );
for ( let item in data ) {
if ( new Date( data[ item ][ 'timestamp' ] ).getTime() + ( parseInt( this.settings.ticketTimeout ) * 1000 ) <= new Date().getTime() ) {
let dat = JSON.parse( data[ item ].data );
for ( let event in dat ) {
for ( let ticket in dat[ event ] ) {
this.temporaryTotals -= this.temporarilySelectedTotals[ data[ item ].user_id ][ event ][ ticket ];
delete this.temporarilySelectedTotals[ data[ item ].user_id ][ event ][ ticket ];
delete this.temporarilySelected[ event ][ ticket ];
}
}
db.deleteDataSimple( 'temp', 'entry_id', data[ item ].entry_id ).then( () => {
console.debug( '[ DB ] Garbage collected a session' );
} ).catch( err => {
console.error( '[ DB ] GC-ERROR: ' + err );
} );
}
}
this.countFreeSeats();
} );
}
// Add lang in the future
handleCall ( call, data, session ) {
return new Promise( ( resolve, reject ) => {
if ( call === 'reserveTicket' ) {
if ( data.count || data.count === 0 ) {
db.getDataSimple( 'temp', 'user_id', session.id ).then( dat => {
// const id = data.id.slice( 0, data.id.indexOf( '_' ) );
if ( dat[ 0 ] ) {
// data.count is the total amount of tickets currently selected
let totalTickets = 0;
// sum up total of tickets
let info = JSON.parse( dat[ 0 ].data );
let tickets = info[ data.eventID ] ?? {};
for ( let ticket in tickets ) {
if ( tickets[ ticket ].count ) {
totalTickets += tickets[ ticket ].count;
} else {
totalTickets += 1;
}
}
// check if total ticket count exceeds max tickets per order
if ( this.settings.maxTickets !== 0 ) {
if ( totalTickets >= this.settings.maxTickets ) {
reject( { 'code': 418, 'message': 'ERR_TOO_MANY_TICKETS' } );
return;
}
}
if ( !this.temporarilySelectedTotals[ session.id ] ) {
this.temporarilySelectedTotals[ session.id ] = {};
}
if ( !this.temporarilySelectedTotals[ session.id ][ data.eventID ] ) {
this.temporarilySelectedTotals[ session.id ][ data.eventID ] = {};
}
if ( !this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] ) {
this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] = 0;
}
let ticketCount = data.count;
// check if total ticket count exceeds max tickets for this event and adjust if necessary
if ( this.events[ data.eventID ].maxTickets == 0 || totalTickets < this.events[ data.eventID ].maxTickets ) {
// check if enough tickets are still available
let isExceeded = false;
if ( ( totalTickets - ( tickets[ data.id ] ? tickets[ data.id ][ 'count' ] : 0 ) + ticketCount ) > this.ticketTotals[ data.eventID ] - ( this.temporaryTotals[ data.eventID ] ?? 0 ) + totalTickets && data.count > 0 ) {
ticketCount = this.ticketTotals[ data.eventID ] - ( this.temporaryTotals[ data.eventID ] ?? 0 ) + ( tickets[ data.id ] ? tickets[ data.id ][ 'count' ] : 0 );
isExceeded = true;
}
info[ data.eventID ] = tickets;
if ( data.count < 1 ) {
if ( Object.keys( info[ data.eventID ] ).length < 1 ) {
delete info[ data.eventID ];
} else {
delete info[ data.eventID ][ data.id ];
}
} else {
info[ data.eventID ][ data.id ] = data;
}
db.writeDataSimple( 'temp', 'user_id', session.id, { 'user_id': session.id, 'timestamp': new Date().toString(), 'data': JSON.stringify( info ) } ).then( () => {
if ( !this.temporarilySelected[ data.eventID ] ) {
this.temporarilySelected[ data.eventID ] = {};
}
if ( !this.temporaryTotals[ data.eventID ] ) {
this.temporaryTotals[ data.eventID ] = 0;
}
this.temporarilySelected[ data.eventID ][ data.id ] = info[ data.eventID ] ? info[ data.eventID ][ data.id ] : undefined;
this.temporaryTotals[ data.eventID ] -= this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ];
this.temporaryTotals[ data.eventID ] += ticketCount;
this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] = ticketCount;
this.countFreeSeats();
if ( isExceeded ) {
reject( { 'code': 409, 'message': { 'count': ticketCount } } );
} else {
resolve( { 'status': 'ok' } );
}
} );
} else {
reject( { 'code': 418, 'message': { 'count': this.settings.maxTickets } } );
}
} else {
if ( !this.temporarilySelectedTotals[ session.id ] ) {
this.temporarilySelectedTotals[ session.id ] = {};
}
if ( !this.temporarilySelectedTotals[ session.id ][ data.eventID ] ) {
this.temporarilySelectedTotals[ session.id ][ data.eventID ] = {};
}
if ( !this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] ) {
this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] = 0;
}
let ticketCount = data.count;
// check if total ticket count exceeds max tickets for this event and adjust if necessary
if ( this.events[ data.eventID ].maxTickets == 0 || ticketCount < this.events[ data.eventID ].maxTickets ) {
// check if enough tickets are still available
let isExceeded = false;
if ( ticketCount > this.ticketTotals[ data.eventID ] - ( this.temporaryTotals[ data.eventID ] ?? 0 ) + this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] && data.count > 0 ) {
ticketCount = this.ticketTotals[ data.eventID ] - ( this.temporaryTotals[ data.eventID ] ?? 0 ) + this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ];
isExceeded = true;
}
let info = {};
info[ data.eventID ] = {};
info[ data.eventID ][ data.id ] = data;
db.writeDataSimple( 'temp', 'user_id', session.id, { 'user_id': session.id, 'timestamp': new Date().toString(), 'data': JSON.stringify( info ) } ).then( () => {
if ( !this.temporarilySelected[ data.eventID ] ) {
this.temporarilySelected[ data.eventID ] = {};
}
if ( !this.temporaryTotals[ data.eventID ] ) {
this.temporaryTotals[ data.eventID ] = 0;
}
this.temporarilySelected[ data.eventID ][ data.id ] = info[ data.eventID ] ? info[ data.eventID ][ data.id ] : undefined;
this.temporaryTotals[ data.eventID ] -= this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ];
this.temporaryTotals[ data.eventID ] += ticketCount;
this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] = ticketCount;
this.countFreeSeats();
if ( isExceeded ) {
reject( { 'code': 409, 'message': { 'count': ticketCount } } );
} else {
resolve( { 'status': 'ok' } );
}
} );
} else {
reject( { 'code': 418, 'message': { 'count': this.settings.maxTickets } } );
}
}
} );
} else {
if ( !this.allSelectedSeats[ data.eventID ] ) {
this.allSelectedSeats[ data.eventID ] = {};
}
if ( !this.temporarilySelected[ data.eventID ] ) {
this.temporarilySelected[ data.eventID ] = {};
}
if ( this.allSelectedSeats[ data.eventID ][ data.id ] || this.temporarilySelected[ data.eventID ][ data.id ] ) {
reject( { 'code': 409, 'message': 'ERR_ALREADY_SELECTED' } );
} else {
db.getDataSimple( 'temp', 'user_id', session.id ).then( dat => {
let info = {};
if ( dat[ 0 ] ) {
info = JSON.parse( dat[ 0 ].data );
}
if ( !info[ data.eventID ] ) {
info[ data.eventID ] = {};
}
info[ data.eventID ][ data.id ] = data;
db.writeDataSimple( 'temp', 'user_id', session.id, { 'user_id': session.id, 'timestamp': new Date().toString(), 'data': JSON.stringify( info ) } ).then( () => {
if ( !this.temporarilySelectedTotals[ session.id ] ) {
this.temporarilySelectedTotals[ session.id ] = {};
}
if ( !this.temporarilySelectedTotals[ session.id ][ data.eventID ] ) {
this.temporarilySelectedTotals[ session.id ][ data.eventID ] = {};
}
if ( !this.temporaryTotals[ data.eventID ] ) {
this.temporaryTotals[ data.eventID ] = 0;
}
this.temporarilySelected[ data.eventID ] = info[ data.eventID ];
this.temporaryTotals[ data.eventID ] += 1;
this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] = 1;
this.countFreeSeats();
resolve( 'ok' );
} ).catch( err => {
console.error( err );
} );
} ).catch( () => {
console.log( '[ Ticketing ] An error occurred loading data form temp database' );
} );
}
}
} else if ( call === 'deselectTicket' ) {
db.getDataSimple( 'temp', 'user_id', session.id ).then( dat => {
let transmit = {};
if ( dat[ 0 ] ) {
transmit = JSON.parse( dat[ 0 ].data );
if ( transmit[ data.eventID ] ) {
if ( transmit[ data.eventID ][ data.id ] ) {
delete transmit[ data.eventID ][ data.id ];
} else {
reject( { 'code': 404, 'message': 'ERR_DATA_NOT_FOUND' } );
}
if ( Object.keys( transmit[ data.eventID ] ).length < 1 ) {
delete transmit[ data.eventID ];
}
} else {
reject( { 'code': 404, 'message': 'ERR_DATA_NOT_FOUND' } );
}
const allSeats = this.temporarilySelected[ data.eventID ];
for ( let seat in allSeats ) {
if ( allSeats[ seat ].component === data.component ) {
if ( allSeats[ seat ].id === data.id ) {
this.temporaryTotals[ data.eventID ] -= 1;
delete this.temporarilySelectedTotals[ session.id ][ data.eventID ][ seat ];
delete this.temporarilySelected[ data.eventID ][ seat ];
}
}
}
} else {
reject( { 'code': 404, 'message': 'ERR_DATA_NOT_FOUND' } );
return;
}
db.writeDataSimple( 'temp', 'user_id', session.id, { 'user_id': session.id, 'data': JSON.stringify( transmit ) } ).then( () => {
this.countFreeSeats();
resolve( 'ok' );
} ).catch( error => {
console.error( error );
reject( { 'code': 500, 'message': 'ERR_DB' } );
} );
} ).catch( error => {
console.error( error );
reject( { 'code': 500, 'message': 'ERR_DB' } );
} );
} else if ( call === 'resetPW' ) {
pwHandler.resetPassword( data.email ).then( () => {
resolve( 'ok' );
} ).catch( error => {
if ( error.code ) {
reject( error );
} else {
reject( { 'code': 500, 'message': error } );
}
} );
} else {
reject( { 'code': 404, 'message': 'Route not found' } );
}
} );
}
getReservedSeats ( event ) {
return this.allSelectedSeats[ event ] ? Object.values( Object.assign( {}, this.allSelectedSeats[ event ], this.temporarilySelected[ event ] ) ) : ( this.temporarilySelected[ event ] ?? {} );
}
countFreeSeats() {
this.freeSeats = {};
for ( let el in this.ticketTotals ) {
this.freeSeats[ el ] = this.ticketTotals[ el ];
}
for ( let el in this.temporaryTotals ) {
this.freeSeats[ el ] -= this.temporaryTotals[ el ];
}
}
getFreeSeatsCount() {
return this.freeSeats;
}
}
module.exports = POSTHandler;

View File

@@ -1,100 +0,0 @@
/*
* libreevent - 2fa.js
*
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const token = require( '../token.js' );
let createSSRApp = require( 'vue' ).createSSRApp;
let renderToString = require( 'vue/server-renderer' ).renderToString;
const fs = require( 'fs' );
const path = require( 'path' );
class TwoFA {
constructor () {
this.tokenStore = {};
this.references = {};
this.pwdChangeTokens = {};
}
registerStandardAuthentication () {
let tok = token.generateToken( 60 );
while ( this.tokenStore[ tok ] ) {
tok = token.generateToken( 60 );
}
this.tokenStore[ tok ] = { 'mode': 'standard' };
return { 'token': tok };
}
registerEnhancedAuthentication () {
let tok = token.generateToken( 60 );
while ( this.tokenStore[ tok ] ) {
tok = token.generateToken( 60 );
}
let code = token.generateNumber( 6 );
this.tokenStore[ tok ] = { 'mode': 'enhanced', 'code': code };
return { 'code': code, 'token': tok };
}
storeTokenReference ( token, sessionID ) {
this.references[ token ] = sessionID;
}
getTokenReference ( token ) {
return this.references[ token ];
}
verifyEnhanced ( token, number = '' ) {
if ( this.tokenStore[ token ]?.mode === 'standard' ) return true;
else if ( this.tokenStore[ token ]?.mode === 'enhanced' ) {
if ( this.tokenStore[ token ].code == number ) {
delete this.tokenStore[ token ];
return true;
} else return false;
} else return false;
}
verifySimple ( token ) {
if ( this.tokenStore[ token ]?.mode === 'standard' ) {
delete this.tokenStore[ token ];
return 'standard';
} else if ( this.tokenStore[ token ]?.mode === 'enhanced' ) return 'enhanced';
else return 'invalid';
}
async generateTwoFAMail ( token, ip, domain, pageName ) {
const app = createSSRApp( {
data() {
return {
token: token,
ip: ip,
host: domain,
pageName: pageName,
};
},
template: '' + fs.readFileSync( path.join( __dirname + '/twoFAMail.html' ) )
} );
return await renderToString( app );
}
async generateSignupEmail ( token, domain, pageName ) {
const app = createSSRApp( {
data() {
return {
token: token,
host: domain,
pageName: pageName,
};
},
template: '' + fs.readFileSync( path.join( __dirname + '/../../ui/en/signup/signupMail.html' ) )
} );
return await renderToString( app );
}
}
module.exports = TwoFA;

View File

@@ -1,79 +0,0 @@
/*
* libreevent - pwdmanager.js
*
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
/*
These functions are required to verify user login and to create new users
and to hash new passwords (if user changes password.)
*/
// import and init
const bcrypt = require( 'bcrypt' );
const db = require( '../db/db.js' );
const mm = require( '../mail/mailSender.js' );
const mailManager = new mm();
const fs = require( 'fs' );
const path = require( 'path' );
const token = require( '../token.js' );
let createSSRApp = require( 'vue' ).createSSRApp;
let renderToString = require( 'vue/server-renderer' ).renderToString;
const settings = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../config/settings.config.json' ) ) );
module.exports.checkpassword = function checkpassword ( email, password ) {
return new Promise( resolve => {
db.getDataSimple( 'user', 'email', email ).then( data => {
if ( data ) {
if ( data[ 0 ] ) {
bcrypt.compare( password, data[ 0 ].pass ).then( res => {
resolve( { 'status': res, 'twoFA': data[ 0 ].two_fa } );
} );
} else {
resolve( false );
}
} else {
resolve( false );
}
} );
} );
};
module.exports.hashPassword = ( password ) => {
return new Promise( resolve => {
resolve( bcrypt.hashSync( password, 10 ) );
} );
};
module.exports.resetPassword = ( email ) => {
return new Promise( ( resolve, reject ) => {
db.checkDataAvailability( 'users', 'email', email ).then( dat => {
if ( dat ) {
const newPW = token.generateToken( 20 );
this.hashPassword( newPW ).then( hash => {
( async () => {
db.writeDataSimple( 'users', 'email', email, { 'pass': hash } );
const app = createSSRApp( {
data() {
return {
password: newPW,
host: settings.yourDomain
};
},
template: '' + fs.readFileSync( path.join( __dirname + '/../../ui/en/signup/pwReset.html' ) )
} );
mailManager.sendMail( email, await renderToString( app ), 'Password reset', settings.mailSender );
resolve( 'ok' );
} )();
} );
} else {
reject( { 'code': 404, 'message': 'ERR_USER_NOT_FOUND' } );
}
} );
} );
};

View File

@@ -1,70 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Two-Factor Authentication</title>
<style>
body {
font-family: sans-serif;
width: 100%;
height: 800px;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.content {
width: 80%;
height: 90%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.ip {
color: rgb(94, 94, 94);
}
.logo {
width: 70vw;
}
.verify {
padding: 20px 30px;
background-color: rgb(0, 7, 87);
text-decoration: none;
color: white;
transition: 0.5s all;
border-radius: 5px;
margin-bottom: 20px;
}
.verify:hover {
background-color: rgb(0, 12, 139);
}
@media only screen and (min-width: 999px) {
.logo {
width: 20vw;
}
.content {
width: 40vw;
}
}
</style>
</head>
<body>
<div class="content">
<img :src="host + '/otherAssets/logo.png'" alt="Logo" class="logo">
<h1>Welcome back!</h1>
<p>It looks like someone is trying to sign in to your account at {{ pageName }}. If it was you, please click the button below to confirm the login. If not, please change your password immediately.</p>
<p class="ip">Logging in from IP {{ ip }}.</p>
<a :href="host + '/user/2fa?token=' + token" class="verify">Verify</a>
</div>
</body>
</html>

View File

@@ -1,234 +0,0 @@
/*
* libreevent - db.js
*
* Created by Janis Hutz 03/26/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const path = require( 'path' );
const fs = require( 'fs' );
const settings = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../config/settings.config.json' ) ) );
const dbRef = {
'user': 'libreevent_users',
'admin': 'libreevent_admin',
'order': 'libreevent_orders',
'users': 'libreevent_users',
'orders': 'libreevent_orders',
'temp': 'libreevent_temp',
'processingOrders': 'libreevent_processing_orders'
};
const letters = [ ',', '{' ];
let dbh;
if ( settings.db === 'mysql' ) {
const dbsoft = require( './mysqldb.js' );
dbh = new dbsoft();
dbh.connect();
} else {
const dbsoft = require( './jsondb.js' );
dbh = new dbsoft();
dbh.connect();
}
module.exports.initDB = () => {
( async() => {
console.log( '[ DB ] Setting up...' );
await dbh.resetDB();
setTimeout( () => {
dbh.setupDB();
}, 2000 );
console.log( '[ DB ] Setting up complete!' );
} )();
};
module.exports.reset = () => {
console.log( '[ DB ] Resetting...' );
this.writeJSONData( 'booked', {} );
this.writeJSONData( 'eventDrafts', {} );
this.writeJSONData( 'events', {} );
this.writeJSONData( 'locations', {} );
this.writeJSONData( 'events', {} );
this.writeJSONData( 'seatplan', {} );
this.writeJSONData( 'tickets', {} );
console.log( '[ DB ] Reset complete!' );
};
module.exports.getDataSimple = ( db, column, searchQuery ) => {
return new Promise( ( resolve, reject ) => {
dbh.query( { 'command': 'getFilteredData', 'property': column, 'searchQuery': searchQuery }, dbRef[ db ] ).then( data => {
resolve( data );
} ).catch( error => {
reject( error );
} );
} );
};
module.exports.getData = ( db ) => {
return new Promise( ( resolve, reject ) => {
dbh.query( { 'command': 'getAllData' }, dbRef[ db ] ).then( data => {
resolve( data );
} ).catch( error => {
reject( error );
} );
} );
};
module.exports.writeDataSimple = ( db, column, searchQuery, data ) => {
return new Promise( ( resolve, reject ) => {
dbh.query( { 'command': 'checkDataAvailability', 'property': column, 'searchQuery': searchQuery }, dbRef[ db ] ).then( res => {
if ( res.length > 0 ) {
dbh.query( { 'command': 'updateData', 'property': column, 'searchQuery': searchQuery, 'newValues': data }, dbRef[ db ] ).then( dat => {
resolve( dat );
} ).catch( error => {
reject( error );
} );
} else {
dbh.query( { 'command': 'addData', 'data': data }, dbRef[ db ] ).then( dat => {
resolve( dat );
} ).catch( error => {
reject( error );
} );
}
} ).catch( error => {
reject( error );
} );
} );
};
module.exports.deleteDataSimple = ( db, column, searchQuery ) => {
return new Promise( ( resolve, reject ) => {
dbh.query( { 'command': 'deleteData', 'property': column, 'searchQuery': searchQuery }, dbRef[ db ] ).then( dat => {
resolve( dat );
} ).catch( error => {
reject( error );
} );
} );
};
module.exports.checkDataAvailability = ( db, column, searchQuery ) => {
return new Promise( ( resolve, reject ) => {
dbh.query( { 'command': 'checkDataAvailability', 'property': column, 'searchQuery': searchQuery }, dbRef[ db ] ).then( res => {
if ( res.length > 0 ) {
resolve( true );
} else {
resolve( false );
}
} ).catch( error => {
reject( error );
} );
} );
};
module.exports.getJSONData = ( file ) => {
return new Promise( ( resolve, reject ) => {
fs.readFile( path.join( __dirname + '/../../data/' + file + '.json' ), ( error, data ) => {
if ( error ) {
reject( 'Error occurred: Error trace: ' + error );
} else {
if ( data.byteLength > 0 ) {
resolve( JSON.parse( data ) ?? {} );
} else {
resolve( { } );
}
}
} );
} );
};
module.exports.getJSONDataSimple = ( file, identifier ) => {
return new Promise( ( resolve, reject ) => {
fs.readFile( path.join( __dirname + '/../../data/' + file + '.json' ), ( error, data ) => {
if ( error ) {
reject( 'Error occurred: Error trace: ' + error );
} else {
if ( data.byteLength > 0 ) {
resolve( JSON.parse( data )[ identifier ] ?? {} );
} else {
resolve( { } );
}
}
} );
} );
};
module.exports.getJSONDataSync = ( file ) => {
return JSON.parse( fs.readFileSync( path.join( __dirname + '/../../' + file ) ) );
};
module.exports.writeJSONDataSimple = ( db, identifier, values ) => {
return new Promise( ( resolve, reject ) => {
fs.readFile( path.join( __dirname + '/../../data/' + db + '.json' ), ( error, data ) => {
if ( error ) {
reject( 'Error occurred: Error trace: ' + error );
} else {
let dat = {};
if ( data.byteLength > 0 ) {
dat = JSON.parse( data ) ?? {};
}
dat[ identifier ] = values;
fs.writeFile( path.join( __dirname + '/../../data/' + db + '.json' ), JSON.stringify( dat ), ( error ) => {
if ( error ) {
reject( 'Error occurred: Error trace: ' + error );
}
resolve( true );
} );
}
} );
} );
};
module.exports.writeJSONData = ( db, data ) => {
return new Promise( ( resolve, reject ) => {
fs.writeFile( path.join( __dirname + '/../../data/' + db + '.json' ), JSON.stringify( data ), ( error ) => {
if ( error ) {
reject( 'Error occurred: Error trace: ' + error );
} else {
resolve( true );
}
} );
} );
};
module.exports.deleteJSONDataSimple = ( db, identifier ) => {
return new Promise( ( resolve, reject ) => {
fs.readFile( path.join( __dirname + '/../../data/' + db + '.json' ), ( error, data ) => {
if ( error ) {
reject( 'Error occurred: Error trace: ' + error );
} else {
let dat = {};
if ( data.byteLength > 0 ) {
dat = JSON.parse( data ) ?? {};
}
delete dat[ identifier ];
fs.writeFile( path.join( __dirname + '/../../data/' + db + '.json' ), JSON.stringify( dat ), ( error ) => {
if ( error ) {
reject( 'Error occurred: Error trace: ' + error );
}
resolve( true );
} );
}
} );
} );
};
module.exports.saveSettings = ( settings ) => {
const settingsString = JSON.stringify( settings );
let settingsToSave = '';
for ( let letter in settingsString ) {
if ( letters.includes( settingsString[ letter ] ) ) {
settingsToSave += settingsString[ letter ] + '\n\t';
} else if ( settingsString[ letter ] === '}' ) {
settingsToSave += '\n' + settingsString[ letter ];
} else {
settingsToSave += settingsString[ letter ];
}
}
fs.writeFileSync( path.join( __dirname + '/../../config/settings.config.json' ), settingsToSave );
};

View File

@@ -1,167 +0,0 @@
/*
* libreevent - jsondb.js
*
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const fs = require( 'fs' );
const path = require( 'path' );
class JSONDB {
constructor () {
this.db = {};
this.dbIndex = { 'libreevent_temp': 0, 'libreevent_admin': 0, 'libreevent_orders': 0, 'libreevent_users': 0, 'libreevent_processing_orders': 0 };
this.isSaving = false;
this.awaitingSaving = true;
}
connect () {
let data = {};
try {
JSON.parse( fs.readFileSync( path.join( __dirname + '/../../data/db.json' ) ) );
} catch ( err ) {
console.error( '[ JSON-DB ] CRITICAL INITIALIZATION FAILURE!' + err );
throw ( 'JSONDB failed to start!' );
}
this.db = data[ 'db' ] ?? { 'libreevent_temp': {}, 'libreevent_admin': {}, 'libreevent_orders': {}, 'libreevent_users': {}, 'libreevent_processing_orders': {} };
this.dbIndex = data[ 'index' ] ?? { 'libreevent_temp': 0, 'libreevent_admin': 0, 'libreevent_orders': 0, 'libreevent_users': 0, 'libreevent_processing_orders': 0 };
this.db[ 'libreevent_temp' ] = {};
this.saveToDisk();
console.log( '[ JSON-DB ] Database initialized successfully' );
return 'connection';
}
async saveToDisk () {
if ( !this.isSaving ) {
this.awaitingSaving = false;
this.save();
} else {
this.awaitingSaving = true;
}
}
save () {
fs.writeFile( path.join( __dirname + '/../../data/db.json' ), JSON.stringify( { 'db': this.db, 'index': this.dbIndex } ), ( err ) => {
if ( err ) console.error( '[ JSON-DB ] An error occurred during saving: ' + err );
this.isSaving = false;
if ( this.awaitingSaving ) {
this.saveToDisk();
}
} );
}
async resetDB () {
this.db = { 'libreevent_temp': {}, 'libreevent_admin': {}, 'libreevent_orders': {}, 'libreevent_users': {}, 'libreevent_processing_orders': {} };
this.dbIndex = { 'libreevent_temp': 0, 'libreevent_admin': 0, 'libreevent_orders': 0, 'libreevent_users': 0, 'libreevent_processing_orders': 0 };
fs.writeFile( path.join( __dirname + '/../../data/db.json' ), JSON.stringify( { 'db': this.db, 'index': this.dbIndex } ) );
}
async setupDB () {
this.db = { 'libreevent_temp': {}, 'libreevent_admin': {}, 'libreevent_orders': {}, 'libreevent_users': {}, 'libreevent_processing_orders': {} };
this.dbIndex = { 'libreevent_temp': 0, 'libreevent_admin': 0, 'libreevent_orders': 0, 'libreevent_users': 0, 'libreevent_processing_orders': 0 };
fs.writeFile( path.join( __dirname + '/../../data/db.json' ), JSON.stringify( { 'db': this.db, 'index': this.dbIndex } ) );
}
query ( operation, table ) {
return new Promise( ( resolve, reject ) => {
/*
Possible operation.command values (all need the table argument of the method call):
- getAllData: no additional instructions needed
- getFilteredData:
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- InnerJoin (Select values that match in both tables):
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
- operation.secondTable (The second table to perform Join operation with)
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
- LeftJoin (Select values in first table and return all corresponding values of second table):
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
- operation.secondTable (The second table to perform Join operation with)
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
- RightJoin (Select values in second table and return all corresponding values of first table):
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
- operation.secondTable (The second table to perform Join operation with)
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
- addData:
- operation.data (key-value pair with all data as values and column to insert into as key)
- updateData:
- operation.newValues (a object with keys being the column and value being the value to be inserted into that column, values are being
sanitised by the function)
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- checkDataAvailability:
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
*/
if ( operation.command === 'getAllData' ) {
let ret = [];
for ( let entry in this.db[ table ] ) {
ret.push( this.db[ table ][ entry ] );
}
resolve( ret );
} else if ( operation.command === 'getFilteredData' || operation.command === 'checkDataAvailability' ) {
let ret = [];
for ( let entry in this.db[ table ] ) {
if ( this.db[ table ][ entry ][ operation.property ] == operation.searchQuery ) {
ret.push( this.db[ table ][ entry ] );
}
}
resolve( ret );
} else if ( operation.command === 'addData' ) {
this.dbIndex[ table ] += 1;
this.db[ table ][ this.dbIndex[ table ] ] = operation.data;
this.saveToDisk();
resolve( true );
} else if ( operation.command === 'updateData' ) {
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
else {
for ( let entry in this.db[ table ] ) {
if ( this.db[ table ][ entry ][ operation.property ] == operation.searchQuery ) {
for ( let changed in operation.newValues ) {
this.db[ table ][ entry ][ changed ] = operation.newValues[ changed ];
}
}
}
}
this.saveToDisk();
resolve( true );
} else if ( operation.command === 'deleteData' ) {
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
else {
for ( let entry in this.db[ table ] ) {
if ( this.db[ table ][ entry ][ operation.property ] == operation.searchQuery ) {
delete this.db[ table ][ entry ];
}
}
}
this.saveToDisk();
resolve( true );
} else if ( operation.command === 'InnerJoin' ) {
// TODO: Finish those when actually needed
} else if ( operation.command === 'LeftJoin' ) {
//
} else if ( operation.command === 'RightJoin' ) {
//
}
} );
}
}
module.exports = JSONDB;

View File

@@ -1,184 +0,0 @@
/*
* libreevent - mysqldb.js
*
* Created by Janis Hutz 07/12/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const mysql = require( 'mysql' );
const fs = require( 'fs' );
const path = require( 'path' );
// If the connection does not work for you, you will need to add your ip
// to the whitelist of the database
class SQLDB {
constructor ( ) {
this.sqlConnection = mysql.createConnection( JSON.parse( fs.readFileSync( path.join( __dirname + '/../../config/db.config.json' ) ) ) );
}
connect ( ) {
const self = this;
this.sqlConnection.connect( function( err ) {
if ( err ) {
console.error( '[ SQL ]: An error ocurred whilst connecting: ' + err.stack );
return;
}
console.log( '[ SQL ] Connected to database successfully' );
self.sqlConnection.query( 'TRUNCATE libreevent_temp;', error => {
if ( error ) {
console.error( '[ SQL ] Unable to truncate libreevent_temp table due to the following error: ' + error.code );
} else {
console.log( '[ SQL ] Truncated temporary data table successfully' );
}
} );
return 'connection';
} );
}
disconnect ( ) {
this.sqlConnection.end();
}
async resetDB ( ) {
this.sqlConnection.query( 'DROP TABLE libreevent_orders;', ( error ) => {
if ( error ) if ( error.code !== 'ER_BAD_TABLE_ERROR' ) throw error;
this.sqlConnection.query( 'DROP TABLE libreevent_users;', ( error ) => {
if ( error ) if ( error.code !== 'ER_BAD_TABLE_ERROR' ) throw error;
this.sqlConnection.query( 'DROP TABLE libreevent_admin;', ( error ) => {
if ( error ) if ( error.code !== 'ER_BAD_TABLE_ERROR' ) throw error;
this.sqlConnection.query( 'DROP TABLE libreevent_temp;', ( error ) => {
if ( error ) if ( error.code !== 'ER_BAD_TABLE_ERROR' ) throw error;
this.sqlConnection.query( 'DROP TABLE libreevent_processing_orders;', ( error ) => {
if ( error ) if ( error.code !== 'ER_BAD_TABLE_ERROR' ) throw error;
return 'done';
} );
} );
} );
} );
} );
}
async setupDB ( ) {
this.sqlConnection.query( 'SELECT @@default_storage_engine;', ( error, results ) => {
if ( error ) throw error;
if ( results[ 0 ][ '@@default_storage_engine' ] !== 'InnoDB' ) throw 'DB HAS TO USE InnoDB!';
} );
this.sqlConnection.query( 'CREATE TABLE libreevent_users ( account_id INT ( 10 ) NOT NULL AUTO_INCREMENT, email TINYTEXT NOT NULL, pass TEXT, name TEXT, first_name TEXT, two_fa TINYTEXT, user_data VARCHAR( 60000 ), mail_confirmed TINYTEXT, marketing TINYTEXT, PRIMARY KEY ( account_id ) ) ENGINE=INNODB;', ( error ) => {
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
this.sqlConnection.query( 'CREATE TABLE libreevent_orders ( order_id INT ( 10 ) NOT NULL AUTO_INCREMENT, order_name TINYTEXT, account_id INT ( 10 ) NOT NULL, tickets VARCHAR( 60000 ), processed TINYTEXT, timestamp TINYTEXT, PRIMARY KEY ( order_id ), FOREIGN KEY ( account_id ) REFERENCES libreevent_users( account_id ) ) ENGINE=INNODB;', ( error ) => {
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
this.sqlConnection.query( 'CREATE TABLE libreevent_admin ( account_id INT NOT NULL AUTO_INCREMENT, email TINYTEXT, pass TEXT, permissions VARCHAR( 1000 ), username TINYTEXT, two_fa TINYTEXT, PRIMARY KEY ( account_id ) );', ( error ) => {
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
this.sqlConnection.query( 'CREATE TABLE libreevent_temp ( entry_id INT NOT NULL AUTO_INCREMENT, user_id TINYTEXT, data VARCHAR( 60000 ), timestamp TINYTEXT, PRIMARY KEY ( entry_id ) );', ( error ) => {
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
this.sqlConnection.query( 'CREATE TABLE libreevent_processing_orders ( entry_id INT NOT NULL AUTO_INCREMENT, user_id TINYTEXT, data VARCHAR( 60000 ), timestamp TINYTEXT, PRIMARY KEY ( entry_id ) );', ( error ) => {
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
return 'DONE';
} );
} );
} );
} );
} );
}
query ( operation, table ) {
return new Promise( ( resolve, reject ) => {
/*
Possible operation.command values (all need the table argument of the method call):
- getAllData: no additional instructions needed
- getFilteredData:
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- InnerJoin (Select values that match in both tables):
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
- operation.secondTable (The second table to perform Join operation with)
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
- LeftJoin (Select values in first table and return all corresponding values of second table):
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
- operation.secondTable (The second table to perform Join operation with)
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
- RightJoin (Select values in second table and return all corresponding values of first table):
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
- operation.secondTable (The second table to perform Join operation with)
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
- addData:
- operation.data (key-value pair with all data as values and column to insert into as key)
- deleteData:
- operation.property (the column to search for the value)
- operation.searchQuery (the value to search for [will be sanitised by method])
- updateData:
- operation.newValues (a object with keys being the column and value being the value to be inserted into that column, values are being
sanitised by the function)
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- checkDataAvailability:
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- fullCustomCommand:
- operation.query (the SQL instruction to be executed) --> NOTE: This command will not be sanitised, so use only with proper sanitisation!
*/
let command = '';
if ( operation.command === 'getAllData' ) {
command = 'SELECT * FROM ' + table;
} else if ( operation.command === 'getFilteredData' || operation.command === 'checkDataAvailability' ) {
command = 'SELECT * FROM ' + table + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery );
} else if ( operation.command === 'fullCustomCommand' ) {
command = operation.query;
} else if ( operation.command === 'addData' ) {
let keys = '';
let values = '';
for ( let key in operation.data ) {
keys += String( key ) + ', ';
values += this.sqlConnection.escape( String( operation.data[ key ] ) ) + ', ' ;
}
command = 'INSERT INTO ' + table + ' (' + keys.slice( 0, keys.length - 2 ) + ') VALUES (' + values.slice( 0, values.length - 2 ) + ');';
} else if ( operation.command === 'updateData' ) {
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
else {
command = 'UPDATE ' + table + ' SET ';
let updatedValues = '';
for ( let value in operation.newValues ) {
updatedValues += value + ' = ' + this.sqlConnection.escape( String( operation.newValues[ value ] ) ) + ', ';
}
command += updatedValues.slice( 0, updatedValues.length - 2 );
command += ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery );
}
} else if ( operation.command === 'deleteData' ) {
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
else {
command = 'DELETE FROM ' + table + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery );
}
} else if ( operation.command === 'InnerJoin' ) {
command = 'SELECT ' + operation.selection + ' FROM ' + table + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery ) + ' INNER JOIN ' + operation.secondTable + ' ON ' + operation.matchingParam;
} else if ( operation.command === 'LeftJoin' ) {
command = 'SELECT ' + operation.selection + ' FROM ' + table + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery ) + ' LEFT JOIN ' + operation.secondTable + ' ON ' + operation.matchingParam;
} else if ( operation.command === 'RightJoin' ) {
command = 'SELECT ' + operation.selection + ' FROM ' + table + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery ) + ' RIGHT JOIN ' + operation.secondTable + ' ON ' + operation.matchingParam;
}
this.sqlConnection.query( command, ( error, results ) => {
if ( error ) reject( error );
resolve( results );
} );
} );
}
}
module.exports = SQLDB;

View File

@@ -1,35 +0,0 @@
const path = require( 'path' );
module.exports = ( app, settings ) => {
app.get( '/eventAssets/:image', ( req, res ) => {
res.sendFile( path.join( __dirname + '/../assets/events/' + req.params.image ) );
} );
app.get( '/otherAssets/:image', ( req, res ) => {
res.sendFile( path.join( __dirname + '/../assets/' + req.params.image ) );
} );
app.get( '/supportFiles/:file', ( req, res ) => {
res.sendFile( path.join( __dirname + '/../ui/home/templates/' + settings.startPage + '/supportFiles/' + req.params.file ) );
} );
app.get( '/startPage/helperFunction', ( req, res ) => {
res.sendFile( path.join( __dirname + '/../ui/home/helper.js' ) );
} );
app.get( '/startPage/mainStyle', ( req, res ) => {
res.sendFile( path.join( __dirname + '/../ui/home/main.css' ) );
} );
app.get( '/startPage/assets/:image', ( req, res ) => {
res.sendFile( path.join( __dirname + '/../ui/home/templates/' + settings.startPage + '/assets/' + req.params.image ) );
} );
app.get( '/startPage/preview/:template', ( req, res ) => {
if ( req.session.loggedInAdmin ) {
res.sendFile( path.join( __dirname + '/../ui/home/templates/' + req.params.template + '/index.html' ) );
} else {
res.status( 403 ).send( 'unauthorized' );
}
} );
};

View File

@@ -1,65 +0,0 @@
/*
*
* LANGUAGE SCHOOL HOSSEGOR - Booking system
* mailManager.js
*
* Developed 2022 by Janis Hutz
*
*/
// import and init of nodemailer middleware
const mailer = require( 'nodemailer' );
const html2text = require( 'html-to-text' );
const db = require( '../db/db.js' );
let transporter = mailer.createTransport( db.getJSONDataSync( '/config/mail.config.json' ) );
class MailManager {
constructor () {
this.options = {
wordwrap: 130
};
}
/*
This method sends a mail with recipient, html, subject and sender as arguments
*/
sendMail ( recipient, html, subject, sender ) {
let text = html2text.convert( html, this.options );
let mailOptions = {
from: sender,
to: recipient,
subject: subject,
html: html,
text: text,
};
transporter.sendMail( mailOptions, function ( error ) {
if ( error ) {
console.error( error );
}
} );
}
sendMailWithAttachment ( recipient, html, subject, attachments, from ) {
// Attachments have to be an array of objects that have filename and path as their keys
let text = html2text.convert( html, this.options );
let mailOptions = {
from: from,
to: recipient,
subject: subject,
html: html,
text: text,
attachments: attachments
};
transporter.sendMail( mailOptions, function ( error ) {
if ( error ) {
console.error( error );
}
} );
}
}
module.exports = MailManager;

View File

@@ -1,32 +0,0 @@
/*
* libreevent - paymentRoutes.js
*
* Created by Janis Hutz 08/02/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const path = require( 'path' );
const fs = require( 'fs' );
// const ph = require( './paymentHandler.js' );
// const paymentHandler = new ph();
module.exports = ( app ) => {
app.get( '/payments/canceled', ( req, res ) => {
res.sendFile( path.join( __dirname + '/../../ui/en/payments/canceled.html' ) );
} );
app.get( '/payments/failed', ( req, res ) => {
res.sendFile( path.join( __dirname + '/../../ui/en/payments/failed.html' ) );
} );
app.get( '/tickets/tickets.pdf', ( req, res ) => {
if ( req.session.lastOrderID ) {
fs.readFile( path.join( __dirname + '/../tickets/store/' + req.session.lastOrderID + '.pdf' ), ( error, data ) => {
if ( error ) res.sendFile( path.join( __dirname + '/../../ui/en/errors/404.html' ) );
else res.send( data );
} );
}
} );
};

View File

@@ -1,5 +0,0 @@
# Plugins
If you want to create a new plugin for libreevent, please follow our guide and guidelines in the official documentation [here](https://libreevent.janishutz.com/docs/contributing/plugins)
Each plugin should have a plugin.json file that uses the layout of the plugin.json file in this directory. As future libreevent might change what is required by the plugin.json file, please follow this repository to get news when this is about to happen. To retain backwards compatibility, we will for as long as possible not remove anything from the plugin.json files as possible, which means you can already update your plugin.json file before the next version of libreevent is released. The plugin.json file already contains a "mainPluginURL" parameter which currently is not in use.

View File

@@ -1,68 +0,0 @@
/*
* libreevent - manager.js
*
* Created by Janis Hutz 07/25/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
/*
This is the plugin manager. It is responsible for installing, updating and uninstalling plugins.
*/
const fs = require( 'fs' );
const path = require( 'path' );
class PluginManager {
constructor ( settings ) {
this.paymentGateway = settings.payments;
this.pluginDetails = {};
fs.readdir( path.join( __dirname + '/others' ), ( err, ls ) => {
for ( let file in ls ) {
const pluginDetail = JSON.parse( fs.readFileSync( path.join( __dirname + '/others/' + ls[ file ] + '/plugin.json' ) ) );
this.pluginDetails[ ls[ file ] ] = pluginDetail;
}
} );
}
getPlugins () {
return this.pluginDetails;
}
loadPaymentGatewaySettings () {
return new Promise( ( resolve, reject ) => {
this.paymentGateway = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../config/settings.config.json' ) ) ).payments;
fs.readFile( path.join( __dirname + '/payments/' + this.paymentGateway + '/configOptions.json' ), ( err, optionsBuffer ) => {
if ( err ) reject( err );
fs.readFile( path.join( __dirname + '/payments/' + this.paymentGateway + '/config.payments.json' ), ( err, configBuffer ) => {
if ( err ) reject( err );
let options, config;
try {
options = JSON.parse( optionsBuffer );
config = JSON.parse( configBuffer );
} catch ( err ) {
reject( err );
return;
}
let f = options;
for ( let s in f ) {
f[ s ][ 'value' ] = config[ s ];
}
resolve( { 'data': f, 'gateway': this.paymentGateway } );
} );
} );
} );
}
savePaymentGatewaySettings ( settings ) {
return new Promise( ( resolve, reject ) => {
fs.writeFile( path.join( __dirname + '/payments/' + this.paymentGateway + '/config.payments.json' ), JSON.stringify( settings ), {}, ( err ) => {
if ( err ) reject( err );
resolve( 'ok' );
} );
} );
}
}
module.exports = PluginManager;

View File

@@ -1,74 +0,0 @@
body {
font-family: sans-serif;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: rgb(180, 191, 255);
}
.material-symbols-outlined {
font-variation-settings:
'FILL' 0,
'wght' 400,
'GRAD' 0,
'opsz' 48
}
.mail-app {
width: 80%;
margin-left: 5%;
margin-top: 1%;
background-color: rgb(144, 160, 255);
border-radius: 50px;
padding: 5%;
}
#toolbar, #editor {
background-color: white;
}
#top-bar {
color: white;
background-color: rgb(0, 8, 53);
margin-bottom: 0;
grid-area: header;
height: 10vh;
display: flex;
width: 100%;
justify-content: center;
align-items: center;
flex-direction: row;
}
#title {
font-size: 120%;
}
#spacer {
width: 10%;
}
.top-toolbar {
display: inline-flex;
flex-direction: row;
justify-content: center;
align-items: center;
margin-right: 10%;
gap: 10%;
}
.top-buttons {
background-color: rgba(0, 0, 0, 0);
color: white;
border-radius: 5px;
border-color: white;
border-style: solid;
padding: 0.5%;
cursor: pointer;
}
.top-buttons:hover {
color: blue;
border-color: blue;
}

View File

@@ -1,118 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<!-- Include Quill stylesheet -->
<meta charset="utf-8">
<title>New Mail :: Webmail | Language School Hossegor - Admin</title>
<link rel="stylesheet" href="/admin/plugins/newsletter/css/mailCompose.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<link href="https://cdn.quilljs.com/1.0.0/quill.snow.css" rel="stylesheet" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200">
</head>
<body>
<div id="top-bar">
<div class="top-toolbar">
<a href="/admin/plugins" title="Back to admin panel" id="back" class="top-buttons"><span class="material-symbols-outlined">arrow_back</span></a>
</div>
<p id="title">Newsletter plugin - libreevent</p>
<p id="spacer"></p>
</div>
<div class="mail-app">
<h1>New newsletter</h1>
<table>
<tr>
<td>
<label for="subject">Subject:</label>
</td><td>
<input type="text" name="subject" id="subject"><br>
</td>
</tr>
</table>
<!-- Create the toolbar container -->
<div id="toolbar">
<span class="ql-formats">
<select class="ql-font" title="Fonts">
<option selected="" title="Default"></option>
<option value="serif" title="Serif"></option>
<option value="monospace" title="Monospace"></option>
</select>
<select class="ql-size" title="Font size">
<option value="small" title="Small"></option>
<option selected="" title="Default"></option>
<option value="large" title="Large"></option>
<option value="huge" title="Huge"></option>
</select>
</span>
<span class="ql-formats">
<button class="ql-bold" title="Bold"></button>
<button class="ql-italic" title="Italic"></button>
<button class="ql-underline" title="Underlined"></button>
<button class="ql-strike" title="Strikethrough"></button>
</span>
<span class="ql-formats">
<select class="ql-color" title="Text colour"></select>
<select class="ql-background" title="Background colour"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered" title="Ordered list"></button>
<button class="ql-list" value="bullet" title="Bullet points"></button>
<select class="ql-align" title="Alignment">
<option selected="" title="left"></option>
<option value="center" title="center"></option>
<option value="right" title="right"></option>
<option value="justify" title="block"></option>
</select>
</span>
<span class="ql-formats">
<button class="ql-link" title="Insert link"></button>
<button class="ql-image" title="Insert image"></button>
</span>
</div>
<!-- Create the editor container -->
<div id="editor">
</div>
<button onclick="sendMail()"><span class="material-symbols-outlined">send</span>Send</button>
</div>
<!-- Include the Quill library -->
<script src="https://cdn.quilljs.com/1.0.0/quill.js"></script>
<!-- Initialize Quill editor -->
<script>
var editor = new Quill( '#editor', {
modules: { toolbar: '#toolbar' },
theme: 'snow',
} );
function sendMail () {
if ( !document.getElementById( 'subject' ).value ) {
alert( 'An email subject is required!' );
return;
}
let dat = { 'subject': document.getElementById( 'subject' ).value, 'message': document.getElementsByClassName( 'ql-editor' )[0].innerHTML };
let options = {
method: 'post',
body: JSON.stringify( dat ),
headers: {
'Content-Type': 'application/json',
'charset': 'utf-8'
},
};
fetch( '/admin/plugins/newsletter/send', options ).then( res => {
if ( res.status === 200 ) {
alert( 'Newsletter sent successfully' );
} else if ( res.status === 403 ) {
alert( 'It appears that you have been logged out or have logged out somewhere else. Please ensure that you are logged in and try again!' );
} else {
alert( 'There was an error sending the mail. Please try again. If the error persists, please contact the developer and tell this status code: ' + res.status );
}
} );
document.getElementById( 'subject' ).value = '';
document.getElementsByClassName( 'ql-editor' )[0].innerHTML = '';
};
</script>
</body>
</html>

View File

@@ -1,32 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Unsubscribe from Newsletter</title>
<link rel="stylesheet" href="/css/errorstyle.css">
<style>
.input {
width: 40%;
padding: 0.5%;
font-size: 120%;
border-radius: 15px;
margin: 2%;
margin-bottom: 4%;
}
</style>
</head>
<body>
<div class="content">
<h1>Unsubscribe from newsletter</h1>
<form action="/mail/unsubscribe/go?lang=en" method="post" class="unsub">
<label for="mail" style="font-size: 120%;">Your email address</label><br>
<input type="email" name="mail" id="mail" class="input" required><br>
<input type="submit" value="Unsubscribe" class="button" style="font-size: 130%;">
</form>
<a href="?lang=de" class="button" style="margin-top: 4%;">DE</a>
</div>
</body>
</html>

View File

@@ -1,33 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Unsubscribed from Newsletter</title>
<link rel="stylesheet" href="/css/errorstyle.css">
<style>
.input {
width: 40%;
padding: 0.5%;
font-size: 120%;
border-radius: 15px;
margin: 2%;
margin-bottom: 4%;
}
</style>
</head>
<body>
<div class="content">
<h1>Unsubscribed from newsletter</h1>
<p>You have successfully unsubscribed from the newsletter.</p>
</div>
<script>
setTimeout( redirect, 7000 );
function redirect () {
location.href = '/';
}
</script>
</body>
</html>

View File

@@ -1,26 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Error - Unsubscribe from Newsletter</title>
<link rel="stylesheet" href="/css/errorstyle.css">
<style>
.input {
width: 40%;
padding: 0.5%;
font-size: 120%;
border-radius: 15px;
margin: 2%;
margin-bottom: 4%;
}
</style>
</head>
<body>
<div class="content">
<h1>An error occurred whilst unsubscribing from the newsletter</h1>
<p>Please check your entries and try again!</p>
</div>
</body>
</html>

View File

@@ -1,49 +0,0 @@
/*
* libreevent - newsletterRoutes.js
*
* Created by Janis Hutz 08/13/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const path = require( 'path' );
const mm = require( './sender.js' );
const bodyParser = require( 'body-parser' );
module.exports = ( app, settings ) => {
const sendMail = new mm( settings );
app.get( '/admin/plugins/newsletter', ( request, response ) => {
if ( request.session.loggedInAdmin ) {
response.sendFile( path.join( __dirname + '/html/compose.html' ) );
} else {
response.status( 403 ).send( 'unauthenticated' );
}
} );
app.get( '/admin/plugins/newsletter/css/:file', ( req, res ) => {
res.sendFile( path.join( __dirname + '/css/' + req.params.file ) );
} );
app.post( '/admin/plugins/newsletter/send', bodyParser.json(), ( request, response ) => {
if ( request.session.loggedInAdmin ) {
response.send( 'ok' );
sendMail.send( request.body.message, request.body.subject );
} else {
response.status( 403 ).send( 'unauthenticated' );
}
} );
app.get( '/mail/unsubscribe', ( request, response ) => {
response.sendFile( path.join( __dirname + '/html/unsubscribe.html' ) );
} );
app.post( '/mail/unsubscribe/go', ( request, response ) => {
if ( !request.body.mail ) {
response.sendFile( path.join( __dirname + '/html/unsubscribeError.html' ) );
} else {
sendMail.unsubscribe( request.body.mail );
response.sendFile( path.join( __dirname + '/html/unsubscribeComplete.html' ) );
}
} );
};

View File

@@ -1,13 +0,0 @@
{
"pluginName": "Newsletter",
"pluginDescription": "Send newsletters to your customers using a file editor",
"creator": "Janis Hutz",
"maintainer": "Janis Hutz",
"pluginWebsite": "https://libreevent.janishutz.com/plugins/newsletter",
"pluginDocs": "https://libreevent.janishutz.com/docs/plugins/newsletter",
"gitURL": "https://github.com/simplePCBuilding/libreevent/tree/master/src/server/backend/plugins/others/newsletter",
"settingsURL": "/admin/plugins/newsletter",
"mainPluginURL": "/admin/plugins/newsletter",
"logo": "",
"version": "1.0.0"
}

View File

@@ -1,33 +0,0 @@
/*
* libreevent - sender.js
*
* Created by Janis Hutz 09/16/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const mm = require( '../../../mail/mailSender.js' );
const mailManager = new mm();
const db = require( '../../../db/db.js' );
class sendMail {
constructor( settings ) {
this.settings = settings;
}
send ( message, subject ) {
db.getDataSimple( 'users', 'marketing', 'true' ).then( users => {
for ( let user in users ) {
mailManager.sendMail( users[ user ].email, message, subject, this.settings.mailSender );
}
} );
}
unsubscribe ( mail ) {
db.writeDataSimple( 'users', 'email', mail, { 'marketing': false } );
}
}
module.exports = sendMail;

View File

@@ -1,47 +0,0 @@
#popup, #popup-data {
display: none;
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
background-color: rgba( 0, 0, 0, 0.5 );
}
.popup-positioning {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.submit {
margin-top: 2%;
background: linear-gradient(90deg, rgb(30, 36, 131), rgb(87, 66, 184), rgb(105, 115, 214), rgb(30, 36, 131), rgb(41, 128, 109), rgb(146, 50, 47));
background-size: 300px;
padding: 10px 20px;
border: none;
border-radius: 20px;
cursor: pointer;
transition: all 3s;
font-size: 75%;
color: white;
}
.submit:hover {
background-size: 200%;
background-position: -100%;
}
.popup-main {
border: none;
border-radius: 20px;
padding: 5%;
background-color: rgb(34, 34, 34);
color: white;
max-width: 70%;
max-height: 70%;
overflow-y: scroll;
}

View File

@@ -1,57 +0,0 @@
html {
height: 98%;
}
body {
font-family: monospace;
height: 100%;
width: 100%;
background-color: rgb(114, 164, 173);
}
.title-area {
font-size: 1000%;
margin: 0;
padding: 0;
}
.title-area h1 {
margin: 0;
padding: 0;
}
.content {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.small {
font-style: italic;
font-size: 80%;
}
.main {
width: 70%;
height: 40%;
font-size: 150%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.form {
width: 70%;
}
.textarea {
width: 100%;
padding: 2%;
border-radius: 10px;
resize: vertical;
font-size: 90%;
}

View File

@@ -1,298 +0,0 @@
nig
cum
pron
jap
xxx
cok
kkk
Africoon
akata
Beaner
Camel jockey
Chink
Coon
Coonass
Dune Coon
Gook
Jungle Bunny
Niglet
Nignog
Porch Monkey
Towel Head
Turk
Wetback
Wigger
Cock
Fag
Kock
Pecker
Tit
Wang
wank
Willy
Willies
Anal
Slut
Retard
Retarded
niga
Raped
Rape
Rapist
Semen
Cums
Cum
Fag
Fags
Tit
Porn
Slut
Smut
Teets
Tits
Jiz
doosh
Dick
Cunt
Boob
scat
slut
twat
pube
pubes
twat
retards
rimming
milf
nazi
orgy
porn
pussy
niggar
Nigga
Nigger
assfucker
assfucka
b00b
b00bs
ballsack
beastial
beastiality
bestial
bestiality
blow job
blowjob
blowjobs
boner
boobs
booobs
boooobs
booooobs
buttplug
c0ck
c0cksucker
chink
cl1t
clit
clitoris
clits
cock-sucker
cockface
cockhead
cockmunch
cockmuncher
cocks
cocksuck
cocksucked
cocksucker
cocksucking
cocksucks
cocksuka
cocksukka
cokmuncher
coksucka
cummer
cumshot
cunilingus
cunillingus
cunnilingus
cuntlick
cuntlicker
cuntlicking
cunts
cyalis
cyberfuc
cyberfuck
cyberfucked
cyberfucker
cyberfuckers
cyberfucking
d1ck
dickhead
dildo
dildos
dog-fucker
doggin
dogging
donkeyribber
doosh
duche
Douche
ejaculate
ejaculated
ejaculates
ejaculating
ejaculatings
ejaculation
ejakulate
F4nny
fagging
faggitt
faggot
faggs
fagot
fagots
fatass
felching
fellate
fellatio
fingerfuck
fingerfucked
fingerfucker
fingerfuckers
fingerfucking
fingerfucks
fistfuck
fistfucked
fistfucker
fistfuckers
fistfucking
fistfuckings
fistfucks
fuckhead
fuckheads
fuckingshitmotherfucker
fuckme
fuckwhit
fuckwit
fukwit
fukwhit
gangbang
gangbanged
gangbangs
gaysex
goatse
hardcoresex
horniest
horny
hotsex
jack-off
jackoff
jerk-off
jism
jizm
jizz
kawk
kondum
kondums
kummer
humming
kunilingus
l3i+ch
l3itch
labia
m0f0
m0fo
m45terbate
ma5terb8
ma5terbate
masochist
master-bate
masterb8
masterbat*
masterbat3
masterbate
masterbation
masterbations
masturbate
nig
n1g
n1gga
n1gger
nazi
nigg3r
nigg4h
nigga
niggah
niggas
niggaz
nigger
niggers
nutsack
orgasim
orgasims
orgasm
orgasms
p0rn
penis
phonesex
penisfucker
phuck
phuk
phuked
phuking
phukked
phukking
phuks
pigfucker
pissoff
porno
pornography
pornos
pusse
pussi
pussies
pussy
pussys
rectum
rimjaw
sadist
schlong
scrotum
shaggin
shagging
shitdick
shited
shitfuck
shithead
sluts
smegma
spunk
t1tt1e5
t1tties
testical
testicle
titfuck
tittie5
tittiefucker
titties
tittyfuck
tittywank
titwank
tw4t
twathead
twatty
twunt
twunter
v14gra
v1gra
vagina
viagra
vulva
w00se
wanker
wanky
whore
xrated
hitler
putin

View File

@@ -1,108 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=7">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Settings - Polls :: libreevent-plugin</title>
<link rel="stylesheet" href="/polls/css/style.css">
<link rel="stylesheet" href="/polls/css/popup.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<style>
.material-symbols-outlined {
font-variation-settings:
'FILL' 0,
'wght' 400,
'GRAD' 0,
'opsz' 24
}
.voting-wrapper {
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
}
.voting {
border-radius: 500px;
border: 1px black solid;
font-size: 150%;
cursor: pointer;
}
.voting-counter {
margin: 0;
font-size: 150%;
margin-left: 10px;
margin-right: 10px;
}
body {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.content {
justify-content: flex-start;
}
.input {
width: 30vw;
padding: 20px;
border-radius: 20px;
border: none;
margin-bottom: 1vh;
margin-top: 5px;
}
.poll {
border: black 2px solid;
padding: 1% 10%;
}
.selected {
background-color: green;
}
</style>
</head>
<body>
<div class="content" id="app">
<h1>Polls plugin - Settings</h1>
<div style="margin-bottom: 1%;">
<a href="/admin/plugins">Back to the admin panel</a><br>
<button @click="addPoll();">Add new poll</button>
</div>
<div v-for="poll in polls" class="poll">
<h3>{{ poll.display }}</h3>
<p>{{ poll.comment }}</p>
<button @click="editPoll( poll.id )">Edit</button>
</div>
<div id="popup">
<div class="popup-positioning">
<div class="popup-main">
<form id="popup-message">
<h2 style="font-size: 200%;">{{ operation }} poll</h2>
<label for="title">Poll title</label><br>
<input type="text" v-model="newPoll.display" name="title" id="title" class="input"><br>
<label for="id">Poll id</label><br>
<input type="text" v-model="newPoll.id" name="id" id="id" class="input"><br>
<label for="comment">Comments</label><br>
<textarea type="text" v-model="newPoll.comment" name="comment" id="comment" class="input" rows="5"></textarea><br>
<label for="id">Allow adding suggestions</label>
<input type="checkbox" v-model="newPoll.allowAdding" name="allowAdding" id="allowAdding"><br>
</form>
<button @click="save()" class="submit">Save</button>
<button @click="closePopup()" class="submit">Cancel</button>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="/polls/js/settings.js"></script>
</body>
</html>

View File

@@ -1,146 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=7">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Polls :: libreevent-plugin</title>
<link rel="stylesheet" href="/polls/css/style.css">
<link rel="stylesheet" href="/polls/css/popup.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<style>
.material-symbols-outlined {
font-variation-settings:
'FILL' 0,
'wght' 400,
'GRAD' 0,
'opsz' 24
}
.voting-wrapper {
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
}
.voting {
border-radius: 500px;
border: 1px black solid;
font-size: 150%;
cursor: pointer;
}
.voting-counter {
margin: 0;
font-size: 150%;
margin-left: 10px;
margin-right: 10px;
}
body {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.content {
justify-content: flex-start;
}
.input {
width: 30vw;
padding: 20px;
border-radius: 20px;
border: none;
margin-bottom: 1vh;
margin-top: 5px;
}
.entry {
border: black 2px solid;
padding: 1% 10%;
width: 40vw;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.selected {
background-color: green;
}
.comment {
width: 50%;
text-align: center;
}
.wrapper {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-bottom: 5vh;
}
</style>
</head>
<body>
<div class="content" id="app">
<div class="wrapper" v-if="hasLoadedBasics && hasLoadedVotes">
<div v-if="votingDetails.display" class="wrapper">
<h1>Voting on {{ votingDetails.display ?? 'untitled' }}</h1>
<p v-if="votingDetails.description" class="comment">{{ votingDetails.description }}</p>
<div style="margin-bottom: 0.5%;" v-if="votingDetails.allowAdding">
<button onclick="location.href = '/'">Back to website</button>
<button @click="addSuggestion();">Add suggestion</button>
</div>
<select v-model="sorting" style="margin-bottom: 1%;">
<option value="newest">Newest</option>
<option value="oldest">Oldest</option>
<option value="nameUp">Alphabetically (A-Z)</option>
<option value="nameDown">Alphabetically (Z-A)</option>
<option value="mostVoted">Most popular</option>
<option value="leastVoted">Least popular</option>
</select>
<div v-for="entry in orderedVotes" class="entry">
<h3>{{ entry.title }}</h3>
<p>{{ entry.comment }}</p>
<div class="voting-wrapper">
<span class="material-symbols-outlined voting" @click="vote( 'up', entry.id )" :class="votedOn[ entry.id ] === 'up' ? 'selected' : ''">arrow_upward</span>
<p class="voting-counter">{{ entry.count ?? 0 }}</p>
<span class="material-symbols-outlined voting" @click="vote( 'down', entry.id )" :class="votedOn[ entry.id ] === 'down' ? 'selected' : ''">arrow_downward</span>
</div>
</div>
<div id="popup">
<div class="popup-positioning">
<div class="popup-main">
<form id="popup-message">
<h2 style="font-size: 200%;">Add new suggestion</h2>
<label for="title">Suggestion title</label><br>
<input type="text" v-model="newSuggestion.title" name="title" id="title" class="input"><br>
<label for="title">Comments</label><br>
<textarea type="text" v-model="newSuggestion.comment" name="comment" id="comment" class="input" rows="5"></textarea><br>
</form>
<button @click="save()" class="submit">Add</button>
<button @click="closePopup()" class="submit">Cancel</button>
</div>
</div>
</div>
</div>
<div v-else style="display: flex; justify-content: center; align-items: center; height: 100vh; font-size: 110%;">
<h1>This poll does not exist!</h1>
</div>
</div>
<div v-else>
<h1>Loading...</h1>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="/polls/js/voting.js"></script>
</body>
</html>

View File

@@ -1,64 +0,0 @@
/* eslint-disable no-undef */
const { createApp } = Vue;
createApp( {
data() {
return {
polls: {},
newPoll: {},
operation: 'Add new',
};
},
methods: {
getData() {
fetch( '/admin/plugins/polls/getData' ).then( response => {
response.json().then( data => {
this.polls = data;
this.newPoll = {};
} );
} );
},
save() {
if ( this.newPoll.comment && this.newPoll.display && this.newPoll.id ) {
this.polls[ this.newPoll.id ] = this.newPoll;
let fetchOptions = {
method: 'post',
body: JSON.stringify( this.polls ),
headers: {
'Content-Type': 'application/json',
'charset': 'utf-8'
},
};
fetch( '/admin/plugins/polls/save', fetchOptions ).then( response => {
if ( response.status !== 200 ) {
alert( 'there was an error updating' );
}
} );
this.closePopup();
this.getData();
} else {
alert( 'Not all required fields are filled out!' );
}
},
closePopup() {
$( '#popup' ).fadeOut( 500 );
$( 'body' ).removeClass( 'menuOpen' );
this.getData();
},
addPoll () {
this.newPoll = { 'allowAdding': true };
this.operation = 'Add new';
$( '#popup' ).fadeIn( 500 );
$( 'body' ).addClass( 'menuOpen' );
},
editPoll ( pollID ) {
this.operation = 'Edit';
this.newPoll = this.polls[ pollID ];
$( '#popup' ).fadeIn( 500 );
$( 'body' ).addClass( 'menuOpen' );
}
},
mounted() {
this.getData();
}
} ).mount( '#app' );

View File

@@ -1,184 +0,0 @@
/* eslint-disable no-undef */
const { createApp } = Vue;
createApp( {
data() {
return {
entries: {},
newSuggestion: {},
votingDetails: {},
votedOn: {},
hasLoadedBasics: false,
hasLoadedVotes: false,
sorting: 'newest',
userIdentifier: '',
};
},
computed: {
orderedVotes() {
if ( this.sorting === 'oldest' ) {
return Object.values( this.entries );
} else if ( this.sorting === 'newest' ) {
const ent = Object.keys( this.entries ).reverse();
let ret = [];
for ( let entry in ent ) {
ret.push( this.entries[ ent[ entry ] ] );
}
return ret;
} else {
let ent = Object.keys( this.entries ).sort( ( a, b ) => {
if ( this.sorting === 'nameUp' ) {
return this.entries[ a ].title.localeCompare( this.entries[ b ].title );
} else if ( this.sorting === 'nameDown' ) {
return this.entries[ b ].title.localeCompare( this.entries[ a ].title );
} else if ( this.sorting === 'mostVoted' ) {
return this.entries[ b ].count - this.entries[ a ].count;
} else if ( this.sorting === 'leastVoted' ) {
return this.entries[ a ].count - this.entries[ b ].count;
}
} );
let ret = [];
for ( let entry in ent ) {
ret.push( this.entries[ ent[ entry ] ] );
}
return ret;
}
}
},
methods: {
getData() {
fetch( '/polls/get/' + location.pathname.substring( 7 ) ).then( response => {
response.json().then( data => {
this.entries = data;
this.fingerprint();
let fetchOptions = {
method: 'post',
body: JSON.stringify( { 'fingerprint': this.userIdentifier } ),
headers: {
'Content-Type': 'application/json',
'charset': 'utf-8'
},
};
fetch( '/voting/getVotedOn/' + location.pathname.substring( 8 ), fetchOptions ).then( res => {
if ( res.status === 200 ) {
res.json().then( json => {
this.votedOn = JSON.parse( localStorage.getItem( 'itemsVotedOn' ) ?? '{}' );
for ( let el in json ) {
if ( json[ el ] === 1 ) {
this.votedOn[ json[ el ] ] = 'up';
} else if ( json[ el ] === -1 ) {
this.votedOn[ json[ el ] ] = 'down';
}
}
localStorage.setItem( 'itemsVotedOn', JSON.stringify( this.votedOn ) );
this.hasLoadedVotes = true;
} );
}
} );
} );
} );
fetch( '/polls/getDetails/' + location.pathname.substring( 7 ) ).then( response => {
response.json().then( data => {
this.votingDetails = data;
this.hasLoadedBasics = true;
} );
} );
this.votedOn = JSON.parse( localStorage.getItem( 'itemsVotedOn' ) ?? '{}' );
},
fingerprint() {
// I am forced to do this because there are idiots in this world
// https://stackoverflow.com/questions/27247806/generate-unique-id-for-each-device
if ( !this.userIdentifier ) {
const navigator_info = window.navigator;
const screen_info = window.screen;
let uid = navigator_info.mimeTypes.length;
uid += navigator_info.userAgent.replace( /\D+/g, '' );
uid += navigator_info.plugins.length;
uid += screen_info.height || '';
uid += screen_info.width || '';
uid += screen_info.pixelDepth || '';
this.userIdentifier = uid;
}
},
save() {
if ( this.newSuggestion.comment && this.newSuggestion.title ) {
let alreadyExists = false;
for ( let el in this.entries ) {
if ( this.entries[ el ][ 'title' ].toLocaleLowerCase() === this.newSuggestion.title.toLocaleLowerCase() ) {
alreadyExists = true;
}
}
if ( !alreadyExists ) {
let fetchOptions = {
method: 'post',
body: JSON.stringify( this.newSuggestion ),
headers: {
'Content-Type': 'application/json',
'charset': 'utf-8'
},
};
fetch( '/polls/add/' + location.pathname.substring( 8 ), fetchOptions ).then( response => {
if ( response.status === 418 ) {
alert( 'One or more of the words in either your description or title is on our blocklist. Please make sure that you are not using any words that are NSFW, racist or similar.' );
} else if ( response.status !== 200 ) {
alert( 'there was an error updating' );
} else {
this.closePopup();
this.getData();
}
} );
} else {
alert( 'An entry with this name exists already. Please vote on that entry.' );
}
} else {
alert( 'Not all required fields are filled out!' );
}
},
vote( type, suggestionID ) {
this.fingerprint();
let voteType = type;
let didDeactivate = false;
if ( this.votedOn[ suggestionID ] === type ) {
didDeactivate = true;
if ( type === 'up' ) {
voteType = 'down';
} else {
voteType = 'up';
}
} else if ( this.votedOn[ suggestionID ] ) {
return;
}
let fetchOptions = {
method: 'post',
body: JSON.stringify( { 'voteType': voteType, 'id': suggestionID, 'fingerprint': this.userIdentifier } ),
headers: {
'Content-Type': 'application/json',
'charset': 'utf-8'
},
};
fetch( '/polls/vote/' + location.pathname.substring( 7 ), fetchOptions ).then( response => {
if ( response.status === 409 ) {
alert( 'You have already voted on this!' );
} else if ( response.status !== 200 ) {
alert( 'there was an error updating' );
} else {
this.votedOn[ suggestionID ] = didDeactivate ? undefined : voteType;
localStorage.setItem( 'itemsVotedOn', JSON.stringify( this.votedOn ) );
this.getData();
}
} );
},
closePopup() {
$( '#popup' ).fadeOut( 500 );
$( 'body' ).removeClass( 'menuOpen' );
this.getData();
},
addSuggestion () {
$( '#popup' ).fadeIn( 500 );
$( 'body' ).addClass( 'menuOpen' );
}
},
mounted() {
this.getData();
}
} ).mount( '#app' );

View File

@@ -1,13 +0,0 @@
{
"pluginName": "Polls",
"pluginDescription": "Create polls to ask the customers questions about your event!",
"creator": "Janis Hutz",
"maintainer": "Janis Hutz",
"pluginWebsite": "https://libreevent.janishutz.com/plugins/polls",
"pluginDocs": "https://libreevent.janishutz.com/docs/plugins/polls",
"gitURL": "https://github.com/simplePCBuilding/libreevent/tree/master/src/server/backend/plugins/others/poll",
"settingsURL": "/admin/plugins/polls",
"mainPluginURL": "/voting",
"logo": "",
"version": "1.0.0"
}

View File

@@ -1,185 +0,0 @@
/*
* libreevent - pollRoutes.js
*
* Created by Janis Hutz 08/13/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const path = require( 'path' );
const fs = require( 'fs' );
const bodyParser = require( 'body-parser' );
// Memory caching system to prevent downtime
// This is basically the same system as can be found in the JSON db's saving system
let votingMemCache = JSON.parse( fs.readFileSync( path.join( __dirname + '/data/voting.json' ) ) );
let hasToSave = false;
let isReadyToSave = true;
const saveVotingData = () => {
if ( isReadyToSave ) {
isReadyToSave = false;
hasToSave = false;
runSave();
} else {
hasToSave = true;
}
};
const runSave = () => {
fs.writeFile( path.join( __dirname + '/data/voting.json' ), JSON.stringify( votingMemCache ), ( err ) => {
if ( err ) {
console.error( err );
} else {
isReadyToSave = true;
if ( hasToSave ) {
runSave();
}
}
} );
};
// There are idiots in this world so we have to filter
// Filter list is from here: https://github.com/BurntRouter/filtered-word-lists plus some extra words
let uidCrossReference = JSON.parse( fs.readFileSync( path.join( __dirname + '/data/voteUIDReferencing.json' ) ) );
const fl = '' + fs.readFileSync( path.join( __dirname + '/data/filterlist.txt' ) );
let votingDirections = { '1': 'up', '-1': 'down' };
let filterList = [];
let prevStart = 0;
for ( let e in fl ) {
if ( fl[ e ] === '\n' ) {
filterList.push( fl.slice( prevStart, e ).toLocaleLowerCase() );
prevStart = parseInt( e ) + 1;
}
}
const filtering = ( string ) => {
// using these filter lists: https://github.com/BurntRouter/filtered-word-lists plus some extra words
// returns true if text is ok
const str = string.toLocaleLowerCase();
for ( let badWord in filterList ) {
if ( str.includes( filterList[ badWord ] ) ) {
return false;
}
}
return true;
};
module.exports = ( app ) => {
app.get( '/polls/:vote', ( req, res ) => {
res.sendFile( path.join( __dirname + '/html/voting.html' ) );
} );
app.get( '/polls/css/:file', ( req, res ) => {
res.sendFile( path.join( __dirname + '/css/' + req.params.file ) );
} );
app.get( '/polls/js/:file', ( req, res ) => {
res.sendFile( path.join( __dirname + '/js/' + req.params.file ) );
} );
app.get( '/polls/getDetails/:vote', ( req, res ) => {
fs.readFile( path.join( __dirname + '/data/votingSettings.json' ), ( error, filedata ) => {
res.send( JSON.parse( filedata )[ req.params.vote ] ?? {} );
} );
} );
app.post( '/polls/getVotedOn/:vote', ( req, res ) => {
res.send( uidCrossReference[ req.body.fingerprint ] ?? {} );
} );
app.get( '/polls/get/:vote', ( req, res ) => {
res.send( votingMemCache[ req.params.vote ] );
} );
app.post( '/polls/vote/:vote/', bodyParser.json(), ( req, res ) => {
// up / down-voting
if ( req.body.fingerprint != '' ) {
if ( uidCrossReference[ req.body.fingerprint ] ) {
if ( uidCrossReference[ req.body.fingerprint ][ req.params.vote ] ) {
if ( uidCrossReference[ req.body.fingerprint ][ req.params.vote ][ req.body.id ] !== 0 ) {
const vote = uidCrossReference[ req.body.fingerprint ][ req.params.vote ][ req.body.id ];
if ( votingDirections[ vote ] === req.body.voteType ) {
res.status( 409 ).send( 'alreadyVotedOn' );
return;
}
} else {
uidCrossReference[ req.body.fingerprint ][ req.params.vote ][ req.body.id ] = 0;
}
} else {
uidCrossReference[ req.body.fingerprint ][ req.params.vote ] = {};
}
} else {
uidCrossReference[ req.body.fingerprint ] = {};
uidCrossReference[ req.body.fingerprint ][ req.params.vote ] = {};
uidCrossReference[ req.body.fingerprint ][ req.params.vote ][ req.body.id ] = 0;
}
if ( votingMemCache[ req.params.vote ] ) {
if ( votingMemCache[ req.params.vote ][ req.body.id ] ) {
if ( req.body.voteType === 'up' ) {
votingMemCache[ req.params.vote ][ req.body.id ].count += 1;
uidCrossReference[ req.body.fingerprint ][ req.params.vote ][ req.body.id ] += 1;
} else if ( req.body.voteType === 'down' ) {
votingMemCache[ req.params.vote ][ req.body.id ].count -= 1;
uidCrossReference[ req.body.fingerprint ][ req.params.vote ][ req.body.id ] -= 1;
}
saveVotingData();
res.send( 'ok' );
} else {
res.status( 404 ).send( 'No Entry on this vote with' );
}
} else {
res.status( 404 ).send( 'No Vote with this ID' );
}
} else {
res.status( 400 ).send( 'Wrong request' );
}
} );
app.post( '/polls/add/:vote', bodyParser.json(), ( req, res ) => {
let data = req.body;
if ( data.title && data.comment ) {
if ( filtering( data.title ) && filtering( data.comment ) ) {
if ( !votingMemCache[ req.params.vote ] ) {
votingMemCache[ req.params.vote ] = {};
}
const id = parseInt( Object.keys( votingMemCache[ req.params.vote ] )[ Object.keys( votingMemCache[ req.params.vote ] ).length - 1 ] ?? 0 ) + 1;
votingMemCache[ req.params.vote ][ id ] = data;
votingMemCache[ req.params.vote ][ id ][ 'id' ] = id;
votingMemCache[ req.params.vote ][ id ][ 'count' ] = 1;
saveVotingData();
res.send( 'ok' );
} else {
res.status( 418 ).send( 'seriously?' );
}
} else {
res.status( 400 ).send( 'incomplete' );
}
} );
app.get( '/admin/plugins/polls', ( req, res ) => {
if ( req.session.loggedInAdmin ) {
res.sendFile( path.join( __dirname + '/html/settings.html' ) );
} else {
res.status( 403 ).send( 'unauthorized' );
}
} );
app.get( '/admin/plugins/polls/getData', ( req, res ) => {
if ( req.session.loggedInAdmin ) {
res.sendFile( path.join( __dirname + '/data/votingSettings.json' ) );
} else {
res.status( 403 ).send( 'unauthorized' );
}
} );
app.post( '/admin/plugins/polls/save', bodyParser.json(), ( req, res ) => {
if ( req.session.loggedInAdmin ) {
fs.writeFileSync( path.join( __dirname + '/data/votingSettings.json' ), JSON.stringify( req.body ) );
res.send( 'ok' );
} else {
res.status( 403 ).send( 'unauthorized' );
}
} );
};

View File

@@ -1,18 +0,0 @@
# Creating new payment provider integrations
All payment gateway integration plugins have to be in their own folders. This folder has to contain a file called [Payment gateway name as camelCase word (starting in small letter)]Routes.js, a file called plugin.json and any number of other files that it needs.
You will also need to add documentation for the user to set up the payment gateway. Read on below to find out how.
## Setting up the routes.js file
Take some inspiration of the stripe or payrexx setup as these are officially supported by the system and have been developed by the original creator.
The express.js routes it has to expose are the following:
- /payments/prepare (POST) (returns object: { 'link': [Link to the payment gateway payment], 'id': [Purchase ID to subscribe to status updating] })
- /payments/getStatus (SSE) (sends updates to the UI once subscribed (like payment success) using the id sent by /payments/prepare)
It can contain any number of (not interfering) routes. Please always use the /payments/ route as a base to avoid running into problems.
## configOption.json
This file contains the settings that should be available in the settings page of libreevent. It should contain the following fields, as required by the settings.vue module.

View File

@@ -1,4 +0,0 @@
{
"APISecret": "",
"instance": ""
}

View File

@@ -1,16 +0,0 @@
{
"APIKey": {
"display": "API key",
"id": "APIKey",
"tooltip":"This is the secret key API key you can get from the stripe dashboard. Please make a test purchase (FREE) before you go live!",
"value": "",
"type": "text"
},
"instance": {
"display": "Instance",
"id": "instance",
"tooltip":"Please specify the instance name you used to create the payrexx account. (e.g. libreevent)",
"value": "",
"type": "text"
}
}

View File

@@ -1,62 +0,0 @@
/*
* libreevent - module.payrexx.js
*
* Created by Janis Hutz 08/12/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const fs = require( 'fs' );
const path = require( 'path' );
const qs = require( 'qs' );
const axios = require( 'axios' );
const Base64 = require( 'crypto-js/enc-base64' );
const hmacSHA256 = require( 'crypto-js/hmac-sha256' );
const payrexxConfig = JSON.parse( fs.readFileSync( path.join( __dirname + '/config.payments.json' ) ) );
const baseUrl = 'https://api.payrexx.com/v1.0/';
if ( !payrexxConfig.APISecret || !payrexxConfig.instance ) {
throw new Error( '[ Payrexx ] No API secret or instance name configured!' );
}
const instance = payrexxConfig.instance;
const secret = payrexxConfig.APISecret;
exports.init = function () {
function buildSignature ( query = '' ) {
return Base64.stringify( hmacSHA256( query, secret ) );
}
function buildBaseUrl ( path ) {
return baseUrl + path + '?instance=' + instance;
}
return {
getGateway: function ( id ) {
const baseUrl = buildBaseUrl( 'Gateway/' + id + '/' );
const url = baseUrl + '&ApiSignature=' + encodeURIComponent( buildSignature() );
return axios.get( url );
},
createGateway: function ( params ) {
if ( !params.amount ) {
return false;
}
const defaultParams = {
currency: 'CHF'
};
let queryParams = Object.assign( {}, defaultParams, params );
const queryStr = qs.stringify( queryParams, { format: 'RFC1738' } );
const signature = buildSignature( queryStr );
queryParams.ApiSignature = signature;
const queryStrSigned = qs.stringify( queryParams );
const baseUrl = buildBaseUrl( 'Gateway/' );
return axios.post( baseUrl, queryStrSigned );
}
};
};

View File

@@ -1,210 +0,0 @@
/*
* libreevent - payrexxRoutes.js
*
* Created by Janis Hutz 08/12/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const db = require( '../../../db/db.js' );
const bodyParser = require( 'body-parser' );
const ticket = require( '../../../tickets/ticketGenerator.js' );
const payrexxModule = require( './module.payrexx.js' );
const payrexx = payrexxModule.init();
const TicketGenerator = new ticket();
const generator = require( '../../../token.js' );
let sessionReference = {};
let waitingClients = {};
let pendingPayments = {};
let gatewayReference = {};
let paymentOk = {};
module.exports = ( app, settings ) => {
app.post( '/payments/prepare', bodyParser.json(), ( req, res ) => {
if ( req.session.loggedInUser ) {
db.getDataSimple( 'users', 'email', req.session.username ).then( user => {
if ( user[ 0 ] ) {
if ( user[ 0 ][ 'mail_confirmed' ] ) {
let purchase = {
'successRedirectUrl': settings.yourDomain + '/payments/success',
'cancelRedirectUrl': settings.yourDomain + '/payments/canceled',
'failedRedirectUrl': settings.yourDomain + '/payments/failed',
'currency': settings.currency,
'basket': [],
'amount': 0,
'referenceId': req.session.id,
};
db.getDataSimple( 'temp', 'user_id', req.session.id ).then( dat => {
if ( dat[ 0 ] ) {
db.getJSONData( 'events' ).then( events => {
let data = JSON.parse( dat[ 0 ].data );
( async () => {
for ( let event in data ) {
for ( let item in data[ event ] ) {
purchase[ 'basket' ].push( {
'name': data[ event ][ item ].name,
'quantity': data[ event ][ item ].count ?? 1,
'amount': Math.round( parseFloat( events[ event ][ 'categories' ][ data[ event ][ item ].category ].price[ data[ event ][ item ][ 'ticketOption' ] ] ) * 100 ),
} );
purchase[ 'amount' ] += Math.round( parseFloat( events[ event ][ 'categories' ][ data[ event ][ item ].category ].price[ data[ event ][ item ][ 'ticketOption' ] ] ) * 100 ) * ( data[ event ][ item ].count ?? 1 );
}
}
const response = await payrexx.createGateway( purchase );
if ( response.status === 200 ) {
const session = response.data.data[ 0 ];
sessionReference[ session.id ] = { 'tok': req.session.id, 'email': req.session.username };
pendingPayments[ req.session.id ] = true;
gatewayReference[ req.session.id ] = session.id;
db.writeDataSimple( 'processingOrders', 'user_id', req.session.id, dat[ 0 ] ).then( () => {
res.send( session.link );
} );
} else {
res.status( 500 ).send( 'ERR_PAYMENT' );
}
} )();
} );
} else {
res.status( 400 ).send( 'ERR_UID_NOT_FOUND' );
}
} ).catch( error => {
console.error( '[ STRIPE ] DB ERROR: ' + error );
res.status( 500 ).send( 'ERR_DB' );
} );
} else {
res.status( 428 ).send( 'ERR_MAIL_UNCONFIRMED' );
}
} else {
res.status( 428 ).send( 'ERR_MAIL_UNCONFIRMED' );
}
} );
} else {
res.status( 403 ).send( 'ERR_UNAUTHORIZED' );
}
} );
app.get( '/payments/status', ( request, response ) => {
response.writeHead( 200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
} );
response.status( 200 );
response.flushHeaders();
response.write( 'data: connected\n\n' );
waitingClients[ request.session.id ] = response;
const ping = setInterval( () => {
if ( !pendingPayments[ request.session.id ] ) {
const stat = TicketGenerator.getGenerationStatus( request.session.id );
if ( stat === 'done' ) {
clearInterval( ping );
setTimeout( () => {
response.write( 'data: ready\n\n' );
delete waitingClients[ request.session.id ];
request.session.lastOrderID = request.session.id;
request.session.id = generator.generateToken( 30 );
response.end();
}, 2000 );
} else if ( stat === 'noTicket' ) {
clearInterval( ping );
response.write( 'data: noData\n\n' );
response.end();
delete waitingClients[ request.session.id ];
}
}
}, 2000 );
} );
app.get( '/user/2fa/ping', ( request, response ) => {
if ( paymentOk[ request.session.id ] === 'ok' ) {
delete paymentOk[ request.session.id ];
response.send( { 'status': 'paymentOk' } );
} else {
if ( !pendingPayments[ request.session.id ] ) {
const stat = TicketGenerator.getGenerationStatus( request.session.id );
if ( stat === 'done' ) {
request.session.lastOrderID = request.session.id;
request.session.id = generator.generateToken( 30 );
response.send( { 'status': 'ticketOk' } );
} else if ( stat === 'noTicket' ) {
response.send( { 'status': 'noTicket' } );
} else {
response.send( '' );
}
} else {
response.send( '' );
}
}
} );
app.post( '/payments/webhook', bodyParser.json(), async ( req, res ) => {
if ( !req.body ) {
if ( !req.body.transaction ) {
res.status( 400 ).send( 'ERR_REQ_WRONG' );
return;
}
}
if ( req.body.transaction.status === 'confirmed' ) {
const response = await payrexx.getGateway( gatewayReference[ req.body.transaction.referenceId ] );
if ( response.status === 200 ) {
const gateway = response.data.data[ 0 ];
res.status( 200 ).end();
if ( gateway.status === 'confirmed' ) {
setTimeout( () => {
if ( waitingClients[ sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ] ) {
waitingClients[ sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ].write( 'data: paymentOk\n\n' );
}
}, 1000 );
db.getDataSimple( 'processingOrders', 'user_id', sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ).then( dat => {
db.getDataSimple( 'users', 'email', sessionReference[ response.data.data[ 0 ].id ][ 'email' ] ).then( user => {
if ( user[ 0 ] && dat[ 0 ] ) {
const tickets = JSON.parse( dat[ 0 ].data );
db.writeDataSimple( 'orders', 'account_id', user[ 0 ].account_id, { 'account_id': user[ 0 ].account_id, 'tickets': dat[ 0 ].data, 'order_name': sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] } ).then( () => {
console.log( sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] );
TicketGenerator.generateTickets( sessionReference[ response.data.data[ 0 ].id ] );
db.getJSONData( 'booked' ).then( ret => {
let booked = ret ?? {};
for ( let event in tickets ) {
if ( !booked[ String( event ) ] ) {
booked[ String( event ) ] = {};
}
for ( let tik in tickets[ event ] ) {
booked[ event ][ tik ] = tickets[ event ][ tik ];
}
}
db.writeJSONData( 'booked', booked ).then( () => {
db.deleteDataSimple( 'temp', 'user_id', sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ).then( () => {
db.deleteDataSimple( 'processingOrders', 'user_id', sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ).then( () => {
delete pendingPayments[ sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ];
} ).catch( error => {
console.error( '[ PAYREXX ] ERROR whilst deleting data from DB: ' + error );
} );
} ).catch( error => {
console.error( '[ PAYREXX ] ERROR whilst deleting data from DB: ' + error );
} );
} );
db.deleteDataSimple( 'temp', 'user_id', sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ).catch( error => {
console.error( '[ PAYREXX ] ERROR whilst deleting data from DB: ' + error );
} );
} );
} );
} else {
TicketGenerator.sendErrorMail( sessionReference[ response.data.data[ 0 ].id ][ 'tok' ], sessionReference[ response.data.data[ 0 ].id ][ 'email' ] );
}
} );
} ).catch( err => {
console.error( err );
} );
}
} else {
res.send( 'ok' );
}
} else {
res.send( 'ok' );
}
} );
};

View File

@@ -1,4 +0,0 @@
{
"APIKey": "",
"endpointSecret": ""
}

View File

@@ -1,16 +0,0 @@
{
"APIKey": {
"display": "API key",
"id": "APIKey",
"tooltip":"This is the secret key API key you can get from the stripe dashboard. Please make a test purchase (FREE) before you go live!",
"value": "",
"type": "text"
},
"endpointSecret": {
"display": "Webhook endpoint secret",
"id": "endpointSecret",
"tooltip":"Please specify the endpoint secret that you can get from the stripe dashboard when creating the webhook integration",
"value": "",
"type": "text"
}
}

View File

@@ -1,204 +0,0 @@
/*
* libreevent - stripeRoutes.js
*
* Created by Janis Hutz 07/25/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const fs = require( 'fs' );
const path = require( 'path' );
const db = require( '../../../db/db.js' );
// TODO: update config files to non-secret version for final version
const stripeConfig = JSON.parse( fs.readFileSync( path.join( __dirname + '/config.payments.json' ) ) );
const stripe = require( 'stripe' )( stripeConfig[ 'APIKey' ] );
const bodyParser = require( 'body-parser' );
const ticket = require( '../../../tickets/ticketGenerator.js' );
const TicketGenerator = new ticket();
const generator = require( '../../../token.js' );
const endpointSecret = stripeConfig[ 'endpointSecret' ];
let sessionReference = {};
let waitingClients = {};
let pendingPayments = {};
let paymentOk = {};
module.exports = ( app, settings ) => {
app.post( '/payments/prepare', bodyParser.json(), ( req, res ) => {
if ( req.session.loggedInUser ) {
db.getDataSimple( 'users', 'email', req.session.username ).then( user => {
if ( user[ 0 ] ) {
if ( user[ 0 ][ 'mail_confirmed' ] ) {
let purchase = {
'line_items': [],
'mode': 'payment',
'success_url': settings.yourDomain + '/payments/success',
'cancel_url': settings.yourDomain + '/payments/canceled',
'submit_type': 'book',
'customer_email': req.session.username
};
// Get cart and prepare order
db.getDataSimple( 'temp', 'user_id', req.session.id ).then( dat => {
if ( dat[ 0 ] ) {
db.getJSONData( 'events' ).then( events => {
let data = JSON.parse( dat[ 0 ].data );
( async () => {
for ( let event in data ) {
for ( let item in data[ event ] ) {
purchase[ 'line_items' ].push( {
'price_data': {
'product_data': {
'name': data[ event ][ item ].name,
},
'currency': settings.currency,
'unit_amount': Math.round( parseFloat( events[ event ][ 'categories' ][ data[ event ][ item ].category ].price[ data[ event ][ item ][ 'ticketOption' ] ] ) * 100 ),
},
'quantity': data[ event ][ item ].count ?? 1,
} );
}
}
const session = await stripe.checkout.sessions.create( purchase );
sessionReference[ session.id ] = { 'tok': req.session.id, 'email': req.session.username };
pendingPayments[ req.session.id ] = true;
db.writeDataSimple( 'processingOrders', 'user_id', req.session.id, dat[ 0 ] ).then( () => {
res.send( session.url );
} );
} )();
} );
} else {
res.status( 400 ).send( 'ERR_UID_NOT_FOUND' );
}
} ).catch( error => {
console.error( '[ STRIPE ] DB ERROR: ' + error );
res.status( 500 ).send( 'ERR_DB' );
} );
} else {
res.status( 428 ).send( 'ERR_MAIL_UNCONFIRMED' );
}
} else {
res.status( 428 ).send( 'ERR_MAIL_UNCONFIRMED' );
}
} );
} else {
res.status( 403 ).send( 'ERR_UNAUTHORIZED' );
}
} );
app.get( '/payments/status', ( request, response ) => {
response.writeHead( 200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
} );
response.status( 200 );
response.flushHeaders();
response.write( 'data: connected\n\n' );
waitingClients[ request.session.id ] = response;
const ping = setInterval( () => {
if ( !pendingPayments[ request.session.id ] ) {
const stat = TicketGenerator.getGenerationStatus( request.session.id );
if ( stat === 'done' ) {
clearInterval( ping );
setTimeout( () => {
response.write( 'data: ready\n\n' );
delete waitingClients[ request.session.id ];
request.session.lastOrderID = request.session.id;
request.session.id = generator.generateToken( 30 );
response.end();
}, 2000 );
} else if ( stat === 'noTicket' ) {
clearInterval( ping );
response.write( 'data: noData\n\n' );
response.end();
delete waitingClients[ request.session.id ];
}
}
}, 2000 );
} );
app.get( '/user/2fa/ping', ( request, response ) => {
if ( paymentOk[ request.session.id ] === 'ok' ) {
delete paymentOk[ request.session.id ];
response.send( { 'status': 'paymentOk' } );
} else {
if ( !pendingPayments[ request.session.id ] ) {
const stat = TicketGenerator.getGenerationStatus( request.session.id );
if ( stat === 'done' ) {
request.session.lastOrderID = request.session.id;
request.session.id = generator.generateToken( 30 );
response.send( { 'status': 'ticketOk' } );
} else if ( stat === 'noTicket' ) {
response.send( { 'status': 'noTicket' } );
} else {
response.send( '' );
}
} else {
response.send( '' );
}
}
} );
app.post( '/payments/webhook', bodyParser.raw( { type: 'application/json' } ), async ( req, res ) => {
const payload = req.body;
const sig = req.headers[ 'stripe-signature' ];
let event;
try {
event = stripe.webhooks.constructEvent( payload, sig, endpointSecret );
} catch ( err ) {
console.error( err );
return res.status( 400 ).send( 'Webhook Error' );
}
res.status( 200 ).end();
if ( event.type === 'checkout.session.completed' ) {
setTimeout( () => {
if ( waitingClients[ sessionReference[ event.data.object.id ][ 'tok' ] ] ) {
waitingClients[ sessionReference[ event.data.object.id ][ 'tok' ] ].write( 'data: paymentOk\n\n' );
}
}, 1000 );
db.getDataSimple( 'processingOrders', 'user_id', sessionReference[ event.data.object.id ][ 'tok' ] ).then( dat => {
db.getDataSimple( 'users', 'email', sessionReference[ event.data.object.id ][ 'email' ] ).then( user => {
if ( user[ 0 ] && dat[ 0 ] ) {
const tickets = JSON.parse( dat[ 0 ].data );
db.writeDataSimple( 'orders', 'account_id', user[ 0 ].account_id, { 'account_id': user[ 0 ].account_id, 'tickets': dat[ 0 ].data, 'order_name': sessionReference[ event.data.object.id ][ 'tok' ] } ).then( () => {
TicketGenerator.generateTickets( sessionReference[ event.data.object.id ] );
db.getJSONData( 'booked' ).then( ret => {
let booked = ret ?? {};
for ( let event in tickets ) {
if ( !booked[ String( event ) ] ) {
booked[ String( event ) ] = {};
}
for ( let tik in tickets[ event ] ) {
booked[ event ][ tik ] = tickets[ event ][ tik ];
}
}
db.writeJSONData( 'booked', booked ).then( () => {
db.deleteDataSimple( 'temp', 'user_id', sessionReference[ event.data.object.id ][ 'tok' ] ).then( () => {
db.deleteDataSimple( 'processingOrders', 'user_id', sessionReference[ event.data.object.id ][ 'tok' ] ).then( () => {
delete pendingPayments[ sessionReference[ event.data.object.id ][ 'tok' ] ];
} ).catch( error => {
console.error( '[ STRIPE ] ERROR whilst deleting data from DB: ' + error );
} );
} ).catch( error => {
console.error( '[ STRIPE ] ERROR whilst deleting data from DB: ' + error );
} );
} ).catch( error => {
console.error( '[ STRIPE ] ERROR whilst getting data from DB: ' + error );
} );
} );
} );
} else {
TicketGenerator.sendErrorMail( sessionReference[ event.data.object.id ][ 'tok' ], sessionReference[ event.data.object.id ][ 'email' ] );
}
} );
} ).catch( err => {
console.error( err );
} );
}
} );
};

View File

@@ -1,13 +0,0 @@
{
"pluginName": "",
"pluginDescription": "",
"creator": "",
"maintainer": "",
"pluginWebsite": "",
"pluginDocs": "",
"gitURL": "",
"settingsURL": "",
"mainPluginURL": "",
"logo": "",
"version": ""
}

View File

@@ -1,23 +0,0 @@
/*
* libreevent - pluginLoader.js
*
* Created by Janis Hutz 08/13/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const fs = require( 'fs' );
const path = require( 'path' );
module.exports = ( app, settings ) => {
console.log( '\n\n[ Plugin Loader ] Loading plugins\n' );
let otherPlugins = fs.readdirSync( path.join( __dirname + '/others' ) );
for ( let plugin in otherPlugins ) {
require( './others/' + otherPlugins[ plugin ] + '/' + otherPlugins[ plugin ] + 'Routes.js' )( app, settings );
console.log( '[ Plugin Loader ] Loaded plugin "' + otherPlugins[ plugin ] + '"' );
}
require( './payments/' + settings.payments + '/' + settings.payments + 'Routes.js' )( app, settings );
console.log( '[ Plugin Loader ] Loaded ' + settings.payments + ' as payment gateway' );
};

View File

@@ -1,3 +0,0 @@
# Ticket Store
Here, all the tickets for all the orders are saved. You may delete the tickets here at any point. Should the user request their tickets once again, the tickets can simply be regenerated from the data stored in the database.

View File

@@ -1,24 +0,0 @@
/*
* libreevent - test.js
*
* Created by Janis Hutz 08/06/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const express = require( 'express' );
let app = express();
const ticket = require( './ticketGenerator.js' );
const TicketGenerator = new ticket();
const http = require( 'http' );
app.get( '/', ( request, response ) => {
response.send( 'ok' );
TicketGenerator.generateTickets( { 'tok': 'hGids5PVsHm_KiK-Wd-8ekvwxpuUPrUX', 'email': 'info@janishutz.com' } );
} );
const PORT = process.env.PORT || 8080;
http.createServer( app ).listen( PORT );

File diff suppressed because one or more lines are too long

View File

@@ -1,31 +0,0 @@
/*
* libreevent - token.js
*
* Created by Janis Hutz 07/08/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
module.exports.generateToken = ( length ) => {
let token = '';
let min = 45;
let max = 122;
for ( let i = 0; i < length; i++ ) {
let randomNumber = Math.floor( Math.random() * ( max - min ) ) + min;
while ( ( 58 < randomNumber && randomNumber < 63 ) || ( 90 < randomNumber && randomNumber < 95 ) || ( 95 < randomNumber && randomNumber < 97 ) ) {
randomNumber = Math.floor( Math.random() * ( max - min ) ) + min;
}
token += String.fromCharCode( randomNumber );
}
return token;
};
module.exports.generateNumber = ( length ) => {
let number = '';
for ( let i = 0; i < length; i++ ) {
number += Math.floor( Math.random() * 10 );
}
return number;
};

View File

@@ -1,55 +0,0 @@
/*
* libreevent - adminAPIRoutes.js
*
* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const posth = require( './api/postHandler.js' );
const geth = require( './api/getHandler.js' );
const postHandler = new posth();
const getHandler = new geth();
const bodyParser = require( 'body-parser' );
module.exports = ( app, settings ) => {
// Add specific routes here to have them be checked first to not get general handling
app.get( '/getAPI/:call', ( req, res ) => {
getHandler.handleCall( req.params.call, req.query, req.session, settings ).then( data => {
if ( req.params.call === 'getReservedSeats' ) {
let dat = data;
dat[ 'reserved' ] = postHandler.getReservedSeats( req.query.event );
res.send( dat );
} else if ( req.params.call === 'getAllEvents' ) {
let dat = data;
const freeSeats = postHandler.getFreeSeatsCount();
for ( let event in freeSeats ) {
dat[ event ][ 'free' ] = freeSeats[ event ];
}
res.send( dat );
} else if ( req.params.call === 'reloadData' ) {
if ( req.session.loggedInAdmin || req.session.loggedInUser ) {
postHandler.loadData();
res.send( 'ok' );
} else {
res.status( 403 ).send( 'unauthorized' );
}
} else {
res.send( data );
}
} ).catch( error => {
res.status( error.code ?? 500 ).send( error.message );
} );
} );
app.post( '/API/:call', bodyParser.json(), ( req, res ) => {
// add lang in the future
postHandler.handleCall( req.params.call, req.body, req.session ).then( data => {
res.send( data );
} ).catch( error => {
res.status( error.code ?? 500 ).send( error.message );
} );
} );
};

View File

@@ -1,238 +0,0 @@
/*
* libreevent - routes.js
*
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const db = require( './db/db.js' );
const pwdmanager = require( './credentials/pwdmanager.js' );
const auth = require( './credentials/2fa.js' );
const twoFA = new auth();
const path = require( 'path' );
const mail = require( './mail/mailSender.js' );
const mailManager = new mail();
const bodyParser = require( 'body-parser' );
const generator = require( './token.js' );
let responseObjects = {};
let authOk = {};
let mailTokens = {};
module.exports = ( app, settings ) => {
app.get( '/user/details', ( request, response ) => {
if ( request.session.loggedInUser ) {
db.getDataSimple( 'users', 'email', request.session.username ).then( data => {
if ( data[ 0 ] ) {
let dat = {};
for ( let element in data[ 0 ] ) {
if ( element !== 'pass' ) {
dat[ element ] = data[ 0 ][ element ];
}
}
response.send( { 'data': dat, 'status': true } );
} else {
response.status( 404 ).send( { 'data': 'This user does not exist', 'status': false } );
}
} ).catch( () => {
console.log( 'dbError' );
response.status( 500 ).send( { 'data': 'There was an error reading data from the database. If this error persists, please contact the administrators', 'status': false } );
} );
} else {
response.status( 403 ).sendFile( path.join( __dirname + '/../ui/en/errors/403.html' ) );
}
} );
// app.get( '/test/user', ( req, res ) => {
// req.session.loggedInUser = true;
// req.session.username = 'info@janishutz.com';
// res.send( 'ok' );
// } );
app.post( '/user/login', bodyParser.json(), ( request, response ) => {
if ( request.body.mail && request.body.password ) {
pwdmanager.checkpassword( request.body.mail, request.body.password ).then( data => {
if ( data.status ) {
request.session.username = request.body.mail;
if ( data.twoFA === 'simple' ) {
( async () => {
let tok = twoFA.registerStandardAuthentication()[ 'token' ];
let ipRetrieved = request.headers[ 'x-forwarded-for' ];
let ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : request.connection.remoteAddress;
mailManager.sendMail( request.body.mail, await twoFA.generateTwoFAMail( tok, ip, settings.yourDomain, settings.name ), 'Verify login', settings.mailSender );
request.session.token = tok;
response.send( { 'status': '2fa' } );
} )();
} else if ( data.twoFA === 'enhanced' ) {
( async () => {
let res = twoFA.registerEnhancedAuthentication();
let ipRetrieved = request.headers[ 'x-forwarded-for' ];
let ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : request.connection.remoteAddress;
mailManager.sendMail( request.body.mail, await twoFA.generateTwoFAMail( res.token, ip, settings.yourDomain, settings.name ), 'Verify login', settings.mailSender );
request.session.token = res.token;
response.send( { 'status': '2fa+', 'code': res.code } );
} )();
} else {
request.session.loggedInUser = true;
response.send( { 'status': 'ok' } );
}
} else {
response.send( { 'status': 'pwErr' } );
}
} );
} else {
response.status( 400 ).send( 'missingCredentials' );
}
} );
app.get( '/user/2fa', ( request, response ) => {
let tokType = twoFA.verifySimple( request.query.token );
if ( tokType === 'standard' ) {
request.session.loggedInUser = true;
if ( responseObjects[ request.query.token ] ) {
responseObjects[ request.query.token ].write( 'data: authenticated\n\n' );
} else {
authOk[ request.query.token ] = 'ok';
}
response.sendFile( path.join( __dirname + '/../ui/en/2fa/2faSimple.html' ) );
} else if ( tokType === 'enhanced' ) {
response.sendFile( path.join( __dirname + '/../ui/en/2fa/2faEnhanced.html' ) );
} else {
response.status( 403 ).sendFile( path.join( __dirname + '/../ui/en/2fa/2faInvalid.html' ) );
}
} );
app.post( '/user/2fa/verify', bodyParser.json(), ( request, response ) => {
let verified = twoFA.verifyEnhanced( request.body.token, request.body.code );
if ( verified ) {
request.session.loggedInUser = true;
if ( responseObjects[ request.body.token ] ) {
responseObjects[ request.body.token ].write( 'data: authenticated\n\n' );
responseObjects[ request.body.token ].end();
delete responseObjects[ request.body.token ];
} else {
authOk[ request.body.token ] = 'ok';
}
response.send( 'ok' );
} else response.status( 403 ).send( 'wrong' );
} );
app.get( '/user/2fa/check', ( request, response ) => {
response.writeHead( 200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
} );
response.status( 200 );
response.flushHeaders();
response.write( 'data: connected\n\n' );
responseObjects[ request.session.token ] = response;
} );
app.get( '/user/2fa/ping', ( request, response ) => {
if ( authOk[ request.session.token ] === 'ok' ) {
delete authOk[ request.session.token ];
response.send( { 'status': 'ok' } );
} else {
response.send( '' );
}
} );
app.get( '/user/logout', ( request, response ) => {
request.session.loggedInUser = false;
request.session.username = '';
response.send( 'logoutOk' );
} );
app.get( '/user/resendEmail', ( req, res ) => {
if ( req.session.username ) {
( async () => {
let tok = generator.generateToken( 60 );
mailTokens[ tok ] = req.session.username;
mailManager.sendMail( req.session.username, await twoFA.generateSignupEmail( tok, settings.yourDomain, settings.name ), 'Confirm your email', settings.mailSender );
} )();
res.send( 'sent' );
} else {
res.status( 403 ).send( 'unauthorised' );
}
} );
app.post( '/user/signup', bodyParser.json(), ( request, response ) => {
if ( request.body.password && request.body.password === request.body.password2 && request.body.firstName && request.body.name && request.body.country && request.body.mail ) {
db.checkDataAvailability( 'users', 'email', request.body.mail ).then( status => {
if ( status ) {
response.send( 'exists' );
} else {
( async () => {
let tok = generator.generateToken( 60 );
mailTokens[ tok ] = request.body.mail;
mailManager.sendMail( request.body.mail, await twoFA.generateSignupEmail( tok, settings.yourDomain, settings.name ), 'Confirm your email', settings.mailSender );
} )();
pwdmanager.hashPassword( request.body.password ).then( hash => {
db.writeDataSimple( 'users', 'email', request.body.mail, {
'email': request.body.mail,
'pass': hash,
'first_name': request.body.firstName,
'name': request.body.name,
'two_fa': 'disabled',
'user_data': JSON.stringify( { 'country': request.body.country } ),
'marketing': request.body.newsletter ?? null
} ).then( () => {
request.session.loggedInUser = true;
request.session.username = request.body.mail;
response.send( 'ok' );
} );
} );
}
} );
} else {
response.status( 400 ).send( 'incomplete' );
}
} );
app.get( '/user/signup/confirm', ( request, response ) => {
if ( Object.keys( mailTokens ).includes( request.query.token ) ) {
request.session.username = mailTokens[ request.query.token ];
db.writeDataSimple( 'users', 'email', request.session.username, { 'mail_confirmed': 'true' } );
delete mailTokens[ request.query.token ];
if ( settings.twoFA === 'allow' ) {
response.sendFile( path.join( __dirname + '/../ui/en/signup/allowTwoFA.html' ) );
} else if ( settings.twoFA === 'enforce' ) {
response.sendFile( path.join( __dirname + '/../ui/en/signup/enforceTwoFA.html' ) );
} else {
response.sendFile( path.join( __dirname + '/../ui/en/signup/disallowTwoFA.html' ) );
}
} else {
response.status( 400 ).sendFile( path.join( __dirname + '/../ui/en/signup/invalid.html' ) );
}
} );
app.post( '/user/settings/:setting', bodyParser.json(), ( request, response ) => {
let call = request.params.setting;
if ( request.session.username ) {
if ( call === '2fa' ) {
db.writeDataSimple( 'users', 'email', request.session.username, { 'two_fa': request.body.twoFA } );
response.send( 'ok' );
} else {
response.status( 404 ).send( 'Not found' );
}
} else {
response.status( 403 ).send( 'unauthorised' );
}
} );
app.post( '/user/settings', bodyParser.json(), ( req, res ) => {
if ( req.session.username ) {
db.writeDataSimple( 'users', 'email', req.session.username, req.body );
res.send( 'ok' );
} else {
res.status( 403 ).send( 'unauthorised' );
}
} );
app.get( '/settings/2fa', ( request, response ) => {
response.send( settings.twoFA );
} );
};

View File

@@ -1,7 +0,0 @@
{
"host": "",
"database": "",
"user": "",
"password": "",
"port": 3306
}

View File

@@ -1,12 +0,0 @@
{
"host":"",
"port": 587,
"secure": false,
"auth": {
"user":"",
"pass":""
},
"tls": {
"servername": ""
}
}

View File

@@ -1,17 +0,0 @@
{
"init":false,
"setupDone":false,
"twoFA":"allow",
"twoFAMode":"simple",
"db":"mysql",
"payments":"stripe",
"name":"libreevent",
"yourDomain":"",
"mailSender":"",
"maxTickets":10,
"currency":"USD",
"gcInterval":300,
"ticketTimeout":900,
"startPage":"default",
"version":"1.0.1"
}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1 +0,0 @@
{}

File diff suppressed because it is too large Load Diff

View File

@@ -1,84 +0,0 @@
{
"name": "libreevent",
"version": "1.0.7",
"description": "Free & Open source event management solution",
"main": "app.js",
"directories": {
"doc": "docs"
},
"devDependencies": {
"acorn": "^8.8.2",
"buffer-from": "^1.1.2",
"camel-case": "^4.1.2",
"clean-css": "^5.3.2",
"commander": "^9.5.0",
"css-b64-images": "^0.2.5",
"debug": "^4.3.4",
"dot-case": "^3.0.4",
"entities": "^4.4.0",
"find-up": "^6.3.0",
"html-minifier-terser": "^7.1.0",
"jju": "^1.4.0",
"locate-path": "^7.2.0",
"lower-case": "^2.0.2",
"minify": "^9.2.0",
"ms": "^2.1.2",
"no-case": "^3.0.4",
"p-limit": "^4.0.0",
"p-locate": "^6.0.0",
"param-case": "^3.0.4",
"pascal-case": "^3.1.2",
"path-exists": "^5.0.0",
"readjson": "^2.2.2",
"relateurl": "^0.2.7",
"response-time": "^2.3.2",
"simport": "^1.2.0",
"source-map": "^0.6.1",
"source-map-support": "^0.5.21",
"terser": "^5.16.5",
"try-catch": "^3.0.1",
"try-to-catch": "^3.0.1",
"tslib": "^2.5.0",
"yocto-queue": "^1.0.0"
},
"dependencies": {
"@pdfme/generator": "^1.2.6",
"@seald-io/nedb": "^4.0.2",
"axios": "^1.6.0",
"bcrypt": "^5.0.1",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"crypto-js": "^4.2.0",
"express": "^4.19.2",
"express-session": "^1.17.3",
"html-to-text": "^9.0.5",
"multer": "^1.4.5-lts.1",
"mysql": "^2.18.1",
"nodemailer": "^6.9.3",
"pdf-lib": "^1.17.1",
"png-to-ico": "^2.1.8",
"qs": "^6.11.2",
"serve-favicon": "^2.5.0",
"serve-static": "^1.15.0",
"stripe": "^12.14.0",
"vue": "^3.3.4"
},
"scripts": {
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
},
"repository": {
"type": "git",
"url": "git+https://github.com/simplePCBuilding/libreevent.git"
},
"keywords": [
"event",
"management",
"solution"
],
"author": "Janis Hutz",
"license": "GPL-3.0-or-later",
"bugs": {
"url": "https://github.com/simplePCBuilding/libreevent/issues"
},
"homepage": "https://libreevent.janishutz.com"
}

View File

@@ -1,79 +0,0 @@
const fs = require( 'fs' );
const path = require( 'path' );
const letters = [ ',', '{' ];
const writeJSONData = ( db, data ) => {
return new Promise( ( resolve, reject ) => {
fs.writeFile( path.join( __dirname + '/data/' + db + '.json' ), JSON.stringify( data ), ( error ) => {
if ( error ) {
reject( 'Error occurred: Error trace: ' + error );
} else {
resolve( true );
}
} );
} );
};
const saveSettings = ( settings ) => {
const settingsString = JSON.stringify( settings );
let settingsToSave = '';
for ( let letter in settingsString ) {
if ( letters.includes( settingsString[ letter ] ) ) {
settingsToSave += settingsString[ letter ] + '\n\t';
} else if ( settingsString[ letter ] === '}' ) {
settingsToSave += '\n' + settingsString[ letter ];
} else {
settingsToSave += settingsString[ letter ];
}
}
fs.writeFileSync( path.join( __dirname + '/config/settings.config.json' ), settingsToSave );
};
console.log( `
_ _ _ _
| (_) | | |
| |_| |__ _ __ ___ _____ _____ _ __ | |_
| | | '_ \\| '__/ _ \\/ _ \\ \\ / / _ \\ '_ \\| __|
| | | |_) | | | __/ __/\\ V / __/ | | | |_
|_|_|_.__/|_| \\___|\\___| \\_/ \\___|_| |_|\\__|
-------------------------------
==> Resetting DBs to prepare for build
` );
writeJSONData( 'booked', {} );
writeJSONData( 'eventDrafts', {} );
writeJSONData( 'events', {} );
writeJSONData( 'locations', {} );
writeJSONData( 'events', {} );
writeJSONData( 'seatplan', {} );
writeJSONData( 'tickets', {} );
writeJSONData( 'rootAccount', {} );
writeJSONData( 'db', {} );
saveSettings( {
'init': false,
'setupDone': false,
'twoFA': 'allow',
'twoFAMode': 'simple',
'db': 'mysql',
'payments': 'stripe',
'name': 'libreevent',
'yourDomain': '',
'mailSender': '',
'maxTickets': 10,
'currency': 'USD',
'gcInterval': 300,
'ticketTimeout': 900,
'startPage': 'default',
'version': '1.0.1'
} );
console.log( ' ==> Done!\n\n' );

View File

@@ -1,93 +0,0 @@
/*
* libreevent - setupRoutes.js
*
* Created by Janis Hutz 07/18/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
let db = null;
let pwm = null;
const fs = require( 'fs' );
const path = require( 'path' );
const bodyParser = require( 'body-parser' );
module.exports = ( app, settings ) => {
let isSetupComplete = settings.setupDone;
/*
Setup start route that checks if setup key was correct
*/
app.post( '/setup/start', bodyParser.json(), ( request, response ) => {
if ( request.body.token === '' + fs.readFileSync( path.join( __dirname + '/../setupkey.txt' ) ) ) {
request.session.setupKeyOk = true;
response.send( 'ok' );
} else {
response.status( 400 ).send( 'incorrect' );
}
} );
app.get( '/setup/getKeyStatus', ( req, res ) => {
if ( req.session.setupKeyOk ) {
res.send( 'ok' );
} else {
res.status( 403 ).send( 'unauthorized' );
}
} );
app.post( '/setup/saveBasicSettings', bodyParser.json(), ( req, res ) => {
if ( req.session.setupKeyOk ) {
fs.writeFileSync( path.join( __dirname + '/../config/db.config.json' ), JSON.stringify( req.body.db ) );
let emailSettings = {};
emailSettings[ 'host' ] = req.body.email.host;
emailSettings[ 'port' ] = req.body.email.port;
emailSettings[ 'secure' ] = false;
emailSettings[ 'auth' ] = { 'user': req.body.email.user, 'pass': req.body.email.pass };
let hostSplit = req.body.email.host.split( '.' );
emailSettings[ 'tls' ] = { 'servername': ( hostSplit[ hostSplit.length - 2 ] + '.' + hostSplit[ hostSplit.length - 1 ] ) };
fs.writeFileSync( path.join( __dirname + '/../config/mail.config.json' ), JSON.stringify( emailSettings ) );
if ( db === null ) {
db = require( '../backend/db/db.js' );
pwm = require( '../admin/pwdmanager.js' );
}
let updatedSettings = settings;
updatedSettings[ 'name' ] = req.body.websiteName;
updatedSettings[ 'yourDomain' ] = req.body.yourDomain;
updatedSettings[ 'mailSender' ] = req.body.mailDisplay;
db.saveSettings( updatedSettings );
res.send( 'ok' );
} else {
res.status( 403 ).send( 'unauthorized' );
}
} );
app.post( '/setup/saveRootAccount', bodyParser.json(), ( req, res ) => {
if ( req.session.setupKeyOk ) {
if ( db === null ) {
db = require( '../backend/db/db.js' );
pwm = require( '../admin/pwdmanager.js' );
}
pwm.hashPassword( req.body.password ).then( hash => {
db.writeJSONData( 'rootAccount', { 'pass': hash, 'email': req.body.mail } );
let updatedSettings = settings;
updatedSettings[ 'setupDone' ] = true;
updatedSettings[ 'init' ] = true;
db.saveSettings( updatedSettings );
isSetupComplete = true;
( async () => {
await db.initDB();
db.reset();
res.send( 'ok' );
} )();
} );
} else {
res.status( 403 ).send( 'unauthorized' );
}
} );
app.get( '/getSetupStatus', ( req, res ) => {
res.send( isSetupComplete );
} );
};

View File

@@ -1,7 +0,0 @@
const db = require( './backend/db/db.js' );
( async () => {
await db.initDB();
db.reset();
console.log( 'DONE' );
} )();

View File

@@ -1 +0,0 @@
klbjdfgsgsdjhlfkgfshjkgsfhjsiutuitpwergvuilavlsuv

View File

@@ -1,19 +0,0 @@
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
let result = '';
// https://stackoverflow.com/questions/36129721/convert-number-to-alphabet-letter
function printToLetter( number ) {
let charIndex = number % alphabet.length;
let quotient = number / alphabet.length;
if ( charIndex - 1 === -1 ) {
charIndex = alphabet.length;
quotient --;
}
result = alphabet.charAt( charIndex - 1 ) + result;
if ( quotient >= 1 ) {
printToLetter( parseInt( quotient ) );
}
}
printToLetter( 150036 );
console.log( result );

View File

@@ -1,132 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Two Factor Authentication Invalid</title>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
background-color: rgb(41, 40, 40);
color: white;
font-size: 150%;
}
.content {
width: 70%;
}
#code {
padding: 0.75%;
border: solid white 1px;
border-radius: 7px;
font-size: 100%;
text-align: center;
}
.submit {
margin-top: 2%;
background: linear-gradient(90deg, rgb(30, 36, 131), rgb(87, 66, 184), rgb(105, 115, 214), rgb(30, 36, 131), rgb(41, 128, 109), rgb(146, 50, 47));
background-size: 300px;
padding: 10px 20px;
border: none;
border-radius: 20px;
cursor: pointer;
transition: all 3s;
font-size: 75%;
color: white;
}
.submit:hover {
background-size: 200%;
background-position: -100%;
}
#popup {
border: none;
border-radius: 20px;
padding: 5%;
background-color: rgb(34, 34, 34);
color: white;
max-width: 70%;
}
#popup::backdrop {
background-color: rgba( 0, 0, 0, 0.8 );
}
</style>
</head>
<body>
<div class="content">
<h1>Two-Factor Authen&shy;tication</h1>
<p id="text">Please enter the code displayed on the login page down below to finish the Two-Factor Authentication.</p>
<form onsubmit="return submitFunction()" id="form">
<input type="text" name="code" id="code"><br>
<input type="submit" value="Submit" class="submit">
</form>
<dialog id="popup">
<p id="popup-message"></p>
<form method="dialog">
<input type="submit" value="Ok" class="submit">
</form>
</dialog>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
function submitFunction () {
let code = document.getElementById( 'code' ).value;
let data = '';
if ( code.includes( ' ' ) ) {
for ( let letter in code ) {
if ( code[ letter ] != ' ' ) {
data += code[ letter ];
}
}
} else {
data = code;
}
if ( data.length == 6 ) {
let fetchOptions = {
method: 'post',
body: JSON.stringify( { 'code': data, 'token': location.search.substring( 7 ) } ),
headers: {
'Content-Type': 'application/json',
'charset': 'utf-8'
}
};
fetch( '/user/2fa/verify', fetchOptions ).then( res => {
res.text().then( data => {
if ( data === 'ok' ) {
document.getElementById( 'text' ).innerText = 'Two-Factor Authentication is complete! Head back to the original page!';
document.getElementById( 'form' ).innerHTML = '';
openPopup( 'You have successfully authorised this login. You may now close this tab and head back to the original tab.' );
} else {
openPopup( 'This code you specified is invalid (or no longer valid). Please try again.' );
}
} );
} );
} else {
openPopup( 'Please enter a six-character code to proceed' );
}
return false;
}
function openPopup ( message ) {
document.getElementById( 'popup-message' ).innerHTML = message;
document.getElementById( 'popup' ).showModal();
}
</script>
</body>
</html>

View File

@@ -1,132 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Two Factor Authentication Invalid</title>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
background-color: rgb(41, 40, 40);
color: white;
font-size: 150%;
}
.content {
width: 70%;
}
#code {
padding: 0.75%;
border: solid white 1px;
border-radius: 7px;
font-size: 100%;
text-align: center;
}
.submit {
margin-top: 2%;
background: linear-gradient(90deg, rgb(30, 36, 131), rgb(87, 66, 184), rgb(105, 115, 214), rgb(30, 36, 131), rgb(41, 128, 109), rgb(146, 50, 47));
background-size: 300px;
padding: 10px 20px;
border: none;
border-radius: 20px;
cursor: pointer;
transition: all 3s;
font-size: 75%;
color: white;
}
.submit:hover {
background-size: 200%;
background-position: -100%;
}
#popup {
border: none;
border-radius: 20px;
padding: 5%;
background-color: rgb(34, 34, 34);
color: white;
max-width: 70%;
}
#popup::backdrop {
background-color: rgba( 0, 0, 0, 0.8 );
}
</style>
</head>
<body>
<div class="content">
<h1>Two-Factor Authen&shy;tication</h1>
<p id="text">Please enter the code displayed on the login page down below to finish the Two-Factor Authentication.</p>
<form onsubmit="return submitFunction()" id="form">
<input type="text" name="code" id="code"><br>
<input type="submit" value="Submit" class="submit">
</form>
<dialog id="popup">
<p id="popup-message"></p>
<form method="dialog">
<input type="submit" value="Ok" class="submit">
</form>
</dialog>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
function submitFunction () {
let code = document.getElementById( 'code' ).value;
let data = '';
if ( code.includes( ' ' ) ) {
for ( let letter in code ) {
if ( code[ letter ] != ' ' ) {
data += code[ letter ];
}
}
} else {
data = code;
}
if ( data.length == 6 ) {
let fetchOptions = {
method: 'post',
body: JSON.stringify( { 'code': data, 'token': location.search.substring( 7 ) } ),
headers: {
'Content-Type': 'application/json',
'charset': 'utf-8'
}
};
fetch( '/admin/2fa/verify', fetchOptions ).then( res => {
res.text().then( data => {
if ( data === 'ok' ) {
document.getElementById( 'text' ).innerText = 'Two-Factor Authentication is complete! Head back to the original page!';
document.getElementById( 'form' ).innerHTML = '';
openPopup( 'You have successfully authorised this login. You may now close this tab and head back to the original tab.' );
} else {
openPopup( 'This code you specified is invalid (or no longer valid). Please try again.' );
}
} );
} );
} else {
openPopup( 'Please enter a six-character code to proceed' );
}
return false;
}
function openPopup ( message ) {
document.getElementById( 'popup-message' ).innerHTML = message;
document.getElementById( 'popup' ).showModal();
}
</script>
</body>
</html>

View File

@@ -1,38 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Two Factor Authentication Invalid</title>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
background-color: rgb(41, 40, 40);
color: white;
font-size: 150%;
}
.content {
width: 70%;
}
</style>
</head>
<body>
<div class="content">
<h1>Two-Factor Authen&shy;tication Token invalid</h1>
<p>The token you have specified is invalid. Please check that the link used is correct. If nothing helps, please try logging in again.</p>
</div>
</body>
</html>

View File

@@ -1,38 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Two Factor Authentication Invalid</title>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
background-color: rgb(41, 40, 40);
color: white;
font-size: 150%;
}
.content {
width: 70%;
}
</style>
</head>
<body>
<div class="content">
<h1>Two-Factor Authen&shy;tication Successful</h1>
<p>Your two-factor authentication has been completed successfully. You were redirected automatically. You may now close this tab and return to the original browser tab.</p>
</div>
</body>
</html>

View File

@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>403</title>
</head>
<body>
<h1>403</h1>
</body>
</html>

View File

@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404</title>
</head>
<body>
<h1>404</h1>
</body>
</html>

View File

@@ -1,59 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment Canceled</title>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
background-color: rgb(41, 40, 40);
color: white;
font-size: 150%;
}
.content {
width: 70%;
}
.submit {
margin-top: 2%;
background: linear-gradient(90deg, rgb(30, 36, 131), rgb(87, 66, 184), rgb(105, 115, 214), rgb(30, 36, 131), rgb(41, 128, 109), rgb(146, 50, 47));
background-size: 300px;
padding: 10px 20px;
border: none;
border-radius: 20px;
cursor: pointer;
transition: all 3s;
font-size: 75%;
color: white;
}
.submit:hover {
background-size: 200%;
background-position: -100%;
}
</style>
</head>
<body>
<div class="content">
<h1>Payment Canceled</h1>
<p>You have canceled your payment!</p>
<p>This was a mistake? Head back to the payment page!</p>
<button onclick="history.back()" class="submit">Back to payment</button>
<button onclick="location.href = '/cart'" class="submit">Back to the cart</button>
</div>
</body>
</html>

View File

@@ -1,57 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment Canceled</title>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
background-color: rgb(41, 40, 40);
color: white;
font-size: 150%;
}
.content {
width: 70%;
}
.submit {
margin-top: 2%;
background: linear-gradient(90deg, rgb(30, 36, 131), rgb(87, 66, 184), rgb(105, 115, 214), rgb(30, 36, 131), rgb(41, 128, 109), rgb(146, 50, 47));
background-size: 300px;
padding: 10px 20px;
border: none;
border-radius: 20px;
cursor: pointer;
transition: all 3s;
font-size: 75%;
color: white;
}
.submit:hover {
background-size: 200%;
background-position: -100%;
}
</style>
</head>
<body>
<div class="content">
<h1>Payment Failed</h1>
<p>Your payment has failed! Please try again or use a different payment method</p>
<button onclick="location.href = '/cart'" class="submit">Back to the cart</button>
</div>
</body>
</html>

View File

@@ -1,69 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Two-Factor Authentication</title>
<style>
body {
font-family: sans-serif;
width: 100%;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.content {
width: 80%;
height: 800px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.ip {
color: rgb(94, 94, 94);
}
.logo {
width: 70vw;
}
.verify {
padding: 20px 30px;
background-color: rgb(0, 7, 87);
text-decoration: none;
color: white;
transition: 0.5s all;
border-radius: 5px;
margin-bottom: 20px;
}
.verify:hover {
background-color: rgb(0, 12, 139);
}
@media only screen and (min-width: 999px) {
.logo {
width: 20vw;
}
.content {
width: 40vw;
}
}
</style>
</head>
<body>
<div class="content">
<img :src="host + '/otherAssets/logo.png'" alt="Logo" class="logo">
<h1>There was an error whilst processing an order</h1>
<p>This order will have to be processed manually!</p>
<p>{{ order_id }}</p>
<p>{{ email }}</p>
</div>
</body>
</html>

View File

@@ -1,67 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Two-Factor Authentication</title>
<style>
body {
font-family: sans-serif;
width: 100%;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.content {
width: 80%;
height: 800px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.ip {
color: rgb(94, 94, 94);
}
.logo {
width: 70vw;
}
.verify {
padding: 20px 30px;
background-color: rgb(0, 7, 87);
text-decoration: none;
color: white;
transition: 0.5s all;
border-radius: 5px;
margin-bottom: 20px;
}
.verify:hover {
background-color: rgb(0, 12, 139);
}
@media only screen and (min-width: 999px) {
.logo {
width: 20vw;
}
.content {
width: 40vw;
}
}
</style>
</head>
<body>
<div class="content">
<img :src="host + '/otherAssets/logo.png'" alt="Logo" class="logo">
<h1>There was an error whilst processing your order at {{ pageName }}</h1>
<p>Your tickets will have to be processed manually and you will receive them as soon as they are ready.</p>
</div>
</body>
</html>

View File

@@ -1,67 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Two-Factor Authentication</title>
<style>
body {
font-family: sans-serif;
width: 100%;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.content {
width: 80%;
height: 800px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.ip {
color: rgb(94, 94, 94);
}
.logo {
width: 70vw;
}
.verify {
padding: 20px 30px;
background-color: rgb(0, 7, 87);
text-decoration: none;
color: white;
transition: 0.5s all;
border-radius: 5px;
margin-bottom: 20px;
}
.verify:hover {
background-color: rgb(0, 12, 139);
}
@media only screen and (min-width: 999px) {
.logo {
width: 20vw;
}
.content {
width: 40vw;
}
}
</style>
</head>
<body>
<div class="content">
<img :src="host + '/otherAssets/logo.png'" alt="Logo" class="logo">
<h1>Thank you for your purchase at {{ pageName }}</h1>
<p>Attached you may find your tickets. Enjoy the event!</p>
</div>
</body>
</html>

View File

@@ -1,122 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email verification</title>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
background-color: rgb(41, 40, 40);
color: white;
font-size: 150%;
}
.content {
width: 70%;
}
#code {
padding: 0.75%;
border: solid white 1px;
border-radius: 7px;
font-size: 100%;
text-align: center;
}
.submit {
margin-top: 2%;
background: linear-gradient(90deg, rgb(30, 36, 131), rgb(87, 66, 184), rgb(105, 115, 214), rgb(30, 36, 131), rgb(41, 128, 109), rgb(146, 50, 47));
background-size: 300px;
padding: 10px 20px;
border: none;
border-radius: 20px;
cursor: pointer;
transition: all 3s;
font-size: 75%;
color: white;
}
.submit:hover {
background-size: 200%;
background-position: -100%;
}
#popup {
border: none;
border-radius: 20px;
padding: 5%;
background-color: rgb(34, 34, 34);
color: white;
max-width: 70%;
}
#popup::backdrop {
background-color: rgba( 0, 0, 0, 0.8 );
}
</style>
</head>
<body>
<div class="content">
<h1>Email Verification</h1>
<p id="text">We strongly encourage you to enable Two-Factor authentication for your account. Below you have the choice between not enabling it, enabling a mode where you just have to click the link in the email and you're in (simple) and a mode where you have to click the link in the mail and confirm the login by typing the code displayed on the main window (enhanced).</p>
<form onsubmit="return submitFunction()" id="form">
<select name="2fa" id="2fa">
<option value="enhanced">Enhanced</option>
<option value="simple">Simple</option>
<option value="disabled">Disabled</option>
</select><br>
<input type="submit" value="Submit" class="submit">
</form>
<dialog id="popup">
<p id="popup-message"></p>
<form method="dialog">
<input type="submit" value="Ok" class="submit">
</form>
</dialog>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
function submitFunction () {
let mode = document.getElementById( '2fa' ).value;
let fetchOptions = {
method: 'post',
body: JSON.stringify( { 'twoFA': mode } ),
headers: {
'Content-Type': 'application/json',
'charset': 'utf-8'
}
};
fetch( '/user/settings/2fa', fetchOptions ).then( res => {
res.text().then( data => {
if ( data === 'ok' ) {
document.getElementById( 'text' ).innerText = 'Your settings have been saved! You may change them at any point. You may now close this tab.';
document.getElementById( 'form' ).innerHTML = '';
openPopup( 'Your settings have been saved! You may change them at any point. You may now close this tab.' );
} else {
openPopup( 'An error occurred whilst saving your settings. Please try again. If it does not work, you can change this setting later at any point in the account page.' );
}
} );
} );
return false;
}
function openPopup ( message ) {
document.getElementById( 'popup-message' ).innerHTML = message;
document.getElementById( 'popup' ).showModal();
}
</script>
</body>
</html>

View File

@@ -1,38 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Verification Successful</title>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
background-color: rgb(41, 40, 40);
color: white;
font-size: 150%;
}
.content {
width: 70%;
}
</style>
</head>
<body>
<div class="content">
<h1>Email Verification Successful</h1>
<p>Thank you! Your email address has been verified successfully. You may now close this tab.</p>
</div>
</body>
</html>

View File

@@ -1,121 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email verification</title>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
background-color: rgb(41, 40, 40);
color: white;
font-size: 150%;
}
.content {
width: 70%;
}
#code {
padding: 0.75%;
border: solid white 1px;
border-radius: 7px;
font-size: 100%;
text-align: center;
}
.submit {
margin-top: 2%;
background: linear-gradient(90deg, rgb(30, 36, 131), rgb(87, 66, 184), rgb(105, 115, 214), rgb(30, 36, 131), rgb(41, 128, 109), rgb(146, 50, 47));
background-size: 300px;
padding: 10px 20px;
border: none;
border-radius: 20px;
cursor: pointer;
transition: all 3s;
font-size: 75%;
color: white;
}
.submit:hover {
background-size: 200%;
background-position: -100%;
}
#popup {
border: none;
border-radius: 20px;
padding: 5%;
background-color: rgb(34, 34, 34);
color: white;
max-width: 70%;
}
#popup::backdrop {
background-color: rgba( 0, 0, 0, 0.8 );
}
</style>
</head>
<body>
<div class="content">
<h1>Email Verification</h1>
<p id="text">This website requires you to use Two-Factor Authentication. Please choose your mode below. By default, the enhanced mode is enabled which requires you to type a 6-character code into a field after confirming the mail address. You can change this setting at any point later.</p>
<form onsubmit="return submitFunction()" id="form">
<select name="2fa" id="2fa">
<option value="enhanced">Enhanced</option>
<option value="simple">Simple</option>
</select><br>
<input type="submit" value="Submit" class="submit">
</form>
<dialog id="popup">
<p id="popup-message"></p>
<form method="dialog">
<input type="submit" value="Ok" class="submit">
</form>
</dialog>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
function submitFunction () {
let mode = document.getElementById( '2fa' ).value;
let fetchOptions = {
method: 'post',
body: JSON.stringify( { 'twoFA': mode } ),
headers: {
'Content-Type': 'application/json',
'charset': 'utf-8'
}
};
fetch( '/user/settings/2fa', fetchOptions ).then( res => {
res.text().then( data => {
if ( data === 'ok' ) {
document.getElementById( 'text' ).innerText = 'Your settings have been saved! You may change them at any point. You may now close this tab.';
document.getElementById( 'form' ).innerHTML = '';
openPopup( 'Your settings have been saved! You may change them at any point. You may now close this tab.' );
} else {
openPopup( 'An error occurred whilst saving your settings. Please try again. If it does not work, you can change this setting later at any point in the account page.' );
}
} );
} );
return false;
}
function openPopup ( message ) {
document.getElementById( 'popup-message' ).innerHTML = message;
document.getElementById( 'popup' ).showModal();
}
</script>
</body>
</html>

View File

@@ -1,38 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Verification Token Invalid</title>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
background-color: rgb(41, 40, 40);
color: white;
font-size: 150%;
}
.content {
width: 70%;
}
</style>
</head>
<body>
<div class="content">
<h1>Email Verification Token Invalid</h1>
<p>The email verification token you specified is invalid. Please check that it is correct and try again. If it still doesn't work, please request a new one by logging into your <a href="/account">account</a> and clicking "resend email verification token"</p>
</div>
</body>
</html>

View File

@@ -1,70 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Two-Factor Authentication</title>
<style>
body {
font-family: sans-serif;
width: 100%;
height: 800px;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.content {
width: 80%;
height: 90%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.ip {
color: rgb(94, 94, 94);
}
.logo {
width: 70vw;
}
.verify {
padding: 20px 30px;
background-color: rgb(0, 7, 87);
text-decoration: none;
color: white;
transition: 0.5s all;
border-radius: 5px;
margin-bottom: 20px;
}
.verify:hover {
background-color: rgb(0, 12, 139);
}
@media only screen and (min-width: 999px) {
.logo {
width: 20vw;
}
.content {
width: 40vw;
}
}
</style>
</head>
<body>
<div class="content">
<img :src="host + '/otherAssets/logo.png'" alt="Logo" class="logo">
<h1>Password reset</h1>
<p>Your new password is:</p>
<p>{{ password }}</p>
<p>Use it to sign into your account now and change it as soon as you have logged in.</p>
</div>
</body>
</html>

View File

@@ -1,69 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Two-Factor Authentication</title>
<style>
body {
font-family: sans-serif;
width: 100%;
height: 800px;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.content {
width: 80%;
height: 90%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.ip {
color: rgb(94, 94, 94);
}
.logo {
width: 70vw;
}
.verify {
padding: 20px 30px;
background-color: rgb(0, 7, 87);
text-decoration: none;
color: white;
transition: 0.5s all;
border-radius: 5px;
margin-bottom: 20px;
}
.verify:hover {
background-color: rgb(0, 12, 139);
}
@media only screen and (min-width: 999px) {
.logo {
width: 20vw;
}
.content {
width: 40vw;
}
}
</style>
</head>
<body>
<div class="content">
<img :src="host + '/otherAssets/logo.png'" alt="Logo" class="logo">
<h1>Welcome to {{ pageName }}</h1>
<p>To complete your signup at {{ pageName }}, we need you to click the link below to confirm your email.</p>
<a :href="host + '/user/signup/confirm?token=' + token" class="verify">Confirm</a>
</div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More