fixed permission implementation for ressources

This commit is contained in:
2025-11-21 00:58:34 +01:00
parent 0d17fda341
commit d23bfa0376
6 changed files with 857 additions and 54 deletions

View File

@@ -0,0 +1,86 @@
import { useState, useEffect, useCallback } from 'react'
import { useAuth } from '../contexts/AuthContext'
export const usePermissions = () => {
const { authFetch, isAuthenticated } = useAuth()
const [permissions, setPermissions] = useState({
hasFullAccess: false,
accessibleSpaces: [],
canCreateSpace: false,
canDeleteSpace: false,
canCreateFqdn: {},
canDeleteFqdn: {},
canUploadCSR: {},
canSignCSR: {},
})
const [loading, setLoading] = useState(true)
const fetchPermissions = useCallback(async () => {
if (!isAuthenticated) {
setLoading(false)
return
}
try {
setLoading(true)
const response = await authFetch('/api/user/permissions')
if (response.ok) {
const data = await response.json()
setPermissions({
hasFullAccess: data.hasFullAccess || false,
accessibleSpaces: data.accessibleSpaces || [],
canCreateSpace: data.permissions?.canCreateSpace || false,
canDeleteSpace: data.permissions?.canDeleteSpace || false,
canCreateFqdn: data.permissions?.canCreateFqdn || {},
canDeleteFqdn: data.permissions?.canDeleteFqdn || {},
canUploadCSR: data.permissions?.canUploadCSR || {},
canSignCSR: data.permissions?.canSignCSR || {},
})
}
} catch (err) {
console.error('Error fetching permissions:', err)
} finally {
setLoading(false)
}
}, [isAuthenticated, authFetch])
useEffect(() => {
if (isAuthenticated) {
fetchPermissions()
} else {
setPermissions({
hasFullAccess: false,
accessibleSpaces: [],
canCreateSpace: false,
canDeleteSpace: false,
canCreateFqdn: {},
canDeleteFqdn: {},
canUploadCSR: {},
canSignCSR: {},
})
setLoading(false)
}
}, [isAuthenticated, fetchPermissions])
const canCreateSpace = () => permissions.canCreateSpace
const canDeleteSpace = (spaceId) => permissions.canDeleteSpace
const canCreateFqdn = (spaceId) => permissions.canCreateFqdn[spaceId] === true
const canDeleteFqdn = (spaceId) => permissions.canDeleteFqdn[spaceId] === true
const canUploadCSR = (spaceId) => permissions.canUploadCSR[spaceId] === true
const canSignCSR = (spaceId) => permissions.canSignCSR[spaceId] === true
const hasAccessToSpace = (spaceId) => permissions.accessibleSpaces.includes(spaceId)
return {
permissions,
loading,
refreshPermissions: fetchPermissions,
canCreateSpace,
canDeleteSpace,
canCreateFqdn,
canDeleteFqdn,
canUploadCSR,
canSignCSR,
hasAccessToSpace,
}
}

View File

