v1.2.2: Fix Nominatim CSP, Tenant Admin kann eigene Symbole hochladen
This commit is contained in:
@@ -51,7 +51,7 @@ const nextConfig = {
|
|||||||
"style-src 'self' 'unsafe-inline'",
|
"style-src 'self' 'unsafe-inline'",
|
||||||
"img-src 'self' data: blob: https://*.tile.openstreetmap.org https://api.maptiler.com https://server.arcgisonline.com https://*.geo.admin.ch http://localhost:9000 http://minio:9000",
|
"img-src 'self' data: blob: https://*.tile.openstreetmap.org https://api.maptiler.com https://server.arcgisonline.com https://*.geo.admin.ch http://localhost:9000 http://minio:9000",
|
||||||
"font-src 'self' data:",
|
"font-src 'self' data:",
|
||||||
"connect-src 'self' ws: wss: https://api.maptiler.com https://*.tile.openstreetmap.org https://api.open-meteo.com https://server.arcgisonline.com https://*.geo.admin.ch",
|
"connect-src 'self' ws: wss: https://api.maptiler.com https://*.tile.openstreetmap.org https://nominatim.openstreetmap.org https://api.open-meteo.com https://server.arcgisonline.com https://*.geo.admin.ch",
|
||||||
"frame-ancestors 'self'",
|
"frame-ancestors 'self'",
|
||||||
"base-uri 'self'",
|
"base-uri 'self'",
|
||||||
"form-action 'self'",
|
"form-action 'self'",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lageplan",
|
"name": "lageplan",
|
||||||
"version": "1.2.1",
|
"version": "1.2.2",
|
||||||
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -552,7 +552,8 @@ export default function AdminPage() {
|
|||||||
setUploadFiles(null)
|
setUploadFiles(null)
|
||||||
setUploadCategory('')
|
setUploadCategory('')
|
||||||
setUploadIconName('')
|
setUploadIconName('')
|
||||||
fetchData()
|
if (user?.role === 'TENANT_ADMIN') fetchTenantSymbols()
|
||||||
|
else fetchData()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({ title: 'Upload-Fehler', description: error instanceof Error ? error.message : 'Fehler', variant: 'destructive' })
|
toast({ title: 'Upload-Fehler', description: error instanceof Error ? error.message : 'Fehler', variant: 'destructive' })
|
||||||
} finally { setIsUploading(false) }
|
} finally { setIsUploading(false) }
|
||||||
@@ -896,7 +897,7 @@ export default function AdminPage() {
|
|||||||
placeholder="Symbole suchen..."
|
placeholder="Symbole suchen..."
|
||||||
value={symbolSearch}
|
value={symbolSearch}
|
||||||
onChange={e => setSymbolSearch(e.target.value)}
|
onChange={e => setSymbolSearch(e.target.value)}
|
||||||
className="w-48"
|
className="w-full sm:w-64"
|
||||||
/>
|
/>
|
||||||
<Select value={symbolCatFilter} onValueChange={setSymbolCatFilter}>
|
<Select value={symbolCatFilter} onValueChange={setSymbolCatFilter}>
|
||||||
<SelectTrigger className="w-[180px]">
|
<SelectTrigger className="w-[180px]">
|
||||||
@@ -909,9 +910,13 @@ export default function AdminPage() {
|
|||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<span className="text-sm text-muted-foreground ml-auto">
|
<span className="text-sm text-muted-foreground mr-auto">
|
||||||
{tenantSymbols.filter(s => s.isActive).length} aktiv / {tenantSymbols.length} gesamt
|
{tenantSymbols.filter(s => s.isActive).length} aktiv / {tenantSymbols.length} gesamt
|
||||||
</span>
|
</span>
|
||||||
|
<Button onClick={() => setIsUploadDialogOpen(true)}>
|
||||||
|
<Upload className="w-4 h-4 mr-2" />
|
||||||
|
Eigene Symbole hochladen
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bulk category action */}
|
{/* Bulk category action */}
|
||||||
@@ -1050,7 +1055,7 @@ export default function AdminPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const MAX_SIZE = 5 * 1024 * 1024 // 5MB
|
|||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const user = await getSession()
|
const user = await getSession()
|
||||||
if (!user || !isAdmin(user.role)) {
|
if (!user || (user.role !== 'SERVER_ADMIN' && user.role !== 'TENANT_ADMIN')) {
|
||||||
return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 403 })
|
return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 403 })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,27 +55,25 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
// Generate safe filename
|
// Generate safe filename
|
||||||
const ext = file.name.split('.').pop()?.toLowerCase() || 'png'
|
const ext = file.name.split('.').pop()?.toLowerCase() || 'png'
|
||||||
|
const isTenantAdmin = user.role === 'TENANT_ADMIN'
|
||||||
|
const prefix = isTenantAdmin ? `tenant-${user.tenantId}/icons` : 'icons'
|
||||||
const safeFileName = `${uuidv4()}.${ext}`
|
const safeFileName = `${uuidv4()}.${ext}`
|
||||||
const fileKey = `icons/${safeFileName}`
|
const fileKey = `${prefix}/${safeFileName}`
|
||||||
|
|
||||||
// Upload to MinIO
|
// Upload to MinIO
|
||||||
const buffer = Buffer.from(await file.arrayBuffer())
|
const buffer = Buffer.from(await file.arrayBuffer())
|
||||||
await uploadFile(fileKey, buffer, file.type)
|
await uploadFile(fileKey, buffer, file.type)
|
||||||
|
|
||||||
// TENANT_ADMIN: icons get tenantId. SERVER_ADMIN: global icons (tenantId=null)
|
// Save to DB
|
||||||
const tenantId = user.role === 'SERVER_ADMIN' ? null : user.tenantId || null
|
|
||||||
|
|
||||||
// Create database entry
|
|
||||||
const icon = await (prisma as any).iconAsset.create({
|
const icon = await (prisma as any).iconAsset.create({
|
||||||
data: {
|
data: {
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
fileKey,
|
|
||||||
mimeType: file.type,
|
|
||||||
categoryId,
|
categoryId,
|
||||||
iconType: iconType as any,
|
iconType: iconType as any,
|
||||||
isSystem: false,
|
fileKey,
|
||||||
isActive: true,
|
mimeType: file.type,
|
||||||
tenantId,
|
isSystem: !isTenantAdmin, // true für Server Admin, false für Tenant Admin
|
||||||
|
tenantId: isTenantAdmin ? user.tenantId : null,
|
||||||
ownerId: user.id,
|
ownerId: user.id,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export async function GET() {
|
|||||||
icons: {
|
icons: {
|
||||||
where: user?.tenantId
|
where: user?.tenantId
|
||||||
? { isActive: true, OR: [{ tenantId: null }, { tenantId: user.tenantId }] }
|
? { isActive: true, OR: [{ tenantId: null }, { tenantId: user.tenantId }] }
|
||||||
: { isActive: true },
|
: { isActive: true, tenantId: null },
|
||||||
orderBy: { name: 'asc' },
|
orderBy: { name: 'asc' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user