Initial commit from template

This commit is contained in:
Lumina
2025-12-23 04:19:57 +01:00
commit b3d8fe8dfe
76 changed files with 10491 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
---
name: dependency-scanner
description: Scannt npm Dependencies auf bekannte Sicherheitsluecken und veraltete Pakete. Nutze diesen Skill nach npm install, bei neuen Dependencies, oder wenn der User nach "vulnerabilities", "outdated", "npm audit", "dependency check", "pakete pruefen", "sicherheitsluecken" fragt. Gibt Ergebnisse zurueck die im Chat angezeigt werden.
---
# Dependency Security Scanner
Scannt npm/pnpm Dependencies auf Sicherheitsluecken und veraltete Pakete.
## Automatische Ausfuehrung
Fuehre diesen Scan aus:
1. Nach `npm install` oder `pnpm install`
2. Bei neuen Dependencies in package.json
3. Regelmaessig als Security Check
## Scripts
### Vollstaendiger Scan
```bash
bash scan-deps.sh
```
### Nur Audit (Vulnerabilities)
```bash
bash audit.sh
```
### Veraltete Pakete pruefen
```bash
bash check-outdated.sh
```
### Automatisch fixen
```bash
bash fix-vulnerabilities.sh
```
## Scan Ablauf
### 1. npm audit durchfuehren
```bash
# Mit pnpm
pnpm audit --json
# Mit npm
npm audit --json
```
### 2. Veraltete Pakete pruefen
```bash
# Mit pnpm
pnpm outdated --json
# Mit npm
npm outdated --json
```
### 3. License Check (optional)
```bash
npx license-checker --summary
```
## Report Format fuer Chat
Gib das Ergebnis in diesem Format zurueck:
```markdown
## Dependency Security Report
**Scan Datum**: [timestamp]
**Pakete geprueft**: [count]
### Vulnerabilities
| Severity | Count |
|----------|-------|
| Critical | 0 |
| High | 2 |
| Moderate | 5 |
| Low | 3 |
### Kritische Issues
1. **lodash** (4.17.15) - Prototype Pollution
- Fix: `pnpm update lodash`
2. **axios** (0.21.0) - SSRF Vulnerability
- Fix: `pnpm update axios`
### Veraltete Pakete
| Paket | Aktuell | Neueste | Typ |
|-------|---------|---------|-----|
| react | 18.2.0 | 19.0.0 | major |
| next | 14.0.0 | 15.1.0 | major |
### Empfehlungen
1. **Sofort**: Kritische Vulnerabilities fixen
2. **Diese Woche**: High Severity fixen
3. **Geplant**: Major Updates evaluieren
### Automatischer Fix
Fuehre aus: `pnpm audit fix` oder `bash fix-vulnerabilities.sh`
```
## Wichtig
- Zeige Ergebnisse IMMER im Chat an
- Bei kritischen Vulnerabilities: Warnung hervorheben
- Schlage konkrete Fix-Befehle vor
- Bei Major Updates: Changelog verlinken

View File

@@ -0,0 +1,16 @@
#!/bin/bash
# Schneller Vulnerability Audit
set -e
# Package Manager erkennen
if [ -f "pnpm-lock.yaml" ]; then
echo "Fuehre pnpm audit aus..."
pnpm audit
elif [ -f "yarn.lock" ]; then
echo "Fuehre yarn audit aus..."
yarn audit
else
echo "Fuehre npm audit aus..."
npm audit
fi

View File

@@ -0,0 +1,22 @@
#!/bin/bash
# Prueft auf veraltete Pakete
set -e
echo "Pruefe veraltete Pakete..."
echo ""
# Package Manager erkennen
if [ -f "pnpm-lock.yaml" ]; then
pnpm outdated || true
elif [ -f "yarn.lock" ]; then
yarn outdated || true
else
npm outdated || true
fi
echo ""
echo "Zum Updaten:"
echo " Alle: pnpm update"
echo " Einzeln: pnpm update <paket>"
echo " Major: pnpm update <paket>@latest"

View File

@@ -0,0 +1,60 @@
#!/bin/bash
# Automatischer Fix fuer Vulnerabilities
set -e
# Farben
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "Dependency Vulnerability Fix"
echo "============================"
echo ""
# Package Manager erkennen
if [ -f "pnpm-lock.yaml" ]; then
PKG_MANAGER="pnpm"
elif [ -f "yarn.lock" ]; then
PKG_MANAGER="yarn"
else
PKG_MANAGER="npm"
fi
# Vorher: Audit Status
echo -e "${YELLOW}Status vor Fix:${NC}"
$PKG_MANAGER audit 2>/dev/null | tail -5 || true
echo ""
# Fix durchfuehren
echo -e "${YELLOW}Fuehre automatischen Fix durch...${NC}"
echo ""
case "$PKG_MANAGER" in
"pnpm")
# pnpm hat kein direktes audit fix
echo "pnpm: Update betroffene Pakete..."
pnpm update
;;
"yarn")
yarn audit fix || yarn upgrade
;;
"npm")
npm audit fix
;;
esac
echo ""
# Nachher: Audit Status
echo -e "${YELLOW}Status nach Fix:${NC}"
$PKG_MANAGER audit 2>/dev/null | tail -5 || true
echo ""
echo -e "${GREEN}Fix abgeschlossen!${NC}"
echo ""
echo "Naechste Schritte:"
echo " 1. Teste die Anwendung: pnpm dev"
echo " 2. Fuehre Tests aus: pnpm test"
echo " 3. Bei Problemen: git checkout package.json pnpm-lock.yaml"

View File

