mirror of
https://github.com/janishutz/libreevent.git
synced 2025-11-25 05:14:23 +00:00
restruct + 2fa + auth
This commit is contained in:
@@ -36,6 +36,7 @@ app.use( cookieParser() );
|
||||
app.use( express.static( '../webapp/dist' ) );
|
||||
|
||||
require( './admin/routes.js' )( app, settings ); // admin route
|
||||
require( './backend/userRoutes.js' )( app, settings ); // user route
|
||||
|
||||
app.use( ( request, response ) => {
|
||||
response.sendFile( path.join( __dirname + '/../webapp/dist/index.html' ) );
|
||||
|
||||
25
src/server/backend/credentials/pwdmanager.js
Normal file
25
src/server/backend/credentials/pwdmanager.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* libreevent - pwdmanager.js
|
||||
*
|
||||
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
These functions are required to verify user login and to create new users
|
||||
and to hash new passwords (if user changes password.)
|
||||
*/
|
||||
|
||||
// import and init
|
||||
const bcrypt = require( 'bcrypt' );
|
||||
const db = require( '../db/db.js' );
|
||||
|
||||
module.exports.checkpassword = function checkpassword ( username, password ) {
|
||||
return new Promise( resolve => {
|
||||
db.getData( 'user', username ).then( data => {
|
||||
resolve( bcrypt.compareSync( password, data ) );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
@@ -11,7 +11,6 @@ const path = require( 'path' );
|
||||
const fs = require( 'fs' );
|
||||
|
||||
module.exports.getData = function getData ( db, searchQuery ) {
|
||||
console.log( db + searchQuery );
|
||||
return new Promise( resolve => {
|
||||
resolve( '$2b$05$ElMYWoMjk7567lXkIkee.e.6cxCrWU4gkfuNLB8gmGYLQQPm7gT3O' );
|
||||
} );
|
||||
|
||||
@@ -1 +1,14 @@
|
||||
class
|
||||
/*
|
||||
* libreevent - jsondb.js
|
||||
*
|
||||
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
class JSONDB {
|
||||
constructor () {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* libreevent - routes.js
|
||||
*
|
||||
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const db = require( './db/db.js' );
|
||||
|
||||
module.exports = ( app, settings ) => {
|
||||
app.post( '/api/reserveTicket', ( request, response ) ) {
|
||||
db.getData( 'test', request.body );
|
||||
response.send( 'ok' );
|
||||
};
|
||||
};
|
||||
38
src/server/backend/userRoutes.js
Normal file
38
src/server/backend/userRoutes.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* libreevent - routes.js
|
||||
*
|
||||
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const db = require( './db/db.js' );
|
||||
const pwdmanager = require( './credentials/pwdmanager.js' );
|
||||
|
||||
module.exports = ( app, settings ) => {
|
||||
app.post( '/api/reserveTicket', ( request, response ) => {
|
||||
db.getData( 'test', request.body );
|
||||
response.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 => {
|
||||
if ( data ) {
|
||||
if ( settings.twoFA ) {
|
||||
// TODO: Support both methods of 2fa
|
||||
response.send( '2fa' );
|
||||
} else {
|
||||
request.session.loggedInUser = true;
|
||||
response.send( 'ok' );
|
||||
}
|
||||
} else {
|
||||
response.send( 'pwErr' );
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
response.send( 'missingCredentials' );
|
||||
}
|
||||
} );
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"init":true
|
||||
"init":true,
|
||||
"twoFA": true
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div id="notifications" @click="handleNotifications();">
|
||||
<div class="message-box" :class="location">
|
||||
<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>
|
||||
@@ -21,7 +21,12 @@
|
||||
location: {
|
||||
type: String,
|
||||
'default': 'topleft',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
'default': 'default',
|
||||
}
|
||||
// Size options: small, default (default option), big, bigger, huge
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -119,9 +124,32 @@
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
color: white;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.default {
|
||||
height: 10vh;
|
||||
width: 15vw;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.small {
|
||||
height: 7vh;
|
||||
width: 11vw;
|
||||
}
|
||||
|
||||
.big {
|
||||
height: 12vh;
|
||||
width: 17vw;
|
||||
}
|
||||
|
||||
.bigger {
|
||||
height: 15vh;
|
||||
width: 20vw;
|
||||
}
|
||||
|
||||
.huge {
|
||||
height: 20vh;
|
||||
width: 25vw;
|
||||
}
|
||||
|
||||
.topleft {
|
||||
|
||||
@@ -55,6 +55,10 @@ router.beforeEach( ( to, from ) => {
|
||||
return { name: 'purchase' };
|
||||
} else if ( to.name.substring( 0, 5 ) === 'setup' && !backendStore.getVisitedSetupPages[ to.name.substring( 5 ).toLowerCase() ] && to.name.substring( 5 ).toLowerCase() !== 'start' && to.name.substring( 5 ).toLowerCase() !== 'root' ) {
|
||||
return { name: 'setupStart' };
|
||||
} else if ( to.name === '2fa' && !userStore.getUserTwoFACompliant ) {
|
||||
return { name: 'login' };
|
||||
} else if ( to.name === 'Admin2fa' && !userStore.getAdminTwoFACompliant ) {
|
||||
return { name: 'adminLogin' };
|
||||
}
|
||||
} );
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export default [
|
||||
{
|
||||
path: '/tickets',
|
||||
name: 'tickets',
|
||||
component: () => import( '../views/OrderView.vue' ),
|
||||
component: () => import( '../views/purchasing/OrderView.vue' ),
|
||||
meta: {
|
||||
title: 'Order ticket - libreevent'
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export default [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import( '../views/LoginView.vue' ),
|
||||
component: () => import( '../views/user/LoginView.vue' ),
|
||||
meta: {
|
||||
title: 'Login - libreevent'
|
||||
}
|
||||
@@ -43,7 +43,7 @@ export default [
|
||||
{
|
||||
path: '/admin/login',
|
||||
name: 'adminLogin',
|
||||
component: () => import( '../views/AdminLoginView.vue' ),
|
||||
component: () => import( '../views/admin/AdminLoginView.vue' ),
|
||||
meta: {
|
||||
title: 'Login :: Admin - libreevent'
|
||||
}
|
||||
@@ -51,7 +51,7 @@ export default [
|
||||
{
|
||||
path: '/signup',
|
||||
name: 'signup',
|
||||
component: () => import( '../views/SignupView.vue' ),
|
||||
component: () => import( '../views/user/SignupView.vue' ),
|
||||
meta: {
|
||||
title: 'Signup - libreevent'
|
||||
}
|
||||
@@ -59,15 +59,23 @@ export default [
|
||||
{
|
||||
path: '/account',
|
||||
name: 'account',
|
||||
component: () => import( '../views/AccountView.vue' ),
|
||||
component: () => import( '../views/user/AccountView.vue' ),
|
||||
meta: {
|
||||
title: 'Account - libreevent'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/twoFactors',
|
||||
name: '2fa',
|
||||
component: () => import( '../views/user/TwoFA.vue' ),
|
||||
meta: {
|
||||
title: 'Two Factor Authentication - libreevent'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/tickets/details',
|
||||
name: 'ticketDetails',
|
||||
component: () => import( '../views/TicketsDetailsView.vue' ),
|
||||
component: () => import( '../views/purchasing/TicketsDetailsView.vue' ),
|
||||
meta: {
|
||||
title: 'Details - libreevent',
|
||||
transition: 'scale'
|
||||
@@ -76,7 +84,7 @@ export default [
|
||||
{
|
||||
path: '/tickets/order',
|
||||
name: 'ticketOrder',
|
||||
component: () => import( '../views/TicketsOrderingView.vue' ),
|
||||
component: () => import( '../views/purchasing/TicketsOrderingView.vue' ),
|
||||
meta: {
|
||||
title: 'Order ticket - libreevent',
|
||||
transition: 'scale'
|
||||
@@ -85,7 +93,7 @@ export default [
|
||||
{
|
||||
path: '/cart',
|
||||
name: 'cart',
|
||||
component: () => import( '../views/CartView.vue' ),
|
||||
component: () => import( '../views/purchasing/CartView.vue' ),
|
||||
meta: {
|
||||
title: 'Cart - libreevent',
|
||||
transition: 'scale'
|
||||
@@ -94,7 +102,7 @@ export default [
|
||||
{
|
||||
path: '/purchase',
|
||||
name: 'purchase',
|
||||
component: () => import( '@/views/PurchaseView.vue' ),
|
||||
component: () => import( '@/views/purchasing/PurchaseView.vue' ),
|
||||
meta: {
|
||||
title: 'Purchase - libreevent',
|
||||
transition: 'scale'
|
||||
@@ -103,21 +111,12 @@ export default [
|
||||
{
|
||||
path: '/pay',
|
||||
name: 'pay',
|
||||
component: () => import( '@/views/PaymentView.vue' ),
|
||||
component: () => import( '@/views/purchasing/PaymentView.vue' ),
|
||||
meta: {
|
||||
title: 'Pay - libreevent',
|
||||
transition: 'scale',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
component: () => import( '@/views/404.vue' ),
|
||||
meta: {
|
||||
title: '404 - Page not found :: libreevent',
|
||||
transition: 'scale',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/admin/seatplan',
|
||||
name: 'adminSeatplanEditor',
|
||||
@@ -136,4 +135,13 @@ export default [
|
||||
adminAuthRequired: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
component: () => import( '@/views/404.vue' ),
|
||||
meta: {
|
||||
title: '404 - Page not found :: libreevent',
|
||||
transition: 'scale',
|
||||
}
|
||||
},
|
||||
]
|
||||
@@ -10,7 +10,7 @@
|
||||
export default {
|
||||
path: '/setup',
|
||||
name: 'setup',
|
||||
component: () => import( '../views/SetupView.vue' ),
|
||||
component: () => import( '../views/setup/SetupView.vue' ),
|
||||
meta: {
|
||||
title: 'Login :: Admin - libreevent',
|
||||
setupAuthRequired: true,
|
||||
|
||||
@@ -10,10 +10,12 @@
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useUserStore = defineStore ( 'user', {
|
||||
state: () => ( { 'isUserAuth': true, 'isAdminAuth': true, 'userData': {} } ),
|
||||
state: () => ( { 'isUserAuth': false, 'isAdminAuth': false, 'userData': {}, 'isTwoFACompliantUser': false, 'isTwoFACompliantAdmin': false } ),
|
||||
getters: {
|
||||
getUserAuthenticated: ( state ) => state.isUserAuth,
|
||||
getAdminAuthenticated: ( state ) => state.isAdminAuth,
|
||||
getUserTwoFACompliant: ( state ) => state.isTwoFACompliantUser,
|
||||
getAdminTwoFACompliant: ( state ) => state.isTwoFACompliantAdmin,
|
||||
},
|
||||
actions: {
|
||||
setUserAuth ( auth ) {
|
||||
@@ -21,6 +23,12 @@ export const useUserStore = defineStore ( 'user', {
|
||||
},
|
||||
setAdminAuth ( auth ) {
|
||||
this.isAdminAuth = auth;
|
||||
},
|
||||
setUser2fa ( auth ) {
|
||||
this.isTwoFACompliantUser = auth;
|
||||
},
|
||||
setAdmin2fa ( auth ) {
|
||||
this.isTwoFACompliantAdmin = auth;
|
||||
}
|
||||
}
|
||||
} );
|
||||
@@ -1,84 +0,0 @@
|
||||
<!--
|
||||
* libreevent - LoginView.vue
|
||||
*
|
||||
* Created by Janis Hutz 05/14/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="login">
|
||||
<div class="login-app">
|
||||
<h1>Log in</h1>
|
||||
<form>
|
||||
<label for="mail">Email</label><br>
|
||||
<input type="email" v-model="formData[ 'mail' ]" name="mail" id="mail" required><br><br>
|
||||
<label for="password">Password</label><br>
|
||||
<input type="password" v-model="formData[ 'password' ]" name="password" id="password" required>
|
||||
</form>
|
||||
<button @click="login();" class="button">Log in</button>
|
||||
<router-link to="/signup" class="button">Sign up instead</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useUserStore } from '@/stores/userStore';
|
||||
import { mapStores } from 'pinia';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
formData: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores( useUserStore )
|
||||
},
|
||||
methods: {
|
||||
login () {
|
||||
this.userStore.setUserAuth( true );
|
||||
this.$router.push( sessionStorage.getItem( 'redirect' ) ? sessionStorage.getItem( 'redirect' ) : '/account' );
|
||||
sessionStorage.removeItem( 'redirect' );
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
/* TODO: Update colour to image */
|
||||
.login {
|
||||
background-color: green;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.login-app {
|
||||
background-color: var( --background-color );
|
||||
min-height: fit-content;
|
||||
min-height: fit-content;
|
||||
padding: 5% 20%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 5px 10px;
|
||||
margin-top: 2%;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: initial;
|
||||
}
|
||||
</style>
|
||||
122
src/webapp/src/views/user/LoginView.vue
Normal file
122
src/webapp/src/views/user/LoginView.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<!--
|
||||
* libreevent - LoginView.vue
|
||||
*
|
||||
* Created by Janis Hutz 05/14/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="login">
|
||||
<div class="login-app">
|
||||
<h1>Log in</h1>
|
||||
<form>
|
||||
<label for="mail">Email</label><br>
|
||||
<input type="email" v-model="formData[ 'mail' ]" name="mail" id="mail" required><br><br>
|
||||
<label for="password">Password</label><br>
|
||||
<input type="password" v-model="formData[ 'password' ]" name="password" id="password" required>
|
||||
</form>
|
||||
<button @click="login();" class="button">Log in</button>
|
||||
<router-link to="/signup" class="button">Sign up instead</router-link>
|
||||
</div>
|
||||
<notifications ref="notification" location="topright" size="bigger"></notifications>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useUserStore } from '@/stores/userStore';
|
||||
import { mapStores } from 'pinia';
|
||||
import notifications from '@/components/notifications/notifications.vue';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
formData: {}
|
||||
}
|
||||
},
|
||||
components: {
|
||||
notifications,
|
||||
},
|
||||
computed: {
|
||||
...mapStores( useUserStore )
|
||||
},
|
||||
methods: {
|
||||
login () {
|
||||
if ( this.formData.mail ) {
|
||||
if ( this.formData.password ) {
|
||||
let fetchOptions = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( this.formData ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
}
|
||||
};
|
||||
fetch( localStorage.getItem( 'url' ) + '/user/login', fetchOptions ).then( res => {
|
||||
res.text().then( text => {
|
||||
console.log( text );
|
||||
if ( text === 'ok' ) {
|
||||
this.userStore.setUserAuth( true );
|
||||
this.$router.push( sessionStorage.getItem( 'redirect' ) ? sessionStorage.getItem( 'redirect' ) : '/account' );
|
||||
sessionStorage.removeItem( 'redirect' );
|
||||
} else if ( text === '2fa' ) {
|
||||
this.userStore.setUser2fa( true );
|
||||
this.$router.push( '/twoFactors' );
|
||||
} else {
|
||||
this.$refs.notification.createNotification( 'The credentials you provided do not match our records.', 5, 'error', 'normal' );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
} else {
|
||||
this.$refs.notification.createNotification( 'A password is required to log in', 5, 'error', 'normal' );
|
||||
}
|
||||
} else {
|
||||
this.$refs.notification.createNotification( 'An email address is required to log in', 5, 'error', 'normal' );
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
/* TODO: Update colour to image */
|
||||
.login {
|
||||
background-color: green;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.login-app {
|
||||
background-color: var( --background-color );
|
||||
min-height: fit-content;
|
||||
min-height: fit-content;
|
||||
padding: 5% 20%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 5px 10px;
|
||||
margin-top: 2%;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
#missing-email, #missing-password, #credentials-wrong {
|
||||
display: none;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
6
src/webapp/src/views/user/TwoFA.vue
Normal file
6
src/webapp/src/views/user/TwoFA.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<div id="2fa">
|
||||
<h1>Two Factor Authentication</h1>
|
||||
<p>We have sent you an email containing a link for Authentication.</p>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user