root account setup email and pw checker + other

This commit is contained in:
2023-08-25 09:57:29 +02:00
parent 1705128482
commit 86a7ec13ad
8 changed files with 483 additions and 15 deletions

View File

@@ -74,7 +74,7 @@
This method deletes a notification and, in case the notification is being displayed, hides it. This method deletes a notification and, in case the notification is being displayed, hides it.
*/ */
try { try {
delete notifications[ id ]; delete this.notifications[ id ];
delete this.queue[ this.queue.findIndex( id ) ]; delete this.queue[ this.queue.findIndex( id ) ];
} catch ( error ) { } catch ( error ) {
console.log( 'notification to be deleted is nonexistent or currently being displayed' ); console.log( 'notification to be deleted is nonexistent or currently being displayed' );

View File

@@ -4,7 +4,9 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title> <title>No page title :: libreevent</title>
<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">
<script defer src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@@ -141,6 +141,15 @@
margin-bottom: 20px; margin-bottom: 20px;
} }
select {
width: 50%;
text-align: center;
padding: 10px;
border-radius: 500px;
border-style: solid;
background-color: #b4d9ff;
}
.wrapper { .wrapper {
width: 100vw; width: 100vw;
display: flex; display: flex;

View File

@@ -0,0 +1,236 @@
<template>
<div id="notifications" @click="handleNotifications();">
<div class="message-box" :class="[ location, size ]">
<div class="message-container" :class="messageType">
<span class="material-symbols-outlined types hide" v-if="messageType == 'hide'">question_mark</span>
<span class="material-symbols-outlined types" v-else-if="messageType == 'ok'" style="background-color: green;">done</span>
<span class="material-symbols-outlined types" v-else-if="messageType == 'error'" style="background-color: red;">close</span>
<span class="material-symbols-outlined types progress-spinner" v-else-if="messageType == 'progress'" style="background-color: blue;">progress_activity</span>
<span class="material-symbols-outlined types" v-else-if="messageType == 'info'" style="background-color: lightblue;">info</span>
<span class="material-symbols-outlined types" v-else-if="messageType == 'warning'" style="background-color: orangered;">warning</span>
<p class="message">{{ message }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'notificationsAPI',
props: {
location: {
type: String,
'default': 'topleft',
},
size: {
type: String,
'default': 'default',
}
// Size options: small, default (default option), big, bigger, huge
},
data () {
return {
notifications: {},
queue: [],
message: '',
messageType: 'hide',
notificationDisplayTime: 0,
notificationPriority: 'normal',
currentlyDisplayedNotificationID: 0,
currentID: { 'critical': 0, 'medium': 1000, 'low': 100000 },
displayTimeCurrentNotification: 0,
notificationScheduler: null,
}
},
methods: {
createNotification( message, showDuration, messageType, priority ) {
/*
Takes a notification options array that contains: message, showDuration (in seconds), messageType (ok, error, progress, info) and priority (low, normal, critical).
Returns a notification ID which can be used to cancel the notification. The component will throttle notifications and display
one at a time and prioritize messages with higher priority. Use vue refs to access these methods.
*/
let id = 0;
if ( priority === 'critical' ) {
this.currentID[ 'critical' ] += 1;
id = this.currentID[ 'critical' ];
} else if ( priority === 'normal' ) {
this.currentID[ 'medium' ] += 1;
id = this.currentID[ 'medium' ];
} else if ( priority === 'low' ) {
this.currentID[ 'low' ] += 1;
id = this.currentID[ 'low' ];
}
this.notifications[ id ] = { 'message': message, 'showDuration': showDuration, 'messageType': messageType, 'priority': priority, 'id': id };
this.queue.push( id );
console.log( 'scheduled notification: ' + id + ' (' + message + ')' );
if ( this.displayTimeCurrentNotification >= this.notificationDisplayTime ) {
this.handleNotifications();
}
return id;
},
cancelNotification ( id ) {
/*
This method deletes a notification and, in case the notification is being displayed, hides it.
*/
try {
delete this.notifications[ id ];
delete this.queue[ this.queue.findIndex( id ) ];
} catch ( error ) {
console.log( 'notification to be deleted is nonexistent or currently being displayed' );
}
if ( this.currentlyDisplayedNotificationID == id ) {
this.handleNotifications();
}
},
handleNotifications () {
/*
This methods should NOT be called in any other component than this one!
*/
this.displayTimeCurrentNotification = 0;
this.notificationDisplayTime = 0;
this.message = '';
this.queue.sort();
if ( this.queue.length > 0 ) {
this.message = this.notifications[ this.queue[ 0 ] ][ 'message' ];
this.messageType = this.notifications[ this.queue[ 0 ] ][ 'messageType' ];
this.priority = this.notifications[ this.queue[ 0 ] ][ 'priority' ];
this.currentlyDisplayedNotificationID = this.notifications[ this.queue[ 0 ] ][ 'id' ];
this.notificationDisplayTime = this.notifications[ this.queue[ 0 ] ][ 'showDuration' ];
this.queue.reverse();
this.queue.pop();
} else {
this.messageType = 'hide';
}
}
},
created () {
this.notificationScheduler = setInterval( () => {
if ( this.displayTimeCurrentNotification >= this.notificationDisplayTime ) {
this.handleNotifications();
} else {
this.displayTimeCurrentNotification += 0.5;
}
}, 500 );
},
unmounted ( ) {
clearInterval( this.notificationScheduler );
}
}
</script>
<style scoped>
.message-box {
position: fixed;
z-index: 10;
color: white;
transition: all 0.5s;
}
.default {
height: 10vh;
width: 15vw;
}
.small {
height: 7vh;
width: 11vw;
}
.big {
height: 12vh;
width: 17vw;
}
.bigger {
height: 15vh;
width: 20vw;
}
.huge {
height: 20vh;
width: 25vw;
}
.topleft {
top: 3vh;
left: 0.5vw;
}
.topright {
top: 3vh;
right: 0.5vw;
}
.bottomright {
bottom: 3vh;
right: 0.5vw;
}
.bottomleft {
top: 3vh;
right: 0.5vw;
}
.message-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
opacity: 1;
transition: all 0.5s;
cursor: default;
}
.types {
color: white;
border-radius: 100%;
margin-right: auto;
margin-left: 5%;
padding: 1.5%;
font-size: 200%;
}
.message {
margin-right: 5%;
text-align: end;
}
.ok {
background-color: rgb(1, 71, 1);
}
.error {
background-color: rgb(114, 1, 1);
}
.info {
background-color: rgb(44, 112, 151);
}
.warning {
background-color: orange;
}
.hide {
opacity: 0;
}
.progress {
background-color: rgb(0, 0, 99);
}
.progress-spinner {
animation: spin 2s infinite linear;
}
@keyframes spin {
from {
transform: rotate( 0deg );
}
to {
transform: rotate( 720deg );
}
}
</style>

View File

@@ -15,9 +15,14 @@
<p>You may find more infos about this part <a href="https://libreevent.janishutz.com/docs/setup/setup#basic-setup" target="_blank">here</a></p> <p>You may find more infos about this part <a href="https://libreevent.janishutz.com/docs/setup/setup#basic-setup" target="_blank">here</a></p>
<h2>Database</h2> <h2>Database</h2>
<p>A database is a piece of software that specializes in storing data. libreevent can use most SQL based databases as well as a custom JSON-based database. You are strongly encouraged to use a SQL based database, as they perform significantly better. Read more <p>A database is a piece of software that specializes in storing data. libreevent can use most SQL based databases as well as a custom JSON-based database. You are strongly encouraged to use a SQL based database, as they perform significantly better. Read more
<a href="https://libreevent.janishutz.com/docs/setup/installation#database">here</a> <a href="https://libreevent.janishutz.com/docs/setup/installation#database" target="_blank">here</a>
</p> </p>
<form> <label for="dbType">Database type</label><br>
<select name="dbType" id="dbType" v-model="formData.dbType">
<option value="mysql">SQL-Database</option>
<option value="json">JSON-Database</option>
</select>
<form v-if="formData.dbType === 'mysql'">
<label for="host">Database host name</label><br> <label for="host">Database host name</label><br>
<input type="url" name="host" id="host"><br> <input type="url" name="host" id="host"><br>
<label for="database">Database name</label><br> <label for="database">Database name</label><br>
@@ -30,7 +35,12 @@
<input type="number" name="port" id="port" min="1" value="3306" max="65535"><br> <input type="number" name="port" id="port" min="1" value="3306" max="65535"><br>
</form> </form>
<h2>Email</h2> <h2>Email</h2>
<p>An email address is required for libreevent to send out mails to users automatically, including their ticket and, in case Two-Factor-Authentication is enabled,
a Two-Factor-Authentication email.</p>
<h3>Account</h3> <h3>Account</h3>
<p>Here you have to enter the connection details for an email account. Most webhosting plans come with email addresses, so you might as well create a new one.
Note that you can customize how the sender of the mail appears down below in the display section.</p>
<form> <form>
<label for="host">SMTP Server</label><br> <label for="host">SMTP Server</label><br>
<input type="url" name="host" id="host"><br> <input type="url" name="host" id="host"><br>
@@ -42,6 +52,10 @@
<input type="password" name="pass" id="pass"><br> <input type="password" name="pass" id="pass"><br>
</form> </form>
<h3>Display</h3> <h3>Display</h3>
<p>Here you can adjust how the email sender appears to the customer. This also means, that the email address shown below might receive a response if
a customer does not possess the ability to read, which might happen from time to time. All mails contain the information that one should not respond
to them.
</p>
<form> <form>
<label for="display">Display name (what is shown to user in from field)</label><br> <label for="display">Display name (what is shown to user in from field)</label><br>
<input type="url" name="display" id="display"><br> <input type="url" name="display" id="display"><br>
@@ -53,6 +67,12 @@
</div> </div>
</template> </template>
<style scoped>
#dbType {
margin-bottom: 5%;
}
</style>
<script> <script>
import { useBackendStore } from '@/stores/backendStore.js'; import { useBackendStore } from '@/stores/backendStore.js';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
@@ -60,7 +80,10 @@
export default { export default {
data () { data () {
return { return {
formData: {} formData: {
'dbType': 'mysql',
},
} }
}, },
computed: { computed: {

View File

@@ -11,7 +11,7 @@
<div class="home"> <div class="home">
<img alt="Libreevent logo" src="../assets/logo.png"> <img alt="Libreevent logo" src="../assets/logo.png">
<h1>Welcome to libreǝvent!</h1> <h1>Welcome to libreǝvent!</h1>
<p>Let's start the setup by entering the setup key below! You may define a setup key in the setupkey.txt file of libreevent. See <a href="https://libreevent.janishutz.com/docs/setup/installation" target="_blank">here</a> for more instructions</p> <p>Let's start the setup by entering the setup key below! You may define a setup key in the <i>setupkey.txt</i> file of libreevent. See <a href="https://libreevent.janishutz.com/docs/setup/installation" target="_blank">here</a> for more instructions</p>
<form> <form>
<label for="key">Your setup key</label><br> <label for="key">Your setup key</label><br>
<input type="text" v-model="formData[ 'key' ]" required name="key" id="key"> <input type="text" v-model="formData[ 'key' ]" required name="key" id="key">
@@ -48,4 +48,8 @@
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
} }
form {
width: 50%;
}
</style> </style>

View File

@@ -13,8 +13,11 @@
<img src="@/assets/logo.png" alt="libreevent logo" style="height: 30vh;"> <img src="@/assets/logo.png" alt="libreevent logo" style="height: 30vh;">
<h1>Setup complete!</h1> <h1>Setup complete!</h1>
<p>Congratulations on finishing the setup of libreǝvent!</p> <p>Congratulations on finishing the setup of libreǝvent!</p>
<p>Please restart the node.js application to have it load the actual user interface for libreevent and finish setup. You may then log in at <a :href="windowURL" target="_blank">{{ windowURL }}</a>. You may open that link now and then come back to that page once libreevent is restarted.</p> <p>Please restart the node.js application to have it load the actual user interface for libreevent and finish setup.
<p>In the admin panel, there are a few things you still need to change. You may find a list of all things <a href="https://libreevent.janishutz.com/docs/setup/afterSetup" target="_blank">here</a></p> You may then log in at <a :href="windowURL" target="_blank">{{ windowURL }}</a>.
You may open that link now and then come back to that page once libreevent is restarted.</p>
<p>In the admin panel, there are a few things you still need to change. You may find a list of all things
<a href="https://libreevent.janishutz.com/docs/setup/afterSetup" target="_blank">here</a></p>
<div class="list-wrapper"> <div class="list-wrapper">
<ul> <ul>
<li>Choose a payment gateway and set it up</li> <li>Choose a payment gateway and set it up</li>

View File

@@ -11,9 +11,13 @@
<div class="wrapper"> <div class="wrapper">
<div class="content"> <div class="content">
<h1>Root account</h1> <h1>Root account</h1>
<p>The root account is the most powerful account. Therefore, it should only be used if really necessary and should have a strong password. It also always requires Two Factor Authentication for added security. You may log into the root account by typing 'root' into the Email/Username field on the admin login screen.</p> <p>The root account is the most powerful account. Therefore, it should only be used if really necessary and should have a strong password.
It also always requires Two Factor Authentication for added security.
You may log into the root account by typing 'root' into the Email-Address field on the admin login screen.
Therefore, the email used for the root account may also be used for an additional admin account.</p>
<p>You may find more infos about this part <a href="https://libreevent.janishutz.com/docs/setup/setup#root-account" target="_blank">here</a></p> <p>You may find more infos about this part <a href="https://libreevent.janishutz.com/docs/setup/setup#root-account" target="_blank">here</a></p>
<p>Password requirements:</p> <p>By default (when the toggle "Enforce password requirements" below is enabled), libreevent forces you to follow the password requirements listed below.
You may turn off those password requirements and use any password, but we strongly advice against this.</p>
<ul style="list-style: none;"> <ul style="list-style: none;">
<li>At least 15 characters long</li> <li>At least 15 characters long</li>
<li>At least 2 special characters</li> <li>At least 2 special characters</li>
@@ -22,36 +26,223 @@
</ul> </ul>
<form> <form>
<label for="mail">Email address for 2FA</label><br> <label for="mail">Email address for 2FA</label><br>
<input type="email" name="mail" id="mail"><br> <input type="email" name="mail" id="mail" v-model="formData.mail" @keyup="emailLiveChecker()"><br>
<p v-if="emailStatus" class="email-status">{{ emailStatus }}</p>
<label for="password">Password</label><br> <label for="password">Password</label><br>
<input type="password" name="password" id="password"><br> <input type="password" name="password" id="password" v-model="formData.password"><br>
<label for="password2">Confirm password</label><br> <label for="password2">Confirm password</label><br>
<input type="password" name="password2" id="password2"> <input type="password" name="password2" id="password2" v-model="formData.password2"><br>
<label for="pwCheck">Enforce password requirements (leaving this turned on is strongly recommended)</label><br>
<label class="switch">
<input type="checkbox" v-model="passwordCheck">
<span class="slider round"></span>
</label><br>
</form> </form>
<button @click="submit()" class="button">Continue</button> <button @click="submit()" class="button">Continue</button>
</div> </div>
<notifications ref="notification" location="topright" size="bigger"></notifications>
</div> </div>
</template> </template>
<script> <script>
import { useBackendStore } from '@/stores/backendStore.js'; import { useBackendStore } from '@/stores/backendStore.js';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import notifications from '../components/notifications.vue';
const lookup = [ '@', '!', '.', ',', '?', '%', '&', '-', '_', ':', ';', '*', '§', '<', '>', '{', '}', '[', ']', '(', ')', '/', '#' ];
// TODO: Also add this to user signup
export default { export default {
data () { data () {
return { return {
formData: {} formData: {},
passwordCheck: true,
emailStatus: '',
} }
}, },
components: {
notifications,
},
computed: { computed: {
...mapStores( useBackendStore ) ...mapStores( useBackendStore )
}, },
methods: { methods: {
emailLiveChecker () {
setTimeout( () => {
if ( this.checkEmail() ) {
this.emailStatus = '';
} else {
this.emailStatus = 'Invalid email address';
}
}, 100 );
},
checkEmail () {
const mail = this.formData.mail ?? '';
let stat = { 'atPos': 0, 'topLevelPos': 0 };
for ( let l in mail ) {
if ( stat[ 'atPos' ] > 0 ) {
if ( mail[ l ] === '@' ) {
return false;
} else if ( mail[ l ] === '.' ) {
if ( stat[ 'topLevelPos' ] > 0 ) {
if ( l > stat[ 'topLevelPos' ] + 2 ) {
stat[ 'topLevelPos' ] = parseInt( l );
} else {
return false;
}
} else {
if ( l > stat[ 'atPos' ] + 2 ) {
stat[ 'topLevelPos' ] = parseInt( l );
} else {
return false;
}
}
} else if ( !( /[a-z]/.test( mail[ l ] ) || /[A-Z]/.test( mail[ l ] ) || /[1-9]/.test( mail[ l ] ) ) ) {
return false
}
} else {
if ( mail[ l ] === '@' ) {
if ( l > 2 ) {
stat[ 'atPos' ] = parseInt( l );
} else {
return false;
}
} else if ( !( /[a-z]/.test( mail[ l ] ) || /[A-Z]/.test( mail[ l ] ) || /[1-9]/.test( mail[ l ] ) || mail[ l ] === '.' ) ) {
return false;
}
}
}
if ( mail.length > stat[ 'topLevelPos' ] + 2 && stat[ 'topLevelPos' ] > 0 && stat[ 'atPos' ] > 0 ) {
return true;
} else {
return false;
}
},
submit () { submit () {
// TODO: Require confirming email before proceeding // TODO: Maybe require confirming email before proceeding
if ( this.formData.mail && this.formData.password && this.formData.password2 ) {
if ( this.checkEmail() ) {
if ( this.formData.password == this.formData.password2 ) {
if ( this.passwordCheck ) {
let requirementsCount = { 'special': 0, 'numbers': 0, 'lower': 0, 'upper': 0, 'incorrect': '' };
const pw = this.formData.password;
for ( let l in pw ) {
console.log( pw[ l ] );
if ( /[a-z]/.test( pw[ l ] ) ) {
requirementsCount[ 'lower' ] += 1;
} else if ( /[A-Z]/.test( pw[ l ] ) ) {
requirementsCount[ 'upper' ] += 1;
} else if ( lookup.includes( pw[ l ] ) ) {
requirementsCount[ 'special' ] += 1;
} else if ( !isNaN( pw[ l ] * 1 ) ) {
requirementsCount[ 'number' ] += 1;
} else {
console.log( 'incorrect letter' );
requirementsCount[ 'incorrect' ] = pw[ l ];
break;
}
}
if ( requirementsCount[ 'incorrect' ] ) {
this.$refs.notification.createNotification( `Character "${ requirementsCount[ 'incorrect' ] }" cannot be used for passwords`, 5, 'error', 'normal' );
} else {
if ( pw.length > 14 ) {
if ( requirementsCount[ 'lower' ] > 1 && requirementsCount[ 'upper' ] > 1 && requirementsCount[ 'special' ] > 1 && requirementsCount[ 'numbers' ] > 1 ) {
this.proceed();
} else {
this.$refs.notification.createNotification( 'Your password does not fulfill the requirements', 5, 'error', 'normal' );
}
} else {
this.$refs.notification.createNotification( 'Your password is not long enough', 5, 'error', 'normal' );
}
}
} else {
this.proceed();
}
} else {
this.$refs.notification.createNotification( 'Passwords do not match', 10, 'error', 'normal' );
}
} else {
this.$refs.notification.createNotification( 'The email address you entered is not an email address', 10, 'error', 'normal' );
}
} else {
this.$refs.notification.createNotification( 'One or more fields missing!', 10, 'error', 'normal' );
}
},
proceed () {
this.backendStore.addVisitedSetupPages( 'page', true ); this.backendStore.addVisitedSetupPages( 'page', true );
this.$router.push( 'page' ); this.$router.push( 'page' );
} }
}, },
}; };
</script> </script>
<style scoped>
.email-status {
margin-top: -10px;
color: red;
font-style: italic;
margin-bottom: 20px;
}
/* https://www.w3schools.com/howto/tryit.asp?filename=tryhow_css_switch */
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #2196F3;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
</style>