@@ -0,0 +1,160 @@
#!/bin/bash
# Vollstaendiger Dependency Security Scan
set -e
# Farben
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}================================${NC}"
echo -e "${BLUE}Dependency Security Scanner${NC}"
echo -e "${BLUE}================================${NC}"
echo ""
# Package Manager erkennen
if [ -f "pnpm-lock.yaml" ]; then
PKG_MANAGER="pnpm"
elif [ -f "yarn.lock" ]; then
PKG_MANAGER="yarn"
elif [ -f "package-lock.json" ]; then
PKG_MANAGER="npm"
else
PKG_MANAGER="npm"
fi
echo "Package Manager: $PKG_MANAGER"
echo ""
# Temporaere Dateien
AUDIT_FILE=$(mktemp)
OUTDATED_FILE=$(mktemp)
# ================================
# 1. VULNERABILITY AUDIT
# ================================
echo -e "${YELLOW}[1/3] Pruefe Sicherheitsluecken...${NC}"
case "$PKG_MANAGER" in
"pnpm")
pnpm audit --json > "$AUDIT_FILE" 2>/dev/null || true
;;
"yarn")
yarn audit --json > "$AUDIT_FILE" 2>/dev/null || true
;;
"npm")
npm audit --json > "$AUDIT_FILE" 2>/dev/null || true
;;
esac
# Audit Ergebnisse parsen
if [ -s "$AUDIT_FILE" ]; then
CRITICAL=$(jq '.metadata.vulnerabilities.critical // 0' "$AUDIT_FILE" 2>/dev/null || echo "0")
HIGH=$(jq '.metadata.vulnerabilities.high // 0' "$AUDIT_FILE" 2>/dev/null || echo "0")
MODERATE=$(jq '.metadata.vulnerabilities.moderate // 0' "$AUDIT_FILE" 2>/dev/null || echo "0")
LOW=$(jq '.metadata.vulnerabilities.low // 0' "$AUDIT_FILE" 2>/dev/null || echo "0")
TOTAL=$((CRITICAL + HIGH + MODERATE + LOW))
else
CRITICAL=0
HIGH=0
MODERATE=0
LOW=0
TOTAL=0
fi
echo ""
echo "Vulnerabilities gefunden:"
echo -e " ${RED}Critical: $CRITICAL${NC}"
echo -e " ${RED}High: $HIGH${NC}"
echo -e " ${YELLOW}Moderate: $MODERATE${NC}"
echo -e " Low: $LOW"
echo ""
# ================================
# 2. OUTDATED CHECK
# ================================
echo -e "${YELLOW}[2/3] Pruefe veraltete Pakete...${NC}"
case "$PKG_MANAGER" in
"pnpm")
pnpm outdated --json > "$OUTDATED_FILE" 2>/dev/null || true
;;
"npm")
npm outdated --json > "$OUTDATED_FILE" 2>/dev/null || true
;;
"yarn")
yarn outdated --json > "$OUTDATED_FILE" 2>/dev/null || true
;;
esac
if [ -s "$OUTDATED_FILE" ]; then
OUTDATED_COUNT=$(jq 'length' "$OUTDATED_FILE" 2>/dev/null || echo "0")
echo "Veraltete Pakete: $OUTDATED_COUNT"
if [ "$OUTDATED_COUNT" -gt 0 ]; then
echo ""
echo "Top 10 veraltete Pakete:"
jq -r 'to_entries | .[:10][] | " \(.key): \(.value.current // "?") -> \(.value.latest // "?")"' "$OUTDATED_FILE" 2>/dev/null || true
fi
else
OUTDATED_COUNT=0
echo "Keine veralteten Pakete gefunden."
fi
echo ""
# ================================
# 3. SCORE BERECHNEN
# ================================
echo -e "${YELLOW}[3/3] Berechne Security Score...${NC}"
SCORE=$((100 - (CRITICAL * 25) - (HIGH * 10) - (MODERATE * 3) - (LOW * 1)))
if [ $SCORE -lt 0 ]; then SCORE=0; fi
echo ""
echo -e "${BLUE}================================${NC}"
echo -e "${BLUE}ERGEBNIS${NC}"
echo -e "${BLUE}================================${NC}"
echo ""
if [ $SCORE -ge 90 ]; then
echo -e "Security Score: ${GREEN}$SCORE/100${NC}"
elif [ $SCORE -ge 70 ]; then
echo -e "Security Score: ${YELLOW}$SCORE/100${NC}"
else
echo -e "Security Score: ${RED}$SCORE/100${NC}"
fi
echo ""
echo "Zusammenfassung:"
echo " Vulnerabilities: $TOTAL"
echo " Veraltete Pakete: $OUTDATED_COUNT"
# ================================
# EMPFEHLUNGEN
# ================================
echo ""
echo -e "${BLUE}Empfehlungen:${NC}"
if [ "$CRITICAL" -gt 0 ] || [ "$HIGH" -gt 0 ]; then
echo -e " ${RED}DRINGEND: Fuehre '$PKG_MANAGER audit fix' aus${NC}"
fi
if [ "$OUTDATED_COUNT" -gt 10 ]; then
echo -e " ${YELLOW}Updates verfuegbar: '$PKG_MANAGER update'${NC}"
fi
if [ "$TOTAL" -eq 0 ] && [ "$OUTDATED_COUNT" -lt 5 ]; then
echo -e " ${GREEN}Alles in Ordnung! Dependencies sind sicher.${NC}"
fi
# Cleanup
rm -f "$AUDIT_FILE" "$OUTDATED_FILE"
# Exit mit Fehler bei kritischen Issues
if [ "$CRITICAL" -gt 0 ]; then
exit 1
fi

View File

@@ -0,0 +1,55 @@
---
name: postgres-connect
description: Verbindet mit der PostgreSQL Datenbank. Erkennt das Betriebssystem, installiert psql CLI falls noetig, liest DATABASE_URL aus .env und stellt Verbindung her. Nutze diesen Skill wenn der User "Datenbank verbinden", "DB Verbindung", "psql", "PostgreSQL connect" oder aehnliches erwaehnt.
---
# PostgreSQL Datenbank Verbindung
Dieser Skill verbindet automatisch mit der PostgreSQL Datenbank des Projekts.
## Automatischer Ablauf
1. **OS erkennen** mit dem Script `detect-os.sh`
2. **psql pruefen** - ist PostgreSQL CLI installiert?
3. **Falls nicht installiert** - Installation je nach OS
4. **DATABASE_URL laden** aus `.env.local` oder `.env`
5. **Verbindung herstellen** mit psql
## Scripts verwenden
### OS-Erkennung
```bash
bash detect-os.sh
```
### psql Installation
```bash
bash install-psql.sh
```
### Verbindung herstellen
```bash
bash connect.sh
```
## Manuelle Befehle nach Verbindung
```sql
-- Alle Tabellen anzeigen
\dt
-- Tabellen-Schema
\d table_name
-- Supabase Auth Users
SELECT id, email, created_at FROM auth.users LIMIT 10;
-- RLS Policies pruefen
SELECT * FROM pg_policies WHERE schemaname = 'public';
```
## Troubleshooting
- **Connection refused**: Supabase Projekt evtl. pausiert
- **SSL required**: `?sslmode=require` an URL anhaengen
- **Auth failed**: Passwort URL-encoded? (@ -> %40)

