Respect per-user visibility; only 'vorstand' overrides visibility; UI shows contactHidden per-member
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 47s

This commit is contained in:
Torsten Schulz (local)
2026-02-11 13:24:01 +01:00
parent ce5915a3bc
commit 141a15a6cb
3 changed files with 49 additions and 65 deletions

View File

@@ -115,7 +115,10 @@
</div>
</td>
<td class="px-4 py-3 whitespace-nowrap">
<template v-if="canViewContactData">
<template v-if="member.contactHidden">
<span class="text-sm text-gray-400">Kontaktdaten nur für Vorstand sichtbar</span>
</template>
<template v-else>
<a
v-if="member.email"
:href="`mailto:${member.email}`"
@@ -123,18 +126,14 @@
>
{{ member.email }}
</a>
<span
v-else
class="text-sm text-gray-400"
>-</span>
<span v-else class="text-sm text-gray-400">-</span>
</template>
<span
v-else
class="text-sm text-gray-400"
>Nur für Vorstand</span>
</td>
<td class="px-4 py-3 whitespace-nowrap">
<template v-if="canViewContactData">
<template v-if="member.contactHidden">
<span class="text-sm text-gray-400">Kontaktdaten nur für Vorstand sichtbar</span>
</template>
<template v-else>
<a
v-if="member.phone"
:href="`tel:${member.phone}`"
@@ -142,15 +141,8 @@
>
{{ member.phone }}
</a>
<span
v-else
class="text-sm text-gray-400"
>-</span>
<span v-else class="text-sm text-gray-400">-</span>
</template>
<span
v-else
class="text-sm text-gray-400"
>Nur für Vorstand</span>
</td>
<td class="px-4 py-3 whitespace-nowrap">
<button
@@ -296,44 +288,22 @@
</div>
<div class="grid sm:grid-cols-2 gap-3 text-gray-600">
<template v-if="canViewContactData">
<div
v-if="member.email"
class="flex items-center"
>
<Mail
:size="16"
class="mr-2 text-primary-600"
/>
<a
:href="`mailto:${member.email}`"
class="hover:text-primary-600"
>{{ member.email }}</a>
</div>
<div
v-if="member.phone"
class="flex items-center"
>
<Phone
:size="16"
class="mr-2 text-primary-600"
/>
<a
:href="`tel:${member.phone}`"
class="hover:text-primary-600"
>{{ member.phone }}</a>
</div>
</template>
<div
v-else
class="col-span-2 flex items-center text-gray-500 text-sm italic"
>
<Mail
:size="16"
class="mr-2"
/>
<template v-if="member.contactHidden">
<div class="col-span-2 flex items-center text-gray-500 text-sm italic">
<Mail :size="16" class="mr-2" />
Kontaktdaten nur für Vorstand sichtbar
</div>
</template>
<template v-else>
<div v-if="member.email" class="flex items-center">
<Mail :size="16" class="mr-2 text-primary-600" />
<a :href="`mailto:${member.email}`" class="hover:text-primary-600">{{ member.email }}</a>
</div>
<div v-if="member.phone" class="flex items-center">
<Phone :size="16" class="mr-2 text-primary-600" />
<a :href="`tel:${member.phone}`" class="hover:text-primary-600">{{ member.phone }}</a>
</div>
</template>
<div
v-if="member.address"
class="flex items-start col-span-2"

View File

@@ -152,6 +152,8 @@ export default defineEventHandler(async (event) => {
// Respektiere individuelle Sichtbarkeitspräferenzen (user.visibility)
const currentUserToken = token
const isViewerAuthenticated = !!currentUser
// Only 'vorstand' may override member visibility
const isPrivilegedViewer = currentUser ? hasRole(currentUser, 'vorstand') : false
const sanitizedMembers = mergedMembers.map(member => {
// Default: show email/phone/address to other logged-in members unless member.visibility explicitly hides them
@@ -161,6 +163,15 @@ export default defineEventHandler(async (event) => {
const showPhone = visibility.showPhone === undefined ? true : Boolean(visibility.showPhone)
const showAddress = visibility.showAddress === undefined ? false : Boolean(visibility.showAddress)
// Determine if contact info existed but was hidden to the viewer
const hadEmail = !!member.email
const hadPhone = !!member.phone
const hadAddress = !!member.address
const emailVisible = (isPrivilegedViewer || (isViewerAuthenticated && showEmail))
const phoneVisible = (isPrivilegedViewer || (isViewerAuthenticated && showPhone))
const addressVisible = (isPrivilegedViewer || (isViewerAuthenticated && showAddress))
const contactHidden = (!emailVisible && hadEmail) || (!phoneVisible && hadPhone) || (!addressVisible && hadAddress)
return {
id: member.id,
name: member.name,
@@ -172,10 +183,12 @@ export default defineEventHandler(async (event) => {
lastLogin: member.lastLogin,
isMannschaftsspieler: member.isMannschaftsspieler,
notes: member.notes || '',
// Only include contact fields when viewer is authenticated and the member allows it
email: (isViewerAuthenticated && showEmail) ? member.email : undefined,
phone: (isViewerAuthenticated && showPhone) ? member.phone : undefined,
address: (isViewerAuthenticated && showAddress) ? member.address : undefined
// Privileged viewers (vorstand) always see contact fields
email: emailVisible ? member.email : undefined,
phone: phoneVisible ? member.phone : undefined,
address: addressVisible ? member.address : undefined,
// Flag for UI: data existed but is hidden to the current viewer
contactHidden
}
})

View File

@@ -64,9 +64,10 @@ export default defineEventHandler(async (event) => {
const visibility = body.visibility || body.visibilityPreferences || null
if (visibility && typeof visibility === 'object') {
user.visibility = user.visibility || {}
if (typeof visibility.showEmail === 'boolean') user.visibility.showEmail = visibility.showEmail
if (typeof visibility.showPhone === 'boolean') user.visibility.showPhone = visibility.showPhone
if (typeof visibility.showAddress === 'boolean') user.visibility.showAddress = visibility.showAddress
// Coerce values to booleans to be robust against string values from clients
if (visibility.showEmail !== undefined) user.visibility.showEmail = Boolean(visibility.showEmail)
if (visibility.showPhone !== undefined) user.visibility.showPhone = Boolean(visibility.showPhone)
if (visibility.showAddress !== undefined) user.visibility.showAddress = Boolean(visibility.showAddress)
}
// Handle password change