diff --git a/backend/models/community/user.js b/backend/models/community/user.js index 3124f9c..20066ec 100644 --- a/backend/models/community/user.js +++ b/backend/models/community/user.js @@ -11,13 +11,32 @@ const User = sequelize.define('user', { set(value) { if (value) { const encrypted = encrypt(value); - this.setDataValue('email', encrypted); + // Konvertiere Hex-String zu Buffer für die Speicherung + const buffer = Buffer.from(encrypted, 'hex'); + this.setDataValue('email', buffer); } }, get() { const encrypted = this.getDataValue('email'); if (encrypted) { - return decrypt(encrypted); + try { + // Konvertiere Buffer zu String für die Entschlüsselung + const encryptedString = encrypted.toString('hex'); + const decrypted = decrypt(encryptedString); + if (decrypted) { + return decrypted; + } + } catch (error) { + console.warn('Email decryption failed, treating as plain text:', error.message); + } + + // Fallback: Versuche es als Klartext zu lesen + try { + return encrypted.toString('utf8'); + } catch (error) { + console.warn('Email could not be read as plain text:', error.message); + return null; + } } return null; } diff --git a/backend/models/type/user_param.js b/backend/models/type/user_param.js index 2fae1d9..10d33c5 100644 --- a/backend/models/type/user_param.js +++ b/backend/models/type/user_param.js @@ -35,6 +35,11 @@ const UserParamType = sequelize.define('user_param_type', { unit: { type: DataTypes.STRING, allowNull: true + }, + immutable: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false } }, { tableName: 'user_param', diff --git a/backend/routers/settingsRouter.js b/backend/routers/settingsRouter.js index 3cabda2..dcb203d 100644 --- a/backend/routers/settingsRouter.js +++ b/backend/routers/settingsRouter.js @@ -8,6 +8,7 @@ const settingsController = new SettingsController(); router.post('/filter', authenticate, settingsController.filterSettings.bind(settingsController)); router.post('/update', authenticate, settingsController.updateSetting.bind(settingsController)); router.post('/account', authenticate, settingsController.getAccountSettings.bind(settingsController)); +router.post('/set-account', authenticate, settingsController.setAccountSettings.bind(settingsController)); router.post('/getparamvalues', settingsController.getTypeParamValues.bind(settingsController)); router.post('/getparamvalueid', settingsController.getTypeParamValueId.bind(settingsController)); router.post('/getparamvalue/:id', settingsController.getTypeParamValue.bind(settingsController)); diff --git a/backend/services/settingsService.js b/backend/services/settingsService.js index edc36a8..5c39d11 100644 --- a/backend/services/settingsService.js +++ b/backend/services/settingsService.js @@ -103,6 +103,7 @@ class SettingsService extends BaseService{ gender: field.gender, datatype: field.datatype, unit: field.unit, + immutable: field.immutable, value: field.user_params.length > 0 ? field.user_params[0].value : null, options: options.map(opt => ({ id: opt.id, value: opt.value })), visibility @@ -117,6 +118,19 @@ class SettingsService extends BaseService{ if (!paramType) { throw new Error('Parameter type not found'); } + + // Prüfe ob das Feld unveränderlich ist + if (paramType.immutable) { + const userParam = await UserParam.findOne({ + where: { userId: user.id, paramTypeId: settingId } + }); + + // Wenn bereits ein Wert existiert, ist das Feld unveränderlich + if (userParam && userParam.value) { + throw new Error('This field cannot be changed. Please contact support for modifications.'); + } + } + const userParam = await UserParam.findOne({ where: { userId: user.id, paramTypeId: settingId } }); @@ -257,6 +271,81 @@ class SettingsService extends BaseService{ } } + async getAccountSettings(hashedUserId) { + try { + const user = await this.getUserByHashedId(hashedUserId); + if (!user) { + throw new Error('User not found'); + } + + // Die Email wird automatisch durch den Getter entschlüsselt + // Falls die Entschlüsselung fehlschlägt, verwende null + let email = null; + try { + email = user.email; // Getter entschlüsselt automatisch + } catch (decryptError) { + console.warn('Email decryption failed, using null:', decryptError.message); + email = null; + } + + return { + username: user.username, + email: email, + showinsearch: user.searchable + }; + } catch (error) { + console.error('Error getting account settings:', error); + throw error; + } + } + + async setAccountSettings({ userId, settings }) { + try { + const user = await this.getUserByHashedId(userId); + if (!user) { + throw new Error('User not found'); + } + + // Update username if provided + if (settings.username !== undefined) { + await user.update({ username: settings.username }); + } + + // Update email if provided + if (settings.email !== undefined) { + await user.update({ email: settings.email }); + } + + // Update searchable flag if provided + if (settings.showinsearch !== undefined) { + await user.update({ searchable: settings.showinsearch }); + } + + // Update password if provided and not empty + if (settings.newpassword && settings.newpassword.trim() !== '') { + if (!settings.oldpassword || settings.oldpassword.trim() === '') { + throw new Error('Old password is required to change password'); + } + + // Verify old password + const bcrypt = await import('bcrypt'); + const match = await bcrypt.compare(settings.oldpassword, user.password); + if (!match) { + throw new Error('Old password is incorrect'); + } + + // Hash new password + const hashedPassword = await bcrypt.hash(settings.newpassword, 10); + await user.update({ password: hashedPassword }); + } + + return { success: true }; + } catch (error) { + console.error('Error setting account settings:', error); + throw error; + } + } + async getVisibilities() { return UserParamVisibilityType.findAll(); } diff --git a/backend/utils/initializeTypes.js b/backend/utils/initializeTypes.js index e69681f..fc89467 100644 --- a/backend/utils/initializeTypes.js +++ b/backend/utils/initializeTypes.js @@ -24,7 +24,7 @@ const initializeTypes = async () => { }; const userParams = { language: { type: 'singleselect', setting: 'personal' }, - birthdate: { type: 'date', setting: 'personal' }, + birthdate: { type: 'date', setting: 'personal', immutable: true }, zip: { type: 'string', setting: 'personal' }, town: { type: 'string', setting: 'personal' }, bodyheight: { type: 'float', setting: 'view', unit: 'cm' }, @@ -54,6 +54,7 @@ const initializeTypes = async () => { if (item.minAge) createItem.minAge = item.minAge; if (item.gender) createItem.gender = item.gender; if (item.unit) createItem.unit = item.unit; + if (item.immutable) createItem.immutable = item.immutable; await UserParamType.findOrCreate({ where: { description: key }, defaults: createItem diff --git a/frontend/src/components/SettingsWidget.vue b/frontend/src/components/SettingsWidget.vue index 9f25316..bab1dfc 100644 --- a/frontend/src/components/SettingsWidget.vue +++ b/frontend/src/components/SettingsWidget.vue @@ -5,38 +5,52 @@
| @@ -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; +} diff --git a/frontend/src/components/form/InputNumberWidget.vue b/frontend/src/components/form/InputNumberWidget.vue index 865c21a..e0df04d 100644 --- a/frontend/src/components/form/InputNumberWidget.vue +++ b/frontend/src/components/form/InputNumberWidget.vue @@ -2,7 +2,7 @@ @@ -34,6 +34,11 @@ export default { max: { type: Number, required: false + }, + disabled: { + type: Boolean, + required: false, + default: false } }, methods: { diff --git a/frontend/src/components/form/InputStringWidget.vue b/frontend/src/components/form/InputStringWidget.vue index 5ed9dee..2288b78 100644 --- a/frontend/src/components/form/InputStringWidget.vue +++ b/frontend/src/components/form/InputStringWidget.vue @@ -2,7 +2,7 @@ @@ -31,6 +31,11 @@ export default { type: String, required: false, default: null + }, + disabled: { + type: Boolean, + required: false, + default: false } }, methods: { diff --git a/frontend/src/components/form/MultiselectWidget.vue b/frontend/src/components/form/MultiselectWidget.vue index 13f0d29..a9e8d00 100644 --- a/frontend/src/components/form/MultiselectWidget.vue +++ b/frontend/src/components/form/MultiselectWidget.vue @@ -10,6 +10,7 @@ :preserve-search="true" :placeholder="$t('select_option')" :track-by="'value'" + :disabled="disabled" > {{ getTranslation(option) }} @@ -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 { diff --git a/frontend/src/components/form/SelectDropdownWidget.vue b/frontend/src/components/form/SelectDropdownWidget.vue index f46c249..0cba402 100644 --- a/frontend/src/components/form/SelectDropdownWidget.vue +++ b/frontend/src/components/form/SelectDropdownWidget.vue @@ -1,7 +1,7 @@ |