View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Testet die Datenbankverbindung ohne interaktive Shell
set -e
# DATABASE_URL laden
if [ -f .env.local ]; then
DATABASE_URL=$(grep -E "^DATABASE_URL=" .env.local | cut -d '=' -f2- | tr -d '"' | tr -d "'")
fi
if [ -z "$DATABASE_URL" ] && [ -f .env ]; then
DATABASE_URL=$(grep -E "^DATABASE_URL=" .env | cut -d '=' -f2- | tr -d '"' | tr -d "'")
fi
if [ -z "$DATABASE_URL" ]; then
echo "DATABASE_URL nicht gefunden"
exit 1
fi
# Verbindung testen
echo "Teste Verbindung..."
if psql "$DATABASE_URL" -c "SELECT 1;" > /dev/null 2>&1; then
echo "Verbindung erfolgreich!"
# Zusaetzliche Infos
echo ""
echo "Datenbank-Info:"
psql "$DATABASE_URL" -c "SELECT current_database() as database, current_user as user, version();" 2>/dev/null
echo ""
echo "Tabellen:"
psql "$DATABASE_URL" -c "\dt" 2>/dev/null || echo "(keine Tabellen gefunden)"
else
echo "Verbindung fehlgeschlagen!"
exit 1
fi

View File

@@ -0,0 +1,27 @@
#!/bin/bash
# Verbindet mit der PostgreSQL Datenbank aus .env
set -e
# DATABASE_URL aus .env.local oder .env laden
if [ -f .env.local ]; then
DATABASE_URL=$(grep -E "^DATABASE_URL=" .env.local | cut -d '=' -f2- | tr -d '"' | tr -d "'")
fi
if [ -z "$DATABASE_URL" ] && [ -f .env ]; then
DATABASE_URL=$(grep -E "^DATABASE_URL=" .env | cut -d '=' -f2- | tr -d '"' | tr -d "'")
fi
if [ -z "$DATABASE_URL" ]; then
echo "Fehler: DATABASE_URL nicht in .env.local oder .env gefunden."
echo ""
echo "Bitte fuege DATABASE_URL zu .env.local hinzu:"
echo 'DATABASE_URL="postgresql://user:password@host:port/database"'
exit 1
fi
echo "Verbinde mit Datenbank..."
echo "URL: ${DATABASE_URL%%@*}@***" # URL ohne Passwort ausgeben
# Verbindung herstellen
psql "$DATABASE_URL"

View File

@@ -0,0 +1,28 @@
#!/bin/bash
# Erkennt das Betriebssystem und gibt es aus
OS_TYPE=$(uname -s)
case "$OS_TYPE" in
"Darwin")
echo "macos"
;;
"Linux")
# Unterscheide zwischen verschiedenen Linux-Distributionen
if [ -f /etc/alpine-release ]; then
echo "alpine"
elif [ -f /etc/debian_version ]; then
echo "debian"
elif [ -f /etc/redhat-release ]; then
echo "redhat"
else
echo "linux"
fi
;;
"MINGW"*|"MSYS"*|"CYGWIN"*)
echo "windows"
;;
*)
echo "unknown"
;;
esac

View File

@@ -0,0 +1,72 @@
#!/bin/bash
# Installiert PostgreSQL CLI basierend auf dem Betriebssystem
set -e
# OS erkennen
OS_TYPE=$(uname -s)
# Pruefen ob psql bereits installiert ist
if command -v psql &> /dev/null; then
echo "psql ist bereits installiert: $(psql --version)"
exit 0
fi
echo "psql nicht gefunden. Starte Installation..."
case "$OS_TYPE" in
"Darwin")
# macOS - mit Homebrew
if ! command -v brew &> /dev/null; then
echo "Homebrew nicht gefunden. Bitte installiere Homebrew zuerst:"
echo '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
exit 1
fi
echo "Installiere PostgreSQL via Homebrew..."
brew install postgresql
;;
"Linux")
if [ -f /etc/alpine-release ]; then
# Alpine Linux
echo "Installiere PostgreSQL Client auf Alpine..."
apk add --no-cache postgresql-client
elif [ -f /etc/debian_version ]; then
# Debian/Ubuntu
echo "Installiere PostgreSQL Client auf Debian/Ubuntu..."
sudo apt-get update
sudo apt-get install -y postgresql-client
elif [ -f /etc/redhat-release ]; then
# RHEL/CentOS/Fedora
echo "Installiere PostgreSQL Client auf RHEL/CentOS..."
sudo yum install -y postgresql
else
echo "Unbekannte Linux-Distribution. Bitte installiere postgresql-client manuell."
exit 1
fi
;;
"MINGW"*|"MSYS"*|"CYGWIN"*)
# Windows
echo "Windows erkannt."
echo "Bitte installiere PostgreSQL von: https://www.postgresql.org/download/windows/"
echo "Oder nutze: winget install PostgreSQL.PostgreSQL"
exit 1
;;
*)
echo "Unbekanntes Betriebssystem: $OS_TYPE"
exit 1
;;
esac
# Verifizieren
if command -v psql &> /dev/null; then
echo "Installation erfolgreich: $(psql --version)"
else
echo "Installation fehlgeschlagen."
exit 1
fi

View File

