Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 52s
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.
167 lines
5.8 KiB
JavaScript
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
|
|
}
|
|
})
|
|
|