permission missing fix when certs is empty #4

Merged
nick.adam merged 1 commits from fix/emptyCerts into development 2025-11-21 22:05:07 +00:00
9 changed files with 134 additions and 84 deletions
Showing only changes of commit 97163becfa - Show all commits

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -35,21 +35,31 @@ export const PermissionsProvider = ({ children }) => {
} }
const response = await authFetch('/api/user/permissions') const response = await authFetch('/api/user/permissions')
if (response.ok && isMountedRef.current) { if (response.ok && isMountedRef.current) {
const data = await response.json() try {
setPermissions({ const data = await response.json()
isAdmin: data.isAdmin || false, // Nur Permissions aktualisieren, wenn Daten erfolgreich geparst wurden
hasFullAccess: data.hasFullAccess || false, setPermissions({
accessibleSpaces: data.accessibleSpaces || [], isAdmin: data.isAdmin || false,
canCreateSpace: data.permissions?.canCreateSpace || false, hasFullAccess: data.hasFullAccess || false,
canDeleteSpace: data.permissions?.canDeleteSpace || false, accessibleSpaces: Array.isArray(data.accessibleSpaces) ? data.accessibleSpaces : [],
canCreateFqdn: data.permissions?.canCreateFqdn || {}, canCreateSpace: data.permissions?.canCreateSpace || false,
canDeleteFqdn: data.permissions?.canDeleteFqdn || {}, canDeleteSpace: data.permissions?.canDeleteSpace || false,
canUploadCSR: data.permissions?.canUploadCSR || {}, canCreateFqdn: data.permissions?.canCreateFqdn || {},
canSignCSR: data.permissions?.canSignCSR || {}, canDeleteFqdn: data.permissions?.canDeleteFqdn || {},
}) canUploadCSR: data.permissions?.canUploadCSR || {},
canSignCSR: data.permissions?.canSignCSR || {},
})
} catch (parseErr) {
console.error('Error parsing permissions response:', parseErr)
// Bei Parse-Fehler Permissions nicht zurücksetzen, nur loggen
}
} else if (response.status === 401 && isMountedRef.current) {
// Bei 401 Unauthorized werden Permissions zurückgesetzt (wird von AuthContext gehandelt)
console.log('Unauthorized - permissions will be cleared by auth context')
} }
} catch (err) { } catch (err) {
console.error('Error fetching permissions:', err) console.error('Error fetching permissions:', err)
// Bei Netzwerkfehlern etc. Permissions nicht zurücksetzen
} finally { } finally {
if (isInitial && isMountedRef.current) { if (isInitial && isMountedRef.current) {
setLoading(false) setLoading(false)

View File

@@ -1,3 +0,0 @@
// Re-export from PermissionsContext for backward compatibility
export { usePermissions } from '../contexts/PermissionsContext'

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
import { usePermissions } from '../hooks/usePermissions' import { usePermissions } from '../contexts/PermissionsContext'
const Permissions = () => { const Permissions = () => {
const { authFetch } = useAuth() const { authFetch } = useAuth()

View File

@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useParams, useNavigate } from 'react-router-dom' import { useParams, useNavigate } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
import { usePermissions } from '../hooks/usePermissions' import { usePermissions } from '../contexts/PermissionsContext'
const SpaceDetail = () => { const SpaceDetail = () => {
const { id } = useParams() const { id } = useParams()
@@ -423,40 +423,64 @@ const SpaceDetail = () => {
} }
const handleViewCertificates = async (fqdn) => { const handleViewCertificates = async (fqdn) => {
if (!fqdn || !fqdn.id) {
console.error('Invalid FQDN provided to handleViewCertificates')
return
}
setSelectedFqdn(fqdn) setSelectedFqdn(fqdn)
setLoadingCertificates(true) setLoadingCertificates(true)
setCertificates([]) setCertificates([])
setShowCertificatesModal(true) // Öffne Modal sofort, auch wenn noch geladen wird
try { try {
const response = await authFetch(`/api/spaces/${id}/fqdns/${fqdn.id}/certificates`) const response = await authFetch(`/api/spaces/${id}/fqdns/${fqdn.id}/certificates`)
if (response.ok) { if (response.ok) {
const certs = await response.json() try {
setCertificates(certs) const certs = await response.json()
// Stelle sicher, dass certs ein Array ist
setCertificates(Array.isArray(certs) ? certs : [])
} catch (parseErr) {
console.error('Error parsing certificates response:', parseErr)
setCertificates([])
}
} else { } else {
console.error('Fehler beim Laden der Zertifikate') // Bei Fehler-Response (404, 403, etc.) setze leeres Array
console.error('Fehler beim Laden der Zertifikate:', response.status, response.statusText)
setCertificates([])
} }
} catch (err) { } catch (err) {
console.error('Error fetching certificates:', err) console.error('Error fetching certificates:', err)
setCertificates([])
} finally { } finally {
setLoadingCertificates(false) setLoadingCertificates(false)
setShowCertificatesModal(true)
} }
} }
const handleRefreshCertificate = async (cert) => { const handleRefreshCertificate = async (cert) => {
if (!cert || !cert.id || !selectedFqdn || !selectedFqdn.id) {
console.error('Invalid certificate or FQDN for refresh')
return
}
setRefreshingCertificate(cert.id) setRefreshingCertificate(cert.id)
try { try {
const response = await authFetch(`/api/spaces/${id}/fqdns/${selectedFqdn.id}/certificates/${cert.id}/refresh`, { const response = await authFetch(`/api/spaces/${id}/fqdns/${selectedFqdn.id}/certificates/${cert.id}/refresh`, {
method: 'POST' method: 'POST'
}) })
if (response.ok) { if (response.ok) {
const result = await response.json() try {
// Aktualisiere Zertifikat in der Liste const result = await response.json()
setCertificates(prev => prev.map(c => // Aktualisiere Zertifikat in der Liste
c.id === cert.id setCertificates(prev => prev.map(c =>
? { ...c, certificatePEM: result.certificatePEM } c.id === cert.id
: c ? { ...c, certificatePEM: result.certificatePEM }
)) : c
))
} catch (parseErr) {
console.error('Error parsing refresh response:', parseErr)
}
} }
} catch (err) { } catch (err) {
console.error('Error refreshing certificate:', err) console.error('Error refreshing certificate:', err)
@@ -1478,10 +1502,13 @@ const SpaceDetail = () => {
<div className="bg-slate-800 rounded-lg border border-slate-600 max-w-4xl w-full max-h-[90vh] overflow-y-auto"> <div className="bg-slate-800 rounded-lg border border-slate-600 max-w-4xl w-full max-h-[90vh] overflow-y-auto">
<div className="p-6"> <div className="p-6">
<div className="flex justify-between items-center mb-6"> <div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-white">Zertifikate für {selectedFqdn.fqdn}</h2> <h2 className="text-2xl font-bold text-white">
Zertifikate für {selectedFqdn?.fqdn || 'Unbekannter FQDN'}
</h2>
<button <button
onClick={closeCertificatesModal} onClick={closeCertificatesModal}
className="text-slate-400 hover:text-white transition-colors" className="text-slate-400 hover:text-white transition-colors"
aria-label="Schließen"
> >
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@@ -1501,64 +1528,80 @@ const SpaceDetail = () => {
{certificates.length} {certificates.length === 1 ? 'Zertifikat' : 'Zertifikate'} gefunden {certificates.length} {certificates.length === 1 ? 'Zertifikat' : 'Zertifikate'} gefunden
</p> </p>
</div> </div>
{certificates.map((cert, index) => ( {certificates.map((cert, index) => {
<div key={cert.id} className="bg-slate-700/30 rounded-lg p-4 border border-slate-600"> // Sicherstellen, dass cert ein gültiges Objekt ist
<div className="flex justify-between items-start mb-3"> if (!cert || !cert.id) {
<div className="flex-1"> return null
<div className="flex items-center gap-2 mb-2"> }
<span className="text-xs font-semibold text-purple-400 bg-purple-500/20 px-2 py-1 rounded">
#{certificates.length - index} const certId = cert.id || 'unknown'
</span> const certCertificateId = cert.certificateId || 'N/A'
<h4 className="text-white font-semibold">CA-Zertifikat-ID: {cert.certificateId}</h4> const certCreatedAt = cert.createdAt ? new Date(cert.createdAt) : null
</div> const certStatus = cert.status || 'unknown'
<div className="space-y-1"> const certProviderId = cert.providerId
<p className="text-slate-400 text-xs"> const certPEM = cert.certificatePEM
<span className="font-semibold text-slate-300">Interne UID:</span>{' '}
<span className="font-mono text-xs">{cert.id}</span> return (
</p> <div key={certId} className="bg-slate-700/30 rounded-lg p-4 border border-slate-600">
<p className="text-slate-400 text-sm"> <div className="flex justify-between items-start mb-3">
<span className="font-semibold text-slate-300">Erstellt:</span>{' '} <div className="flex-1">
{new Date(cert.createdAt).toLocaleString('de-DE')} <div className="flex items-center gap-2 mb-2">
</p> <span className="text-xs font-semibold text-purple-400 bg-purple-500/20 px-2 py-1 rounded">
<p className="text-slate-400 text-sm"> #{certificates.length - index}
<span className="font-semibold text-slate-300">Status:</span>{' '}
<span className={`inline-block px-2 py-0.5 rounded text-xs ${
cert.status === 'issued'
? 'bg-green-500/20 text-green-400'
: cert.status === 'pending'
? 'bg-yellow-500/20 text-yellow-400'
: 'bg-red-500/20 text-red-400'
}`}>
{cert.status}
</span> </span>
</p> <h4 className="text-white font-semibold">CA-Zertifikat-ID: {certCertificateId}</h4>
{cert.providerId && ( </div>
<p className="text-slate-400 text-sm"> <div className="space-y-1">
<span className="font-semibold text-slate-300">Provider:</span>{' '} <p className="text-slate-400 text-xs">
{cert.providerId} <span className="font-semibold text-slate-300">Interne UID:</span>{' '}
<span className="font-mono text-xs">{certId}</span>
</p> </p>
)} {certCreatedAt && !isNaN(certCreatedAt.getTime()) && (
<p className="text-slate-400 text-sm">
<span className="font-semibold text-slate-300">Erstellt:</span>{' '}
{certCreatedAt.toLocaleString('de-DE')}
</p>
)}
<p className="text-slate-400 text-sm">
<span className="font-semibold text-slate-300">Status:</span>{' '}
<span className={`inline-block px-2 py-0.5 rounded text-xs ${
certStatus === 'issued'
? 'bg-green-500/20 text-green-400'
: certStatus === 'pending'
? 'bg-yellow-500/20 text-yellow-400'
: 'bg-red-500/20 text-red-400'
}`}>
{certStatus}
</span>
</p>
{certProviderId && (
<p className="text-slate-400 text-sm">
<span className="font-semibold text-slate-300">Provider:</span>{' '}
{certProviderId}
</p>
)}
</div>
</div> </div>
<button
onClick={() => handleRefreshCertificate(cert)}
disabled={refreshingCertificate === certId || !selectedFqdn}
className="ml-4 px-3 py-1 bg-blue-600 hover:bg-blue-700 disabled:bg-slate-600 disabled:cursor-not-allowed text-white text-sm rounded-lg transition-colors"
title="Zertifikat von CA abrufen"
>
{refreshingCertificate === certId ? 'Aktualisiere...' : 'Aktualisieren'}
</button>
</div> </div>
<button {certPEM && (
onClick={() => handleRefreshCertificate(cert)} <div className="mt-3">
disabled={refreshingCertificate === cert.id} <h5 className="text-sm font-semibold text-slate-300 mb-2">Zertifikat (PEM):</h5>
className="ml-4 px-3 py-1 bg-blue-600 hover:bg-blue-700 disabled:bg-slate-600 disabled:cursor-not-allowed text-white text-sm rounded-lg transition-colors" <pre className="text-xs text-slate-200 bg-slate-900/50 p-3 rounded overflow-auto max-h-60 font-mono">
title="Zertifikat von CA abrufen" {certPEM}
> </pre>
{refreshingCertificate === cert.id ? 'Aktualisiere...' : 'Aktualisieren'} </div>
</button> )}
</div> </div>
{cert.certificatePEM && ( )
<div className="mt-3"> })}
<h5 className="text-sm font-semibold text-slate-300 mb-2">Zertifikat (PEM):</h5>
<pre className="text-xs text-slate-200 bg-slate-900/50 p-3 rounded overflow-auto max-h-60 font-mono">
{cert.certificatePEM}
</pre>
</div>
)}
</div>
))}
</div> </div>
)} )}
</div> </div>

View File

@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
import { usePermissions } from '../hooks/usePermissions' import { usePermissions } from '../contexts/PermissionsContext'
const Spaces = () => { const Spaces = () => {
const navigate = useNavigate() const navigate = useNavigate()

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
import { usePermissions } from '../hooks/usePermissions' import { usePermissions } from '../contexts/PermissionsContext'
const Users = () => { const Users = () => {
const { authFetch } = useAuth() const { authFetch } = useAuth()