@@ -1,11 +1,13 @@
import { useState, useEffect } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext'
import { usePermissions } from '../hooks/usePermissions'
const SpaceDetail = () => {
const { id } = useParams()
const navigate = useNavigate()
const { authFetch } = useAuth()
const { canCreateFqdn, canDeleteFqdn, canSignCSR, canUploadCSR, refreshPermissions } = usePermissions()
const [space, setSpace] = useState(null)
const [fqdns, setFqdns] = useState([])
const [showForm, setShowForm] = useState(false)
@@ -123,6 +125,8 @@ const SpaceDetail = () => {
setFqdns([...fqdns, newFqdn])
setFormData({ fqdn: '', description: '' })
setShowForm(false)
// Aktualisiere Berechtigungen nach dem Erstellen eines FQDNs
refreshPermissions()
} else {
let errorMessage = 'Fehler beim Erstellen des FQDN'
try {
@@ -176,6 +180,8 @@ const SpaceDetail = () => {
setShowDeleteModal(false)
setFqdnToDelete(null)
setConfirmChecked(false)
// Aktualisiere Berechtigungen nach dem Löschen eines FQDNs
refreshPermissions()
} else {
const errorData = await response.json().catch(() => ({ error: 'Fehler beim Löschen' }))
alert(errorData.error || 'Fehler beim Löschen des FQDN')
@@ -586,7 +592,13 @@ const SpaceDetail = () => {
</h3>
<button
onClick={() => setShowForm(!showForm)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg transition-all duration-200"
disabled={!canCreateFqdn(id) && !showForm}
className={`px-4 py-2 font-semibold rounded-lg transition-all duration-200 ${
canCreateFqdn(id) || showForm
? 'bg-blue-600 hover:bg-blue-700 text-white'
: 'bg-slate-600 text-slate-400 cursor-not-allowed opacity-50'
}`}
title={!canCreateFqdn(id) && !showForm ? 'Keine Berechtigung zum Erstellen von FQDNs' : ''}
>
{showForm ? 'Abbrechen' : '+ Neuer FQDN'}
</button>
@@ -630,11 +642,18 @@ const SpaceDetail = () => {
<button
onClick={(e) => {
e.stopPropagation()
handleRequestSigning(fqdn)
if (canSignCSR(id)) {
handleRequestSigning(fqdn)
}
}}
className="p-2 text-purple-400 hover:text-purple-300 hover:bg-purple-500/20 rounded-lg transition-colors"
title="CSR signieren lassen"
aria-label="CSR signieren lassen"
disabled={!canSignCSR(id)}
className={`p-2 rounded-lg transition-colors ${
canSignCSR(id)
? 'text-purple-400 hover:text-purple-300 hover:bg-purple-500/20'
: 'text-slate-500 cursor-not-allowed opacity-50'
}`}
title={canSignCSR(id) ? 'CSR signieren lassen' : 'Keine Berechtigung zum Signieren von CSRs'}
aria-label={canSignCSR(id) ? 'CSR signieren lassen' : 'Keine Berechtigung'}
>
<svg
className="w-5 h-5"
@@ -692,16 +711,23 @@ const SpaceDetail = () => {
}
e.target.value = ''
}}
disabled={uploadingCSR}
disabled={uploadingCSR || !canUploadCSR(id)}
/>
<button
className="p-2 text-blue-400 hover:text-blue-300 hover:bg-blue-500/20 rounded-lg transition-colors"
title="CSR hochladen"
aria-label="CSR hochladen"
disabled={!canUploadCSR(id)}
className={`p-2 rounded-lg transition-colors ${
canUploadCSR(id)
? 'text-blue-400 hover:text-blue-300 hover:bg-blue-500/20'
: 'text-slate-500 cursor-not-allowed opacity-50'
}`}
title={canUploadCSR(id) ? 'CSR hochladen' : 'Keine Berechtigung zum Hochladen von CSRs'}
aria-label={canUploadCSR(id) ? 'CSR hochladen' : 'Keine Berechtigung'}
onClick={(e) => {
e.stopPropagation()
e.preventDefault()
e.currentTarget.parentElement.querySelector('input[type="file"]')?.click()
if (canUploadCSR(id)) {
e.currentTarget.parentElement.querySelector('input[type="file"]')?.click()
}
}}
>
<svg
@@ -806,11 +832,18 @@ const SpaceDetail = () => {
<button
onClick={(e) => {
e.stopPropagation()
handleDelete(fqdn)
if (canDeleteFqdn(id)) {
handleDelete(fqdn)
}
}}
className="p-2 text-red-400 hover:text-red-300 hover:bg-red-500/20 rounded-lg transition-colors"
title="FQDN löschen"
aria-label="FQDN löschen"
disabled={!canDeleteFqdn(id)}
className={`p-2 rounded-lg transition-colors ${
canDeleteFqdn(id)
? 'text-red-400 hover:text-red-300 hover:bg-red-500/20'
: 'text-slate-500 cursor-not-allowed opacity-50'
}`}
title={canDeleteFqdn(id) ? 'FQDN löschen' : 'Keine Berechtigung zum Löschen von FQDNs'}
aria-label={canDeleteFqdn(id) ? 'FQDN löschen' : 'Keine Berechtigung'}
>
<svg
className="w-5 h-5"

View File

@@ -1,10 +1,12 @@
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext'
import { usePermissions } from '../hooks/usePermissions'
const Spaces = () => {
const navigate = useNavigate()
const { authFetch } = useAuth()
const { canCreateSpace, canDeleteSpace, refreshPermissions } = usePermissions()
const [spaces, setSpaces] = useState([])
const [showForm, setShowForm] = useState(false)
const [formData, setFormData] = useState({
@@ -72,6 +74,8 @@ const Spaces = () => {
setSpaces([...spaces, newSpace])
setFormData({ name: '', description: '' })
setShowForm(false)
// Aktualisiere Berechtigungen nach dem Erstellen eines Spaces
refreshPermissions()
} else {
const errorData = await response.json()
setError(errorData.error || 'Fehler beim Erstellen des Space')
@@ -140,6 +144,8 @@ const Spaces = () => {
setConfirmChecked(false)
setDeleteFqdnsChecked(false)
setFqdnCount(0)
// Aktualisiere Berechtigungen nach dem Löschen eines Spaces
refreshPermissions()
} else {
const errorText = await response.text()
let errorMessage = 'Fehler beim Löschen des Space'
@@ -188,7 +194,13 @@ const Spaces = () => {
</div>
<button
onClick={() => setShowForm(!showForm)}
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg shadow-lg hover:shadow-xl transition-all duration-200"
disabled={!canCreateSpace() && !showForm}
className={`px-6 py-3 font-semibold rounded-lg shadow-lg transition-all duration-200 ${
canCreateSpace() || showForm
? 'bg-blue-600 hover:bg-blue-700 text-white hover:shadow-xl'
: 'bg-slate-600 text-slate-400 cursor-not-allowed opacity-50'
}`}
title={!canCreateSpace() && !showForm ? 'Keine Berechtigung zum Erstellen von Spaces' : ''}
>
{showForm ? 'Abbrechen' : '+ Neuer Space'}
</button>
@@ -340,11 +352,18 @@ const Spaces = () => {
<button
onClick={(e) => {
e.stopPropagation()
handleDelete(space)
if (canDeleteSpace(space.id)) {
handleDelete(space)
}
}}
className="p-2 text-red-400 hover:text-red-300 hover:bg-red-500/20 rounded-lg transition-colors"
title="Space löschen"
aria-label="Space löschen"
disabled={!canDeleteSpace(space.id)}
className={`p-2 rounded-lg transition-colors ${
canDeleteSpace(space.id)
? 'text-red-400 hover:text-red-300 hover:bg-red-500/20'
: 'text-slate-500 cursor-not-allowed opacity-50'
}`}
title={canDeleteSpace(space.id) ? 'Space löschen' : 'Keine Berechtigung zum Löschen von Spaces'}
aria-label={canDeleteSpace(space.id) ? 'Space löschen' : 'Keine Berechtigung'}
>
<svg
className="w-5 h-5"