diff --git a/src/server/admin/2fa.js b/src/server/admin/2fa.js
index 0d0962d..35baf58 100644
--- a/src/server/admin/2fa.js
+++ b/src/server/admin/2fa.js
@@ -8,8 +8,10 @@
*/
const token = require( '../backend/token.js' );
-// let createSSRApp = require( 'vue' ).createSSRApp;
-// let renderToString = require( 'vue/server-renderer' ).renderToString;
+let createSSRApp = require( 'vue' ).createSSRApp;
+let renderToString = require( 'vue/server-renderer' ).renderToString;
+const fs = require( 'fs' );
+const path = require( 'path' );
class TwoFA {
constructor () {
@@ -61,6 +63,22 @@ class TwoFA {
} 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;
\ No newline at end of file
diff --git a/src/server/admin/adminRoutes.js b/src/server/admin/adminRoutes.js
index d090c49..499b276 100644
--- a/src/server/admin/adminRoutes.js
+++ b/src/server/admin/adminRoutes.js
@@ -12,13 +12,15 @@ const pwdmanager = require( './pwdmanager.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();
let responseObjects = {};
let authOk = {};
module.exports = ( app, settings ) => {
/*
- Admin login route that checks the password
+ Admin login route that checks the password
*/
app.post( '/admin/auth', ( request, response ) => {
@@ -26,18 +28,26 @@ module.exports = ( app, settings ) => {
pwdmanager.checkpassword( request.body.mail, request.body.password ).then( data => {
request.session.username = request.body.mail;
if ( data ) {
- // TODO: Send mails
+ request.session.username = request.body.mail;
// TODO: Check if user has 2fa enabled
if ( settings.twoFA === 'standard' ) {
- let tok = twoFA.registerStandardAuthentication()[ 'token' ];
- request.session.token = tok;
- console.log( 'http://localhost:8081/admin/2fa?token=' + tok );
- response.send( { 'status': '2fa' } );
+ ( 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 ( settings.twoFA === 'enhanced' ) {
- let res = twoFA.registerEnhancedAuthentication();
- console.log( 'http://localhost:8081/admin/2fa?token=' + res.token );
- request.session.token = res.token;
- response.send( { 'status': '2fa+', 'code': res.code } );
+ ( 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 admin account login', settings.mailSender );
+ request.session.token = res.token;
+ response.send( { 'status': '2fa+', 'code': res.code } );
+ } )();
} else {
request.session.loggedInUser = true;
response.send( { 'status': 'ok' } );
diff --git a/src/server/admin/pwdmanager.js b/src/server/admin/pwdmanager.js
index fcb6b8a..cf6b816 100644
--- a/src/server/admin/pwdmanager.js
+++ b/src/server/admin/pwdmanager.js
@@ -21,7 +21,17 @@ const db = require( '../backend/db/db.js' );
module.exports.checkpassword = ( username, password ) => {
return new Promise( resolve => {
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 );
+ }
} );
} );
};
diff --git a/src/server/admin/twoFAMail.html b/src/server/admin/twoFAMail.html
new file mode 100644
index 0000000..ed91688
--- /dev/null
+++ b/src/server/admin/twoFAMail.html
@@ -0,0 +1,70 @@
+
+
+
+
+
+ Two-Factor Authentication
+
+
+
+
+
![Logo]()
+
Welcome back!
+
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 change your password immediately or have it changed by the root account!
+
Logging in from IP {{ ip }}.
+
Verify
+
+
+
\ No newline at end of file
diff --git a/src/server/backend/credentials/2fa.js b/src/server/backend/credentials/2fa.js
index 413e9e8..4351d8a 100644
--- a/src/server/backend/credentials/2fa.js
+++ b/src/server/backend/credentials/2fa.js
@@ -8,13 +8,16 @@
*/
const token = require( '../token.js' );
-// let createSSRApp = require( 'vue' ).createSSRApp;
-// let renderToString = require( 'vue/server-renderer' ).renderToString;
+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 () {
@@ -61,6 +64,29 @@ class TwoFA {
} else if ( this.tokenStore[ token ]?.mode === 'enhanced' ) return 'enhanced';
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;
\ No newline at end of file
diff --git a/src/server/backend/credentials/pwdmanager.js b/src/server/backend/credentials/pwdmanager.js
index 4487798..ed93a46 100644
--- a/src/server/backend/credentials/pwdmanager.js
+++ b/src/server/backend/credentials/pwdmanager.js
@@ -19,9 +19,17 @@ const db = require( '../db/db.js' );
module.exports.checkpassword = function checkpassword ( email, password ) {
return new Promise( resolve => {
db.getDataSimple( 'user', 'email', email ).then( data => {
- bcrypt.compare( password, data ).then( data => {
- resolve( data );
- } );
+ if ( data ) {
+ if ( data[ 0 ] ) {
+ bcrypt.compare( password, data[ 0 ].pass ).then( res => {
+ resolve( res );
+ } );
+ } else {
+ resolve( false );
+ }
+ } else {
+ resolve( false );
+ }
} );
} );
};
diff --git a/src/server/backend/credentials/twoFAMail.html b/src/server/backend/credentials/twoFAMail.html
new file mode 100644
index 0000000..e6ac8b7
--- /dev/null
+++ b/src/server/backend/credentials/twoFAMail.html
@@ -0,0 +1,70 @@
+
+
+
+
+
+ Two-Factor Authentication
+
+
+
+
+
![Logo]()
+
Welcome back!
+
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.
+
Logging in from IP {{ ip }}.
+
Verify
+
+
+
\ No newline at end of file
diff --git a/src/server/backend/db/mysqldb.js b/src/server/backend/db/mysqldb.js
index d4d45bc..1e4a987 100644
--- a/src/server/backend/db/mysqldb.js
+++ b/src/server/backend/db/mysqldb.js
@@ -55,11 +55,11 @@ class SQLDB {
if ( error ) throw error;
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;
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;
- 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;
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;
diff --git a/src/server/backend/userRoutes.js b/src/server/backend/userRoutes.js
index 41b3ef2..5d0057a 100644
--- a/src/server/backend/userRoutes.js
+++ b/src/server/backend/userRoutes.js
@@ -12,32 +12,61 @@ 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();
let responseObjects = {};
let authOk = {};
module.exports = ( app, settings ) => {
- app.post( '/api/reserveTicket', ( request, response ) => {
- db.getData( 'test', request.body );
- response.send( 'ok' );
+ app.get( '/user/details', ( request, response ) => {
+ if ( request.session.loggedInUser ) {
+ 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 ) => {
if ( request.body.mail && request.body.password ) {
pwdmanager.checkpassword( request.body.mail, request.body.password ).then( data => {
request.session.username = request.body.mail;
+ // TODO: Check if user has 2fa enabled
if ( data ) {
- // TODO: Send mails
if ( settings.twoFA === 'standard' ) {
- let tok = twoFA.registerStandardAuthentication()[ 'token' ];
- request.session.token = tok;
- console.log( 'http://localhost:8081/user/2fa?token=' + tok );
- response.send( { 'status': '2fa' } );
+ ( 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 ( settings.twoFA === 'enhanced' ) {
- let res = twoFA.registerEnhancedAuthentication();
- console.log( 'http://localhost:8081/user/2fa?token=' + res.token );
- request.session.token = res.token;
- response.send( { 'status': '2fa+', 'code': res.code } );
+ ( 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' } );
@@ -108,6 +137,11 @@ module.exports = ( app, settings ) => {
} );
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' );
+ 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 );
+ } );
} );
};
\ No newline at end of file
diff --git a/src/server/config/settings.config.json b/src/server/config/settings.config.json
index d6df282..5e0d7af 100644
--- a/src/server/config/settings.config.json
+++ b/src/server/config/settings.config.json
@@ -4,5 +4,6 @@
"db": "mysql",
"payments": "stripe",
"name": "libreevent",
- "yourDomain": "http://localhost:8081"
+ "yourDomain": "http://localhost:8081",
+ "mailSender": "libreevent "
}
\ No newline at end of file
diff --git a/src/server/package-lock.json b/src/server/package-lock.json
index 32436f8..430722a 100644
--- a/src/server/package-lock.json
+++ b/src/server/package-lock.json
@@ -21,7 +21,8 @@
"nodemailer": "^6.9.3",
"serve-favicon": "^2.5.0",
"serve-static": "^1.15.0",
- "stripe": "^12.14.0"
+ "stripe": "^12.14.0",
+ "vue": "^3.3.4"
},
"devDependencies": {
"acorn": "^8.8.2",
@@ -59,6 +60,17 @@
"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": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
@@ -104,8 +116,7 @@
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.18",
@@ -242,6 +253,108 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.4.tgz",
"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": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -615,6 +728,11 @@
"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": {
"version": "4.3.4",
"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",
"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": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -1429,6 +1552,17 @@
"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": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -1625,6 +1759,23 @@
"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": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -1848,6 +1999,38 @@
"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": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -2159,6 +2342,14 @@
"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": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -2397,6 +2588,18 @@
"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": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
diff --git a/src/server/package.json b/src/server/package.json
index 32a2808..8e8db35 100644
--- a/src/server/package.json
+++ b/src/server/package.json
@@ -54,7 +54,8 @@
"nodemailer": "^6.9.3",
"serve-favicon": "^2.5.0",
"serve-static": "^1.15.0",
- "stripe": "^12.14.0"
+ "stripe": "^12.14.0",
+ "vue": "^3.3.4"
},
"scripts": {
"test": "test.js"
diff --git a/src/server/prepareDB.js b/src/server/prepareDB.js
index 41a38f8..1af6f87 100644
--- a/src/server/prepareDB.js
+++ b/src/server/prepareDB.js
@@ -1,6 +1,24 @@
const sql = require( './backend/db/mysqldb.js' );
const sqlDB = new sql();
+const db = require( './backend/db/db.js' );
+
sqlDB.connect();
-// sqlDB.resetDB();
-sqlDB.setupDB();
\ No newline at end of file
+
+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 );
\ No newline at end of file
diff --git a/src/webapp/main/notes.md b/src/webapp/main/notes.md
index 9fe969d..808cc40 100644
--- a/src/webapp/main/notes.md
+++ b/src/webapp/main/notes.md
@@ -1,9 +1,16 @@
# Account view:
+- Maybe add multi-language support
+
- 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)
- Other optimisation for seat plan editor
+
- Implement Permission system
- Seat numbering
diff --git a/src/webapp/main/src/router/mainRoutes.js b/src/webapp/main/src/router/mainRoutes.js
index 195421f..c9456ea 100644
--- a/src/webapp/main/src/router/mainRoutes.js
+++ b/src/webapp/main/src/router/mainRoutes.js
@@ -116,6 +116,15 @@ export default [
transition: 'scale'
}
},
+ {
+ path: '/guest',
+ name: 'guestPurchase',
+ component: () => import( '@/views/purchasing/GuestPurchaseView.vue' ),
+ meta: {
+ title: 'Guest purchase - ',
+ transition: 'scale'
+ }
+ },
{
path: '/admin/seatplan',
name: 'adminSeatplanEditor',
diff --git a/src/webapp/main/src/views/admin/TwoFA.vue b/src/webapp/main/src/views/admin/TwoFA.vue
index 6473829..d88607b 100644
--- a/src/webapp/main/src/views/admin/TwoFA.vue
+++ b/src/webapp/main/src/views/admin/TwoFA.vue
@@ -32,64 +32,72 @@
...mapStores( useUserStore ),
},
created () {
- if ( !!window.EventSource ) {
- setTimeout( () => {
- let startNotification = this.$refs.notification.createNotification( 'Connecting to status service', 20, 'progress', 'normal' );
- let source = new EventSource( localStorage.getItem( 'url' ) + '/admin/2fa/check', { withCredentials: true } );
-
- let self = this;
+ if ( this.userStore.getAdminTwoFACompliant ) {
+ if ( !!window.EventSource ) {
+ setTimeout( () => {
+ let startNotification = this.$refs.notification.createNotification( 'Connecting to status service', 20, 'progress', 'normal' );
+ let source = new EventSource( localStorage.getItem( 'url' ) + '/admin/2fa/check', { withCredentials: true } );
+
+ let self = this;
- source.onmessage = ( e ) => {
- if ( e.data === 'authenticated' ) {
- self.userStore.setAdminAuth( true );
- self.$router.push( '/admin' );
- console.log( e.data );
+ source.onmessage = ( e ) => {
+ if ( e.data === 'authenticated' ) {
+ self.userStore.setAdminAuth( true );
+ self.$router.push( '/admin' );
+ console.log( e.data );
+ }
}
- }
- source.onopen = e => {
- self.$refs.notification.createNotification( 'Connected to status service', 5, 'ok', 'normal' );
- self.$refs.notification.cancelNotification( startNotification );
- };
-
- source.addEventListener( 'error', function(e) {
- if ( e.eventPhase == EventSource.CLOSED ) source.close();
-
- if ( e.target.readyState == EventSource.CLOSED ) {
- console.log( e );
+ source.onopen = e => {
+ self.$refs.notification.createNotification( 'Connected to status service', 5, 'ok', 'normal' );
self.$refs.notification.cancelNotification( startNotification );
- self.$refs.notification.createNotification( 'Could not connect to status service', 5, 'error', 'normal' );
- }
- }, false)
- }, 300 );
- } else {
- setTimeout( () => {
- this.$refs.notification.createNotification( 'Unsupported browser detected. Redirection might take longer to occur!', 20, 'warning', 'normal' );
- }, 300 );
- // ping server every 5s to check if logged in
- this.serverPing = setInterval( () => {
- fetch( '/admin/2fa/ping' ).then( res => {
- if ( res.status === 200 ) {
- res.json().then( data => {
- if ( data ) {
- if ( data.status === 'ok' ) {
- this.userStore.setUserAuth( true );
- this.$router.push( sessionStorage.getItem( 'redirect' ) ?? '/account' );
+ };
+
+ source.addEventListener( 'error', function(e) {
+ if ( e.eventPhase == EventSource.CLOSED ) source.close();
+
+ if ( e.target.readyState == EventSource.CLOSED ) {
+ console.log( e );
+ self.$refs.notification.cancelNotification( startNotification );
+ self.$refs.notification.createNotification( 'Could not connect to status service', 5, 'error', 'normal' );
+ }
+ }, false)
+ }, 300 );
+ } else {
+ setTimeout( () => {
+ this.$refs.notification.createNotification( 'Unsupported browser detected. Redirection might take longer to occur!', 20, 'warning', 'normal' );
+ }, 300 );
+ // ping server every 5s to check if logged in
+ this.serverPing = setInterval( () => {
+ fetch( '/admin/2fa/ping' ).then( res => {
+ if ( res.status === 200 ) {
+ res.json().then( data => {
+ if ( data ) {
+ if ( data.status === 'ok' ) {
+ this.userStore.setUserAuth( true );
+ this.$router.push( sessionStorage.getItem( 'redirect' ) ?? '/account' );
+ }
}
- }
- } );
- } else {
- console.error( 'Request failed' );
+ } );
+ } else {
+ console.error( 'Request failed' );
+ this.$refs.notification.createNotification( 'We are sorry, but an error occurred. You will not be redirected automatically', 300, 'error', 'normal' );
+ }
+ } ).catch( error => {
+ console.error( error );
this.$refs.notification.createNotification( 'We are sorry, but an error occurred. You will not be redirected automatically', 300, 'error', 'normal' );
- }
- } ).catch( error => {
- console.error( error );
- this.$refs.notification.createNotification( 'We are sorry, but an error occurred. You will not be redirected automatically', 300, 'error', 'normal' );
- } );
- }, 5000 );
+ } );
+ }, 5000 );
+ }
+ let code = sessionStorage.getItem( '2faCode' ) ? sessionStorage.getItem( '2faCode' ) : '';
+ 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' );
+ }
}
- let code = sessionStorage.getItem( '2faCode' ) ? sessionStorage.getItem( '2faCode' ) : '';
- this.code = { '1': code.slice( 0, 3 ), '2': code.substring( 3 ) };
},
}
diff --git a/src/webapp/main/src/views/purchasing/GuestPurchaseView.vue b/src/webapp/main/src/views/purchasing/GuestPurchaseView.vue
index b69f2b3..ff45877 100644
--- a/src/webapp/main/src/views/purchasing/GuestPurchaseView.vue
+++ b/src/webapp/main/src/views/purchasing/GuestPurchaseView.vue
@@ -5,4 +5,16 @@
* https://janishutz.com, development@janishutz.com
*
*
--->
\ No newline at end of file
+-->
+
+
+
+
Guest purchase
+
+
+
+
\ No newline at end of file
diff --git a/src/webapp/main/src/views/purchasing/PurchaseView.vue b/src/webapp/main/src/views/purchasing/PurchaseView.vue
index ea52ef0..62898bf 100644
--- a/src/webapp/main/src/views/purchasing/PurchaseView.vue
+++ b/src/webapp/main/src/views/purchasing/PurchaseView.vue
@@ -217,6 +217,7 @@ export default {
},
methods: {
loadData () {
+ // TODO: Also load the customer data from server!
this.cartNotEmpty = false;
let cart = JSON.parse( localStorage.getItem( 'cart' ) );
diff --git a/src/webapp/main/src/views/user/AccountView.vue b/src/webapp/main/src/views/user/AccountView.vue
index 328e8ac..473a28a 100644
--- a/src/webapp/main/src/views/user/AccountView.vue
+++ b/src/webapp/main/src/views/user/AccountView.vue
@@ -1,7 +1,19 @@
Account
-
+
Welcome, {{ accountData.first_name }} {{ accountData.name }}!
+
+
+ |
+ Email
+ |
+
+ {{ accountData.email }}
+ |
+
+
+
+
{ savePwd( data ) }">
@@ -10,4 +22,50 @@
nav {
display: initial;
}
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/src/webapp/main/src/views/user/TwoFA.vue b/src/webapp/main/src/views/user/TwoFA.vue
index 6ab81b8..98529da 100644
--- a/src/webapp/main/src/views/user/TwoFA.vue
+++ b/src/webapp/main/src/views/user/TwoFA.vue
@@ -33,62 +33,70 @@
...mapStores( useUserStore ),
},
created () {
- if ( !!window.EventSource ) {
- setTimeout( () => {
- let startNotification = this.$refs.notification.createNotification( 'Connecting to status service', 20, 'progress', 'normal' );
- let source = new EventSource( localStorage.getItem( 'url' ) + '/user/2fa/check', { withCredentials: true } );
-
- let self = this;
+ if ( this.userStore.getUserTwoFACompliant ) {
+ if ( !!window.EventSource ) {
+ setTimeout( () => {
+ let startNotification = this.$refs.notification.createNotification( 'Connecting to status service', 20, 'progress', 'normal' );
+ let source = new EventSource( localStorage.getItem( 'url' ) + '/user/2fa/check', { withCredentials: true } );
+
+ let self = this;
- source.onmessage = ( e ) => {
- if ( e.data === 'authenticated' ) {
- self.userStore.setUserAuth( true );
- self.$router.push( sessionStorage.getItem( 'redirect' ) ?? '/account' );
+ source.onmessage = ( e ) => {
+ if ( e.data === 'authenticated' ) {
+ self.userStore.setUserAuth( true );
+ self.$router.push( sessionStorage.getItem( 'redirect' ) ?? '/account' );
+ }
}
- }
- source.onopen = e => {
- self.$refs.notification.createNotification( 'Connected to status service', 5, 'ok', 'normal' );
- self.$refs.notification.cancelNotification( startNotification );
- };
-
- source.addEventListener( 'error', function( e ) {
- if ( e.eventPhase == EventSource.CLOSED ) source.close();
-
- if ( e.target.readyState == EventSource.CLOSED ) {
+ source.onopen = e => {
+ self.$refs.notification.createNotification( 'Connected to status service', 5, 'ok', 'normal' );
self.$refs.notification.cancelNotification( startNotification );
- self.$refs.notification.createNotification( 'Could not connect to status service', 5, 'error', 'normal' );
- }
- }, false)
- }, 300 );
- } else {
- setTimeout( () => {
- this.$refs.notification.createNotification( 'Unsupported browser detected. Redirection might take longer to occur!', 20, 'warning', 'normal' );
- }, 300 );
- // ping server every 5s to check if logged in
- this.serverPing = setInterval( () => {
- fetch( '/user/2fa/ping' ).then( res => {
- if ( res.status === 200 ) {
- res.json().then( data => {
- if ( data ) {
- if ( data.status === 'ok' ) {
- this.userStore.setUserAuth( true );
- this.$router.push( sessionStorage.getItem( 'redirect' ) ?? '/account' );
+ };
+
+ source.addEventListener( 'error', function( e ) {
+ if ( e.eventPhase == EventSource.CLOSED ) source.close();
+
+ if ( e.target.readyState == EventSource.CLOSED ) {
+ self.$refs.notification.cancelNotification( startNotification );
+ self.$refs.notification.createNotification( 'Could not connect to status service', 5, 'error', 'normal' );
+ }
+ }, false)
+ }, 300 );
+ } else {
+ setTimeout( () => {
+ this.$refs.notification.createNotification( 'Unsupported browser detected. Redirection might take longer to occur!', 20, 'warning', 'normal' );
+ }, 300 );
+ // ping server every 5s to check if logged in
+ this.serverPing = setInterval( () => {
+ fetch( '/user/2fa/ping' ).then( res => {
+ if ( res.status === 200 ) {
+ res.json().then( data => {
+ if ( data ) {
+ if ( data.status === 'ok' ) {
+ this.userStore.setUserAuth( true );
+ this.$router.push( sessionStorage.getItem( 'redirect' ) ?? '/account' );
+ }
}
- }
- } );
- } else {
- console.error( 'Request failed' );
+ } );
+ } else {
+ console.error( 'Request failed' );
+ this.$refs.notification.createNotification( 'We are sorry, but an error occurred. You will not be redirected automatically', 300, 'error', 'normal' );
+ }
+ } ).catch( error => {
+ console.error( error );
this.$refs.notification.createNotification( 'We are sorry, but an error occurred. You will not be redirected automatically', 300, 'error', 'normal' );
- }
- } ).catch( error => {
- console.error( error );
- this.$refs.notification.createNotification( 'We are sorry, but an error occurred. You will not be redirected automatically', 300, 'error', 'normal' );
- } );
- }, 5000 );
+ } );
+ }, 5000 );
+ }
+ let code = sessionStorage.getItem( '2faCode' ) ? sessionStorage.getItem( '2faCode' ) : '';
+ 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' );
+ }
}
- let code = sessionStorage.getItem( '2faCode' ) ? sessionStorage.getItem( '2faCode' ) : '';
- this.code = { '1': code.slice( 0, 3 ), '2': code.substring( 3 ) };
},
unmounted() {
clearInterval( this.serverPing );
diff --git a/website/src/setup/installation.md b/website/src/setup/installation.md
index 4d349f3..8b307c0 100644
--- a/website/src/setup/installation.md
+++ b/website/src/setup/installation.md
@@ -21,7 +21,9 @@ In the database, all the userdata is stored. libreevent currently supports two d
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.
-- 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.