added login, first preparation for menu

This commit is contained in:
Torsten Schulz
2024-07-21 13:09:56 +02:00
parent bbf4a2deb3
commit 597761cb15
10 changed files with 391 additions and 100 deletions

View File

@@ -3,6 +3,7 @@ import path from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import chatRouter from './routers/chatRouter.js'; import chatRouter from './routers/chatRouter.js';
import authRouter from './routers/authRouter.js'; import authRouter from './routers/authRouter.js';
import navigationRouter from './routers/navigationRouter.js'
import cors from 'cors'; import cors from 'cors';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@@ -15,10 +16,11 @@ app.use(express.json()); // To handle JSON request bodies
app.use('/api/chat', chatRouter); app.use('/api/chat', chatRouter);
app.use('/api/auth', authRouter); app.use('/api/auth', authRouter);
app.use('/api/navigation', navigationRouter);
app.use('/images', express.static(path.join(__dirname, '../frontend/public/images'))); app.use('/images', express.static(path.join(__dirname, '../frontend/public/images')));
app.get('*', (req, res) => { app.use((req, res) => {
res.sendFile(path.join(__dirname, '../frontend/dist/index.html')); res.status(404).send('404 Not Found');
}); });
export default app; export default app;

View File

@@ -1,40 +1,11 @@
import bcrypt from 'bcrypt'; import * as userService from '../services/authService.js';
import { v4 as uuidv4 } from 'uuid';
import User from '../models/community/user.js';
import UserParam from '../models/community/user_param.js';
import UserParamType from '../models/type/user_param.js';
import { sendAccountActivationEmail, sendPasswordResetEmail } from '../services/emailService.js';
import i18n from '../utils/i18n.js';
const saltRounds = 10;
export const register = async (req, res) => { export const register = async (req, res) => {
const { email, username, password, language } = req.body; const { email, username, password, language } = req.body;
try { try {
const hashedPassword = await bcrypt.hash(password, saltRounds); const result = await userService.registerUser({ email, username, password, language });
const resetToken = uuidv4(); res.status(201).json(result);
const user = await User.create({
email,
username,
password: hashedPassword,
resetToken: resetToken,
active: false,
registration_date: new Date()
});
const languageType = await UserParamType.findOne({ where: { description: 'language' } });
if (!languageType) {
return res.status(500).json({ error: 'Language type not found' });
}
console.log(user.id, languageType.id);
await UserParam.create({
userId: user.id,
paramTypeId: languageType.id,
value: language
});
const activationLink = `${process.env.FRONTEND_URL}/activate?token=${resetToken}`;
await sendAccountActivationEmail(email, activationLink, username, resetToken, language);
res.status(201).json({ id: user.hashedId, username: user.username, active: user.active });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
res.status(500).json({ error: error.message }); res.status(500).json({ error: error.message });
@@ -42,56 +13,33 @@ export const register = async (req, res) => {
}; };
export const login = async (req, res) => { export const login = async (req, res) => {
const { email, password } = req.body; const { username, password } = req.body;
try { try {
const user = await User.findOne({ where: { email } }); const result = await userService.loginUser({ username, password });
if (!user) { res.status(200).json(result);
return res.status(401).json({ error: 'Invalid email or password' });
}
if (!user.active) {
return res.status(403).json({ error: 'Account not activated' });
}
const match = await bcrypt.compare(password, user.password);
if (!match) {
return res.status(401).json({ error: 'Invalid email or password' });
}
res.status(200).json({ id: user.hashed_id, username: user.username });
} catch (error) { } catch (error) {
res.status(500).json({ error: 'Error logging in' }); res.status(500).json({ error: error.message });
} }
}; };
export const forgotPassword = async (req, res) => { export const forgotPassword = async (req, res) => {
const { email } = req.body; const { email } = req.body;
try { try {
const user = await User.findOne({ where: { email } }); const result = await userService.handleForgotPassword({ email });
if (!user) { res.status(200).json(result);
return res.status(404).json({ error: 'Email not found' });
}
const resetToken = uuidv4();
const resetLink = `${process.env.FRONTEND_URL}/reset-password?token=${resetToken}`;
await user.update({ reset_token: resetToken });
const languageParam = await UserParam.findOne({ where: { user_id: user.id, param_type_id: languageType.id } });
const userLanguage = languageParam ? languageParam.value : 'en';
await sendPasswordResetEmail(email, resetLink, userLanguage);
res.status(200).json({ message: 'Password reset email sent' });
} catch (error) { } catch (error) {
res.status(500).json({ error: 'Error processing forgot password' }); res.status(500).json({ error: error.message });
} }
}; };
export const activateAccount = async (req, res) => { export const activateAccount = async (req, res) => {
const { token } = req.body; const { token } = req.body;
try { try {
const user = await User.findOne({ where: { reset_token: token } }); const result = await userService.activateUserAccount({ token });
if (!user) { res.status(200).json(result);
return res.status(404).json({ error: 'Invalid token' });
}
await user.update({ active: true, reset_token: null });
res.status(200).json({ message: 'Account activated' });
} catch (error) { } catch (error) {
res.status(500).json({ error: 'Error activating account' }); res.status(500).json({ error: error.message });
} }
}; };

View File

@@ -0,0 +1,208 @@
const menuStructure = {
home: {
visible: ["all"],
children: {},
path: "/"
},
friends: {
visible: ["all"],
children: {
manageFriends : {
visible: ["all"],
path: "/socialnetwork/friends"
}
},
showLoggedinFriends: 1
},
socialnetwork: {
visible: ["all"],
children: {
guestbook: {
visible: ["all"],
path: "/socialnetwork/guestbook"
},
usersearch: {
visible: ["all"],
path: "/socialnetwork/search"
},
forum: {
visible: ["all"],
path: "/socialnetwork/forum",
showForums: 1
},
gallery: {
visible: ["all"],
path: "/socialnetwork/gallery"
},
blockedUsers: {
visible: ["all"],
path: "/socialnetwork/blocked"
},
oneTimeInvitation: {
visible: ["all"],
path: "/socialnetwork/onetimeinvitation"
},
diary: {
visible: ["all"],
path: "/socialnetwork/diary"
}
}
},
chats: {
visible: ["all"],
children: {
multiChat: {
visible: ["over12"],
action: "openMultiChat"
},
randomChat: {
visible: ["over12"],
action: "openRanomChat"
}
}
},
falukant: {
visible: ["all"],
children: {
create: {
visible: ["nofalukantaccount"],
path: "/falukant/create"
},
overview: {
visible: ["hasfalukantaccount"],
path: "/falukant/home"
},
towns: {
visible: ["hasfalukantaccount"],
path: "/falukant/towns"
},
directors: {
visible: ["hasfalukantaccount"],
path: "/falukant/directors"
},
factory: {
visible: ["hasfalukantaccount"],
path: "/falukant/factory"
},
family: {
visible: ["hasfalukantaccount"],
path: "/falukant/family"
},
house: {
visible: ["hasfalukantaccount"],
path: "/falukant/house"
},
nobility: {
visible: ["hasfalukantaccount"],
path: "/falukant/nobility"
},
politics: {
visible: ["hasfalukantaccount"],
path: "/falukant/politics"
},
education: {
visible: ["hasfalukantaccount"],
path: "/falukant/education"
},
bank: {
visible: ["hasfalukantaccount"],
path: "/falukant/bank"
},
darknet: {
visible: ["hasfalukantaccount"],
path: "/falukant/darknet"
},
reputation: {
visible: ["hasfalukantaccount"],
path: "/falukant/reputation"
},
moneyhistory: {
visible: ["hasfalukantaccount"],
path: "/falukant/moneyhistory"
}
}
},
minigames: {
visible: ["all"],
},
settings: {
visible: ["all"],
children: {
homepage: {
visible: ["all"],
path: "/settings/homepage"
},
account: {
visible: ["all"],
path: "/settings/account"
},
personal: {
visible: ["all"],
path: "/settings/account"
},
view: {
visible: ["all"],
path: "/settings/account"
},
interrests: {
visible: ["all"],
path: "/settings/interrests"
},
sexuality: {
visible: ["over14"],
path: "/setting/sexuality"
},
notifications: {
visible: ["all"],
path: "/settings/notifications"
}
}
},
administration: {
visible: ["anyadmin"],
children: {
contactrequests: {
visible: ["mainadmin", "contactrequests"],
path: "/admin/contacts"
},
useradministration: {
visible: ["mainadmin", "useradministration"],
path: "/admin/users"
},
forum: {
visible: ["mainadmin", "forum"],
path: "/admin/forum"
},
userrights: {
visible: ["mainadmin", "rights"],
path: "/admin/rights"
},
interrests: {
visible: ["mainadmin", "interrests"],
path: "/admin/interrests"
},
falukant: {
visible: ["mainadmin", "falukant"],
children: {
logentries: {
visible: ["mainadmin", "falukant"],
path: "/admin/falukant/logentries"
},
edituser: {
visible: ["mainadmin", "falukant"],
path: "/admin/falukant/edituser"
},
database: {
visible: ["mainadmin", "falukant"],
path: "/admin/falukant/database"
},
}
}
}
}
};
export const menu = async (req, res) => {
const { userid } = req.params;
res.status(200).json({ userId: userid });
}

View File

@@ -19,9 +19,6 @@ const User = sequelize.define('user', {
password: { password: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: false,
set(value) {
this.setDataValue('password', bcrypt.hashSync(value, 10));
}
}, },
registrationDate: { registrationDate: {
type: DataTypes.DATE, type: DataTypes.DATE,

View File

@@ -0,0 +1,7 @@
import { Router } from 'express';
import { menu } from '../controllers/navigationController.js';
const router = Router();
router.get('/:userid', menu);
export default router;

View File

@@ -0,0 +1,78 @@
import bcrypt from 'bcrypt';
import { v4 as uuidv4 } from 'uuid';
import User from '../models/community/user.js';
import UserParam from '../models/community/user_param.js';
import UserParamType from '../models/type/user_param.js';
import { sendAccountActivationEmail, sendPasswordResetEmail } from './emailService.js';
const saltRounds = 10;
export const registerUser = async ({ email, username, password, language }) => {
const hashedPassword = await bcrypt.hash(password, saltRounds);
const resetToken = uuidv4();
const user = await User.create({
email,
username,
password: hashedPassword,
resetToken: resetToken,
active: false,
registration_date: new Date()
});
const languageType = await UserParamType.findOne({ where: { description: 'language' } });
if (!languageType) {
throw new Error('Language type not found');
}
await UserParam.create({
userId: user.id,
paramTypeId: languageType.id,
value: language
});
const activationLink = `${process.env.FRONTEND_URL}/activate?token=${resetToken}`;
await sendAccountActivationEmail(email, activationLink, username, resetToken, language);
return { id: user.hashedId, username: user.username, active: user.active };
};
export const loginUser = async ({ username, password }) => {
console.log('check login');
const user = await User.findOne({ where: { username } });
if (!user) {
throw new Error('credentialsinvalid');
}
const match = await bcrypt.compare(password, user.password);
if (!match) {
throw new Error('credentialsinvalid');
}
return { id: user.hashedId, username: user.username, active: user.active };
};
export const handleForgotPassword = async ({ email }) => {
const user = await User.findOne({ where: { email } });
if (!user) {
throw new Error('Email not found');
}
const resetToken = uuidv4();
const resetLink = `${process.env.FRONTEND_URL}/reset-password?token=${resetToken}`;
await user.update({ reset_token: resetToken });
const languageParam = await UserParam.findOne({ where: { user_id: user.id, param_type_id: languageType.id } });
const userLanguage = languageParam ? languageParam.value : 'en';
await sendPasswordResetEmail(email, resetLink, userLanguage);
return { message: 'Password reset email sent' };
};
export const activateUserAccount = async ({ token }) => {
const user = await User.findOne({ where: { reset_token: token } });
if (!user) {
throw new Error('Invalid token');
}
await user.update({ active: true, reset_token: null });
return { message: 'Account activated' };
};

View File

@@ -1,7 +1,7 @@
<template> <template>
<nav> <nav>
<ul> <ul>
<li v-for="item in menuItems" :key="item.text"> <li v-for="item in menu" :key="item.text">
<a href="#">{{ $t(`navigation.${item.text}`) }}</a> <a href="#">{{ $t(`navigation.${item.text}`) }}</a>
<ul v-if="item.submenu"> <ul v-if="item.submenu">
<li v-for="subitem in item.submenu" :key="subitem.text"> <li v-for="subitem in item.submenu" :key="subitem.text">
@@ -18,16 +18,17 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex';
export default { export default {
name: 'AppNavigation', name: 'AppNavigation',
data() { computed: {
return { ...mapGetters('menu'),
menuItems: [ },
{ text: 'home' }, created() {
{ text: 'about', submenu: [{ text: 'team' }, { text: 'company' }] }, if(this.$store.getters.hashedId) {
{ text: 'services', submenu: [{ text: 'consulting' }, { text: 'development' }] } this.$store.dispatch('loadMenu');
] }
};
}, },
methods: { methods: {
accessMailbox() { accessMailbox() {
@@ -43,11 +44,12 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../assets/styles.scss'; @import '../assets/styles.scss';
nav,
nav > ul{ nav > ul{
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
background-color: #343a40; background-color: #F9A22C;
color: white; color: #000000;
padding: 10px; padding: 10px;
flex-direction: row; flex-direction: row;
} }
@@ -59,7 +61,7 @@ li {
margin: 5px 0; margin: 5px 0;
} }
a { a {
color: white; color: #000000;
text-decoration: none; text-decoration: none;
} }
.right-block { .right-block {

View File

@@ -1,11 +1,13 @@
import { createStore } from 'vuex'; import { createStore } from 'vuex';
import dialogs from './modules/dialogs'; import dialogs from './modules/dialogs';
import loadMenu from '../utils/menuLoader.js';
const store = createStore({ const store = createStore({
state: { state: {
isLoggedIn: false, isLoggedIn: false,
user: null, user: null,
language: navigator.language.startsWith('de') ? 'de' : 'en', language: navigator.language.startsWith('de') ? 'de' : 'en',
menu: [],
}, },
mutations: { mutations: {
dologin(state, user) { dologin(state, user) {
@@ -13,6 +15,7 @@ const store = createStore({
state.user = user; state.user = user;
localStorage.setItem('isLoggedIn', 'true'); localStorage.setItem('isLoggedIn', 'true');
localStorage.setItem('user', JSON.stringify(user)); localStorage.setItem('user', JSON.stringify(user));
console.log(state.user);
}, },
dologout(state) { dologout(state) {
state.isLoggedIn = false; state.isLoggedIn = false;
@@ -34,11 +37,16 @@ const store = createStore({
}, },
setLanguage(state, language) { setLanguage(state, language) {
state.language = language; state.language = language;
},
setMenu(state, menu) {
state.menu = menu;
} }
}, },
actions: { actions: {
login({ commit }, user) { async login({ commit, dispatch }, user) { // Dispatch hinzufügen
commit('dologin', user); commit('dologin', user);
await dispatch('loadMenu'); // Korrekte Verwendung von dispatch
dispatch('startMenuReload');
}, },
logout({ commit }) { logout({ commit }) {
commit('dologout'); commit('dologout');
@@ -49,11 +57,26 @@ const store = createStore({
setLanguage({ commit }, language) { setLanguage({ commit }, language) {
commit('setLanguage', language); commit('setLanguage', language);
}, },
async loadMenu({ commit }) {
try {
const menu = await loadMenu();
commit('setMenu', menu);
} catch (err) {
console.error(err);
commit('setMenu', []);
}
},
startMenuReload({ dispatch }) {
setInterval(() => {
dispatch('loadMenu');
}, 5000);
},
}, },
getters: { getters: {
isLoggedIn: state => state.isLoggedIn, isLoggedIn: state => state.isLoggedIn,
user: state => state.user, user: state => state.user,
language: state => state.language, language: state => state.language,
menu: state => state.menu,
}, },
modules: { modules: {
dialogs, dialogs,

View File

@@ -0,0 +1,19 @@
import axios from 'axios';
import store from '../store';
const loadMenu = async () => {
try {
console.log(store.getters.user);
const userId = store.getters.user ? store.getters.user.id : null;
if (!userId) {
throw new Error('User ID not found');
}
const response = await axios.get('/api/navigation/' + userId);
return response.data;
} catch (err) {
console.error(err);
return [];
}
};
export default loadMenu;

View File

@@ -11,30 +11,24 @@
<div> <div>
<div> <div>
<div> <div>
<input data-object-name="user-name" size="20" type="text" <input v-model="username" size="20" type="text"
:placeholder="$t('home.nologin.login.name')" :placeholder="$t('home.nologin.login.name')"
:title="$t('home.nologin.login.namedescription')"> :title="$t('home.nologin.login.namedescription')">
</div> </div>
<div> <div>
<input data-object-name="password" size="20" type="password" <input v-model="password" size="20" type="password"
:placeholder="$t('home.nologin.login.password')" :placeholder="$t('home.nologin.login.password')"
:title="$t('home.nologin.login.passworddescription')"> :title="$t('home.nologin.login.passworddescription')">
</div> </div>
<div> <div>
<label id="o1p5irxv" name="o1p5irxv" class="Wt-valid" title=""><input id="ino1p5irxv" <label><input type="checkbox"><span>Eingeloggt bleiben</span></label>
data-object-name="remember-me" name="ino1p5irxv" type="checkbox"
onchange="var e=event||window.event,o=this;Wt._p_.update(o,'s53',e,true);"><span
id="to1p5irxv" name="to1p5irxv" style="white-space:normal;">Eingeloggt bleiben</span></label>
</div> </div>
</div> </div>
<div class="Wt-buttons"> <div>
<button id="o1p5irxz" data-object-name="login" type="button" <button type="button" @click="doLogin">Einloggen</button>
onclick="var e=event||window.event,o=this;if(o.classList.contains('Wt-disabled')){Wt4_9_1.cancelEvent(e);return;}Wt._p_.update(o,'s56',e,true);"
class="Wt-btn with-label">Einloggen</button>
</div> </div>
<div class="Wt-buttons"> <div>
<span id="o1p5iry0" data-object-name="lost-password" @click="openPasswordResetDialog" <span @click="openPasswordResetDialog" class="link">{{
class="link">{{
$t('home.nologin.login.lostpassword') }}</span> | <span id="o1p5iry1" $t('home.nologin.login.lostpassword') }}</span> | <span id="o1p5iry1"
@click="openRegisterDialog" class="link">{{ $t('home.nologin.login.register') }}</span> @click="openRegisterDialog" class="link">{{ $t('home.nologin.login.register') }}</span>
</div> </div>
@@ -51,15 +45,24 @@
import RandomChatDialog from '@/dialogues/chat/RandomChatDialog.vue'; import RandomChatDialog from '@/dialogues/chat/RandomChatDialog.vue';
import RegisterDialog from '@/dialogues/auth/RegisterDialog.vue'; import RegisterDialog from '@/dialogues/auth/RegisterDialog.vue';
import PasswordResetDialog from '@/dialogues/auth/PasswordResetDialog.vue'; import PasswordResetDialog from '@/dialogues/auth/PasswordResetDialog.vue';
import apiClient from '@/utils/axios.js';
import { mapActions } from 'vuex';
export default { export default {
name: 'HomeNoLoginView', name: 'HomeNoLoginView',
data() {
return {
username: '',
password: '',
};
},
components: { components: {
RandomChatDialog, RandomChatDialog,
RegisterDialog, RegisterDialog,
PasswordResetDialog, PasswordResetDialog,
}, },
methods: { methods: {
...mapActions(['login']),
openRandomChat() { openRandomChat() {
this.$refs.randomChatDialog.open(); this.$refs.randomChatDialog.open();
}, },
@@ -68,6 +71,10 @@ export default {
}, },
openPasswordResetDialog() { openPasswordResetDialog() {
this.$refs.passwordResetDialog.open(); this.$refs.passwordResetDialog.open();
},
async doLogin() {
const response = await apiClient.post('/api/auth/login', { username: this.username, password: this.password });
this.login(response.data);
} }
} }
}; };