working 2fa system

This commit is contained in:
2023-08-02 14:13:21 +02:00
parent 242bfa012e
commit de3ab81be2
21 changed files with 717 additions and 143 deletions

View File

@@ -8,8 +8,10 @@
*/ */
const token = require( '../backend/token.js' ); const token = require( '../backend/token.js' );
// let createSSRApp = require( 'vue' ).createSSRApp; let createSSRApp = require( 'vue' ).createSSRApp;
// let renderToString = require( 'vue/server-renderer' ).renderToString; let renderToString = require( 'vue/server-renderer' ).renderToString;
const fs = require( 'fs' );
const path = require( 'path' );
class TwoFA { class TwoFA {
constructor () { constructor () {
@@ -61,6 +63,22 @@ class TwoFA {
} else if ( this.tokenStore[ token ]?.mode === 'enhanced' ) return 'enhanced'; } else if ( this.tokenStore[ token ]?.mode === 'enhanced' ) return 'enhanced';
else return 'invalid'; 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; module.exports = TwoFA;

View File

@@ -12,6 +12,8 @@ const pwdmanager = require( './pwdmanager.js' );
const auth = require( './2fa.js' ); const auth = require( './2fa.js' );
const twoFA = new auth(); const twoFA = new auth();
const path = require( 'path' ); const path = require( 'path' );
const mail = require( '../backend/mail/mailSender.js' );
const mailManager = new mail();
let responseObjects = {}; let responseObjects = {};
let authOk = {}; let authOk = {};
@@ -26,18 +28,26 @@ module.exports = ( app, settings ) => {
pwdmanager.checkpassword( request.body.mail, request.body.password ).then( data => { pwdmanager.checkpassword( request.body.mail, request.body.password ).then( data => {
request.session.username = request.body.mail; request.session.username = request.body.mail;
if ( data ) { if ( data ) {
// TODO: Send mails request.session.username = request.body.mail;
// TODO: Check if user has 2fa enabled // TODO: Check if user has 2fa enabled
if ( settings.twoFA === 'standard' ) { if ( settings.twoFA === 'standard' ) {
( async () => {
let tok = twoFA.registerStandardAuthentication()[ 'token' ]; 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; request.session.token = tok;
console.log( 'http://localhost:8081/admin/2fa?token=' + tok );
response.send( { 'status': '2fa' } ); response.send( { 'status': '2fa' } );
} )();
} else if ( settings.twoFA === 'enhanced' ) { } else if ( settings.twoFA === 'enhanced' ) {
( async () => {
let res = twoFA.registerEnhancedAuthentication(); let res = twoFA.registerEnhancedAuthentication();
console.log( 'http://localhost:8081/admin/2fa?token=' + res.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( res.token, ip, settings.yourDomain, settings.name ), 'Verify admin account login', settings.mailSender );
request.session.token = res.token; request.session.token = res.token;
response.send( { 'status': '2fa+', 'code': res.code } ); response.send( { 'status': '2fa+', 'code': res.code } );
} )();
} else { } else {
request.session.loggedInUser = true; request.session.loggedInUser = true;
response.send( { 'status': 'ok' } ); response.send( { 'status': 'ok' } );

View File

@@ -21,7 +21,17 @@ const db = require( '../backend/db/db.js' );
module.exports.checkpassword = ( username, password ) => { module.exports.checkpassword = ( username, password ) => {
return new Promise( resolve => { return new Promise( resolve => {
db.getDataSimple( 'admin', 'email', username ).then( data => { db.getDataSimple( 'admin', 'email', username ).then( data => {
resolve( bcrypt.compareSync( password, data ) ); if ( data ) {
if ( data[ 0 ] ) {
bcrypt.compare( password, data[ 0 ].pass ).then( res => {
resolve( res );
} );
} else {
resolve( false );
}
} else {
resolve( false );
}
} ); } );
} ); } );
}; };

View File

@@ -0,0 +1,70 @@
<!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/profile/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

@@ -8,13 +8,16 @@
*/ */
const token = require( '../token.js' ); const token = require( '../token.js' );
// let createSSRApp = require( 'vue' ).createSSRApp; let createSSRApp = require( 'vue' ).createSSRApp;
// let renderToString = require( 'vue/server-renderer' ).renderToString; let renderToString = require( 'vue/server-renderer' ).renderToString;
const fs = require( 'fs' );
const path = require( 'path' );
class TwoFA { class TwoFA {
constructor () { constructor () {
this.tokenStore = {}; this.tokenStore = {};
this.references = {}; this.references = {};
this.pwdChangeTokens = {};
} }
registerStandardAuthentication () { registerStandardAuthentication () {
@@ -61,6 +64,29 @@ class TwoFA {
} else if ( this.tokenStore[ token ]?.mode === 'enhanced' ) return 'enhanced'; } else if ( this.tokenStore[ token ]?.mode === 'enhanced' ) return 'enhanced';
else return 'invalid'; else return 'invalid';
} }
generatePwdChangeToken () {
// TODO: Gen token and store in store
return 'test';
}
async generateTwoFAMail ( token, ip, domain, pageName ) {
const tok = this.generatePwdChangeToken();
const app = createSSRApp( {
data() {
return {
token: token,
ip: ip,
host: domain,
pageName: pageName,
pwdChangeToken: tok,
};
},
template: '' + fs.readFileSync( path.join( __dirname + '/twoFAMail.html' ) )
} );
return await renderToString( app );
}
} }
module.exports = TwoFA; module.exports = TwoFA;

View File

@@ -19,9 +19,17 @@ const db = require( '../db/db.js' );
module.exports.checkpassword = function checkpassword ( email, password ) { module.exports.checkpassword = function checkpassword ( email, password ) {
return new Promise( resolve => { return new Promise( resolve => {
db.getDataSimple( 'user', 'email', email ).then( data => { db.getDataSimple( 'user', 'email', email ).then( data => {
bcrypt.compare( password, data ).then( data => { if ( data ) {
resolve( data ); if ( data[ 0 ] ) {
bcrypt.compare( password, data[ 0 ].pass ).then( res => {
resolve( res );
} ); } );
} else {
resolve( false );
}
} else {
resolve( false );
}
} ); } );
} ); } );
}; };

View File

@@ -0,0 +1,70 @@
<!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 <a :href="host + '/account/changePassword?token=' + pwdChangeToken">change</a> 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

@@ -55,11 +55,11 @@ class SQLDB {
if ( error ) throw error; if ( error ) throw error;
if ( results[ 0 ][ '@@default_storage_engine' ] !== 'InnoDB' ) return 'DB HAS TO USE InnoDB!'; if ( results[ 0 ][ '@@default_storage_engine' ] !== 'InnoDB' ) return '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, street TEXT, house_number TINYTEXT, country TEXT, phone TEXT, name TEXT, first_name TEXT, data VARCHAR( 10000 ), PRIMARY KEY ( account_id ) ) ENGINE=INNODB;', ( error ) => { this.sqlConnection.query( 'CREATE TABLE libreevent_users ( account_id INT ( 10 ) NOT NULL AUTO_INCREMENT, email TINYTEXT NOT NULL, pass TEXT, street TEXT, house_number TINYTEXT, country TEXT, phone TEXT, name TEXT, first_name TEXT, two_fa TINYTEXT, PRIMARY KEY ( account_id ) ) ENGINE=INNODB;', ( error ) => {
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw 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, account_id INT ( 10 ) NOT NULL, seats VARCHAR( 60000 ), PRIMARY KEY ( order_id ), FOREIGN KEY ( account_id ) REFERENCES libreevent_users( account_id ) ) ENGINE=INNODB;', ( error ) => { this.sqlConnection.query( 'CREATE TABLE libreevent_orders ( order_id INT ( 10 ) NOT NULL AUTO_INCREMENT, account_id INT ( 10 ) NOT NULL, seats VARCHAR( 60000 ), 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; 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 ), PRIMARY KEY ( account_id ) );', ( 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; 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 ) => { 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; if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;

View File

@@ -12,32 +12,61 @@ const pwdmanager = require( './credentials/pwdmanager.js' );
const auth = require( './credentials/2fa.js' ); const auth = require( './credentials/2fa.js' );
const twoFA = new auth(); const twoFA = new auth();
const path = require( 'path' ); const path = require( 'path' );
const mail = require( './mail/mailSender.js' );
const mailManager = new mail();
let responseObjects = {}; let responseObjects = {};
let authOk = {}; let authOk = {};
module.exports = ( app, settings ) => { module.exports = ( app, settings ) => {
app.post( '/api/reserveTicket', ( request, response ) => { app.get( '/user/details', ( request, response ) => {
db.getData( 'test', request.body ); if ( request.session.loggedInUser ) {
response.send( 'ok' ); db.getDataSimple( 'users', 'email', request.session.username ).then( data => {
if ( data[ 0 ] ) {
let dat = data[ 0 ];
delete dat[ 'pass' ];
response.send( { 'data': dat, 'status': true } );
} else {
response.send( { 'data': 'This user does not exist', 'status': false } );
}
} ).catch( () => {
response.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 ).send( 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', ( request, response ) => { app.post( '/user/login', ( request, response ) => {
if ( request.body.mail && request.body.password ) { if ( request.body.mail && request.body.password ) {
pwdmanager.checkpassword( request.body.mail, request.body.password ).then( data => { pwdmanager.checkpassword( request.body.mail, request.body.password ).then( data => {
request.session.username = request.body.mail; request.session.username = request.body.mail;
// TODO: Check if user has 2fa enabled
if ( data ) { if ( data ) {
// TODO: Send mails
if ( settings.twoFA === 'standard' ) { if ( settings.twoFA === 'standard' ) {
( async () => {
let tok = twoFA.registerStandardAuthentication()[ 'token' ]; 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; request.session.token = tok;
console.log( 'http://localhost:8081/user/2fa?token=' + tok );
response.send( { 'status': '2fa' } ); response.send( { 'status': '2fa' } );
} )();
} else if ( settings.twoFA === 'enhanced' ) { } else if ( settings.twoFA === 'enhanced' ) {
( async () => {
let res = twoFA.registerEnhancedAuthentication(); let res = twoFA.registerEnhancedAuthentication();
console.log( 'http://localhost:8081/user/2fa?token=' + res.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( res.token, ip, settings.yourDomain, settings.name ), 'Verify login', settings.mailSender );
request.session.token = res.token; request.session.token = res.token;
response.send( { 'status': '2fa+', 'code': res.code } ); response.send( { 'status': '2fa+', 'code': res.code } );
} )();
} else { } else {
request.session.loggedInUser = true; request.session.loggedInUser = true;
response.send( { 'status': 'ok' } ); response.send( { 'status': 'ok' } );
@@ -108,6 +137,11 @@ module.exports = ( app, settings ) => {
} ); } );
app.post( '/user/signup', ( request, response ) => { app.post( '/user/signup', ( request, response ) => {
// TODO: Make sure that user does not exist yet first and if user
// exists, send back info that it is that way
response.send( 'ok' ); response.send( 'ok' );
db.writeDataSimple( 'users', 'email', request.body.email, { 'pass': pwdmanager.hashPassword( request.query.password ), 'street': '', 'house_number': '', 'country': request.query.country, 'first_name': request.query.firstName, 'name': request.query.name, 'two_fa': String( request.query.twoFA ) } ).then( res => {
console.log( res );
} );
} ); } );
}; };

View File

@@ -4,5 +4,6 @@
"db": "mysql", "db": "mysql",
"payments": "stripe", "payments": "stripe",
"name": "libreevent", "name": "libreevent",
"yourDomain": "http://localhost:8081" "yourDomain": "http://localhost:8081",
"mailSender": "libreevent <info@libreevent.janishutz.com>"
} }

View File

@@ -21,7 +21,8 @@
"nodemailer": "^6.9.3", "nodemailer": "^6.9.3",
"serve-favicon": "^2.5.0", "serve-favicon": "^2.5.0",
"serve-static": "^1.15.0", "serve-static": "^1.15.0",
"stripe": "^12.14.0" "stripe": "^12.14.0",
"vue": "^3.3.4"
}, },
"devDependencies": { "devDependencies": {
"acorn": "^8.8.2", "acorn": "^8.8.2",
@@ -59,6 +60,17 @@
"yocto-queue": "^1.0.0" "yocto-queue": "^1.0.0"
} }
}, },
"node_modules/@babel/parser": {
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz",
"integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==",
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
@@ -104,8 +116,7 @@
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15", "version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
"dev": true
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.18", "version": "0.3.18",
@@ -242,6 +253,108 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.4.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.4.tgz",
"integrity": "sha512-CukZhumInROvLq3+b5gLev+vgpsIqC2D0deQr/yS1WnxvmYLlJXZpaQrQiseMY+6xusl79E04UjWoqyr+t1/Ew==" "integrity": "sha512-CukZhumInROvLq3+b5gLev+vgpsIqC2D0deQr/yS1WnxvmYLlJXZpaQrQiseMY+6xusl79E04UjWoqyr+t1/Ew=="
}, },
"node_modules/@vue/compiler-core": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz",
"integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==",
"dependencies": {
"@babel/parser": "^7.21.3",
"@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz",
"integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==",
"dependencies": {
"@vue/compiler-core": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz",
"integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==",
"dependencies": {
"@babel/parser": "^7.20.15",
"@vue/compiler-core": "3.3.4",
"@vue/compiler-dom": "3.3.4",
"@vue/compiler-ssr": "3.3.4",
"@vue/reactivity-transform": "3.3.4",
"@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.0",
"postcss": "^8.1.10",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz",
"integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==",
"dependencies": {
"@vue/compiler-dom": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"node_modules/@vue/reactivity": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz",
"integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==",
"dependencies": {
"@vue/shared": "3.3.4"
}
},
"node_modules/@vue/reactivity-transform": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz",
"integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==",
"dependencies": {
"@babel/parser": "^7.20.15",
"@vue/compiler-core": "3.3.4",
"@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.0"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz",
"integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==",
"dependencies": {
"@vue/reactivity": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz",
"integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==",
"dependencies": {
"@vue/runtime-core": "3.3.4",
"@vue/shared": "3.3.4",
"csstype": "^3.1.1"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz",
"integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==",
"dependencies": {
"@vue/compiler-ssr": "3.3.4",
"@vue/shared": "3.3.4"
},
"peerDependencies": {
"vue": "3.3.4"
}
},
"node_modules/@vue/shared": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz",
"integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
},
"node_modules/abbrev": { "node_modules/abbrev": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -615,6 +728,11 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/csstype": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -774,6 +892,11 @@
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
}, },
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/etag": { "node_modules/etag": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -1429,6 +1552,17 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/magic-string": {
"version": "0.30.2",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz",
"integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
"engines": {
"node": ">=12"
}
},
"node_modules/make-dir": { "node_modules/make-dir": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -1625,6 +1759,23 @@
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
} }
}, },
"node_modules/nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/negotiator": { "node_modules/negotiator": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -1848,6 +1999,38 @@
"url": "https://ko-fi.com/killymxi" "url": "https://ko-fi.com/killymxi"
} }
}, },
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/postcss": {
"version": "8.4.27",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
"integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/process-nextick-args": { "node_modules/process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -2159,6 +2342,14 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": { "node_modules/source-map-support": {
"version": "0.5.21", "version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -2397,6 +2588,18 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/vue": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz",
"integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==",
"dependencies": {
"@vue/compiler-dom": "3.3.4",
"@vue/compiler-sfc": "3.3.4",
"@vue/runtime-dom": "3.3.4",
"@vue/server-renderer": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"node_modules/webidl-conversions": { "node_modules/webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",

View File

@@ -54,7 +54,8 @@
"nodemailer": "^6.9.3", "nodemailer": "^6.9.3",
"serve-favicon": "^2.5.0", "serve-favicon": "^2.5.0",
"serve-static": "^1.15.0", "serve-static": "^1.15.0",
"stripe": "^12.14.0" "stripe": "^12.14.0",
"vue": "^3.3.4"
}, },
"scripts": { "scripts": {
"test": "test.js" "test": "test.js"

View File

@@ -1,6 +1,24 @@
const sql = require( './backend/db/mysqldb.js' ); const sql = require( './backend/db/mysqldb.js' );
const sqlDB = new sql(); const sqlDB = new sql();
const db = require( './backend/db/db.js' );
sqlDB.connect(); sqlDB.connect();
// sqlDB.resetDB();
sqlDB.setupDB(); sqlDB.resetDB();
setTimeout( () => {
sqlDB.setupDB();
setTimeout( () => {
db.writeDataSimple( 'admin', 'email', 'info@janishutz.com', { 'email': 'info@janishutz.com', 'pass': '$2b$05$ElMYWoMjk7567lXkIkee.e.6cxCrWU4gkfuNLB8gmGYLQQPm7gT3O', 'username': 'jhutz' } );
}, 1000 );
setTimeout( () => {
db.writeDataSimple( 'user', 'email', 'info@janishutz.com', { 'email': 'info@janishutz.com', 'pass': '$2b$05$ElMYWoMjk7567lXkIkee.e.6cxCrWU4gkfuNLB8gmGYLQQPm7gT3O', 'name': 'Hutz', 'first_name': 'Janis', 'two_fa': 'true' } );
}, 1000 );
setTimeout( () => {
db.writeDataSimple( 'admin', 'email', 'info@janishutz.com', { 'email': 'info@janishutz.com', 'pass': '$2b$05$ElMYWoMjk7567lXkIkee.e.6cxCrWU4gkfuNLB8gmGYLQQPm7gT3O', 'username': 'jhutz', 'permissions': JSON.stringify( { 'test': true } ), 'two_fa': true } );
}, 2000 );
}, 1000 );

View File

@@ -1,9 +1,16 @@
# Account view: # Account view:
- Maybe add multi-language support
- make pricing groups changeable in UI (event categories) - make pricing groups changeable in UI (event categories)
- Create password changing endpoint (to reset forgotten pwd)
- Add Admin profile (page to change account settings per person like changing pwd)
- Fix text field overflow (text too big for box) - Fix text field overflow (text too big for box)
- Other optimisation for seat plan editor - Other optimisation for seat plan editor
- Implement Permission system - Implement Permission system
- Seat numbering - Seat numbering

View File

@@ -116,6 +116,15 @@ export default [
transition: 'scale' transition: 'scale'
} }
}, },
{
path: '/guest',
name: 'guestPurchase',
component: () => import( '@/views/purchasing/GuestPurchaseView.vue' ),
meta: {
title: 'Guest purchase - ',
transition: 'scale'
}
},
{ {
path: '/admin/seatplan', path: '/admin/seatplan',
name: 'adminSeatplanEditor', name: 'adminSeatplanEditor',

View File

@@ -32,6 +32,7 @@
...mapStores( useUserStore ), ...mapStores( useUserStore ),
}, },
created () { created () {
if ( this.userStore.getAdminTwoFACompliant ) {
if ( !!window.EventSource ) { if ( !!window.EventSource ) {
setTimeout( () => { setTimeout( () => {
let startNotification = this.$refs.notification.createNotification( 'Connecting to status service', 20, 'progress', 'normal' ); let startNotification = this.$refs.notification.createNotification( 'Connecting to status service', 20, 'progress', 'normal' );
@@ -90,6 +91,13 @@
} }
let code = sessionStorage.getItem( '2faCode' ) ? sessionStorage.getItem( '2faCode' ) : ''; let code = sessionStorage.getItem( '2faCode' ) ? sessionStorage.getItem( '2faCode' ) : '';
this.code = { '1': code.slice( 0, 3 ), '2': code.substring( 3 ) }; this.code = { '1': code.slice( 0, 3 ), '2': code.substring( 3 ) };
} else {
if ( this.userStore.getAdminAuthenticated ) {
this.$router.push( '/admin' );
} else {
this.$router.push( '/admin/login' );
}
}
}, },
} }
</script> </script>

View File

@@ -6,3 +6,15 @@
* *
* *
--> -->
<template>
<div>
<h1>Guest purchase</h1>
</div>
</template>
<script>
export default {
}
</script>

View File

@@ -217,6 +217,7 @@ export default {
}, },
methods: { methods: {
loadData () { loadData () {
// TODO: Also load the customer data from server!
this.cartNotEmpty = false; this.cartNotEmpty = false;
let cart = JSON.parse( localStorage.getItem( 'cart' ) ); let cart = JSON.parse( localStorage.getItem( 'cart' ) );

View File

@@ -1,7 +1,19 @@
<template> <template>
<div> <div>
<h1>Account</h1> <h1>Account</h1>
<p>Welcome, {{ accountData.first_name }} {{ accountData.name }}!</p>
<table>
<tr>
<td>
Email
</td>
<td>
{{ accountData.email }}
</td>
</tr>
</table>
<notifications ref="notification" location="topright" size="bigger"></notifications>
<popups ref="popups" size="big" @data="data => { savePwd( data ) }"></popups>
</div> </div>
</template> </template>
@@ -11,3 +23,49 @@
display: initial; display: initial;
} }
</style> </style>
<script>
import { useUserStore } from '@/stores/userStore';
import { mapStores } from 'pinia';
import notifications from '@/components/notifications/notifications.vue';
import popups from '@/components/notifications/popups.vue';
export default {
data () {
return {
accountData: {},
}
},
components: {
notifications,
popups,
},
computed: {
...mapStores( useUserStore )
},
created () {
// TODO: Also get all orders of user (using join functions)
fetch( '/user/details' ).then( res => {
if ( res.status === 200 ) {
res.json().then( data => {
if ( data.status ) {
this.accountData = data.data;
console.log( data.data );
} else {
this.userStore.setUserAuth( false );
this.userStore.setUser2fa( false );
this.$router.push( '/login' );
}
} );
} else if ( res.status === 403 ) {
this.userStore.setUserAuth( false );
this.userStore.setUser2fa( false );
this.$router.push( '/login' );
}
} );
if ( this.userStore.getUserTwoFACompliant ) {
this.userStore.setUser2fa( false );
}
}
}
</script>

View File

@@ -33,6 +33,7 @@
...mapStores( useUserStore ), ...mapStores( useUserStore ),
}, },
created () { created () {
if ( this.userStore.getUserTwoFACompliant ) {
if ( !!window.EventSource ) { if ( !!window.EventSource ) {
setTimeout( () => { setTimeout( () => {
let startNotification = this.$refs.notification.createNotification( 'Connecting to status service', 20, 'progress', 'normal' ); let startNotification = this.$refs.notification.createNotification( 'Connecting to status service', 20, 'progress', 'normal' );
@@ -89,6 +90,13 @@
} }
let code = sessionStorage.getItem( '2faCode' ) ? sessionStorage.getItem( '2faCode' ) : ''; let code = sessionStorage.getItem( '2faCode' ) ? sessionStorage.getItem( '2faCode' ) : '';
this.code = { '1': code.slice( 0, 3 ), '2': code.substring( 3 ) }; this.code = { '1': code.slice( 0, 3 ), '2': code.substring( 3 ) };
} else {
if ( this.userStore.getUserAuthenticated ) {
this.$router.push( '/account' );
} else {
this.$router.push( '/login' );
}
}
}, },
unmounted() { unmounted() {
clearInterval( this.serverPing ); clearInterval( this.serverPing );

View File

@@ -21,7 +21,9 @@ In the database, all the userdata is stored. libreevent currently supports two d
Generally MySQL, except: Generally MySQL, except:
- If your organisation is small and does only sell a few tickets at a time, the JSON based database works perfectly fine. - If your organisation is small and does only sell a few tickets at a time, the JSON based database works perfectly fine.
- Your web hosting plan does not includes MySQL and you've got no access to MySQL in any other way. *NOTE: Free MySQL services should NEVER be used in such an application, as most hosting plans include MySQL which is much more reliable and if you lose access to the database, you can only access the root account and all other data (and therefore all user accounts) is lost.* - Your web hosting plan does not includes MySQL and you've got no access to MySQL in any other way. *NOTE: Free MySQL services should NEVER be used in such an application, as most hosting plans include MySQL which is much more reliable and if you lose access to the database, you can only access the root account and all other user data (and therefore all user accounts) is lost. The event data is always stored in JSON format as it is more efficient.*
**NOTE: The JSON database is really slow and should only be used if you have a small event where you expect to sell less than 5 ticket per minute! The amount of tickets sold per minute that the system can handle really depends on the speed of the server the website runs on.**
MySQL generally is more difficult to set up, but we'll run you through the process here to make the process easier for you. If you chose the JSON based database, skip ahead to the next chapter. MySQL generally is more difficult to set up, but we'll run you through the process here to make the process easier for you. If you chose the JSON based database, skip ahead to the next chapter.