Files
harheimertc/server/api/members.get.js
Torsten Schulz (local) 57b32debeb Enhance user contact data visibility based on role permissions
This commit introduces role-based access control for user contact information in the CMS. It updates the user list display to show email and phone details only to users with the 'vorstand' role, while masking this information for others. Additionally, it modifies the API endpoints to ensure that contact data is only returned for authorized users, improving data privacy and security.
2026-02-06 10:12:37 +01:00

167 lines
5.8 KiB
JavaScript

import { verifyToken, getUserFromToken, hasRole } from '../utils/auth.js'
import { readMembers } from '../utils/members.js'
import { readUsers, migrateUserRoles } from '../utils/auth.js'
export default defineEventHandler(async (event) => {
try {
const token = getCookie(event, 'auth_token')
if (!token) {
throw createError({
statusCode: 401,
message: 'Nicht authentifiziert.'
})
}
const decoded = verifyToken(token)
if (!decoded) {
throw createError({
statusCode: 401,
message: 'Ungültiges Token.'
})
}
const currentUser = await getUserFromToken(token)
// Get manual members and registered users
const manualMembers = await readMembers()
const registeredUsers = await readUsers()
// Merge members: combine manual + registered, detect duplicates
const mergedMembers = []
// Create lookup maps for O(1) matching instead of O(n) findIndex
const emailToIndexMap = new Map() // email -> index in mergedMembers
const nameToIndexMap = new Map() // name -> index in mergedMembers
// First, add all manual members and build lookup maps
for (let i = 0; i < manualMembers.length; i++) {
const member = manualMembers[i]
const normalizedEmail = member.email?.toLowerCase().trim() || ''
const fullName = `${member.firstName || ''} ${member.lastName || ''}`.trim()
const normalizedName = fullName.toLowerCase()
const memberIndex = mergedMembers.length
mergedMembers.push({
...member,
name: fullName, // Computed for display
source: 'manual',
editable: true,
hasLogin: false
})
// Build lookup maps (only for manual members)
if (normalizedEmail) {
// Only add if not already present (prefer first occurrence)
if (!emailToIndexMap.has(normalizedEmail)) {
emailToIndexMap.set(normalizedEmail, memberIndex)
}
}
if (normalizedName) {
// Only add if not already present (prefer first occurrence)
if (!nameToIndexMap.has(normalizedName)) {
nameToIndexMap.set(normalizedName, memberIndex)
}
}
}
// Then add registered users (only active ones)
for (const user of registeredUsers) {
if (!user.active) continue
const normalizedEmail = user.email?.toLowerCase().trim() || ''
const normalizedName = user.name?.toLowerCase().trim() || ''
// Check if this user matches an existing manual member using O(1) lookup
let matchedManualIndex = -1
// Try to match by email first (O(1) lookup)
if (normalizedEmail && emailToIndexMap.has(normalizedEmail)) {
matchedManualIndex = emailToIndexMap.get(normalizedEmail)
// Verify it's still a manual member (safety check)
if (mergedMembers[matchedManualIndex]?.source !== 'manual') {
matchedManualIndex = -1
}
}
// If no email match, try name (O(1) lookup)
if (matchedManualIndex === -1 && normalizedName && nameToIndexMap.has(normalizedName)) {
matchedManualIndex = nameToIndexMap.get(normalizedName)
// Verify it's still a manual member and email doesn't conflict (safety check)
const candidate = mergedMembers[matchedManualIndex]
if (candidate?.source === 'manual') {
// Additional safety: if candidate has email, make sure it doesn't conflict
const candidateEmail = candidate.email?.toLowerCase().trim() || ''
if (!candidateEmail || candidateEmail === normalizedEmail) {
// Safe to match by name
} else {
// Email mismatch - don't match by name alone
matchedManualIndex = -1
}
} else {
matchedManualIndex = -1
}
}
if (matchedManualIndex !== -1) {
// Merge with existing manual member
const migratedUser = migrateUserRoles({ ...user })
const roles = Array.isArray(migratedUser.roles) ? migratedUser.roles : (migratedUser.role ? [migratedUser.role] : ['mitglied'])
mergedMembers[matchedManualIndex] = {
...mergedMembers[matchedManualIndex],
hasLogin: true,
loginEmail: user.email,
loginRoles: roles,
loginRole: roles[0] || 'mitglied', // Rückwärtskompatibilität
lastLogin: user.lastLogin,
isMannschaftsspieler: user.isMannschaftsspieler === true || mergedMembers[matchedManualIndex].isMannschaftsspieler === true
}
} else {
// Add as new member (from login system)
const migratedUser = migrateUserRoles({ ...user })
const roles = Array.isArray(migratedUser.roles) ? migratedUser.roles : (migratedUser.role ? [migratedUser.role] : ['mitglied'])
mergedMembers.push({
id: user.id,
name: user.name,
email: user.email,
phone: user.phone || '',
address: '',
notes: `Rolle(n): ${roles.join(', ')}`,
source: 'login',
editable: false,
hasLogin: true,
loginEmail: user.email,
loginRoles: roles,
loginRole: roles[0] || 'mitglied', // Rückwärtskompatibilität
lastLogin: user.lastLogin,
isMannschaftsspieler: user.isMannschaftsspieler === true
})
}
}
// Sort by name
mergedMembers.sort((a, b) => a.name.localeCompare(b.name))
// Serverseitiger Datenschutz: Kontaktdaten nur für Vorstand
const isVorstand = hasRole(currentUser, 'vorstand')
const safeMembers = isVorstand
? mergedMembers
: mergedMembers.map(m => ({
...m,
email: undefined,
phone: undefined,
address: undefined
}))
return {
success: true,
members: safeMembers
}
} catch (error) {
console.error('Fehler beim Abrufen der Mitgliederliste:', error)
throw error
}
})