@@ -0,0 +1,148 @@
---
name: semgrep-security
description: Security Scanner mit Semgrep. WICHTIG - Dieser Skill wird automatisch bei JEDER Code-Implementierung aktiviert. Scannt neuen und geaenderten Code auf Sicherheitsluecken, Vulnerabilities und Best Practice Verstoesse. Aktiviert bei Begriffen wie "implementieren", "code schreiben", "feature erstellen", "komponente bauen", "funktion erstellen", "api route", "fertig", "done", "commit", "security", "scan", "vulnerabilities", "sicherheit".
---
# Semgrep Security Scanner
Dieser Skill scannt automatisch Code auf Sicherheitsprobleme nach jeder Implementierung.
## WICHTIG: Automatische Ausfuehrung
Nach JEDER Code-Implementierung fuehre folgende Schritte aus:
1. **Semgrep installieren** (falls nicht vorhanden)
2. **Geaenderte Dateien scannen**
3. **Probleme fixen oder reporten**
## Scripts
### Installation pruefen und durchfuehren
```bash
bash install-semgrep.sh
```
### Scan durchfuehren
```bash
# Alle Dateien scannen
bash scan.sh
# Nur bestimmte Dateien scannen
bash scan.sh app/api/users/route.ts components/UserForm.tsx
```
### Autofix anwenden
```bash
bash autofix.sh
```
## Workflow nach Implementierung
### 1. Installation sicherstellen
```bash
# Pruefen ob semgrep installiert ist
which semgrep || bash install-semgrep.sh
```
### 2. Geaenderte Dateien ermitteln
```bash
# Untracked und modified files
git status --porcelain | grep -E '^\?\?|^ M|^M' | cut -c4-
```
### 3. Security Scan ausfuehren
```bash
# Mit Auto-Config (empfohlen)
semgrep --config=auto --json .
# Fuer TypeScript/React spezifisch
semgrep --config=p/typescript --config=p/react --json .
# OWASP Top 10
semgrep --config=p/owasp-top-ten --json .
```
### 4. Ergebnisse analysieren und fixen
```bash
# Mit Autofix
semgrep --config=auto --autofix .
# Nur Report ohne Fix
semgrep --config=auto --sarif -o results.sarif .
```
## Haeufige Security Issues und Fixes
### SQL Injection
```typescript
// SCHLECHT
const query = `SELECT * FROM users WHERE id = ${userId}`;
// GUT
const { data } = await supabase.from('users').select('*').eq('id', userId);
```
### XSS (Cross-Site Scripting)
```typescript
// SCHLECHT
<div dangerouslySetInnerHTML={{ __html: userInput }} />
// GUT
<div>{sanitize(userInput)}</div>
```
### Hardcoded Secrets
```typescript
// SCHLECHT
const apiKey = "sk-1234567890";
// GUT
const apiKey = process.env.API_KEY;
```
### Insecure Direct Object Reference
```typescript
// SCHLECHT - Keine Auth-Pruefung
const user = await getUser(req.query.id);
// GUT - Mit RLS oder Auth-Check
const { data: { user } } = await supabase.auth.getUser();
const profile = await supabase.from('profiles').select().eq('user_id', user.id);
```
## Report Format
Nach jedem Scan zeige:
```markdown
## Security Scan Ergebnisse
**Score**: 85/100
| Severity | Count |
|----------|-------|
| Critical | 0 |
| High | 1 |
| Medium | 3 |
| Low | 5 |
### Gefundene Issues
1. **[HIGH] Potential XSS** in `components/Comment.tsx:42`
- Problem: Unescaped user input
- Fix: Verwende DOMPurify oder sanitize-html
### Automatisch gefixt
- 2 Issues wurden automatisch behoben
### Naechste Schritte
- [ ] Issue #1 manuell pruefen
```
## Best Practices
1. **Immer scannen** nach Code-Aenderungen
2. **Autofix nutzen** fuer einfache Issues
3. **Kritische Issues** sofort beheben
4. **False Positives** in `.semgrepignore` ausschliessen
5. **CI/CD Integration** fuer automatische Scans

View File

@@ -0,0 +1,54 @@
#!/bin/bash
# Fuehrt Semgrep Autofix durch
set -e
# Farben
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Pruefen ob Semgrep installiert ist
if ! command -v semgrep &> /dev/null; then
echo -e "${YELLOW}Semgrep nicht gefunden. Installiere...${NC}"
bash "$(dirname "$0")/install-semgrep.sh"
fi
# Argumente
if [ $# -gt 0 ]; then
SCAN_TARGET="$@"
else
SCAN_TARGET="."
fi
echo "Starte Semgrep Autofix..."
echo "========================="
echo ""
# Autofix durchfuehren
# Nur Rules mit Autofix-Support
semgrep \
--config=auto \
--autofix \
--dryrun \
$SCAN_TARGET 2>&1 | head -50
echo ""
read -p "Fixes anwenden? (y/n) " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Wende Fixes an..."
semgrep \
--config=auto \
--autofix \
$SCAN_TARGET
echo ""
echo -e "${GREEN}Fixes angewendet!${NC}"
echo ""
echo "Bitte pruefen Sie die Aenderungen:"
echo " git diff"
else
echo "Autofix abgebrochen."
fi

View File

@@ -0,0 +1,86 @@
#!/bin/bash
# Installiert Semgrep basierend auf dem Betriebssystem
set -e
echo "Pruefe Semgrep Installation..."
# Pruefen ob bereits installiert
if command -v semgrep &> /dev/null; then
echo "Semgrep ist bereits installiert: $(semgrep --version)"
exit 0
fi
echo "Semgrep nicht gefunden. Starte Installation..."
# OS erkennen
OS_TYPE=$(uname -s)
case "$OS_TYPE" in
"Darwin")
# macOS
echo "macOS erkannt - nutze Homebrew"
if command -v brew &> /dev/null; then
brew install semgrep
else
echo "Homebrew nicht gefunden - nutze pip3"
pip3 install semgrep
fi
;;
"Linux")
echo "Linux erkannt"
# Versuche verschiedene Paketmanager
if command -v apt-get &> /dev/null; then
echo "Nutze apt-get..."
# Semgrep via Python da apt Version oft veraltet
pip3 install semgrep
elif command -v apk &> /dev/null; then
echo "Alpine erkannt - nutze pip3"
apk add --no-cache python3 py3-pip
pip3 install semgrep
elif command -v yum &> /dev/null; then
echo "RHEL/CentOS erkannt - nutze pip3"
pip3 install semgrep
else
echo "Nutze pip3 als Fallback"
pip3 install semgrep
fi
;;
"MINGW"*|"MSYS"*|"CYGWIN"*)
# Windows
echo "Windows erkannt"
if command -v choco &> /dev/null; then
choco install semgrep -y
elif command -v pip3 &> /dev/null; then
pip3 install semgrep
elif command -v pip &> /dev/null; then
pip install semgrep
else
echo "Bitte installiere Python und pip: https://www.python.org/downloads/"
exit 1
fi
;;
*)
echo "Unbekanntes OS: $OS_TYPE - nutze pip3"
pip3 install semgrep
;;
esac
# Verifizieren
if command -v semgrep &> /dev/null; then
echo ""
echo "Installation erfolgreich!"
semgrep --version
else
echo ""
echo "Installation fehlgeschlagen."
echo "Bitte manuell installieren: https://semgrep.dev/docs/getting-started/"
exit 1
fi

