mirror of
https://github.com/janishutz/libreevent.git
synced 2026-09-22 13:15:25 +02:00
restruct + 2fa + auth
This commit is contained in:
1 parent
d0647ba1bb
commit
865141d945
26 files changed
+279
-127
No files matched your search
@@ -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' ) );
|
||||
|
||||
@@ -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' );
|
||||
};
|
||||
};
|
||||
@@ -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>
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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>
|
||||
File renamed without changes.
@@ -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