Änderung: Erweiterung der Benutzerkontoeinstellungen und Verbesserung der E-Mail-Verschlüsselung

Änderungen:
- Implementierung von neuen Methoden `getAccountSettings` und `setAccountSettings` im `SettingsService`, um Benutzerkontoeinstellungen zu verwalten.
- Anpassung der E-Mail-Verschlüsselung im `User`-Modell zur Verwendung von Buffer für die Speicherung und zur Verbesserung der Fehlerbehandlung bei der Entschlüsselung.
- Hinzufügung eines neuen `immutable`-Feldes im `UserParamType`-Modell, um unveränderliche Einstellungen zu kennzeichnen.
- Anpassungen in den Frontend-Komponenten zur Berücksichtigung von unveränderlichen Feldern und zur Verbesserung der Benutzeroberfläche.

Diese Anpassungen verbessern die Sicherheit der Benutzerdaten und erweitern die Funktionalität der Kontoeinstellungen.
This commit is contained in:
Torsten Schulz (local)
2025-09-15 11:48:00 +02:00
parent eedb1aa7d5
commit d6bfe50b4e
18 changed files with 355 additions and 28 deletions

View File

@@ -5,38 +5,52 @@
<td>
<InputStringWidget v-if="setting.datatype == 'string'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
:tooltipTr="setting.immutable ? $t('settings.immutable.tooltip') : `settings.personal.tooltip.${setting.name}`"
:value=setting.value
:disabled="setting.immutable && setting.value ? true : false"
:list="languagesList()" @input="handleInput(setting.id, $event)" />
<DateInputWidget v-else-if="setting.datatype == 'date'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
:tooltipTr="setting.immutable ? $t('settings.immutable.tooltip') : `settings.personal.tooltip.${setting.name}`"
:value=setting.value
:disabled="setting.immutable && setting.value ? true : false"
@input="handleInput(setting.id, $event)" />
<SelectDropdownWidget v-else-if="setting.datatype == 'singleselect'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
:tooltipTr="setting.immutable ? $t('settings.immutable.tooltip') : `settings.personal.tooltip.${setting.name}`"
:value=setting.value
:disabled="setting.immutable && setting.value ? true : false"
:list="getSettingOptions(setting.name, setting.options)"
@input="handleInput(setting.id, $event)" />
<InputNumberWidget v-else-if="setting.datatype == 'int'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToInt(setting.value)"
:tooltipTr="setting.immutable ? $t('settings.immutable.tooltip') : `settings.personal.tooltip.${setting.name}`"
:value="convertToInt(setting.value)"
:disabled="setting.immutable && setting.value ? true : false"
min="0" max="200" @input="handleInput(setting.id, $event)" />
<FloatInputWidget v-else-if="setting.datatype == 'float'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToFloat(setting.value)"
:tooltipTr="setting.immutable ? $t('settings.immutable.tooltip') : `settings.personal.tooltip.${setting.name}`"
:value="convertToFloat(setting.value)"
:disabled="setting.immutable && setting.value ? true : false"
@input="handleInput(setting.id, $event)" />
<CheckboxWidget v-else-if="setting.datatype == 'bool'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToBool(setting.value)"
:tooltipTr="setting.immutable ? $t('settings.immutable.tooltip') : `settings.personal.tooltip.${setting.name}`"
:value="convertToBool(setting.value)"
:disabled="setting.immutable && setting.value ? true : false"
@input="handleInput(setting.id, $event)" />
<MultiselectWidget v-else-if="setting.datatype == 'multiselect'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="setting.value"
:tooltipTr="setting.immutable ? $t('settings.immutable.tooltip') : `settings.personal.tooltip.${setting.name}`"
:value="setting.value"
:disabled="setting.immutable && setting.value ? true : false"
:list="getSettingOptions(setting.name, setting.options)"
@input="handleInput(setting.id, $event)" />
@@ -51,6 +65,11 @@
</option>
</select>
</td>
<td v-if="setting.immutable && setting.value">
<button @click="openContactDialog" class="contact-button">
{{ $t('settings.immutable.supportContact') }}
</button>
</td>
</tr>
</table>
</div>
@@ -93,6 +112,7 @@ export default {
},
async mounted() {
await this.fetchSettings();
await this.fetchAccountData();
},
methods: {
async fetchSettings() {
@@ -111,6 +131,17 @@ export default {
}
}
},
async fetchAccountData() {
if (this.user && this.user.id) {
try {
const response = await apiClient.post('/api/settings/account', { userId: this.user.id });
this.userEmail = response.data.email;
this.userUsername = response.data.username;
} catch (err) {
console.error('Error fetching account data:', err);
}
}
},
getSettingOptions(fieldName, options) {
return options.map((option) => {
return {
@@ -123,6 +154,14 @@ export default {
if (['object', 'array'].includes(typeof value)) {
return;
}
// Prüfe ob das Setting unveränderlich ist
const setting = this.settings.find(s => s.id === settingId);
if (setting && setting.immutable && setting.value) {
alert(this.$t('settings.immutable.tooltip'));
return;
}
try {
const userid = this.user.id;
await apiClient.post('/api/settings/update', {
@@ -133,6 +172,9 @@ export default {
this.fetchSettings();
} catch (err) {
console.error('Error updating setting:', err);
if (err.response && err.response.data && err.response.data.error) {
alert(err.response.data.error);
}
}
},
languagesList() {
@@ -168,10 +210,35 @@ export default {
console.error('Error updating visibility:', err);
}
},
openContactDialog() {
// Erstelle vorgefertigte Daten für Support-Anfrage
const prefilledData = {
email: this.userEmail || "",
name: this.userUsername || "",
message: this.createSupportMessage(),
acceptDataSave: true
};
this.$root.$refs.contactDialog.open(prefilledData);
},
createSupportMessage() {
// Erstelle eine vorgefertigte Nachricht für unveränderliche Felder
const immutableFields = this.settings.filter(s => s.immutable && s.value);
if (immutableFields.length === 0) {
return this.$t('settings.immutable.supportMessage.general');
}
const fieldNames = immutableFields.map(field =>
this.$t(`settings.personal.label.${field.name}`)
).join(', ');
return this.$t('settings.immutable.supportMessage.specific', { fields: fieldNames });
},
},
data() {
return {
settings: [],
userEmail: "",
userUsername: "",
};
}
};
@@ -181,4 +248,23 @@ export default {
label {
float: left;
}
.contact-button {
background-color: #007bff;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background-color 0.2s;
}
.contact-button:hover {
background-color: #0056b3;
}
.contact-button:active {
background-color: #004085;
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<label>
<input type="checkbox" :checked="value" @change="updateValue($event.target.checked)" :title="$t(tooltipTr)" />
<input type="checkbox" :checked="value" :disabled="disabled" @change="updateValue($event.target.checked)" :title="$t(tooltipTr)" />
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
</label>
</template>
@@ -26,6 +26,11 @@ export default {
type: Number,
required: false,
default: 10
},
disabled: {
type: Boolean,
required: false,
default: false
}
},
methods: {

View File

@@ -2,7 +2,7 @@
<label>
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
<input type="date" v-model="internalValue" :placeholder="$t(labelTr)" :title="$t(tooltipTr)"
@change="updateValue($event.target.value)" />
:disabled="disabled" @change="updateValue($event.target.value)" />
</label>
</template>
@@ -28,6 +28,11 @@ export default {
type: Number,
required: false,
default: 10
},
disabled: {
type: Boolean,
required: false,
default: false
}
},
data() {

View File

@@ -2,7 +2,7 @@
<label>
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
<input type="number" :value="formattedValue" :placeholder="$t(labelTr)" :title="$t(tooltipTr)"
@change="updateValue($event.target.value)" :step="step" />
:disabled="disabled" @change="updateValue($event.target.value)" :step="step" />
<span v-if="postfix">{{ postfix }}</span>
</label>
</template>
@@ -37,6 +37,11 @@ export default {
type: String,
required: false,
default: ''
},
disabled: {
type: Boolean,
required: false,
default: false
}
},
computed: {

View File

@@ -1,6 +1,6 @@
<template>
<div class="dropdown-container">
<div class="dropdown-header" @click="toggleDropdown">
<div class="dropdown-header" @click="disabled ? null : toggleDropdown" :class="{ disabled: disabled }">
<table>
<tr>
<td v-for="(column, index) in columns" :key="column.field">
@@ -48,6 +48,11 @@ export default {
type: String,
default: "Select an option",
},
disabled: {
type: Boolean,
required: false,
default: false
},
},
emits: ['update:modelValue'],
data() {
@@ -63,6 +68,7 @@ export default {
},
methods: {
toggleDropdown() {
if (this.disabled) return;
this.isOpen = !this.isOpen;
},
selectOption(option) {
@@ -129,4 +135,10 @@ tr:hover {
background-color: #e0e0e0;
cursor: pointer;
}
.dropdown-header.disabled {
background-color: #f5f5f5;
color: #999;
cursor: not-allowed;
}
</style>

View File

@@ -2,7 +2,7 @@
<label>
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
<input type="number" :value="value" :title="$t(tooltipTr)" :min="min" :max="max"
@change="updateValue($event.target.value)" />
:disabled="disabled" @change="updateValue($event.target.value)" />
</label>
</template>
@@ -34,6 +34,11 @@ export default {
max: {
type: Number,
required: false
},
disabled: {
type: Boolean,
required: false,
default: false
}
},
methods: {

View File

@@ -2,7 +2,7 @@
<label>
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
<input type="text" :value="value" :placeholder="$t(labelTr)" :title="$t(tooltipTr)"
@change="validateAndUpdate($event.target.value)" />
:disabled="disabled" @change="validateAndUpdate($event.target.value)" />
</label>
</template>
@@ -31,6 +31,11 @@ export default {
type: String,
required: false,
default: null
},
disabled: {
type: Boolean,
required: false,
default: false
}
},
methods: {

View File

@@ -10,6 +10,7 @@
:preserve-search="true"
:placeholder="$t('select_option')"
:track-by="'value'"
:disabled="disabled"
>
<template #option="{ option }">
<span v-if="option && option.value">{{ getTranslation(option) }}</span>
@@ -42,6 +43,11 @@ export default {
required: true,
default: () => [] // Standardwert hinzufügen, um undefined zu vermeiden
},
disabled: {
type: Boolean,
required: false,
default: false
},
},
data() {
return {

View File

@@ -1,7 +1,7 @@
<template>
<label>
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
<select :title="$t(tooltipTr)" :value="value" @change="updateValue($event.target.value)">
<select :title="$t(tooltipTr)" :value="value" :disabled="disabled" @change="updateValue($event.target.value)">
<option v-if="allowNone" :value="noneValue">{{ $t('none') }}</option>
<option v-for="item in list" :key="item.value" :value="item.value">
{{ item.captionTr ? $t(item.captionTr) : item.caption }}
@@ -44,6 +44,11 @@ export default {
type: String,
required: false,
default: ''
},
disabled: {
type: Boolean,
required: false,
default: false
}
},
methods: {

View File

@@ -40,11 +40,12 @@ export default {
};
},
methods: {
open() {
this.email = "";
this.name = "";
this.message = "";
this.acceptDataSave = false;
open(prefilledData = {}) {
// Verwende vorgefertigte Daten oder Standardwerte
this.email = prefilledData.email || "";
this.name = prefilledData.name || "";
this.message = prefilledData.message || "";
this.acceptDataSave = prefilledData.acceptDataSave || false;
this.error = "";
this.$refs.dialog.open();
},

View File

@@ -167,6 +167,14 @@
},
"flirt": {
"title": "Flirt"
}
},
"immutable": {
"tooltip": "Dieses Feld kann nicht geändert werden. Für Änderungen wenden Sie sich bitte an den Support.",
"supportContact": "Support kontaktieren",
"supportMessage": {
"general": "Hallo,\n\nich möchte eine Änderung an meinen unveränderlichen Profildaten beantragen.\n\nBitte kontaktieren Sie mich für weitere Details.\n\nMit freundlichen Grüßen",
"specific": "Hallo,\n\nich möchte eine Änderung an folgenden unveränderlichen Profildaten beantragen: {fields}\n\nBitte kontaktieren Sie mich für weitere Details.\n\nMit freundlichen Grüßen"
}
}
}
}

View File

@@ -167,6 +167,14 @@
},
"flirt": {
"title": "Flirt"
}
},
"immutable": {
"tooltip": "This field cannot be changed. Please contact support for modifications.",
"supportContact": "Contact Support",
"supportMessage": {
"general": "Hello,\n\nI would like to request a change to my immutable profile data.\n\nPlease contact me for further details.\n\nBest regards",
"specific": "Hello,\n\nI would like to request a change to the following immutable profile data: {fields}\n\nPlease contact me for further details.\n\nBest regards"
}
}
}
}

View File

@@ -11,15 +11,15 @@
</div>
<div>
<label><span>{{ $t("settings.account.newpassword") }} </span><input type="password" v-model="newpassword"
:placeholder="$t('settings.account.newpassword')" /></label>
:placeholder="$t('settings.account.newpassword')" autocomplete="new-password" /></label>
</div>
<div>
<label><span>{{ $t("settings.account.newpasswordretype") }} </span><input type="password"
v-model="newpasswordretype" :placeholder="$t('settings.account.newpasswordretype')" /></label>
v-model="newpasswordretype" :placeholder="$t('settings.account.newpasswordretype')" autocomplete="new-password" /></label>
</div>
<div>
<label><span>{{ $t("settings.account.oldpassword") }} </span><input type="password"
v-model="oldpassword" :placeholder="$t('settings.account.oldpassword')" /></label>
v-model="oldpassword" :placeholder="$t('settings.account.oldpassword')" autocomplete="current-password" /></label>
</div>
<div>
<button @click="changeAccount">{{ $t("settings.account.changeaction") }}</button>
@@ -50,13 +50,74 @@ export default {
oldpassword: "",
};
},
methods: {},
methods: {
async changeAccount() {
try {
// Prüfe ob ein neues Passwort eingegeben wurde
const hasNewPassword = this.newpassword && this.newpassword.trim() !== '';
if (hasNewPassword) {
// Validiere Passwort-Wiederholung nur wenn ein neues Passwort eingegeben wurde
if (this.newpassword !== this.newpasswordretype) {
alert('Die Passwörter stimmen nicht überein.');
return;
}
// Prüfe ob das alte Passwort eingegeben wurde
if (!this.oldpassword || this.oldpassword.trim() === '') {
alert('Bitte geben Sie Ihr aktuelles Passwort ein, um das Passwort zu ändern.');
return;
}
}
// Bereite die Daten für den API-Aufruf vor
const accountData = {
userId: this.user.id,
settings: {
username: this.username,
email: this.email,
showinsearch: this.showInSearch
}
};
// Füge Passwort-Daten nur hinzu, wenn ein neues Passwort eingegeben wurde
if (hasNewPassword) {
accountData.settings.newpassword = this.newpassword;
accountData.settings.oldpassword = this.oldpassword;
}
// API-Aufruf zum Speichern der Account-Einstellungen
await apiClient.post('/api/settings/set-account', accountData);
alert('Account-Einstellungen erfolgreich gespeichert!');
// Leere die Passwort-Felder nach erfolgreichem Speichern
this.newpassword = '';
this.newpasswordretype = '';
this.oldpassword = '';
} catch (error) {
console.error('Fehler beim Speichern der Account-Einstellungen:', error);
if (error.response && error.response.data && error.response.data.error) {
alert('Fehler: ' + error.response.data.error);
} else {
alert('Ein Fehler ist aufgetreten beim Speichern der Account-Einstellungen.');
}
}
}
},
async mounted() {
const response = await apiClient.post('/api/settings/account', { userId: this.user.id });
console.log(response.data);
this.username = response.data.username;
this.showInSearch = response.data.showinsearch;
this.email = response.data.email;
// Stelle sicher, dass Passwort-Felder leer sind
this.newpassword = '';
this.newpasswordretype = '';
this.oldpassword = '';
console.log(this.showInSearch);
},
};