View File

@@ -0,0 +1,91 @@
# Custom Semgrep Rules fuer Next.js Security
rules:
# Verhindere dangerouslySetInnerHTML ohne Sanitization
- id: next-xss-dangerous-html
patterns:
- pattern: dangerouslySetInnerHTML={{ __html: $VAR }}
- pattern-not: dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize($VAR) }}
- pattern-not: dangerouslySetInnerHTML={{ __html: sanitize($VAR) }}
message: "XSS Risiko: Verwende DOMPurify.sanitize() bevor du dangerouslySetInnerHTML nutzt"
severity: ERROR
languages: [typescript, javascript]
# Verhindere hardcoded API Keys
- id: next-hardcoded-api-key
patterns:
- pattern-either:
- pattern: |
$KEY = "sk-..."
- pattern: |
$KEY = "pk_..."
- pattern: |
apiKey: "..."
- pattern: |
api_key = "..."
message: "Hardcoded API Key gefunden. Verwende Environment Variables."
severity: ERROR
languages: [typescript, javascript]
# Verhindere ungeschuetzte API Routes
- id: next-unprotected-api-route
patterns:
- pattern: |
export async function $METHOD(request: NextRequest) {
...
$DB.$OPERATION(...)
...
}
- pattern-not: |
export async function $METHOD(request: NextRequest) {
...
auth.getUser()
...
}
- pattern-not: |
export async function $METHOD(request: NextRequest) {
...
getSession()
...
}
message: "API Route ohne Authentication. Fuege Auth-Check hinzu."
severity: WARNING
languages: [typescript]
paths:
include:
- "app/api/**"
# Verhindere Secrets in Client Components
- id: next-secret-in-client
patterns:
- pattern-inside: |
"use client"
...
- pattern-either:
- pattern: process.env.SUPABASE_SERVICE_ROLE_KEY
- pattern: process.env.DATABASE_URL
- pattern: process.env.$SECRET_KEY
message: "Server-only Secret in Client Component. Verschiebe in Server Component oder API Route."
severity: ERROR
languages: [typescript, javascript]
# Verhindere eval und new Function
- id: next-no-eval
patterns:
- pattern-either:
- pattern: eval(...)
- pattern: new Function(...)
message: "eval() und new Function() sind Sicherheitsrisiken. Finde eine Alternative."
severity: ERROR
languages: [typescript, javascript]
# Supabase RLS Check
- id: supabase-rls-bypass
patterns:
- pattern: supabaseAdmin.from($TABLE)
- pattern-not-inside: |
// RLS bypassed intentionally
...
message: "supabaseAdmin umgeht RLS. Stelle sicher dass dies beabsichtigt ist."
severity: WARNING
languages: [typescript, javascript]

View File

@@ -0,0 +1,29 @@
#!/bin/bash
# Scannt nur geaenderte Dateien (staged + unstaged)
set -e
# Farben
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "Ermittle geaenderte Dateien..."
# Geaenderte Dateien ermitteln (TypeScript/JavaScript)
CHANGED_FILES=$(git status --porcelain 2>/dev/null | \
grep -E '\.(ts|tsx|js|jsx)$' | \
sed 's/^...//' | \
tr '\n' ' ')
if [ -z "$CHANGED_FILES" ]; then
echo -e "${GREEN}Keine geaenderten TypeScript/JavaScript Dateien gefunden.${NC}"
exit 0
fi
echo "Geaenderte Dateien:"
echo "$CHANGED_FILES" | tr ' ' '\n' | grep -v '^$'
echo ""
# Scan durchfuehren
bash "$(dirname "$0")/scan.sh" $CHANGED_FILES

View File

