From 31b007d14500e52831836130f94115677af97877 Mon Sep 17 00:00:00 2001 From: elpatron Date: Mon, 6 Oct 2025 17:25:25 +0200 Subject: [PATCH] CalDAV: Support Basic auth; trim+validate UUID; deprecate query token via headers; ICS end time helper; docs+instructions updated --- Dockerfile | 4 +- docker-compose-prod.yml | 5 +- docs/caldav-setup.md | 121 +++ docs/rate-limiting.md | 46 +- docs/redis-migration.md | 355 +++++++++ docs/session-management.md | 251 ++++++ package.json | 1 + pnpm-lock.yaml | 791 ++++++++++++++++++- scripts/rebuild-prod.sh | 19 +- src/client/components/admin-availability.tsx | 39 +- src/client/components/admin-bookings.tsx | 9 +- src/client/components/admin-calendar.tsx | 19 +- src/client/components/admin-gallery.tsx | 29 +- src/client/components/admin-reviews.tsx | 15 +- src/client/components/auth-provider.tsx | 49 +- src/client/components/user-profile.tsx | 8 +- src/client/rpc-client.ts | 26 +- src/server/index.ts | 37 +- src/server/lib/auth.ts | 101 ++- src/server/lib/rate-limiter.ts | 130 ++- src/server/routes/caldav.ts | 122 ++- src/server/routes/rpc.ts | 1 + src/server/rpc/auth.ts | 93 ++- src/server/rpc/bookings.ts | 112 +-- src/server/rpc/gallery.ts | 45 +- src/server/rpc/index.ts | 8 + src/server/rpc/recurring-availability.ts | 113 ++- src/server/rpc/reviews.ts | 66 +- src/server/rpc/treatments.ts | 17 +- 29 files changed, 2311 insertions(+), 321 deletions(-) create mode 100644 docs/caldav-setup.md create mode 100644 docs/redis-migration.md create mode 100644 docs/session-management.md diff --git a/Dockerfile b/Dockerfile index 5145f8e..d37f149 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,8 +22,8 @@ RUN pnpm build # Production stage FROM node:22-alpine AS production -# Install pnpm and su-exec -RUN npm install -g pnpm ts-node && apk add --no-cache su-exec +# Install pnpm and required runtime tools +RUN npm install -g pnpm ts-node && apk add --no-cache su-exec curl # Set working directory WORKDIR /app diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index 3ca9eca..71b8c26 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -17,7 +17,7 @@ services: networks: - stargirlnails-network healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"] + test: ["CMD", "curl", "-fsS", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 @@ -36,6 +36,7 @@ services: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy-data:/data - caddy-config:/config + - caddy-logs:/var/log/caddy networks: - stargirlnails-network depends_on: @@ -49,6 +50,8 @@ volumes: driver: local caddy-config: driver: local + caddy-logs: + driver: local # Netzwerk für interne Kommunikation networks: diff --git a/docs/caldav-setup.md b/docs/caldav-setup.md new file mode 100644 index 0000000..4fc9abc --- /dev/null +++ b/docs/caldav-setup.md @@ -0,0 +1,121 @@ +# CalDAV-Kalender Einrichtung + +## Übersicht + +Die App bietet einen CalDAV-Endpunkt, um Buchungen in externe Kalender-Apps zu synchronisieren. Aus Sicherheitsgründen erfolgt die Authentifizierung per Header. Unterstützt werden: + +- Authorization: Bearer +- Basic Auth, wobei der Token als Benutzername übergeben wird (Passwort leer/optional) + +## Token generieren + +1. Als Admin einloggen +2. Im Admin-Bereich den CalDAV-Token generieren +3. Token und URL werden angezeigt + +**Wichtig:** Der Token ist 24 Stunden gültig. Danach muss ein neuer Token generiert werden. + +## Endpunkt + +``` +GET /caldav/calendar/events.ics +Authorization: Bearer + +oder + +Authorization: Basic # Token als Benutzername, Passwort leer +``` + +## Unterstützte Kalender-Apps + +### ✅ Thunderbird (empfohlen) + +1. Kalender → Neuer Kalender → Im Netzwerk +2. Format: CalDAV +3. Standort: CalDAV-URL eingeben +4. Benutzername: Den generierten Token eingeben (Basic Auth) +5. Passwort: Leer lassen + +### ✅ Outlook (mit Einschränkungen) + +1. Datei → Kontoeinstellungen → Internetkalender +2. URL eingeben +3. Erweiterte Einstellungen → Benutzerdefinierte Header: + ``` + Authorization: Bearer + ``` + +**Hinweis:** Nicht alle Outlook-Versionen unterstützen benutzerdefinierte Header. + +### ⚠️ Apple Calendar (eingeschränkt) + +Apple Calendar unterstützt keine Authorization-Header für Kalenderabonnements. + +**Alternativen:** +- Manuelle ICS-Datei importieren (nicht automatisch aktualisiert) +- CalDAV-Bridge verwenden (z.B. über Proxy) + +### ⚠️ Google Calendar (eingeschränkt) + +Google Calendar unterstützt keine Authorization-Header für URL-Abonnements. + +**Alternativen:** +- Google Apps Script als Bridge verwenden +- Manuelle ICS-Datei importieren + +## Testen mit cURL + +```bash +# Bearer +curl -H "Authorization: Bearer " \ + https://deine-domain.de/caldav/calendar/events.ics + +# Basic (Token als Benutzername, Passwort leer) +curl -H "Authorization: Basic $(printf "%s" ":" | base64)" \ + https://deine-domain.de/caldav/calendar/events.ics +``` + +Erwartete Antwort: ICS-Datei mit allen bestätigten und ausstehenden Buchungen. + +## Sicherheit + +### Warum Authorization-Header? + +- **Query-Parameter** (`?token=...`) werden in Browser-History, Server-Logs und Referrer-Headers gespeichert +- **Authorization-Header** werden nicht geloggt und nicht in der URL sichtbar +- Folgt REST-API Best Practices + +### Token-Verwaltung + +- Token sind 24 Stunden gültig +- Abgelaufene Token werden automatisch beim nächsten Zugriff gelöscht +- Bei Bedarf neuen Token generieren (alte Token werden nicht automatisch invalidiert) + +### Backward Compatibility + +Die alte Methode mit Query-Parameter (`?token=...`) wird noch unterstützt, aber als **deprecated** markiert. Der Server sendet zusätzlich Response-Header (`Deprecation: true` und `Warning: 299 ...`). Eine Warnung wird im Server-Log ausgegeben. + +## Troubleshooting + +### "Unauthorized - Token required" + +- Prüfe, ob der Authorization-Header korrekt gesetzt ist +- Format: `Authorization: Bearer ` (mit Leerzeichen nach "Bearer") + +### "Unauthorized - Invalid or expired token" + +- Token ist abgelaufen (24h Gültigkeit) +- Generiere einen neuen Token im Admin-Bereich + +### Kalender zeigt keine Termine + +- Prüfe, ob Buchungen mit Status "confirmed" oder "pending" existieren +- Teste den Endpunkt mit cURL +- Prüfe Server-Logs auf Fehler + +## Zukünftige Verbesserungen + +- [ ] Langlebige Token mit Refresh-Mechanismus +- [ ] Token-Revocation-Endpoint +- [ ] CalDAV-Bridge für Apple Calendar und Google Calendar +- [ ] Webhook-basierte Push-Notifications statt Polling diff --git a/docs/rate-limiting.md b/docs/rate-limiting.md index 47cfe04..1eb0ce2 100644 --- a/docs/rate-limiting.md +++ b/docs/rate-limiting.md @@ -37,17 +37,38 @@ Das System verwendet ein Rate-Limiting, um Spam und Missbrauch des Buchungsformu - **Zeitfenster:** 10 Minuten - **Verhalten:** Nach 5 Anfragen muss der Nutzer 10 Minuten warten +### Login (IP-basiert) +- **Limit:** 5 fehlgeschlagene Login-Versuche pro IP-Adresse +- **Zeitfenster:** 15 Minuten +- **Verhalten:** Das Limit zählt nur fehlgeschlagene Versuche. Nach erfolgreichem Login wird der Zähler zurückgesetzt. Bei Überschreitung muss der Nutzer 15 Minuten warten. +- **Zweck:** Schutz vor Brute-Force-Angriffen auf Admin-Accounts + +### Admin-Operationen (Benutzer-basiert) +- **Limit:** 30 Anfragen pro Admin-Benutzer +- **Zeitfenster:** 5 Minuten +- **Verhalten:** Nach 30 Anfragen muss der Admin 5 Minuten warten +- **Zweck:** Verhindert versehentliches Spam durch Admin-UI (z.B. Doppelklicks, fehlerhafte Skripte) +- **Betroffene Endpoints:** Treatments (create/update/remove), Bookings (manualCreate/updateStatus/remove), Recurring Availability (alle Schreiboperationen), Gallery (alle Schreiboperationen), Reviews (approve/reject/delete) + +### Admin-Operationen (IP-basiert) +- **Limit:** 50 Anfragen pro IP-Adresse +- **Zeitfenster:** 5 Minuten +- **Verhalten:** Nach 50 Anfragen muss 5 Minuten gewartet werden +- **Zweck:** Zusätzlicher Schutz gegen IP-basierte Angriffe auf Admin-Endpoints + ## Wie es funktioniert -Das Rate-Limiting prüft **beide** Kriterien: +Das Rate-Limiting prüft die passenden Kriterien je Endpoint. Für Admin-Operationen werden **beide** Limits geprüft: 1. **E-Mail-Adresse:** Verhindert, dass dieselbe Person mit derselben E-Mail zu viele Anfragen stellt 2. **IP-Adresse:** Verhindert, dass jemand mit verschiedenen E-Mail-Adressen von derselben IP aus spammt +3. **Benutzer-basiert (Admin):** Limitiert Anfragen je Admin-Benutzer +4. **IP-basiert (Admin):** Limitiert Anfragen je IP zusätzlich Wenn eines der Limits überschritten wird, erhält der Nutzer eine Fehlermeldung mit Angabe der Wartezeit. ## IP-Erkennung -Das System erkennt die Client-IP auch hinter Proxies und Load Balancern durch folgende Headers: +Das System erkennt die Client-IP auch hinter Proxies und Load Balancern durch folgende Headers (unterstützt `Headers`-API und einfache Record-Objekte): - `x-forwarded-for` - `x-real-ip` - `cf-connecting-ip` (Cloudflare) @@ -60,7 +81,7 @@ Das System erkennt die Client-IP auch hinter Proxies und Load Balancern durch fo ## Anpassung -Die Limits können in `src/server/lib/rate-limiter.ts` in der Funktion `checkBookingRateLimit()` angepasst werden: +Die Limits können in `src/server/lib/rate-limiter.ts` angepasst werden. Beispiele: ```typescript // E-Mail-Limit anpassen @@ -74,6 +95,23 @@ const ipConfig: RateLimitConfig = { maxRequests: 5, // Anzahl der Anfragen windowMs: 10 * 60 * 1000, // Zeitfenster in Millisekunden }; + +// Login-Bruteforce-Schutz +const loginConfig: RateLimitConfig = { + maxRequests: 5, + windowMs: 15 * 60 * 1000, +}; + +// Admin-Operationen +const adminUserConfig: RateLimitConfig = { + maxRequests: 30, + windowMs: 5 * 60 * 1000, +}; + +const adminIpConfig: RateLimitConfig = { + maxRequests: 50, + windowMs: 5 * 60 * 1000, +}; ``` ## Fehlermeldungen @@ -91,3 +129,5 @@ Für Produktionsumgebungen empfehlen sich: - ✅ Whitelist für vertrauenswürdige IPs (z.B. Admin-Zugang) - ✅ Anpassung der Limits basierend auf tatsächlichem Nutzungsverhalten +Siehe auch `docs/redis-migration.md` für Hinweise zur Migration auf Redis in Multi-Instance-Setups. + diff --git a/docs/redis-migration.md b/docs/redis-migration.md new file mode 100644 index 0000000..a20eafa --- /dev/null +++ b/docs/redis-migration.md @@ -0,0 +1,355 @@ +# Redis Migration Guide + +## Overview + +This guide covers migrating from in-memory KV storage to Redis for multi-instance SaaS deployments. The current in-memory session storage works well for single-instance deployments but requires centralized storage for horizontal scaling. + +### When to Migrate + +**Current Setup (Sufficient For):** +- Single-instance deployments +- Development environments +- Small-scale production deployments + +**Redis Required For:** +- Multiple server instances behind load balancer +- Container orchestration (Kubernetes, Docker Swarm) +- High-availability SaaS deployments +- Session persistence across server restarts + +## What Needs Migration + +### Critical Data (Must Migrate) +- **Sessions** (`sessionsKV`): Essential for authentication across instances +- **CSRF tokens**: Stored within session objects +- **Rate limiting data**: Currently in-memory Map in `rate-limiter.ts` + +### Optional Data (Can Remain File-based) +- Bookings, treatments, reviews, gallery photos +- Can stay in file-based KV for now +- Migrate later if performance becomes an issue + +## Redis Setup + +### Installation Options + +#### Self-hosted Redis +```bash +# Docker +docker run -d -p 6379:6379 redis:alpine + +# Ubuntu/Debian +sudo apt update +sudo apt install redis-server + +# macOS (Homebrew) +brew install redis +brew services start redis +``` + +#### Managed Services +- **Redis Cloud**: Managed Redis service +- **AWS ElastiCache**: Redis-compatible cache +- **Azure Cache for Redis**: Microsoft's managed service +- **DigitalOcean Managed Databases**: Redis option available + +### Configuration + +Add to `.env`: +```bash +# Redis Configuration (required for multi-instance deployments) +REDIS_URL=redis://localhost:6379 +REDIS_TLS_ENABLED=false # Set to true for production +REDIS_PASSWORD=your_redis_password # Optional +``` + +## Code Changes + +### 1. Install Redis Client + +```bash +pnpm add ioredis +pnpm add -D @types/ioredis +``` + +### 2. Create Redis KV Adapter + +Create `src/server/lib/create-redis-kv.ts`: + +```typescript +import Redis from 'ioredis'; + +interface RedisKVStore { + getItem(key: string): Promise; + setItem(key: string, value: T): Promise; + removeItem(key: string): Promise; + getAllItems(): Promise; + subscribe(): AsyncIterable; +} + +export function createRedisKV(namespace: string): RedisKVStore { + const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379', { + retryDelayOnFailover: 100, + enableReadyCheck: false, + maxRetriesPerRequest: null, + lazyConnect: true, + }); + + const prefix = `${namespace}:`; + + return { + async getItem(key: string): Promise { + const value = await redis.get(`${prefix}${key}`); + return value ? JSON.parse(value) : null; + }, + + async setItem(key: string, value: T): Promise { + await redis.set(`${prefix}${key}`, JSON.stringify(value)); + }, + + async removeItem(key: string): Promise { + await redis.del(`${prefix}${key}`); + }, + + async getAllItems(): Promise { + const keys = await redis.keys(`${prefix}*`); + if (keys.length === 0) return []; + + const values = await redis.mget(...keys); + return values + .filter(v => v !== null) + .map(v => JSON.parse(v!)); + }, + + async* subscribe(): AsyncIterable { + const pubsub = new Redis(process.env.REDIS_URL || 'redis://localhost:6379'); + await pubsub.subscribe(`${namespace}:changes`); + + for await (const message of pubsub) { + if (message[0] === 'message' && message[1] === `${namespace}:changes`) { + yield; + } + } + } + }; +} + +// Helper to notify subscribers of changes +export async function notifyRedisChanges(namespace: string): Promise { + const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379'); + await redis.publish(`${namespace}:changes`, 'update'); + await redis.quit(); +} +``` + +### 3. Update Session Storage + +In `src/server/lib/auth.ts`: + +```typescript +import { createRedisKV } from './create-redis-kv.js'; + +// Replace this line: +// export const sessionsKV = createKV("sessions"); + +// With this: +export const sessionsKV = createRedisKV("sessions"); +``` + +### 4. Update Rate Limiter + +In `src/server/lib/rate-limiter.ts`: + +```typescript +import Redis from 'ioredis'; + +const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379', { + retryDelayOnFailover: 100, + enableReadyCheck: false, + maxRetriesPerRequest: null, + lazyConnect: true, +}); + +export async function checkBookingRateLimit( + email?: string, + ip?: string +): Promise<{ allowed: boolean; resetTime?: number }> { + const now = Date.now(); + const windowMs = 15 * 60 * 1000; // 15 minutes + const maxRequests = 3; + + const results = await Promise.all([ + email ? checkRateLimit(`booking:email:${email}`, maxRequests, windowMs) : { allowed: true }, + ip ? checkRateLimit(`booking:ip:${ip}`, maxRequests, windowMs) : { allowed: true } + ]); + + return { + allowed: results.every(r => r.allowed), + resetTime: results.find(r => !r.allowed)?.resetTime + }; +} + +async function checkRateLimit( + key: string, + maxRequests: number, + windowMs: number +): Promise<{ allowed: boolean; resetTime?: number }> { + const now = Date.now(); + const windowStart = now - windowMs; + + // Use Redis sorted set for sliding window + const pipeline = redis.pipeline(); + + // Remove old entries + pipeline.zremrangebyscore(key, 0, windowStart); + + // Count current entries + pipeline.zcard(key); + + // Add current request + pipeline.zadd(key, now, `${now}-${Math.random()}`); + + // Set expiry + pipeline.expire(key, Math.ceil(windowMs / 1000)); + + const results = await pipeline.exec(); + const currentCount = results?.[1]?.[1] as number || 0; + + return { + allowed: currentCount < maxRequests, + resetTime: currentCount >= maxRequests ? windowStart + windowMs : undefined + }; +} +``` + +### 5. Environment Configuration + +Update `.env.example`: + +```bash +# Redis Configuration (optional - required for multi-instance SaaS deployments) +# For single-instance deployments, the default in-memory storage is sufficient +# See docs/redis-migration.md for migration guide +REDIS_URL=redis://localhost:6379 +REDIS_TLS_ENABLED=false # Enable for production +REDIS_PASSWORD=your_redis_password # Optional +``` + +## Migration Strategy + +### Development Testing + +1. **Start Redis locally**: + ```bash + docker run -d -p 6379:6379 redis:alpine + ``` + +2. **Test session functionality**: + - Login/logout + - Session validation + - CSRF token generation/validation + - Session rotation + +3. **Test rate limiting**: + - Verify rate limit enforcement + - Check sliding window behavior + +4. **Simulate multi-instance**: + - Run multiple server instances + - Verify session sharing works + +### Staging Deployment + +1. **Deploy Redis in staging** +2. **Run single instance first** to verify functionality +3. **Scale to multiple instances** and test session sharing +4. **Monitor Redis memory usage** and connection pool + +### Production Rollout + +#### Option A: Blue/Green Deployment +1. Deploy new version with Redis to green environment +2. Test thoroughly with production-like data +3. Switch traffic to green environment +4. **Note**: Existing sessions will be lost (users need to re-login) + +#### Option B: Gradual Migration +1. Deploy Redis alongside existing system +2. Write to both stores temporarily (dual-write) +3. Read from Redis first, fallback to in-memory +4. After verification period, remove in-memory store + +## Monitoring & Maintenance + +### Key Metrics + +- **Redis memory usage**: Monitor `used_memory` and `used_memory_peak` +- **Connection pool**: Track active connections and pool utilization +- **Session operations**: Monitor session creation/validation latency +- **Rate limit hits**: Track rate limit enforcement effectiveness + +### Backup Strategy + +- **Enable Redis persistence**: Configure RDB snapshots or AOF logging +- **Regular backups**: Backup Redis data regularly +- **Session data**: Ephemeral, but rate limit data should be backed up + +### Scaling Considerations + +- **Redis Cluster**: For horizontal scaling across multiple nodes +- **Redis Sentinel**: For high availability with automatic failover +- **Connection pooling**: Configure appropriate pool sizes + +## Rollback Plan + +If issues arise, you can revert to in-memory storage: + +1. **Remove Redis imports** and revert to `createKV` +2. **Remove Redis environment variables** +3. **Redeploy** with in-memory storage +4. **Note**: All sessions will be lost (users need to re-login) + +## Cost Considerations + +### Redis Hosting Costs + +- **Self-hosted**: Server costs + maintenance +- **Managed services**: + - Redis Cloud: ~$7/month for 30MB + - AWS ElastiCache: ~$15/month for t3.micro + - Azure Cache: ~$16/month for Basic tier + +### Memory Requirements + +**Session storage**: Minimal +- Example: 1000 concurrent users × 1KB per session = ~1MB +- CSRF tokens add minimal overhead + +**Rate limiting**: Negligible +- Sliding window data is automatically cleaned up +- Minimal memory footprint per IP/email + +## Alternative Solutions + +### Sticky Sessions +- Load balancer routes user to same instance +- **Pros**: Simpler implementation +- **Cons**: Less resilient, harder to scale + +### Database-backed Sessions +- Use PostgreSQL/MySQL instead of Redis +- **Pros**: No additional infrastructure +- **Cons**: Higher latency, more database load + +### JWT Tokens +- Stateless authentication tokens +- **Pros**: No server-side session storage needed +- **Cons**: No server-side session invalidation, different security model + +## References + +- [Session Management Guide](session-management.md) +- [Rate Limiting Documentation](rate-limiting.md) +- [Redis Documentation](https://redis.io/documentation) +- [ioredis Library](https://github.com/luin/ioredis) +- [Redis Best Practices](https://redis.io/docs/manual/admin/) diff --git a/docs/session-management.md b/docs/session-management.md new file mode 100644 index 0000000..28f81de --- /dev/null +++ b/docs/session-management.md @@ -0,0 +1,251 @@ +# Session Management & CSRF Protection + +## Overview + +This application uses **HttpOnly cookie-based session management** with CSRF protection to provide secure authentication while protecting against common web vulnerabilities like XSS and CSRF attacks. + +### Security Benefits + +- **XSS Protection**: SessionId stored in HttpOnly cookies is not accessible to malicious JavaScript +- **CSRF Protection**: Double-submit cookie pattern prevents cross-site request forgery +- **Session Rotation**: New sessions created after login and password changes prevent session fixation +- **GDPR Compliance**: HttpOnly cookies provide better privacy protection than localStorage + +## Architecture + +### Session Storage +Sessions are stored in an in-memory KV store with the following structure: +```typescript +type Session = { + id: string; + userId: string; + expiresAt: string; + createdAt: string; + csrfToken?: string; +} +``` + +- **Expiration**: 24 hours +- **Storage**: In-memory KV store (single-instance deployment) +- **CSRF Token**: Cryptographically secure 64-character hex string + +### Cookie Configuration + +#### Session Cookie (`sessionId`) +- **Type**: HttpOnly, Secure (production), SameSite=Lax +- **Path**: `/` +- **MaxAge**: 86400 seconds (24 hours) +- **Purpose**: Authenticates user across requests + +#### CSRF Cookie (`csrf-token`) +- **Type**: Non-HttpOnly, Secure (production), SameSite=Lax +- **Path**: `/` +- **MaxAge**: 86400 seconds (24 hours) +- **Purpose**: Provides CSRF token for JavaScript to include in requests + +### CSRF Protection + +The application uses the **double-submit cookie pattern**: + +1. **Server-side**: CSRF token generated and stored in session +2. **Client-side**: Same token stored in non-HttpOnly cookie +3. **Validation**: Token from `X-CSRF-Token` header must match session token +4. **Timing-safe comparison**: Prevents timing attacks + +### Session Rotation + +Sessions are automatically rotated (new session created, old invalidated) after: +- Successful login +- Password changes + +This prevents session fixation attacks. + +## Implementation Details + +### Server-side + +#### Cookie Parsing Middleware (`src/server/routes/rpc.ts`) +```typescript +// Cookie parsing middleware - extracts sessionId from cookies +rpcApp.use("/*", async (c, next) => { + try { + const sessionId = getCookie(c, SESSION_COOKIE_NAME); + c.set('sessionId', sessionId || null); + await next(); + } catch (error) { + console.error("Cookie parsing error:", error); + c.set('sessionId', null); + await next(); + } +}); +``` + +#### Authentication Helper (`src/server/lib/auth.ts`) +Key functions: +- `generateCSRFToken()`: Creates cryptographically secure token +- `getSessionFromCookies(c)`: Extracts and validates session from cookies +- `validateCSRFToken(c, sessionId)`: Validates CSRF token from header +- `assertOwner(c)`: Validates owner role with session and CSRF checks +- `rotateSession(oldSessionId, userId)`: Creates new session, invalidates old + +#### RPC Handler Updates +All admin-only RPC handlers now: +- Accept Hono `context` parameter +- Use `assertOwner(context)` for authentication +- Remove `sessionId` from input schemas +- Automatically get session from cookies + +### Client-side + +#### RPC Client Configuration (`src/client/rpc-client.ts`) +```typescript +const link = new RPCLink({ + url: `${window.location.origin}/rpc`, + headers: () => { + const csrfToken = getCSRFToken(); + return csrfToken ? { 'X-CSRF-Token': csrfToken } : {}; + }, + fetch: (request, init) => { + return fetch(request, { + ...init, + credentials: 'include' // Include cookies with all requests + }); + } +}); +``` + +#### AuthProvider Updates (`src/client/components/auth-provider.tsx`) +- Removed all localStorage usage +- Sessions managed entirely server-side +- No client-side sessionId storage + +## Security Features + +### XSS Protection +- SessionId not accessible to JavaScript (HttpOnly cookie) +- Malicious scripts cannot steal session tokens + +### CSRF Protection +- Token validation on all state-changing operations (non-GET requests) +- Double-submit cookie pattern prevents CSRF attacks +- Timing-safe comparison prevents timing attacks + +### Session Fixation Prevention +- Session rotation after authentication events +- Old sessions invalidated when new ones created + +### Secure Defaults +- Secure flag enabled in production (requires HTTPS) +- SameSite=Lax prevents most CSRF attacks +- HttpOnly cookies prevent XSS token theft + +## Development vs Production + +### Development +- `secure: false` - allows cookies over HTTP (localhost) +- `NODE_ENV !== 'production'` detection + +### Production +- `secure: true` - requires HTTPS +- All security flags enabled + +## API Reference + +### Key Functions (`src/server/lib/auth.ts`) + +#### `generateCSRFToken(): string` +Creates a cryptographically secure random token using `crypto.randomBytes(32).toString('hex')`. + +#### `getSessionFromCookies(c: Context): Promise` +Extracts sessionId from cookies, validates session exists and hasn't expired. + +#### `validateCSRFToken(c: Context, sessionId: string): Promise` +Validates CSRF token from `X-CSRF-Token` header against session token using timing-safe comparison. + +#### `assertOwner(c: Context): Promise` +Validates user has owner role and session is valid. Automatically validates CSRF token for non-GET requests. + +#### `rotateSession(oldSessionId: string, userId: string): Promise` +Creates new session with new ID and CSRF token, deletes old session. + +## Migration Guide + +### Existing Password Hashes +- Base64 password hashes automatically migrated to bcrypt on server startup +- No manual intervention required + +### Session Invalidation +- Old localStorage sessions will be invalidated +- Users need to re-login once after deployment + +### Testing Migration +1. Deploy new version +2. Verify login creates cookies (check browser DevTools) +3. Test CSRF protection by manually calling API without token +4. Verify session rotation after password change + +## Troubleshooting + +### Cookies Not Being Sent +- Check `credentials: 'include'` in fetch configuration +- Verify CORS settings allow credentials +- Check cookie domain/path configuration + +### CSRF Validation Failing +- Ensure `X-CSRF-Token` header is set +- Verify CSRF cookie is accessible to JavaScript +- Check token format (64-character hex string) + +### Session Expired Errors +- Check cookie expiration settings +- Verify server time synchronization +- Check session cleanup logic + +### Cross-Origin Issues +- Review CORS configuration for credentials +- Ensure domain configuration matches deployment +- Check SameSite cookie settings + +## Future Scaling + +For multi-instance deployments, see [Redis Migration Guide](redis-migration.md) for migrating to centralized session storage. + +## Environment Configuration + +### Required Environment Variables + +```bash +# Domain Configuration +DOMAIN=localhost:5173 # For production: your-domain.com +# Note: Session cookies are scoped to this domain + +# Server Configuration +NODE_ENV=development # Set to 'production' for production deployment +PORT=3000 +``` + +### Optional Environment Variables + +```bash +# Redis Configuration (for multi-instance deployments) +# See docs/redis-migration.md for migration guide +REDIS_URL=redis://localhost:6379 +REDIS_TLS_ENABLED=false # Set to true for production +REDIS_PASSWORD=your_redis_password # Optional +``` + +### Cookie Behavior by Environment + +- **Development** (`NODE_ENV !== 'production'`): + - `secure: false` - cookies work over HTTP + - `sameSite: 'Lax'` - allows cross-site navigation + +- **Production** (`NODE_ENV === 'production'`): + - `secure: true` - requires HTTPS + - `sameSite: 'Lax'` - prevents most CSRF attacks + +## References + +- [OWASP CSRF Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) +- [MDN HttpOnly Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) +- [RFC 6265 - HTTP State Management](https://tools.ietf.org/html/rfc6265) diff --git a/package.json b/package.json index c0273d8..b1598d8 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "preview": "vite preview" }, "dependencies": { + "@hono/cors": "^2.0.0", "@types/bcrypt": "^5.0.2", "bcrypt": "^5.1.1", "@hono/node-server": "^1.19.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7751dc1..e25a829 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: version: 1.8.8(@opentelemetry/api@1.9.0) '@orpc/server': specifier: ^1.8.8 - version: 1.8.8(@opentelemetry/api@1.9.0)(crossws@0.3.5) + version: 1.8.8(@opentelemetry/api@1.9.0)(crossws@0.3.5)(ws@8.18.3) '@orpc/tanstack-query': specifier: ^1.8.8 version: 1.8.8(@opentelemetry/api@1.9.0)(@orpc/client@1.8.8(@opentelemetry/api@1.9.0))(@tanstack/query-core@5.85.5) @@ -26,18 +26,27 @@ importers: '@tanstack/react-query': specifier: ^5.85.5 version: 5.85.5(react@19.1.1) + '@types/bcrypt': + specifier: ^5.0.2 + version: 5.0.2 + bcrypt: + specifier: ^5.1.1 + version: 5.1.1 dotenv: specifier: ^17.2.3 version: 17.2.3 hono: specifier: ^4.9.4 version: 4.9.4 + isomorphic-dompurify: + specifier: ^2.16.0 + version: 2.28.0(postcss@8.5.6) jsonrepair: specifier: ^3.13.0 version: 3.13.0 openai: specifier: ^5.17.0 - version: 5.17.0(zod@4.1.5) + version: 5.17.0(ws@8.18.3)(zod@4.1.5) react: specifier: ^19.1.1 version: 19.1.1 @@ -106,6 +115,15 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@asamuzakjp/css-color@4.0.5': + resolution: {integrity: sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==} + + '@asamuzakjp/dom-selector@6.6.1': + resolution: {integrity: sha512-8QT9pokVe1fUt1C8IrJketaeFOdRfTOS96DL3EBjE8CRZm3eHnwMlQe2NPoOSEYPwJ5Q25uYoX1+m9044l3ysQ==} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -227,6 +245,40 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.14': + resolution: {integrity: sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + '@esbuild/aix-ppc64@0.25.5': resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} engines: {node: '>=18'} @@ -482,6 +534,10 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@mapbox/node-pre-gyp@1.0.11': + resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} + hasBin: true + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -775,6 +831,9 @@ packages: '@types/babel__traverse@7.20.7': resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} + '@types/bcrypt@5.0.2': + resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -792,6 +851,9 @@ packages: '@types/react@19.1.11': resolution: {integrity: sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@typescript-eslint/eslint-plugin@8.40.0': resolution: {integrity: sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -857,6 +919,9 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -871,9 +936,21 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -882,6 +959,14 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + aproba@2.1.0: + resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} + + are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -891,6 +976,13 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bcrypt@5.1.1: + resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} + engines: {node: '>= 10.0.0'} + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -921,6 +1013,10 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -932,9 +1028,16 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -955,9 +1058,21 @@ packages: crossws@0.3.5: resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + cssstyle@5.3.1: + resolution: {integrity: sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==} + engines: {node: '>=20'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + data-urls@6.0.0: + resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==} + engines: {node: '>=20'} + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -967,12 +1082,18 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} @@ -984,6 +1105,9 @@ packages: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} + dompurify@3.2.7: + resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} + dotenv@17.2.3: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} @@ -991,10 +1115,17 @@ packages: electron-to-chromium@1.5.171: resolution: {integrity: sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + esbuild@0.25.5: resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} engines: {node: '>=18'} @@ -1106,11 +1237,23 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1123,6 +1266,10 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -1147,6 +1294,9 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} @@ -1157,6 +1307,26 @@ packages: resolution: {integrity: sha512-61hl6MF6ojTl/8QSRu5ran6GXt+6zsngIUN95KzF5v5UjiX/xnrLR358BNRawwIRO49JwUqJqQe3Rb2v559R8Q==} engines: {node: '>=16.9.0'} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1173,6 +1343,13 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -1180,6 +1357,10 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -1188,9 +1369,16 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isomorphic-dompurify@2.28.0: + resolution: {integrity: sha512-9G5v8g4tYoix5odskjG704Khm1zNrqqqOC4YjCwEUhx0OvuaijRCprAV2GwJ9iw/01c6H1R+rs/2AXPZLlgDaQ==} + engines: {node: '>=18'} + jiti@2.5.1: resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} hasBin: true @@ -1202,6 +1390,15 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsdom@27.0.0: + resolution: {integrity: sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==} + engines: {node: '>=20'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1306,15 +1503,26 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.2.2: + resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1330,14 +1538,31 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + minizlib@3.0.2: resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} engines: {node: '>= 18'} + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + mkdirp@3.0.1: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} engines: {node: '>=10'} @@ -1354,22 +1579,50 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-mock-http@1.0.2: resolution: {integrity: sha512-zWaamgDUdo9SSLw47we78+zYw/bDr5gH8pH7oRRs8V3KmBtu8GLgGIbV2p/gRPd3LWpEOpjQj7X1FOU3VFMJ8g==} node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + ofetch@1.4.1: resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + openai@5.17.0: resolution: {integrity: sha512-7djpJzihvQ2KlKdfhLcShB+DYQpKW+EPndSM86AvKfXCISnqDYnA/aZtrUWa+VB8zvG/K0rJ6Cmjtd79b64u2w==} hasBin: true @@ -1401,10 +1654,17 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1455,10 +1715,18 @@ packages: resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} engines: {node: '>=0.10.0'} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1467,14 +1735,32 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + rollup@4.44.0: resolution: {integrity: sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} @@ -1487,6 +1773,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1495,10 +1784,24 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1507,6 +1810,9 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tailwindcss@4.1.12: resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==} @@ -1514,6 +1820,10 @@ packages: resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} engines: {node: '>=6'} + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + tar@7.4.3: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} @@ -1522,10 +1832,28 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + tldts-core@7.0.16: + resolution: {integrity: sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==} + + tldts@7.0.16: + resolution: {integrity: sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==} + hasBin: true + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tough-cookie@6.0.0: + resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + engines: {node: '>=16'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -1656,6 +1984,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -1707,18 +2038,72 @@ packages: yaml: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@8.0.0: + resolution: {integrity: sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==} + engines: {node: '>=20'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@15.1.0: + resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} + engines: {node: '>=20'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -1750,6 +2135,24 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.30 + '@asamuzakjp/css-color@4.0.5': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 11.2.2 + + '@asamuzakjp/dom-selector@6.6.1': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.1.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.2 + + '@asamuzakjp/nwsapi@2.3.9': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -1918,6 +2321,30 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.14(postcss@8.5.6)': + dependencies: + postcss: 8.5.6 + + '@csstools/css-tokenizer@3.0.4': {} + '@esbuild/aix-ppc64@0.25.5': optional: true @@ -2090,6 +2517,21 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@mapbox/node-pre-gyp@1.0.11': + dependencies: + detect-libc: 2.0.4 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.7.0 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.7.2 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2125,7 +2567,7 @@ snapshots: '@orpc/interop@1.8.8': {} - '@orpc/server@1.8.8(@opentelemetry/api@1.9.0)(crossws@0.3.5)': + '@orpc/server@1.8.8(@opentelemetry/api@1.9.0)(crossws@0.3.5)(ws@8.18.3)': dependencies: '@orpc/client': 1.8.8(@opentelemetry/api@1.9.0) '@orpc/contract': 1.8.8(@opentelemetry/api@1.9.0) @@ -2139,6 +2581,7 @@ snapshots: cookie: 1.0.2 optionalDependencies: crossws: 0.3.5 + ws: 8.18.3 transitivePeerDependencies: - '@opentelemetry/api' @@ -2365,6 +2808,10 @@ snapshots: dependencies: '@babel/types': 7.28.2 + '@types/bcrypt@5.0.2': + dependencies: + '@types/node': 22.17.2 + '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} @@ -2381,6 +2828,9 @@ snapshots: dependencies: csstype: 3.1.3 + '@types/trusted-types@2.0.7': + optional: true + '@typescript-eslint/eslint-plugin@8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -2486,6 +2936,8 @@ snapshots: transitivePeerDependencies: - supports-color + abbrev@1.1.1: {} + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -2496,6 +2948,14 @@ snapshots: acorn@8.15.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + agent-base@7.1.4: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -2503,6 +2963,8 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@5.0.1: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -2512,12 +2974,31 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + aproba@2.1.0: {} + + are-we-there-yet@2.0.0: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + arg@4.1.3: {} argparse@2.0.1: {} balanced-match@1.0.2: {} + bcrypt@5.1.1: + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + node-addon-api: 5.1.0 + transitivePeerDependencies: + - encoding + - supports-color + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -2551,6 +3032,8 @@ snapshots: dependencies: readdirp: 4.1.2 + chownr@2.0.0: {} + chownr@3.0.0: {} color-convert@2.0.1: @@ -2559,8 +3042,12 @@ snapshots: color-name@1.1.4: {} + color-support@1.1.3: {} + concat-map@0.0.1: {} + console-control-strings@1.1.0: {} + convert-source-map@2.0.0: {} cookie-es@1.2.2: {} @@ -2579,31 +3066,61 @@ snapshots: dependencies: uncrypto: 0.1.3 + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + cssstyle@5.3.1(postcss@8.5.6): + dependencies: + '@asamuzakjp/css-color': 4.0.5 + '@csstools/css-syntax-patches-for-csstree': 1.0.14(postcss@8.5.6) + css-tree: 3.1.0 + transitivePeerDependencies: + - postcss + csstype@3.1.3: {} + data-urls@6.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + debug@4.4.1: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + deep-is@0.1.4: {} defu@6.1.4: {} + delegates@1.0.0: {} + destr@2.0.5: {} detect-libc@2.0.4: {} diff@4.0.2: {} + dompurify@3.2.7: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dotenv@17.2.3: {} electron-to-chromium@1.5.171: {} + emoji-regex@8.0.0: {} + enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 tapable: 2.2.2 + entities@6.0.1: {} + esbuild@0.25.5: optionalDependencies: '@esbuild/aix-ppc64': 0.25.5 @@ -2763,9 +3280,27 @@ snapshots: flatted@3.3.3: {} + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs.realpath@1.0.0: {} + fsevents@2.3.3: optional: true + gauge@3.0.2: + dependencies: + aproba: 2.1.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + gensync@1.0.0-beta.2: {} glob-parent@5.1.2: @@ -2776,6 +3311,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + globals@14.0.0: {} globals@16.3.0: {} @@ -2800,6 +3344,8 @@ snapshots: has-flag@4.0.0: {} + has-unicode@2.0.1: {} + hermes-estree@0.25.1: {} hermes-parser@0.25.1: @@ -2808,6 +3354,35 @@ snapshots: hono@4.9.4: {} + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -2819,18 +3394,40 @@ snapshots: imurmurhash@0.1.4: {} + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + iron-webcrypto@1.2.1: {} is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: {} + isexe@2.0.0: {} + isomorphic-dompurify@2.28.0(postcss@8.5.6): + dependencies: + dompurify: 3.2.7 + jsdom: 27.0.0(postcss@8.5.6) + transitivePeerDependencies: + - bufferutil + - canvas + - postcss + - supports-color + - utf-8-validate + jiti@2.5.1: {} js-tokens@4.0.0: {} @@ -2839,6 +3436,34 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@27.0.0(postcss@8.5.6): + dependencies: + '@asamuzakjp/dom-selector': 6.6.1 + cssstyle: 5.3.1(postcss@8.5.6) + data-urls: 6.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 7.3.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + ws: 8.18.3 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - postcss + - supports-color + - utf-8-validate + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -2913,6 +3538,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.2.2: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -2921,8 +3548,14 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + make-error@1.3.6: {} + mdn-data@2.12.2: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -2938,12 +3571,25 @@ snapshots: dependencies: brace-expansion: 2.0.2 + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + minipass@7.1.2: {} + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + minizlib@3.0.2: dependencies: minipass: 7.1.2 + mkdirp@1.0.4: {} + mkdirp@3.0.1: {} ms@2.1.3: {} @@ -2952,22 +3598,46 @@ snapshots: natural-compare@1.4.0: {} + node-addon-api@5.1.0: {} + node-fetch-native@1.6.7: {} + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-mock-http@1.0.2: {} node-releases@2.0.19: {} + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + normalize-path@3.0.0: {} + npmlog@5.0.1: + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + + object-assign@4.1.1: {} + ofetch@1.4.1: dependencies: destr: 2.0.5 node-fetch-native: 1.6.7 ufo: 1.6.1 - openai@5.17.0(zod@4.1.5): + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + openai@5.17.0(ws@8.18.3)(zod@4.1.5): optionalDependencies: + ws: 8.18.3 zod: 4.1.5 openapi-types@12.1.3: {} @@ -2993,8 +3663,14 @@ snapshots: dependencies: callsites: 3.1.0 + parse5@7.3.0: + dependencies: + entities: 6.0.1 + path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} picocolors@1.1.1: {} @@ -3028,12 +3704,24 @@ snapshots: react@19.1.1: {} + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + readdirp@4.1.2: {} + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} reusify@1.1.0: {} + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + rollup@4.44.0: dependencies: '@types/estree': 1.0.8 @@ -3060,34 +3748,73 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.44.0 fsevents: 2.3.3 + rrweb-cssom@0.8.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.26.0: {} semver@6.3.1: {} semver@7.7.2: {} + set-blocking@2.0.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + signal-exit@3.0.7: {} + source-map-js@1.2.1: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strip-json-comments@3.1.1: {} supports-color@7.2.0: dependencies: has-flag: 4.0.0 + symbol-tree@3.2.4: {} + tailwindcss@4.1.12: {} tapable@2.2.2: {} + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + tar@7.4.3: dependencies: '@isaacs/fs-minipass': 4.0.1 @@ -3102,10 +3829,26 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tldts-core@7.0.16: {} + + tldts@7.0.16: + dependencies: + tldts-core: 7.0.16 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tough-cookie@6.0.0: + dependencies: + tldts: 7.0.16 + + tr46@0.0.3: {} + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + ts-api-utils@2.1.0(typescript@5.9.2): dependencies: typescript: 5.9.2 @@ -3178,6 +3921,8 @@ snapshots: dependencies: punycode: 2.3.1 + util-deprecate@1.0.2: {} + v8-compile-cache-lib@3.0.1: {} vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)): @@ -3205,14 +3950,52 @@ snapshots: jiti: 2.5.1 lightningcss: 1.30.1 + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@3.0.1: {} + + webidl-conversions@8.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@15.1.0: + dependencies: + tr46: 6.0.0 + webidl-conversions: 8.0.0 + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which@2.0.2: dependencies: isexe: 2.0.0 + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + word-wrap@1.2.5: {} + wrappy@1.0.2: {} + + ws@8.18.3: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + yallist@3.1.1: {} + yallist@4.0.0: {} + yallist@5.0.0: {} yn@3.1.1: {} diff --git a/scripts/rebuild-prod.sh b/scripts/rebuild-prod.sh index 06c2431..2410f12 100644 --- a/scripts/rebuild-prod.sh +++ b/scripts/rebuild-prod.sh @@ -45,5 +45,20 @@ for i in {1..18}; do sleep 5 done -echo "[7/7] Tail recent logs (press Ctrl+C to exit)" -sudo docker compose -f "$COMPOSE_FILE" logs --since=10m -f +echo "[6b/7] Verify HTTP /health via Caddy (localhost)" +for i in {1..12}; do + if curl -fsS http://localhost/health >/dev/null 2>&1; then + echo "Caddy proxy responds OK on /health." + break + fi + sleep 5 +done + +echo "[7/7] Tail recent logs for app and caddy (press Ctrl+C to exit)" +sudo docker compose -f "$COMPOSE_FILE" logs --since=10m -f stargirlnails & +APP_LOG_PID=$! +sudo docker compose -f "$COMPOSE_FILE" logs --since=10m -f caddy & +CADDY_LOG_PID=$! + +trap "echo 'Stopping log tails...'; kill $APP_LOG_PID $CADDY_LOG_PID 2>/dev/null || true" INT TERM +wait $APP_LOG_PID $CADDY_LOG_PID || true diff --git a/src/client/components/admin-availability.tsx b/src/client/components/admin-availability.tsx index 73e6194..7232d8a 100644 --- a/src/client/components/admin-availability.tsx +++ b/src/client/components/admin-availability.tsx @@ -24,12 +24,12 @@ export function AdminAvailability() { // Neue Queries für wiederkehrende Verfügbarkeiten (mit Authentifizierung) const { data: recurringRules, refetch: refetchRecurringRules } = useQuery( queryClient.recurringAvailability.live.adminListRules.experimental_liveOptions({ - input: { sessionId: localStorage.getItem("sessionId") || "" } + input: {} }) ); const { data: timeOffPeriods } = useQuery( queryClient.recurringAvailability.live.adminListTimeOff.experimental_liveOptions({ - input: { sessionId: localStorage.getItem("sessionId") || "" } + input: {} }) ); @@ -177,14 +177,9 @@ export function AdminAvailability() { return; } - const sessionId = localStorage.getItem("sessionId") || ""; - if (!sessionId) { - setErrorMsg("Nicht eingeloggt. Bitte als Inhaber anmelden."); - return; - } createRule( - { sessionId, dayOfWeek: selectedDayOfWeek, startTime: ruleStartTime, endTime: ruleEndTime }, + { dayOfWeek: selectedDayOfWeek, startTime: ruleStartTime, endTime: ruleEndTime }, { onSuccess: () => { setSuccessMsg(`Regel für ${getDayName(selectedDayOfWeek)} erstellt.`); @@ -233,13 +228,8 @@ export function AdminAvailability() {