diff --git a/src/server/backend/db/mysqldb.js b/src/server/backend/db/mysqldb.js index 85c717f..f1b66e8 100644 --- a/src/server/backend/db/mysqldb.js +++ b/src/server/backend/db/mysqldb.js @@ -55,7 +55,7 @@ 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, name TEXT, first_name TEXT, two_fa TINYTEXT, user_data VARCHAR( 60000 ), 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, name TEXT, first_name TEXT, two_fa TINYTEXT, user_data VARCHAR( 60000 ), mail_confirmed TINYTEXT, marketing_ok 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; diff --git a/src/server/backend/userRoutes.js b/src/server/backend/userRoutes.js index 9ace9a2..184462e 100644 --- a/src/server/backend/userRoutes.js +++ b/src/server/backend/userRoutes.js @@ -18,6 +18,7 @@ const bodyParser = require( 'body-parser' ); let responseObjects = {}; let authOk = {}; +let mailTokens = {}; module.exports = ( app, settings ) => { app.get( '/user/details', ( request, response ) => { @@ -134,21 +135,23 @@ module.exports = ( app, settings ) => { app.get( '/user/logout', ( request, response ) => { request.session.loggedInUser = false; + request.session.username = ''; response.send( 'logoutOk' ); } ); app.post( '/user/signup', bodyParser.json(), ( request, response ) => { - // TODO: Make sure that user does not exist yet first and if user - // exists, send back info that it is that way - // TODO: check for 2fa // TODO: Send mail to confirm email address 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 { - db.writeDataSimple( 'users', 'email', request.body.mail, { 'pass': pwdmanager.hashPassword( request.body.password ), 'first_name': request.body.firstName, 'name': request.body.name, 'two_fa': String( request.body.twoFA ), 'user_data': { 'country': request.body.country } } ).then( () => { - response.send( 'ok' ); + 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 } ) } ).then( () => { + request.session.loggedInUser = true; + request.session.username = request.body.mail; + response.send( 'ok' ); + } ); } ); } } ); @@ -156,4 +159,37 @@ module.exports = ( app, settings ) => { 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.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.send( 'unauthorised' ); + } + } ); + + app.get( '/settings/2fa', ( request, response ) => { + response.send( settings.twoFA ); + } ); }; \ No newline at end of file diff --git a/src/server/config/settings.config.json b/src/server/config/settings.config.json index 5e0d7af..7322949 100644 --- a/src/server/config/settings.config.json +++ b/src/server/config/settings.config.json @@ -1,6 +1,7 @@ { "init": true, - "twoFA": "enhanced", + "twoFA": "allow", + "twoFAMode": "enhanced", "db": "mysql", "payments": "stripe", "name": "libreevent", diff --git a/src/server/ui/en/signup/allowTwoFA.html b/src/server/ui/en/signup/allowTwoFA.html new file mode 100644 index 0000000..9e6db51 --- /dev/null +++ b/src/server/ui/en/signup/allowTwoFA.html @@ -0,0 +1,122 @@ + + + + + + Email verification + + + +
+

Email Verification

+

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.

+
+
+ +
+ + +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/server/ui/en/signup/disallowTwoFA.html b/src/server/ui/en/signup/disallowTwoFA.html new file mode 100644 index 0000000..e0f47c6 --- /dev/null +++ b/src/server/ui/en/signup/disallowTwoFA.html @@ -0,0 +1,38 @@ + + + + + + Email Verification Successful + + + +
+

Email Verification Successful

+

Thank you! Your email address has been verified successfully. You may now close this tab.

+
+ + \ No newline at end of file diff --git a/src/server/ui/en/signup/enforceTwoFA.html b/src/server/ui/en/signup/enforceTwoFA.html new file mode 100644 index 0000000..4771818 --- /dev/null +++ b/src/server/ui/en/signup/enforceTwoFA.html @@ -0,0 +1,123 @@ + + + + + + Email verification + + + +
+

Email Verification

+

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).

+
+
+ +
+ + +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/server/ui/en/signup/invalid.html b/src/server/ui/en/signup/invalid.html new file mode 100644 index 0000000..dbe69cb --- /dev/null +++ b/src/server/ui/en/signup/invalid.html @@ -0,0 +1,38 @@ + + + + + + Email Verification Token Invalid + + + +
+

Email Verification Token Invalid

+

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 account and clicking "resend email verification token"

+
+ + \ No newline at end of file diff --git a/src/webapp/main/notes.md b/src/webapp/main/notes.md index 356d677..bc2da71 100644 --- a/src/webapp/main/notes.md +++ b/src/webapp/main/notes.md @@ -5,6 +5,8 @@ - create function that parses DB every 15 minutes and clears out junk +- Require user to confirm email before purchasing + - Create password changing endpoint (to reset forgotten pwd) - Add Admin profile (page to change account settings per person like changing pwd) diff --git a/src/webapp/main/src/views/user/AccountView.vue b/src/webapp/main/src/views/user/AccountView.vue index 473a28a..bc119db 100644 --- a/src/webapp/main/src/views/user/AccountView.vue +++ b/src/webapp/main/src/views/user/AccountView.vue @@ -50,7 +50,11 @@ res.json().then( data => { if ( data.status ) { this.accountData = data.data; - console.log( data.data ); + if ( !data.data.mail_confirmed ) { + setTimeout( () => { + this.$refs.notification.createNotification( 'Your account is unverified. Please confirm your email using the link we have sent to your email!', 20, 'info', 'normal' ); + }, 1000 ); + } } else { this.userStore.setUserAuth( false ); this.userStore.setUser2fa( false ); diff --git a/src/webapp/main/src/views/user/SignupView.vue b/src/webapp/main/src/views/user/SignupView.vue index f3f12fe..a5f1b8e 100644 --- a/src/webapp/main/src/views/user/SignupView.vue +++ b/src/webapp/main/src/views/user/SignupView.vue @@ -108,9 +108,13 @@ console.log( res ); res.text().then( status => { if ( status === 'ok' ) { - this.userStore.setUserAuth( true ); - this.$router.push( sessionStorage.getItem( 'redirect' ) ?? '/account' ); - sessionStorage.removeItem( 'redirect' ); + this.$refs.notification.cancelNotification( progress ); + this.$refs.notification.createNotification( 'Signed up successfully. We have sent you an email. Please confirm it to finish sign-up', 5, 'ok', 'normal' ); + setTimeout( () => { + this.userStore.setUserAuth( true ); + this.$router.push( sessionStorage.getItem( 'redirect' ) ?? '/account' ); + sessionStorage.removeItem( 'redirect' ); + }, 5000 ); } else if ( status === 'exists' ) { this.$refs.notification.cancelNotification( progress ); this.$refs.notification.createNotification( 'An account with this email address already exists. Please log in using it.', 5, 'error', 'normal' );