@@ -0,0 +1,89 @@
#!/bin/bash
# Fuehrt Semgrep Security Scan durch
set -e
# Farben fuer Output
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
# Pruefen ob Semgrep installiert ist
if ! command -v semgrep &> /dev/null; then
echo -e "${YELLOW}Semgrep nicht gefunden. Installiere...${NC}"
bash "$(dirname "$0")/install-semgrep.sh"
fi
# Argumente: spezifische Dateien oder alles scannen
if [ $# -gt 0 ]; then
SCAN_TARGET="$@"
echo "Scanne spezifische Dateien: $SCAN_TARGET"
else
SCAN_TARGET="."
echo "Scanne gesamtes Projekt..."
fi
# Temporaere Datei fuer JSON Output
RESULT_FILE=$(mktemp)
# Scan durchfuehren
echo ""
echo "Starte Security Scan..."
echo "========================"
semgrep \
--config=auto \
--config=p/security-audit \
--config=p/typescript \
--json \
--output="$RESULT_FILE" \
$SCAN_TARGET 2>/dev/null || true
# Ergebnisse parsen
if [ -f "$RESULT_FILE" ]; then
# Anzahl der Findings zaehlen
TOTAL=$(jq '.results | length' "$RESULT_FILE" 2>/dev/null || echo "0")
ERRORS=$(jq '[.results[] | select(.extra.severity == "ERROR")] | length' "$RESULT_FILE" 2>/dev/null || echo "0")
WARNINGS=$(jq '[.results[] | select(.extra.severity == "WARNING")] | length' "$RESULT_FILE" 2>/dev/null || echo "0")
INFO=$(jq '[.results[] | select(.extra.severity == "INFO")] | length' "$RESULT_FILE" 2>/dev/null || echo "0")
echo ""
echo "========================"
echo "Scan Ergebnisse"
echo "========================"
echo ""
if [ "$TOTAL" -eq 0 ]; then
echo -e "${GREEN}Keine Sicherheitsprobleme gefunden!${NC}"
else
echo -e "Gefunden: ${RED}$ERRORS Critical/High${NC}, ${YELLOW}$WARNINGS Medium${NC}, $INFO Low"
echo ""
# Details ausgeben
echo "Details:"
echo "--------"
jq -r '.results[] | "[\(.extra.severity)] \(.check_id)\n File: \(.path):\(.start.line)\n Message: \(.extra.message)\n"' "$RESULT_FILE" 2>/dev/null || true
fi
# Score berechnen
SCORE=$((100 - (ERRORS * 10) - (WARNINGS * 3) - (INFO * 1)))
if [ $SCORE -lt 0 ]; then SCORE=0; fi
echo ""
echo "========================"
echo -e "Security Score: ${GREEN}$SCORE/100${NC}"
echo "========================"
# Cleanup
rm -f "$RESULT_FILE"
# Exit mit Fehler wenn kritische Issues
if [ "$ERRORS" -gt 0 ]; then
echo ""
echo -e "${RED}WARNUNG: Es wurden kritische Sicherheitsprobleme gefunden!${NC}"
exit 1
fi
else
echo -e "${GREEN}Scan abgeschlossen - keine Ergebnisdatei erstellt${NC}"
fi

View File

@@ -0,0 +1,152 @@
---
name: supabase-auth
description: Supabase Authentication Setup und User Management. Nutze diesen Skill fuer Login, Signup, OAuth, Sessions, Password Reset und geschuetzte Routen. Aktiviert bei Begriffen wie "Login", "Anmeldung", "Registrierung", "Signup", "Auth", "Authentication", "User", "Benutzer", "Session", "Passwort", "OAuth", "Google Login", "geschuetzt", "protected route".
---
# Supabase Authentication Skill
Dieser Skill hilft bei Authentication mit Supabase.
## Auth Client Setup
```typescript
// lib/supabase.ts - Browser Client
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
// lib/supabase-admin.ts - Server Client
export const supabaseAdmin = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,
{
auth: {
autoRefreshToken: false,
persistSession: false
}
}
);
```
## Sign Up / Sign In
```typescript
// Email/Password Signup
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'secure-password',
options: {
data: { full_name: 'Max Mustermann' }
}
});
// Email/Password Login
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'password'
});
// Magic Link
const { error } = await supabase.auth.signInWithOtp({
email: 'user@example.com',
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`
}
});
// OAuth (Google, GitHub, etc.)
const { error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`
}
});
```
## Session Management
```typescript
// Aktuelle Session
const { data: { session } } = await supabase.auth.getSession();
// Aktueller User
const { data: { user } } = await supabase.auth.getUser();
// Auth State Listener
supabase.auth.onAuthStateChange((event, session) => {
console.log(event, session);
});
// Logout
await supabase.auth.signOut();
```
## Password Reset
```typescript
// Reset anfordern
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/auth/reset-password`
});
// Neues Passwort setzen
const { error } = await supabase.auth.updateUser({
password: 'new-password'
});
```
## Middleware (Protected Routes)
```typescript
// middleware.ts
import { createServerClient } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';
export async function middleware(request: NextRequest) {
let response = NextResponse.next({ request });
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get: (name) => request.cookies.get(name)?.value,
set: (name, value, options) => {
response.cookies.set({ name, value, ...options });
},
remove: (name, options) => {
response.cookies.set({ name, value: '', ...options });
},
},
}
);
const { data: { session } } = await supabase.auth.getSession();
// Redirect wenn nicht eingeloggt
if (!session && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return response;
}
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*']
};
```
## Auth Context
Siehe `templates/AuthContext.tsx` fuer einen kompletten Auth Provider.
## Best Practices
1. SSR Auth mit @supabase/ssr
2. Middleware fuer Route Protection
3. Service Role Key nur serverseitig
4. User-freundliche Fehlermeldungen
5. Email Verification aktivieren

View File

@@ -0,0 +1,108 @@
'use client';
import { createContext, useContext, useEffect, useState, useCallback } from 'react';
import { User, Session, AuthError } from '@supabase/supabase-js';
import { supabase } from '@/lib/supabase';
interface AuthContextType {
user: User | null;
session: Session | null;
loading: boolean;
signUp: (email: string, password: string, metadata?: Record<string, unknown>) => Promise<{ error: AuthError | null }>;
signIn: (email: string, password: string) => Promise<{ error: AuthError | null }>;
signInWithOAuth: (provider: 'google' | 'github' | 'azure') => Promise<void>;
signOut: () => Promise<void>;
resetPassword: (email: string) => Promise<{ error: AuthError | null }>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [session, setSession] = useState<Session | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Initiale Session holen
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
setUser(session?.user ?? null);
setLoading(false);
});
// Auth State Listener
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setSession(session);
setUser(session?.user ?? null);
}
);
return () => subscription.unsubscribe();
}, []);
const signUp = useCallback(
async (email: string, password: string, metadata?: Record<string, unknown>) => {
const { error } = await supabase.auth.signUp({
email,
password,
options: { data: metadata },
});
return { error };
},
[]
);
const signIn = useCallback(async (email: string, password: string) => {
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
return { error };
}, []);
const signInWithOAuth = useCallback(async (provider: 'google' | 'github' | 'azure') => {
await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});
}, []);
const signOut = useCallback(async () => {
await supabase.auth.signOut();
}, []);
const resetPassword = useCallback(async (email: string) => {
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/auth/reset-password`,
});
return { error };
}, []);
return (
<AuthContext.Provider
value={{
user,
session,
loading,
signUp,
signIn,
signInWithOAuth,
signOut,
resetPassword,
}}
>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}

View File

@@ -0,0 +1,123 @@
'use client';
import { useState } from 'react';
import { useAuth } from '@/contexts/AuthContext';
interface LoginFormProps {
onSuccess?: () => void;
redirectTo?: string;
}
export function LoginForm({ onSuccess, redirectTo = '/dashboard' }: LoginFormProps) {
const { signIn, signInWithOAuth } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError(null);
const { error } = await signIn(email, password);
if (error) {
setError(getErrorMessage(error.message));
setLoading(false);
} else {
onSuccess?.();
window.location.href = redirectTo;
}
};
const handleOAuth = async (provider: 'google' | 'github') => {
setLoading(true);
await signInWithOAuth(provider);
};
return (
<div className="space-y-6 w-full max-w-sm">
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium mb-1">
Email
</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border rounded-md"
required
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium mb-1">
Passwort
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border rounded-md"
required
/>
</div>
{error && (
<p className="text-sm text-red-500">{error}</p>
)}
<button
type="submit"
disabled={loading}
className="w-full py-2 px-4 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50"
>
{loading ? 'Wird geladen...' : 'Anmelden'}
</button>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Oder weiter mit
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<button
type="button"
onClick={() => handleOAuth('google')}
disabled={loading}
className="py-2 px-4 border rounded-md hover:bg-muted disabled:opacity-50"
>
Google
</button>
<button
type="button"
onClick={() => handleOAuth('github')}
disabled={loading}
className="py-2 px-4 border rounded-md hover:bg-muted disabled:opacity-50"
>
GitHub
</button>
</div>
</div>
);
}
function getErrorMessage(message: string): string {
const errorMap: Record<string, string> = {
'Invalid login credentials': 'Ungueltige Anmeldedaten',
'Email not confirmed': 'Bitte bestaetigen Sie zuerst Ihre Email',
'Too many requests': 'Zu viele Versuche. Bitte warten Sie einen Moment.',
};
return errorMap[message] || 'Ein Fehler ist aufgetreten';
}

View File

@@ -0,0 +1,110 @@
---
name: supabase-db
description: Supabase PostgreSQL Datenbank Operationen. Nutze diesen Skill fuer CRUD Operationen, SQL Migrations, Tabellen erstellen, RLS Policies, Queries und TypeScript Types generieren. Aktiviert bei Begriffen wie "Datenbank", "Tabelle erstellen", "SQL", "Query", "Migration", "RLS", "Row Level Security", "select", "insert", "update", "delete".
---
# Supabase PostgreSQL Datenbank Skill
Dieser Skill hilft bei allen Datenbank-Operationen mit Supabase.
## Tabellen erstellen
Verwende immer dieses Pattern:
```sql
CREATE TABLE table_name (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- deine Spalten hier
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS aktivieren
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
-- Updated_at Trigger
CREATE TRIGGER table_name_updated_at
BEFORE UPDATE ON table_name
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
```
## CRUD Operationen
### Select
```typescript
const { data, error } = await supabase
.from('table')
.select('*')
.eq('column', value)
.order('created_at', { ascending: false });
```
### Insert
```typescript
const { data, error } = await supabase
.from('table')
.insert({ column: value })
.select()
.single();
```
### Update
```typescript
const { data, error } = await supabase
.from('table')
.update({ column: newValue })
.eq('id', id)
.select()
.single();
```
### Delete
```typescript
const { error } = await supabase
.from('table')
.delete()
.eq('id', id);
```
## RLS Policies
```sql
-- Lesen: Jeder authentifizierte User
CREATE POLICY "Users can read own data"
ON table_name FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- Schreiben: Nur eigene Daten
CREATE POLICY "Users can insert own data"
ON table_name FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
-- Update: Nur eigene Daten
CREATE POLICY "Users can update own data"
ON table_name FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- Delete: Nur eigene Daten
CREATE POLICY "Users can delete own data"
ON table_name FOR DELETE
TO authenticated
USING (auth.uid() = user_id);
```
## TypeScript Types generieren
```bash
npx supabase gen types typescript --project-id PROJECT_ID > lib/database.types.ts
```
## Best Practices
1. Immer UUIDs als Primary Key
2. Immer created_at/updated_at
3. Immer RLS aktivieren
4. Soft Deletes mit deleted_at
5. Indexes fuer WHERE/ORDER BY Spalten

View File

@@ -0,0 +1,84 @@
-- Migration: {{MIGRATION_NAME}}
-- Created: {{DATE}}
-- ===========================================
-- Tabelle erstellen
-- ===========================================
CREATE TABLE IF NOT EXISTS {{TABLE_NAME}} (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Deine Spalten hier
name TEXT NOT NULL,
description TEXT,
-- Foreign Keys (optional)
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
-- ===========================================
-- Row Level Security
-- ===========================================
ALTER TABLE {{TABLE_NAME}} ENABLE ROW LEVEL SECURITY;
-- Select Policy
CREATE POLICY "{{TABLE_NAME}}_select_policy"
ON {{TABLE_NAME}} FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- Insert Policy
CREATE POLICY "{{TABLE_NAME}}_insert_policy"
ON {{TABLE_NAME}} FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
-- Update Policy
CREATE POLICY "{{TABLE_NAME}}_update_policy"
ON {{TABLE_NAME}} FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- Delete Policy
CREATE POLICY "{{TABLE_NAME}}_delete_policy"
ON {{TABLE_NAME}} FOR DELETE
TO authenticated
USING (auth.uid() = user_id);
-- ===========================================
-- Indexes
-- ===========================================
CREATE INDEX IF NOT EXISTS {{TABLE_NAME}}_user_id_idx ON {{TABLE_NAME}}(user_id);
CREATE INDEX IF NOT EXISTS {{TABLE_NAME}}_created_at_idx ON {{TABLE_NAME}}(created_at DESC);
-- ===========================================
-- Updated_at Trigger
-- ===========================================
-- Funktion (nur einmal pro DB noetig)
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger
CREATE TRIGGER {{TABLE_NAME}}_updated_at
BEFORE UPDATE ON {{TABLE_NAME}}
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
-- ===========================================
-- Grants (optional)
-- ===========================================
-- GRANT SELECT, INSERT, UPDATE, DELETE ON {{TABLE_NAME}} TO authenticated;
-- GRANT SELECT ON {{TABLE_NAME}} TO anon;

View File

@@ -0,0 +1,142 @@
---
name: supabase-storage
description: Supabase Storage fuer Datei-Upload und Download. Nutze diesen Skill fuer Bucket-Erstellung, File Upload, Download, Signed URLs und Storage Policies. Aktiviert bei Begriffen wie "Upload", "Download", "Datei", "File", "Bild hochladen", "Storage", "Bucket", "S3", "Bilder", "Avatar", "Dokumente".
---
# Supabase Storage Skill
Dieser Skill hilft bei allen Storage-Operationen mit Supabase.
## Bucket erstellen
```typescript
// Server-side mit Admin Client
const { data, error } = await supabaseAdmin.storage.createBucket('avatars', {
public: true,
fileSizeLimit: 1024 * 1024 * 2, // 2MB
allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp']
});
```
Oder via SQL:
```sql
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
VALUES (
'avatars',
'avatars',
true,
2097152,
ARRAY['image/png', 'image/jpeg', 'image/webp']
);
```
## File Upload
```typescript
async function uploadFile(file: File, bucket: string, path: string) {
const { data, error } = await supabase.storage
.from(bucket)
.upload(path, file, {
cacheControl: '3600',
upsert: false // true = ueberschreiben erlaubt
});
if (error) throw error;
// Public URL holen
const { data: urlData } = supabase.storage
.from(bucket)
.getPublicUrl(path);
return urlData.publicUrl;
}
```
## File Download
```typescript
// Als Blob
const { data, error } = await supabase.storage
.from('documents')
.download('path/to/file.pdf');
// Public URL (oeffentliche Buckets)
const { data } = supabase.storage
.from('avatars')
.getPublicUrl('user123/avatar.jpg');
// Signed URL (private Buckets, zeitlich begrenzt)
const { data, error } = await supabase.storage
.from('private-docs')
.createSignedUrl('secret.pdf', 3600); // 1 Stunde
```
## File Management
```typescript
// Liste Dateien
const { data, error } = await supabase.storage
.from('bucket')
.list('folder/', {
limit: 100,
sortBy: { column: 'created_at', order: 'desc' }
});
// Loeschen
const { error } = await supabase.storage
.from('bucket')
.remove(['path/file1.jpg', 'path/file2.jpg']);
// Verschieben
const { error } = await supabase.storage
.from('bucket')
.move('old/path.jpg', 'new/path.jpg');
```
## Storage Policies (RLS)
```sql
-- User koennen eigene Dateien hochladen
CREATE POLICY "Users upload own files"
ON storage.objects FOR INSERT
TO authenticated
WITH CHECK (
bucket_id = 'user-files' AND
(storage.foldername(name))[1] = auth.uid()::text
);
-- User koennen eigene Dateien lesen
CREATE POLICY "Users read own files"
ON storage.objects FOR SELECT
TO authenticated
USING (
bucket_id = 'user-files' AND
(storage.foldername(name))[1] = auth.uid()::text
);
-- User koennen eigene Dateien loeschen
CREATE POLICY "Users delete own files"
ON storage.objects FOR DELETE
TO authenticated
USING (
bucket_id = 'user-files' AND
(storage.foldername(name))[1] = auth.uid()::text
);
-- Oeffentlicher Lesezugriff
CREATE POLICY "Public read"
ON storage.objects FOR SELECT
USING (bucket_id = 'public-assets');
```
## React Upload Component
Siehe `templates/FileUpload.tsx` fuer eine fertige Component.
## Best Practices
1. Ordnerstruktur: `{user_id}/{type}/{filename}`
2. File Size Limits setzen
3. MIME Types einschraenken
4. Signed URLs fuer private Dateien
5. Unique Filenames (z.B. mit Timestamp)

View File

@@ -0,0 +1,126 @@
'use client';
import { useState, useCallback } from 'react';
import { supabase } from '@/lib/supabase';
interface FileUploadProps {
bucket: string;
folder?: string;
accept?: string;
maxSize?: number; // in bytes
onUpload: (url: string, path: string) => void;
onError?: (error: Error) => void;
}
export function FileUpload({
bucket,
folder = 'uploads',
accept = 'image/*',
maxSize = 5 * 1024 * 1024, // 5MB default
onUpload,
onError,
}: FileUploadProps) {
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [dragOver, setDragOver] = useState(false);
const uploadFile = useCallback(
async (file: File) => {
// Validierung
if (file.size > maxSize) {
onError?.(new Error(`Datei zu gross. Maximum: ${maxSize / 1024 / 1024}MB`));
return;
}
setUploading(true);
setProgress(0);
try {
// Unique filename generieren
const fileExt = file.name.split('.').pop();
const fileName = `${Date.now()}-${Math.random().toString(36).substring(2)}.${fileExt}`;
const filePath = `${folder}/${fileName}`;
// Upload
const { error: uploadError } = await supabase.storage
.from(bucket)
.upload(filePath, file, {
cacheControl: '3600',
upsert: false,
});
if (uploadError) throw uploadError;
// URL holen
const { data } = supabase.storage.from(bucket).getPublicUrl(filePath);
onUpload(data.publicUrl, filePath);
setProgress(100);
} catch (error) {
console.error('Upload error:', error);
onError?.(error instanceof Error ? error : new Error('Upload fehlgeschlagen'));
} finally {
setUploading(false);
}
},
[bucket, folder, maxSize, onUpload, onError]
);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) uploadFile(file);
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setDragOver(false);
const file = e.dataTransfer.files?.[0];
if (file) uploadFile(file);
};
return (
<div
className={`
relative border-2 border-dashed rounded-lg p-6 text-center
transition-colors cursor-pointer
${dragOver ? 'border-primary bg-primary/5' : 'border-muted-foreground/25'}
${uploading ? 'pointer-events-none opacity-50' : 'hover:border-primary'}
`}
onDragOver={(e) => {
e.preventDefault();
setDragOver(true);
}}
onDragLeave={() => setDragOver(false)}
onDrop={handleDrop}
>
<input
type="file"
accept={accept}
onChange={handleChange}
disabled={uploading}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
/>
{uploading ? (
<div className="space-y-2">
<div className="w-full bg-muted rounded-full h-2">
<div
className="bg-primary h-2 rounded-full transition-all"
style={{ width: `${progress}%` }}
/>
</div>
<p className="text-sm text-muted-foreground">Uploading...</p>
</div>
) : (
<div className="space-y-2">
<p className="text-sm font-medium">
Datei hierher ziehen oder klicken zum Auswaehlen
</p>
<p className="text-xs text-muted-foreground">
Max. {maxSize / 1024 / 1024}MB
</p>
</div>
)}
</div>
);
}