AtAuth
7

Configure Feed

Select the types of activity you want to include in your feed.

feat: Add OIDC provider, passkeys, MFA, and admin UI

Major feature release transforming ATAuth into a full SSO provider:

## OIDC Provider
- OpenID Connect discovery endpoints (/.well-known/*)
- Authorization, token, userinfo, revocation endpoints
- ES256/RS256 JWT signing with key rotation
- PKCE support for public clients

## Authentication Methods
- Passkey/WebAuthn support for passwordless login
- TOTP MFA with backup codes
- Email verification and account recovery

## Admin UI
- React + TailwindCSS admin dashboard
- Manage legacy apps and OIDC clients
- Session management and user administration
- Signing key rotation

## Deployment
- Multi-stage Docker build with admin UI
- Kubernetes manifests with staging/production overlays
- Helm chart for flexible deployment
- Gitea Actions and Tekton CI/CD pipelines
- Database backup CronJob
- Safe deployment runbook

## Backward Compatibility
- Legacy HMAC token flow unchanged
- Existing /auth/* routes preserved
- New features are additive (new tables only)

Bryan Brooks (Feb 4, 2026, 6:15 PM -0600) 27b5f26b 74654f1a

+15192 -42
+6
gateway/.dockerignore
··· 1 1 # Dependencies 2 2 node_modules/ 3 + admin-ui/node_modules/ 3 4 4 5 # Build output (rebuilt in container) 5 6 dist/ 7 + public/admin/ 6 8 7 9 # Development files 8 10 *.log ··· 29 31 30 32 # Data (mounted as volume) 31 33 data/ 34 + 35 + # Kubernetes and Helm (not needed in image) 36 + k8s/ 37 + helm/
+84
gateway/.gitea/workflows/build.yaml
··· 1 + name: Build and Push 2 + 3 + on: 4 + push: 5 + branches: 6 + - main 7 + - develop 8 + tags: 9 + - 'v*' 10 + pull_request: 11 + branches: 12 + - main 13 + 14 + env: 15 + REGISTRY: gitea.cloudforest-basilisk.ts.net 16 + IMAGE_NAME: arcnode.xyz/atauth-gateway 17 + 18 + jobs: 19 + build: 20 + runs-on: ubuntu-latest 21 + steps: 22 + - name: Checkout 23 + uses: actions/checkout@v4 24 + 25 + - name: Set up Docker Buildx 26 + uses: docker/setup-buildx-action@v3 27 + 28 + - name: Log in to Container Registry 29 + uses: docker/login-action@v3 30 + with: 31 + registry: ${{ env.REGISTRY }} 32 + username: ${{ secrets.REGISTRY_USERNAME }} 33 + password: ${{ secrets.REGISTRY_PASSWORD }} 34 + 35 + - name: Extract metadata 36 + id: meta 37 + uses: docker/metadata-action@v5 38 + with: 39 + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 40 + tags: | 41 + type=ref,event=branch 42 + type=ref,event=pr 43 + type=semver,pattern={{version}} 44 + type=semver,pattern={{major}}.{{minor}} 45 + type=sha,prefix= 46 + type=raw,value=latest,enable={{is_default_branch}} 47 + 48 + - name: Build and push 49 + uses: docker/build-push-action@v5 50 + with: 51 + context: ./gateway 52 + push: ${{ github.event_name != 'pull_request' }} 53 + tags: ${{ steps.meta.outputs.tags }} 54 + labels: ${{ steps.meta.outputs.labels }} 55 + cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache 56 + cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max 57 + 58 + deploy: 59 + needs: build 60 + if: github.ref == 'refs/heads/main' && github.event_name == 'push' 61 + runs-on: ubuntu-latest 62 + steps: 63 + - name: Checkout 64 + uses: actions/checkout@v4 65 + 66 + - name: Set up kubectl 67 + uses: azure/setup-kubectl@v3 68 + 69 + - name: Configure kubeconfig 70 + run: | 71 + mkdir -p ~/.kube 72 + echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config 73 + chmod 600 ~/.kube/config 74 + 75 + - name: Deploy to k3s 76 + run: | 77 + # Update image tag in kustomization 78 + cd gateway/k8s 79 + kubectl kustomize . | \ 80 + sed "s|atauth-gateway:latest|${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}|g" | \ 81 + kubectl apply -f - 82 + 83 + # Wait for rollout 84 + kubectl -n atauth rollout status deployment/atauth-gateway --timeout=300s
+276
gateway/DEPLOYMENT.md
··· 1 + # ATAuth Gateway Deployment Guide 2 + 3 + This guide covers deploying ATAuth Gateway using Docker and Kubernetes. 4 + 5 + ## Prerequisites 6 + 7 + - Docker 20.10+ (for Docker deployment) 8 + - Kubernetes 1.25+ (for K8s deployment) 9 + - kubectl configured for your cluster 10 + - Helm 3.x (optional, for Helm deployment) 11 + 12 + ## Quick Start with Docker 13 + 14 + ### Build the Image 15 + 16 + ```bash 17 + cd gateway 18 + docker build -t atauth-gateway:latest . 19 + ``` 20 + 21 + ### Run with Docker 22 + 23 + ```bash 24 + # Generate secrets 25 + export ADMIN_TOKEN=$(openssl rand -hex 32) 26 + export OIDC_KEY_SECRET=$(openssl rand -hex 32) 27 + export MFA_ENCRYPTION_KEY=$(openssl rand -hex 32) 28 + 29 + # Run container 30 + docker run -d \ 31 + --name atauth-gateway \ 32 + -p 3100:3100 \ 33 + -v atauth-data:/app/data \ 34 + -e ADMIN_TOKEN=$ADMIN_TOKEN \ 35 + -e OIDC_KEY_SECRET=$OIDC_KEY_SECRET \ 36 + -e MFA_ENCRYPTION_KEY=$MFA_ENCRYPTION_KEY \ 37 + -e OIDC_ENABLED=true \ 38 + -e OIDC_ISSUER=https://auth.example.com \ 39 + -e OAUTH_CLIENT_ID=https://auth.example.com/client-metadata.json \ 40 + -e OAUTH_REDIRECT_URI=https://auth.example.com/auth/callback \ 41 + -e WEBAUTHN_RP_ID=auth.example.com \ 42 + -e WEBAUTHN_ORIGIN=https://auth.example.com \ 43 + atauth-gateway:latest 44 + ``` 45 + 46 + ### Run with Docker Compose 47 + 48 + ```bash 49 + # Create .env file with secrets 50 + cat > .env << EOF 51 + ADMIN_TOKEN=$(openssl rand -hex 32) 52 + OIDC_KEY_SECRET=$(openssl rand -hex 32) 53 + MFA_ENCRYPTION_KEY=$(openssl rand -hex 32) 54 + EOF 55 + 56 + # Start services 57 + docker-compose up -d 58 + 59 + # View logs 60 + docker-compose logs -f 61 + ``` 62 + 63 + ## Kubernetes Deployment 64 + 65 + ### Option 1: Using kubectl (Kustomize) 66 + 67 + 1. **Update configuration** 68 + 69 + Edit `k8s/configmap.yaml` with your domain settings: 70 + ```yaml 71 + OAUTH_CLIENT_ID: "https://your-domain.com/client-metadata.json" 72 + OIDC_ISSUER: "https://your-domain.com" 73 + WEBAUTHN_RP_ID: "your-domain.com" 74 + ``` 75 + 76 + 2. **Generate and update secrets** 77 + 78 + ```bash 79 + # Generate secrets 80 + ADMIN_TOKEN=$(openssl rand -hex 32) 81 + OIDC_KEY_SECRET=$(openssl rand -hex 32) 82 + MFA_ENCRYPTION_KEY=$(openssl rand -hex 32) 83 + 84 + # Update k8s/secret.yaml with generated values 85 + ``` 86 + 87 + 3. **Deploy** 88 + 89 + ```bash 90 + # Apply all resources 91 + kubectl apply -k k8s/ 92 + 93 + # Or apply individually 94 + kubectl apply -f k8s/namespace.yaml 95 + kubectl apply -f k8s/configmap.yaml 96 + kubectl apply -f k8s/secret.yaml 97 + kubectl apply -f k8s/pvc.yaml 98 + kubectl apply -f k8s/deployment.yaml 99 + kubectl apply -f k8s/service.yaml 100 + kubectl apply -f k8s/ingress.yaml 101 + ``` 102 + 103 + 4. **Verify deployment** 104 + 105 + ```bash 106 + kubectl -n atauth get pods 107 + kubectl -n atauth get svc 108 + kubectl -n atauth logs -f deployment/atauth-gateway 109 + ``` 110 + 111 + ### Option 2: Using Helm 112 + 113 + 1. **Install the chart** 114 + 115 + ```bash 116 + helm install atauth ./helm/atauth-gateway \ 117 + --namespace atauth \ 118 + --create-namespace \ 119 + --set config.oidc.issuer=https://auth.example.com \ 120 + --set config.oauthClientId=https://auth.example.com/client-metadata.json \ 121 + --set config.oauthRedirectUri=https://auth.example.com/auth/callback \ 122 + --set config.passkey.rpId=auth.example.com \ 123 + --set config.passkey.origin=https://auth.example.com \ 124 + --set secrets.adminToken=$(openssl rand -hex 32) \ 125 + --set secrets.oidcKeySecret=$(openssl rand -hex 32) \ 126 + --set secrets.mfaEncryptionKey=$(openssl rand -hex 32) \ 127 + --set ingress.enabled=true \ 128 + --set ingress.hosts[0].host=auth.example.com \ 129 + --set ingress.hosts[0].paths[0].path=/ \ 130 + --set ingress.hosts[0].paths[0].pathType=Prefix 131 + ``` 132 + 133 + 2. **Using a values file** 134 + 135 + Create `my-values.yaml`: 136 + ```yaml 137 + config: 138 + oidc: 139 + issuer: "https://auth.example.com" 140 + oauthClientId: "https://auth.example.com/client-metadata.json" 141 + oauthRedirectUri: "https://auth.example.com/auth/callback" 142 + passkey: 143 + rpId: "auth.example.com" 144 + origin: "https://auth.example.com" 145 + 146 + secrets: 147 + adminToken: "your-generated-token" 148 + oidcKeySecret: "your-oidc-secret" 149 + mfaEncryptionKey: "your-mfa-key" 150 + 151 + ingress: 152 + enabled: true 153 + className: traefik # or nginx 154 + annotations: 155 + cert-manager.io/cluster-issuer: letsencrypt-prod 156 + hosts: 157 + - host: auth.example.com 158 + paths: 159 + - path: / 160 + pathType: Prefix 161 + tls: 162 + - secretName: atauth-tls 163 + hosts: 164 + - auth.example.com 165 + ``` 166 + 167 + Then install: 168 + ```bash 169 + helm install atauth ./helm/atauth-gateway \ 170 + --namespace atauth \ 171 + --create-namespace \ 172 + -f my-values.yaml 173 + ``` 174 + 175 + 3. **Upgrade** 176 + 177 + ```bash 178 + helm upgrade atauth ./helm/atauth-gateway \ 179 + --namespace atauth \ 180 + -f my-values.yaml 181 + ``` 182 + 183 + ## TLS Configuration 184 + 185 + ### With cert-manager 186 + 187 + 1. Install cert-manager if not already installed 188 + 2. Create a ClusterIssuer for Let's Encrypt 189 + 3. Add annotations to Ingress: 190 + ```yaml 191 + annotations: 192 + cert-manager.io/cluster-issuer: letsencrypt-prod 193 + ``` 194 + 195 + ### With Traefik (k3s default) 196 + 197 + ```yaml 198 + annotations: 199 + traefik.ingress.kubernetes.io/router.entrypoints: websecure 200 + traefik.ingress.kubernetes.io/router.tls: "true" 201 + ``` 202 + 203 + ## Environment Variables Reference 204 + 205 + | Variable | Description | Default | 206 + |----------|-------------|---------| 207 + | `PORT` | Server port | `3100` | 208 + | `HOST` | Server host | `0.0.0.0` | 209 + | `DB_PATH` | SQLite database path | `/app/data/gateway.db` | 210 + | `ADMIN_TOKEN` | Admin API token | Required | 211 + | `OAUTH_CLIENT_ID` | AT Protocol OAuth client ID | Required | 212 + | `OAUTH_REDIRECT_URI` | OAuth callback URL | Required | 213 + | `CORS_ORIGINS` | Allowed CORS origins (comma-separated) | `http://localhost:3000` | 214 + | `OIDC_ENABLED` | Enable OIDC provider | `false` | 215 + | `OIDC_ISSUER` | OIDC issuer URL | Required if OIDC enabled | 216 + | `OIDC_KEY_SECRET` | Encryption key for OIDC keys | Required if OIDC enabled | 217 + | `OIDC_KEY_ALGORITHM` | JWT signing algorithm | `ES256` | 218 + | `PASSKEY_ENABLED` | Enable passkey auth | `true` | 219 + | `WEBAUTHN_RP_NAME` | WebAuthn relying party name | `ATAuth` | 220 + | `WEBAUTHN_RP_ID` | WebAuthn relying party ID | Required | 221 + | `WEBAUTHN_ORIGIN` | WebAuthn expected origin | Required | 222 + | `MFA_ENABLED` | Enable TOTP MFA | `true` | 223 + | `MFA_TOTP_ISSUER` | TOTP issuer name | `ATAuth` | 224 + | `MFA_ENCRYPTION_KEY` | Encryption key for TOTP secrets | Required if MFA enabled | 225 + | `EMAIL_ENABLED` | Enable email verification | `false` | 226 + | `EMAIL_PROVIDER` | Email provider (smtp/resend/sendgrid) | `smtp` | 227 + | `EMAIL_FROM` | From address for emails | Required if email enabled | 228 + 229 + ## Health Checks 230 + 231 + The gateway exposes a health endpoint at `/health`: 232 + 233 + ```bash 234 + curl http://localhost:3100/health 235 + ``` 236 + 237 + Response: 238 + ```json 239 + { 240 + "status": "ok", 241 + "service": "atauth-gateway", 242 + "timestamp": "2024-01-15T10:30:00.000Z" 243 + } 244 + ``` 245 + 246 + ## Admin UI 247 + 248 + The Admin UI is available at `/admin` when the gateway is running. Use your `ADMIN_TOKEN` to authenticate. 249 + 250 + ## Troubleshooting 251 + 252 + ### Pod not starting 253 + 254 + Check logs: 255 + ```bash 256 + kubectl -n atauth logs -f deployment/atauth-gateway 257 + ``` 258 + 259 + ### Database permission issues 260 + 261 + Ensure the PVC is properly provisioned and the container has write access: 262 + ```bash 263 + kubectl -n atauth describe pvc atauth-gateway-data 264 + ``` 265 + 266 + ### Ingress not working 267 + 268 + Verify Ingress controller is running: 269 + ```bash 270 + kubectl get pods -A | grep -E "(traefik|ingress-nginx)" 271 + ``` 272 + 273 + Check Ingress status: 274 + ```bash 275 + kubectl -n atauth describe ingress atauth-gateway 276 + ```
+33 -10
gateway/Dockerfile
··· 1 1 # ATAuth Gateway 2 - # AT Protocol OAuth gateway for self-hosted authentication 2 + # AT Protocol OAuth gateway with OIDC provider support 3 3 # 4 4 # Build: docker build -t atauth-gateway . 5 5 # Run: docker run -p 3100:3100 -v ./data:/app/data atauth-gateway 6 6 7 - FROM node:20-alpine AS builder 7 + # ============================================ 8 + # Stage 1: Build Admin UI 9 + # ============================================ 10 + FROM node:20-alpine AS admin-ui-builder 11 + 12 + WORKDIR /app/admin-ui 13 + 14 + # Install dependencies 15 + COPY admin-ui/package.json admin-ui/package-lock.json* ./ 16 + RUN npm ci 17 + 18 + # Build admin UI 19 + COPY admin-ui/ ./ 20 + RUN npm run build 21 + 22 + # ============================================ 23 + # Stage 2: Build Gateway Backend 24 + # ============================================ 25 + FROM node:20-alpine AS backend-builder 8 26 9 27 WORKDIR /app 10 28 11 29 # Install dependencies 12 - COPY package.json ./ 13 - RUN npm install 30 + COPY package.json package-lock.json* ./ 31 + RUN npm ci 14 32 15 33 # Build TypeScript 16 34 COPY tsconfig.json ./ 17 35 COPY src/ ./src/ 18 36 RUN npm run build 19 37 20 - # Production image 38 + # ============================================ 39 + # Stage 3: Production Image 40 + # ============================================ 21 41 FROM node:20-alpine 22 42 23 43 WORKDIR /app ··· 27 47 adduser -u 1001 -G atauth -s /bin/sh -D atauth 28 48 29 49 # Install production dependencies only 30 - COPY package.json ./ 31 - RUN npm install --omit=dev && npm cache clean --force 50 + COPY package.json package-lock.json* ./ 51 + RUN npm ci --omit=dev && npm cache clean --force 52 + 53 + # Copy built backend 54 + COPY --from=backend-builder /app/dist ./dist 32 55 33 - # Copy built files 34 - COPY --from=builder /app/dist ./dist 56 + # Copy built admin UI (built to ../public/admin relative to admin-ui) 57 + COPY --from=admin-ui-builder /app/public/admin ./public/admin 35 58 36 59 # Create data directory 37 - RUN mkdir -p /app/data && chown -R atauth:atauth /app/data 60 + RUN mkdir -p /app/data && chown -R atauth:atauth /app 38 61 39 62 # Switch to non-root user 40 63 USER atauth
+263
gateway/SAFE-DEPLOY.md
··· 1 + # Safe Deployment Runbook for ATAuth Gateway 2 + 3 + This document outlines the safe deployment procedure to avoid breaking production. 4 + 5 + ## Deployment Strategy 6 + 7 + ``` 8 + ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ 9 + │ Develop │────▶│ Staging │────▶│ Production │ 10 + │ (local) │ │ (k3s) │ │ (k3s) │ 11 + └─────────────┘ └─────────────┘ └─────────────┘ 12 + │ │ 13 + auth-staging.* auth.* (existing) 14 + ``` 15 + 16 + ## Pre-Deployment Checklist 17 + 18 + ### Before ANY deployment: 19 + 20 + - [ ] All tests pass locally 21 + - [ ] Code reviewed and merged to appropriate branch 22 + - [ ] Database schema changes are backward compatible 23 + - [ ] New environment variables documented 24 + 25 + ### Before Production deployment: 26 + 27 + - [ ] Successfully deployed and tested in staging 28 + - [ ] Backup job completed successfully 29 + - [ ] Rollback procedure reviewed 30 + - [ ] Maintenance window communicated (if needed) 31 + 32 + --- 33 + 34 + ## Step-by-Step Deployment 35 + 36 + ### 1. Deploy to Staging First 37 + 38 + ```bash 39 + # Build and push staging image 40 + git checkout develop 41 + docker build -t gitea.cloudforest-basilisk.ts.net/arcnode.xyz/atauth-gateway:staging ./gateway 42 + docker push gitea.cloudforest-basilisk.ts.net/arcnode.xyz/atauth-gateway:staging 43 + 44 + # Deploy to staging namespace 45 + kubectl apply -k gateway/k8s/overlays/staging 46 + 47 + # Verify deployment 48 + kubectl -n atauth-staging rollout status deployment/atauth-gateway-staging 49 + kubectl -n atauth-staging logs -f deployment/atauth-gateway-staging 50 + ``` 51 + 52 + ### 2. Test Staging Environment 53 + 54 + ```bash 55 + # Health check 56 + curl https://auth-staging.cloudforest-basilisk.ts.net/health 57 + 58 + # Test OIDC discovery 59 + curl https://auth-staging.cloudforest-basilisk.ts.net/.well-known/openid-configuration 60 + 61 + # Test admin UI 62 + open https://auth-staging.cloudforest-basilisk.ts.net/admin 63 + 64 + # Run integration tests against staging 65 + # ... your test commands ... 66 + ``` 67 + 68 + ### 3. Create Pre-Deployment Backup 69 + 70 + ```bash 71 + # Trigger backup job 72 + kubectl -n atauth create job --from=cronjob/atauth-gateway-backup pre-deploy-$(date +%Y%m%d%H%M) 73 + 74 + # Wait for completion 75 + kubectl -n atauth wait --for=condition=complete job/pre-deploy-$(date +%Y%m%d%H%M) --timeout=120s 76 + 77 + # Verify backup exists 78 + kubectl -n atauth exec -it deploy/atauth-gateway -- ls -la /backups/ 79 + ``` 80 + 81 + ### 4. Deploy to Production 82 + 83 + ```bash 84 + # Tag the release 85 + git checkout main 86 + git tag v1.x.x 87 + git push origin v1.x.x 88 + 89 + # Build production image with version tag 90 + docker build -t gitea.cloudforest-basilisk.ts.net/arcnode.xyz/atauth-gateway:v1.x.x ./gateway 91 + docker push gitea.cloudforest-basilisk.ts.net/arcnode.xyz/atauth-gateway:v1.x.x 92 + 93 + # Update production kustomization with new tag 94 + cd gateway/k8s/overlays/production 95 + # Edit kustomization.yaml to use new tag 96 + 97 + # Apply with dry-run first 98 + kubectl apply -k . --dry-run=server 99 + 100 + # Apply for real 101 + kubectl apply -k . 102 + 103 + # Watch rollout 104 + kubectl -n atauth rollout status deployment/atauth-gateway 105 + ``` 106 + 107 + ### 5. Verify Production 108 + 109 + ```bash 110 + # Health check 111 + curl https://auth.cloudforest-basilisk.ts.net/health 112 + 113 + # Check logs for errors 114 + kubectl -n atauth logs -f deployment/atauth-gateway --since=5m 115 + 116 + # Verify existing sessions still work 117 + # ... test with existing client apps ... 118 + ``` 119 + 120 + --- 121 + 122 + ## Rollback Procedure 123 + 124 + ### Quick Rollback (< 5 minutes) 125 + 126 + ```bash 127 + # Rollback to previous deployment 128 + kubectl -n atauth rollout undo deployment/atauth-gateway 129 + 130 + # Verify rollback 131 + kubectl -n atauth rollout status deployment/atauth-gateway 132 + ``` 133 + 134 + ### Rollback with Database Restore 135 + 136 + If the new version corrupted data: 137 + 138 + ```bash 139 + # 1. Scale down the deployment 140 + kubectl -n atauth scale deployment/atauth-gateway --replicas=0 141 + 142 + # 2. Find the backup to restore 143 + kubectl -n atauth exec -it <backup-pod> -- ls -la /backups/ 144 + 145 + # 3. Restore the database 146 + kubectl -n atauth exec -it <backup-pod> -- sh -c ' 147 + cp /backups/pre-deploy-YYYYMMDD-HHMMSS.db /data/gateway.db 148 + ' 149 + 150 + # 4. Rollback the deployment 151 + kubectl -n atauth rollout undo deployment/atauth-gateway 152 + 153 + # 5. Scale back up 154 + kubectl -n atauth scale deployment/atauth-gateway --replicas=1 155 + ``` 156 + 157 + ### Full Disaster Recovery 158 + 159 + ```bash 160 + # If everything is broken, restore from last known good state: 161 + 162 + # 1. Delete the deployment 163 + kubectl -n atauth delete deployment atauth-gateway 164 + 165 + # 2. Restore database from backup 166 + # (access the PVC directly or use a debug pod) 167 + 168 + # 3. Deploy previous known-good version 169 + kubectl set image deployment/atauth-gateway \ 170 + gateway=gitea.cloudforest-basilisk.ts.net/arcnode.xyz/atauth-gateway:v1.2.0 \ 171 + -n atauth 172 + ``` 173 + 174 + --- 175 + 176 + ## Database Migration Safety 177 + 178 + The new version adds these tables (backward compatible): 179 + 180 + ```sql 181 + -- New tables (won't affect existing functionality) 182 + - oidc_keys 183 + - authorization_codes 184 + - refresh_tokens 185 + - passkey_credentials 186 + - mfa_totp 187 + - mfa_backup_codes 188 + - user_emails 189 + - email_verification_codes 190 + 191 + -- Modified tables (new columns with defaults) 192 + - apps: client_type, client_secret, redirect_uris, etc. 193 + ``` 194 + 195 + ### Migration is SAFE because: 196 + 197 + 1. **New tables only** - existing tables unchanged 198 + 2. **New columns have defaults** - existing rows work 199 + 3. **Legacy mode supported** - old apps use `client_type='legacy'` 200 + 4. **No breaking changes to existing API** - `/auth/*` routes unchanged 201 + 202 + ### To verify schema compatibility: 203 + 204 + ```bash 205 + # Export current schema 206 + kubectl -n atauth exec deploy/atauth-gateway -- \ 207 + sqlite3 /app/data/gateway.db ".schema" > schema-before.sql 208 + 209 + # After deployment, compare 210 + kubectl -n atauth exec deploy/atauth-gateway -- \ 211 + sqlite3 /app/data/gateway.db ".schema" > schema-after.sql 212 + 213 + diff schema-before.sql schema-after.sql 214 + ``` 215 + 216 + --- 217 + 218 + ## Monitoring During Deployment 219 + 220 + ```bash 221 + # Watch pod status 222 + watch kubectl -n atauth get pods 223 + 224 + # Stream logs 225 + kubectl -n atauth logs -f deployment/atauth-gateway 226 + 227 + # Check events 228 + kubectl -n atauth get events --sort-by='.lastTimestamp' 229 + 230 + # Monitor health endpoint 231 + watch -n 5 'curl -s https://auth.cloudforest-basilisk.ts.net/health | jq' 232 + ``` 233 + 234 + --- 235 + 236 + ## Environment URLs 237 + 238 + | Environment | URL | Namespace | 239 + |-------------|-----|-----------| 240 + | Staging | https://auth-staging.cloudforest-basilisk.ts.net | atauth-staging | 241 + | Production | https://auth.cloudforest-basilisk.ts.net | atauth | 242 + 243 + ## Quick Commands Reference 244 + 245 + ```bash 246 + # Deploy staging 247 + kubectl apply -k gateway/k8s/overlays/staging 248 + 249 + # Deploy production 250 + kubectl apply -k gateway/k8s/overlays/production 251 + 252 + # Rollback 253 + kubectl -n atauth rollout undo deployment/atauth-gateway 254 + 255 + # Check status 256 + kubectl -n atauth get all 257 + 258 + # Logs 259 + kubectl -n atauth logs -f deploy/atauth-gateway 260 + 261 + # Backup now 262 + kubectl -n atauth create job --from=cronjob/atauth-gateway-backup manual-backup-$(date +%s) 263 + ```
+24
gateway/admin-ui/.gitignore
··· 1 + # Logs 2 + logs 3 + *.log 4 + npm-debug.log* 5 + yarn-debug.log* 6 + yarn-error.log* 7 + pnpm-debug.log* 8 + lerna-debug.log* 9 + 10 + node_modules 11 + dist 12 + dist-ssr 13 + *.local 14 + 15 + # Editor directories and files 16 + .vscode/* 17 + !.vscode/extensions.json 18 + .idea 19 + .DS_Store 20 + *.suo 21 + *.ntvs* 22 + *.njsproj 23 + *.sln 24 + *.sw?
+73
gateway/admin-ui/README.md
··· 1 + # React + TypeScript + Vite 2 + 3 + This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 + 5 + Currently, two official plugins are available: 6 + 7 + - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh 8 + - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 + 10 + ## React Compiler 11 + 12 + The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). 13 + 14 + ## Expanding the ESLint configuration 15 + 16 + If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: 17 + 18 + ```js 19 + export default defineConfig([ 20 + globalIgnores(['dist']), 21 + { 22 + files: ['**/*.{ts,tsx}'], 23 + extends: [ 24 + // Other configs... 25 + 26 + // Remove tseslint.configs.recommended and replace with this 27 + tseslint.configs.recommendedTypeChecked, 28 + // Alternatively, use this for stricter rules 29 + tseslint.configs.strictTypeChecked, 30 + // Optionally, add this for stylistic rules 31 + tseslint.configs.stylisticTypeChecked, 32 + 33 + // Other configs... 34 + ], 35 + languageOptions: { 36 + parserOptions: { 37 + project: ['./tsconfig.node.json', './tsconfig.app.json'], 38 + tsconfigRootDir: import.meta.dirname, 39 + }, 40 + // other options... 41 + }, 42 + }, 43 + ]) 44 + ``` 45 + 46 + You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: 47 + 48 + ```js 49 + // eslint.config.js 50 + import reactX from 'eslint-plugin-react-x' 51 + import reactDom from 'eslint-plugin-react-dom' 52 + 53 + export default defineConfig([ 54 + globalIgnores(['dist']), 55 + { 56 + files: ['**/*.{ts,tsx}'], 57 + extends: [ 58 + // Other configs... 59 + // Enable lint rules for React 60 + reactX.configs['recommended-typescript'], 61 + // Enable lint rules for React DOM 62 + reactDom.configs.recommended, 63 + ], 64 + languageOptions: { 65 + parserOptions: { 66 + project: ['./tsconfig.node.json', './tsconfig.app.json'], 67 + tsconfigRootDir: import.meta.dirname, 68 + }, 69 + // other options... 70 + }, 71 + }, 72 + ]) 73 + ```
+23
gateway/admin-ui/eslint.config.js
··· 1 + import js from '@eslint/js' 2 + import globals from 'globals' 3 + import reactHooks from 'eslint-plugin-react-hooks' 4 + import reactRefresh from 'eslint-plugin-react-refresh' 5 + import tseslint from 'typescript-eslint' 6 + import { defineConfig, globalIgnores } from 'eslint/config' 7 + 8 + export default defineConfig([ 9 + globalIgnores(['dist']), 10 + { 11 + files: ['**/*.{ts,tsx}'], 12 + extends: [ 13 + js.configs.recommended, 14 + tseslint.configs.recommended, 15 + reactHooks.configs.flat.recommended, 16 + reactRefresh.configs.vite, 17 + ], 18 + languageOptions: { 19 + ecmaVersion: 2020, 20 + globals: globals.browser, 21 + }, 22 + }, 23 + ])
+13
gateway/admin-ui/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <link rel="icon" type="image/svg+xml" href="/vite.svg" /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 + <title>admin-ui</title> 8 + </head> 9 + <body> 10 + <div id="root"></div> 11 + <script type="module" src="/src/main.tsx"></script> 12 + </body> 13 + </html>
+3877
gateway/admin-ui/package-lock.json
··· 1 + { 2 + "name": "admin-ui", 3 + "version": "0.0.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "admin-ui", 9 + "version": "0.0.0", 10 + "dependencies": { 11 + "@tailwindcss/vite": "^4.1.18", 12 + "@tanstack/react-query": "^5.90.20", 13 + "react": "^19.2.0", 14 + "react-dom": "^19.2.0", 15 + "react-router-dom": "^7.13.0", 16 + "tailwindcss": "^4.1.18", 17 + "zustand": "^5.0.11" 18 + }, 19 + "devDependencies": { 20 + "@eslint/js": "^9.39.1", 21 + "@types/node": "^24.10.1", 22 + "@types/react": "^19.2.5", 23 + "@types/react-dom": "^19.2.3", 24 + "@vitejs/plugin-react": "^5.1.1", 25 + "eslint": "^9.39.1", 26 + "eslint-plugin-react-hooks": "^7.0.1", 27 + "eslint-plugin-react-refresh": "^0.4.24", 28 + "globals": "^16.5.0", 29 + "typescript": "~5.9.3", 30 + "typescript-eslint": "^8.46.4", 31 + "vite": "^7.2.4" 32 + } 33 + }, 34 + "node_modules/@babel/code-frame": { 35 + "version": "7.29.0", 36 + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", 37 + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", 38 + "dev": true, 39 + "license": "MIT", 40 + "dependencies": { 41 + "@babel/helper-validator-identifier": "^7.28.5", 42 + "js-tokens": "^4.0.0", 43 + "picocolors": "^1.1.1" 44 + }, 45 + "engines": { 46 + "node": ">=6.9.0" 47 + } 48 + }, 49 + "node_modules/@babel/compat-data": { 50 + "version": "7.29.0", 51 + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", 52 + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", 53 + "dev": true, 54 + "license": "MIT", 55 + "engines": { 56 + "node": ">=6.9.0" 57 + } 58 + }, 59 + "node_modules/@babel/core": { 60 + "version": "7.29.0", 61 + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", 62 + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", 63 + "dev": true, 64 + "license": "MIT", 65 + "dependencies": { 66 + "@babel/code-frame": "^7.29.0", 67 + "@babel/generator": "^7.29.0", 68 + "@babel/helper-compilation-targets": "^7.28.6", 69 + "@babel/helper-module-transforms": "^7.28.6", 70 + "@babel/helpers": "^7.28.6", 71 + "@babel/parser": "^7.29.0", 72 + "@babel/template": "^7.28.6", 73 + "@babel/traverse": "^7.29.0", 74 + "@babel/types": "^7.29.0", 75 + "@jridgewell/remapping": "^2.3.5", 76 + "convert-source-map": "^2.0.0", 77 + "debug": "^4.1.0", 78 + "gensync": "^1.0.0-beta.2", 79 + "json5": "^2.2.3", 80 + "semver": "^6.3.1" 81 + }, 82 + "engines": { 83 + "node": ">=6.9.0" 84 + }, 85 + "funding": { 86 + "type": "opencollective", 87 + "url": "https://opencollective.com/babel" 88 + } 89 + }, 90 + "node_modules/@babel/generator": { 91 + "version": "7.29.1", 92 + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", 93 + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", 94 + "dev": true, 95 + "license": "MIT", 96 + "dependencies": { 97 + "@babel/parser": "^7.29.0", 98 + "@babel/types": "^7.29.0", 99 + "@jridgewell/gen-mapping": "^0.3.12", 100 + "@jridgewell/trace-mapping": "^0.3.28", 101 + "jsesc": "^3.0.2" 102 + }, 103 + "engines": { 104 + "node": ">=6.9.0" 105 + } 106 + }, 107 + "node_modules/@babel/helper-compilation-targets": { 108 + "version": "7.28.6", 109 + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", 110 + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", 111 + "dev": true, 112 + "license": "MIT", 113 + "dependencies": { 114 + "@babel/compat-data": "^7.28.6", 115 + "@babel/helper-validator-option": "^7.27.1", 116 + "browserslist": "^4.24.0", 117 + "lru-cache": "^5.1.1", 118 + "semver": "^6.3.1" 119 + }, 120 + "engines": { 121 + "node": ">=6.9.0" 122 + } 123 + }, 124 + "node_modules/@babel/helper-globals": { 125 + "version": "7.28.0", 126 + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", 127 + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", 128 + "dev": true, 129 + "license": "MIT", 130 + "engines": { 131 + "node": ">=6.9.0" 132 + } 133 + }, 134 + "node_modules/@babel/helper-module-imports": { 135 + "version": "7.28.6", 136 + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", 137 + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", 138 + "dev": true, 139 + "license": "MIT", 140 + "dependencies": { 141 + "@babel/traverse": "^7.28.6", 142 + "@babel/types": "^7.28.6" 143 + }, 144 + "engines": { 145 + "node": ">=6.9.0" 146 + } 147 + }, 148 + "node_modules/@babel/helper-module-transforms": { 149 + "version": "7.28.6", 150 + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", 151 + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", 152 + "dev": true, 153 + "license": "MIT", 154 + "dependencies": { 155 + "@babel/helper-module-imports": "^7.28.6", 156 + "@babel/helper-validator-identifier": "^7.28.5", 157 + "@babel/traverse": "^7.28.6" 158 + }, 159 + "engines": { 160 + "node": ">=6.9.0" 161 + }, 162 + "peerDependencies": { 163 + "@babel/core": "^7.0.0" 164 + } 165 + }, 166 + "node_modules/@babel/helper-plugin-utils": { 167 + "version": "7.28.6", 168 + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", 169 + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", 170 + "dev": true, 171 + "license": "MIT", 172 + "engines": { 173 + "node": ">=6.9.0" 174 + } 175 + }, 176 + "node_modules/@babel/helper-string-parser": { 177 + "version": "7.27.1", 178 + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", 179 + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", 180 + "dev": true, 181 + "license": "MIT", 182 + "engines": { 183 + "node": ">=6.9.0" 184 + } 185 + }, 186 + "node_modules/@babel/helper-validator-identifier": { 187 + "version": "7.28.5", 188 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", 189 + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", 190 + "dev": true, 191 + "license": "MIT", 192 + "engines": { 193 + "node": ">=6.9.0" 194 + } 195 + }, 196 + "node_modules/@babel/helper-validator-option": { 197 + "version": "7.27.1", 198 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", 199 + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", 200 + "dev": true, 201 + "license": "MIT", 202 + "engines": { 203 + "node": ">=6.9.0" 204 + } 205 + }, 206 + "node_modules/@babel/helpers": { 207 + "version": "7.28.6", 208 + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", 209 + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", 210 + "dev": true, 211 + "license": "MIT", 212 + "dependencies": { 213 + "@babel/template": "^7.28.6", 214 + "@babel/types": "^7.28.6" 215 + }, 216 + "engines": { 217 + "node": ">=6.9.0" 218 + } 219 + }, 220 + "node_modules/@babel/parser": { 221 + "version": "7.29.0", 222 + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", 223 + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", 224 + "dev": true, 225 + "license": "MIT", 226 + "dependencies": { 227 + "@babel/types": "^7.29.0" 228 + }, 229 + "bin": { 230 + "parser": "bin/babel-parser.js" 231 + }, 232 + "engines": { 233 + "node": ">=6.0.0" 234 + } 235 + }, 236 + "node_modules/@babel/plugin-transform-react-jsx-self": { 237 + "version": "7.27.1", 238 + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", 239 + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", 240 + "dev": true, 241 + "license": "MIT", 242 + "dependencies": { 243 + "@babel/helper-plugin-utils": "^7.27.1" 244 + }, 245 + "engines": { 246 + "node": ">=6.9.0" 247 + }, 248 + "peerDependencies": { 249 + "@babel/core": "^7.0.0-0" 250 + } 251 + }, 252 + "node_modules/@babel/plugin-transform-react-jsx-source": { 253 + "version": "7.27.1", 254 + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", 255 + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", 256 + "dev": true, 257 + "license": "MIT", 258 + "dependencies": { 259 + "@babel/helper-plugin-utils": "^7.27.1" 260 + }, 261 + "engines": { 262 + "node": ">=6.9.0" 263 + }, 264 + "peerDependencies": { 265 + "@babel/core": "^7.0.0-0" 266 + } 267 + }, 268 + "node_modules/@babel/template": { 269 + "version": "7.28.6", 270 + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", 271 + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", 272 + "dev": true, 273 + "license": "MIT", 274 + "dependencies": { 275 + "@babel/code-frame": "^7.28.6", 276 + "@babel/parser": "^7.28.6", 277 + "@babel/types": "^7.28.6" 278 + }, 279 + "engines": { 280 + "node": ">=6.9.0" 281 + } 282 + }, 283 + "node_modules/@babel/traverse": { 284 + "version": "7.29.0", 285 + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", 286 + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", 287 + "dev": true, 288 + "license": "MIT", 289 + "dependencies": { 290 + "@babel/code-frame": "^7.29.0", 291 + "@babel/generator": "^7.29.0", 292 + "@babel/helper-globals": "^7.28.0", 293 + "@babel/parser": "^7.29.0", 294 + "@babel/template": "^7.28.6", 295 + "@babel/types": "^7.29.0", 296 + "debug": "^4.3.1" 297 + }, 298 + "engines": { 299 + "node": ">=6.9.0" 300 + } 301 + }, 302 + "node_modules/@babel/types": { 303 + "version": "7.29.0", 304 + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", 305 + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", 306 + "dev": true, 307 + "license": "MIT", 308 + "dependencies": { 309 + "@babel/helper-string-parser": "^7.27.1", 310 + "@babel/helper-validator-identifier": "^7.28.5" 311 + }, 312 + "engines": { 313 + "node": ">=6.9.0" 314 + } 315 + }, 316 + "node_modules/@esbuild/aix-ppc64": { 317 + "version": "0.27.2", 318 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", 319 + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", 320 + "cpu": [ 321 + "ppc64" 322 + ], 323 + "license": "MIT", 324 + "optional": true, 325 + "os": [ 326 + "aix" 327 + ], 328 + "engines": { 329 + "node": ">=18" 330 + } 331 + }, 332 + "node_modules/@esbuild/android-arm": { 333 + "version": "0.27.2", 334 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", 335 + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", 336 + "cpu": [ 337 + "arm" 338 + ], 339 + "license": "MIT", 340 + "optional": true, 341 + "os": [ 342 + "android" 343 + ], 344 + "engines": { 345 + "node": ">=18" 346 + } 347 + }, 348 + "node_modules/@esbuild/android-arm64": { 349 + "version": "0.27.2", 350 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", 351 + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", 352 + "cpu": [ 353 + "arm64" 354 + ], 355 + "license": "MIT", 356 + "optional": true, 357 + "os": [ 358 + "android" 359 + ], 360 + "engines": { 361 + "node": ">=18" 362 + } 363 + }, 364 + "node_modules/@esbuild/android-x64": { 365 + "version": "0.27.2", 366 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", 367 + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", 368 + "cpu": [ 369 + "x64" 370 + ], 371 + "license": "MIT", 372 + "optional": true, 373 + "os": [ 374 + "android" 375 + ], 376 + "engines": { 377 + "node": ">=18" 378 + } 379 + }, 380 + "node_modules/@esbuild/darwin-arm64": { 381 + "version": "0.27.2", 382 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", 383 + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", 384 + "cpu": [ 385 + "arm64" 386 + ], 387 + "license": "MIT", 388 + "optional": true, 389 + "os": [ 390 + "darwin" 391 + ], 392 + "engines": { 393 + "node": ">=18" 394 + } 395 + }, 396 + "node_modules/@esbuild/darwin-x64": { 397 + "version": "0.27.2", 398 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", 399 + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", 400 + "cpu": [ 401 + "x64" 402 + ], 403 + "license": "MIT", 404 + "optional": true, 405 + "os": [ 406 + "darwin" 407 + ], 408 + "engines": { 409 + "node": ">=18" 410 + } 411 + }, 412 + "node_modules/@esbuild/freebsd-arm64": { 413 + "version": "0.27.2", 414 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", 415 + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", 416 + "cpu": [ 417 + "arm64" 418 + ], 419 + "license": "MIT", 420 + "optional": true, 421 + "os": [ 422 + "freebsd" 423 + ], 424 + "engines": { 425 + "node": ">=18" 426 + } 427 + }, 428 + "node_modules/@esbuild/freebsd-x64": { 429 + "version": "0.27.2", 430 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", 431 + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", 432 + "cpu": [ 433 + "x64" 434 + ], 435 + "license": "MIT", 436 + "optional": true, 437 + "os": [ 438 + "freebsd" 439 + ], 440 + "engines": { 441 + "node": ">=18" 442 + } 443 + }, 444 + "node_modules/@esbuild/linux-arm": { 445 + "version": "0.27.2", 446 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", 447 + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", 448 + "cpu": [ 449 + "arm" 450 + ], 451 + "license": "MIT", 452 + "optional": true, 453 + "os": [ 454 + "linux" 455 + ], 456 + "engines": { 457 + "node": ">=18" 458 + } 459 + }, 460 + "node_modules/@esbuild/linux-arm64": { 461 + "version": "0.27.2", 462 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", 463 + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", 464 + "cpu": [ 465 + "arm64" 466 + ], 467 + "license": "MIT", 468 + "optional": true, 469 + "os": [ 470 + "linux" 471 + ], 472 + "engines": { 473 + "node": ">=18" 474 + } 475 + }, 476 + "node_modules/@esbuild/linux-ia32": { 477 + "version": "0.27.2", 478 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", 479 + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", 480 + "cpu": [ 481 + "ia32" 482 + ], 483 + "license": "MIT", 484 + "optional": true, 485 + "os": [ 486 + "linux" 487 + ], 488 + "engines": { 489 + "node": ">=18" 490 + } 491 + }, 492 + "node_modules/@esbuild/linux-loong64": { 493 + "version": "0.27.2", 494 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", 495 + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", 496 + "cpu": [ 497 + "loong64" 498 + ], 499 + "license": "MIT", 500 + "optional": true, 501 + "os": [ 502 + "linux" 503 + ], 504 + "engines": { 505 + "node": ">=18" 506 + } 507 + }, 508 + "node_modules/@esbuild/linux-mips64el": { 509 + "version": "0.27.2", 510 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", 511 + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", 512 + "cpu": [ 513 + "mips64el" 514 + ], 515 + "license": "MIT", 516 + "optional": true, 517 + "os": [ 518 + "linux" 519 + ], 520 + "engines": { 521 + "node": ">=18" 522 + } 523 + }, 524 + "node_modules/@esbuild/linux-ppc64": { 525 + "version": "0.27.2", 526 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", 527 + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", 528 + "cpu": [ 529 + "ppc64" 530 + ], 531 + "license": "MIT", 532 + "optional": true, 533 + "os": [ 534 + "linux" 535 + ], 536 + "engines": { 537 + "node": ">=18" 538 + } 539 + }, 540 + "node_modules/@esbuild/linux-riscv64": { 541 + "version": "0.27.2", 542 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", 543 + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", 544 + "cpu": [ 545 + "riscv64" 546 + ], 547 + "license": "MIT", 548 + "optional": true, 549 + "os": [ 550 + "linux" 551 + ], 552 + "engines": { 553 + "node": ">=18" 554 + } 555 + }, 556 + "node_modules/@esbuild/linux-s390x": { 557 + "version": "0.27.2", 558 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", 559 + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", 560 + "cpu": [ 561 + "s390x" 562 + ], 563 + "license": "MIT", 564 + "optional": true, 565 + "os": [ 566 + "linux" 567 + ], 568 + "engines": { 569 + "node": ">=18" 570 + } 571 + }, 572 + "node_modules/@esbuild/linux-x64": { 573 + "version": "0.27.2", 574 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", 575 + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", 576 + "cpu": [ 577 + "x64" 578 + ], 579 + "license": "MIT", 580 + "optional": true, 581 + "os": [ 582 + "linux" 583 + ], 584 + "engines": { 585 + "node": ">=18" 586 + } 587 + }, 588 + "node_modules/@esbuild/netbsd-arm64": { 589 + "version": "0.27.2", 590 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", 591 + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", 592 + "cpu": [ 593 + "arm64" 594 + ], 595 + "license": "MIT", 596 + "optional": true, 597 + "os": [ 598 + "netbsd" 599 + ], 600 + "engines": { 601 + "node": ">=18" 602 + } 603 + }, 604 + "node_modules/@esbuild/netbsd-x64": { 605 + "version": "0.27.2", 606 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", 607 + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", 608 + "cpu": [ 609 + "x64" 610 + ], 611 + "license": "MIT", 612 + "optional": true, 613 + "os": [ 614 + "netbsd" 615 + ], 616 + "engines": { 617 + "node": ">=18" 618 + } 619 + }, 620 + "node_modules/@esbuild/openbsd-arm64": { 621 + "version": "0.27.2", 622 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", 623 + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", 624 + "cpu": [ 625 + "arm64" 626 + ], 627 + "license": "MIT", 628 + "optional": true, 629 + "os": [ 630 + "openbsd" 631 + ], 632 + "engines": { 633 + "node": ">=18" 634 + } 635 + }, 636 + "node_modules/@esbuild/openbsd-x64": { 637 + "version": "0.27.2", 638 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", 639 + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", 640 + "cpu": [ 641 + "x64" 642 + ], 643 + "license": "MIT", 644 + "optional": true, 645 + "os": [ 646 + "openbsd" 647 + ], 648 + "engines": { 649 + "node": ">=18" 650 + } 651 + }, 652 + "node_modules/@esbuild/openharmony-arm64": { 653 + "version": "0.27.2", 654 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", 655 + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", 656 + "cpu": [ 657 + "arm64" 658 + ], 659 + "license": "MIT", 660 + "optional": true, 661 + "os": [ 662 + "openharmony" 663 + ], 664 + "engines": { 665 + "node": ">=18" 666 + } 667 + }, 668 + "node_modules/@esbuild/sunos-x64": { 669 + "version": "0.27.2", 670 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", 671 + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", 672 + "cpu": [ 673 + "x64" 674 + ], 675 + "license": "MIT", 676 + "optional": true, 677 + "os": [ 678 + "sunos" 679 + ], 680 + "engines": { 681 + "node": ">=18" 682 + } 683 + }, 684 + "node_modules/@esbuild/win32-arm64": { 685 + "version": "0.27.2", 686 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", 687 + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", 688 + "cpu": [ 689 + "arm64" 690 + ], 691 + "license": "MIT", 692 + "optional": true, 693 + "os": [ 694 + "win32" 695 + ], 696 + "engines": { 697 + "node": ">=18" 698 + } 699 + }, 700 + "node_modules/@esbuild/win32-ia32": { 701 + "version": "0.27.2", 702 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", 703 + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", 704 + "cpu": [ 705 + "ia32" 706 + ], 707 + "license": "MIT", 708 + "optional": true, 709 + "os": [ 710 + "win32" 711 + ], 712 + "engines": { 713 + "node": ">=18" 714 + } 715 + }, 716 + "node_modules/@esbuild/win32-x64": { 717 + "version": "0.27.2", 718 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", 719 + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", 720 + "cpu": [ 721 + "x64" 722 + ], 723 + "license": "MIT", 724 + "optional": true, 725 + "os": [ 726 + "win32" 727 + ], 728 + "engines": { 729 + "node": ">=18" 730 + } 731 + }, 732 + "node_modules/@eslint-community/eslint-utils": { 733 + "version": "4.9.1", 734 + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", 735 + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", 736 + "dev": true, 737 + "license": "MIT", 738 + "dependencies": { 739 + "eslint-visitor-keys": "^3.4.3" 740 + }, 741 + "engines": { 742 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 743 + }, 744 + "funding": { 745 + "url": "https://opencollective.com/eslint" 746 + }, 747 + "peerDependencies": { 748 + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 749 + } 750 + }, 751 + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 752 + "version": "3.4.3", 753 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 754 + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 755 + "dev": true, 756 + "license": "Apache-2.0", 757 + "engines": { 758 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 759 + }, 760 + "funding": { 761 + "url": "https://opencollective.com/eslint" 762 + } 763 + }, 764 + "node_modules/@eslint-community/regexpp": { 765 + "version": "4.12.2", 766 + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", 767 + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", 768 + "dev": true, 769 + "license": "MIT", 770 + "engines": { 771 + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 772 + } 773 + }, 774 + "node_modules/@eslint/config-array": { 775 + "version": "0.21.1", 776 + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", 777 + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", 778 + "dev": true, 779 + "license": "Apache-2.0", 780 + "dependencies": { 781 + "@eslint/object-schema": "^2.1.7", 782 + "debug": "^4.3.1", 783 + "minimatch": "^3.1.2" 784 + }, 785 + "engines": { 786 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 787 + } 788 + }, 789 + "node_modules/@eslint/config-helpers": { 790 + "version": "0.4.2", 791 + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", 792 + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", 793 + "dev": true, 794 + "license": "Apache-2.0", 795 + "dependencies": { 796 + "@eslint/core": "^0.17.0" 797 + }, 798 + "engines": { 799 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 800 + } 801 + }, 802 + "node_modules/@eslint/core": { 803 + "version": "0.17.0", 804 + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", 805 + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", 806 + "dev": true, 807 + "license": "Apache-2.0", 808 + "dependencies": { 809 + "@types/json-schema": "^7.0.15" 810 + }, 811 + "engines": { 812 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 813 + } 814 + }, 815 + "node_modules/@eslint/eslintrc": { 816 + "version": "3.3.3", 817 + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", 818 + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", 819 + "dev": true, 820 + "license": "MIT", 821 + "dependencies": { 822 + "ajv": "^6.12.4", 823 + "debug": "^4.3.2", 824 + "espree": "^10.0.1", 825 + "globals": "^14.0.0", 826 + "ignore": "^5.2.0", 827 + "import-fresh": "^3.2.1", 828 + "js-yaml": "^4.1.1", 829 + "minimatch": "^3.1.2", 830 + "strip-json-comments": "^3.1.1" 831 + }, 832 + "engines": { 833 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 834 + }, 835 + "funding": { 836 + "url": "https://opencollective.com/eslint" 837 + } 838 + }, 839 + "node_modules/@eslint/eslintrc/node_modules/globals": { 840 + "version": "14.0.0", 841 + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", 842 + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 843 + "dev": true, 844 + "license": "MIT", 845 + "engines": { 846 + "node": ">=18" 847 + }, 848 + "funding": { 849 + "url": "https://github.com/sponsors/sindresorhus" 850 + } 851 + }, 852 + "node_modules/@eslint/js": { 853 + "version": "9.39.2", 854 + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", 855 + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", 856 + "dev": true, 857 + "license": "MIT", 858 + "engines": { 859 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 860 + }, 861 + "funding": { 862 + "url": "https://eslint.org/donate" 863 + } 864 + }, 865 + "node_modules/@eslint/object-schema": { 866 + "version": "2.1.7", 867 + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", 868 + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", 869 + "dev": true, 870 + "license": "Apache-2.0", 871 + "engines": { 872 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 873 + } 874 + }, 875 + "node_modules/@eslint/plugin-kit": { 876 + "version": "0.4.1", 877 + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", 878 + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", 879 + "dev": true, 880 + "license": "Apache-2.0", 881 + "dependencies": { 882 + "@eslint/core": "^0.17.0", 883 + "levn": "^0.4.1" 884 + }, 885 + "engines": { 886 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 887 + } 888 + }, 889 + "node_modules/@humanfs/core": { 890 + "version": "0.19.1", 891 + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", 892 + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", 893 + "dev": true, 894 + "license": "Apache-2.0", 895 + "engines": { 896 + "node": ">=18.18.0" 897 + } 898 + }, 899 + "node_modules/@humanfs/node": { 900 + "version": "0.16.7", 901 + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", 902 + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", 903 + "dev": true, 904 + "license": "Apache-2.0", 905 + "dependencies": { 906 + "@humanfs/core": "^0.19.1", 907 + "@humanwhocodes/retry": "^0.4.0" 908 + }, 909 + "engines": { 910 + "node": ">=18.18.0" 911 + } 912 + }, 913 + "node_modules/@humanwhocodes/module-importer": { 914 + "version": "1.0.1", 915 + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 916 + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 917 + "dev": true, 918 + "license": "Apache-2.0", 919 + "engines": { 920 + "node": ">=12.22" 921 + }, 922 + "funding": { 923 + "type": "github", 924 + "url": "https://github.com/sponsors/nzakas" 925 + } 926 + }, 927 + "node_modules/@humanwhocodes/retry": { 928 + "version": "0.4.3", 929 + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", 930 + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", 931 + "dev": true, 932 + "license": "Apache-2.0", 933 + "engines": { 934 + "node": ">=18.18" 935 + }, 936 + "funding": { 937 + "type": "github", 938 + "url": "https://github.com/sponsors/nzakas" 939 + } 940 + }, 941 + "node_modules/@jridgewell/gen-mapping": { 942 + "version": "0.3.13", 943 + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", 944 + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", 945 + "license": "MIT", 946 + "dependencies": { 947 + "@jridgewell/sourcemap-codec": "^1.5.0", 948 + "@jridgewell/trace-mapping": "^0.3.24" 949 + } 950 + }, 951 + "node_modules/@jridgewell/remapping": { 952 + "version": "2.3.5", 953 + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", 954 + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", 955 + "license": "MIT", 956 + "dependencies": { 957 + "@jridgewell/gen-mapping": "^0.3.5", 958 + "@jridgewell/trace-mapping": "^0.3.24" 959 + } 960 + }, 961 + "node_modules/@jridgewell/resolve-uri": { 962 + "version": "3.1.2", 963 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 964 + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 965 + "license": "MIT", 966 + "engines": { 967 + "node": ">=6.0.0" 968 + } 969 + }, 970 + "node_modules/@jridgewell/sourcemap-codec": { 971 + "version": "1.5.5", 972 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", 973 + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", 974 + "license": "MIT" 975 + }, 976 + "node_modules/@jridgewell/trace-mapping": { 977 + "version": "0.3.31", 978 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 979 + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 980 + "license": "MIT", 981 + "dependencies": { 982 + "@jridgewell/resolve-uri": "^3.1.0", 983 + "@jridgewell/sourcemap-codec": "^1.4.14" 984 + } 985 + }, 986 + "node_modules/@rolldown/pluginutils": { 987 + "version": "1.0.0-rc.2", 988 + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", 989 + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", 990 + "dev": true, 991 + "license": "MIT" 992 + }, 993 + "node_modules/@rollup/rollup-android-arm-eabi": { 994 + "version": "4.57.1", 995 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", 996 + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", 997 + "cpu": [ 998 + "arm" 999 + ], 1000 + "license": "MIT", 1001 + "optional": true, 1002 + "os": [ 1003 + "android" 1004 + ] 1005 + }, 1006 + "node_modules/@rollup/rollup-android-arm64": { 1007 + "version": "4.57.1", 1008 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", 1009 + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", 1010 + "cpu": [ 1011 + "arm64" 1012 + ], 1013 + "license": "MIT", 1014 + "optional": true, 1015 + "os": [ 1016 + "android" 1017 + ] 1018 + }, 1019 + "node_modules/@rollup/rollup-darwin-arm64": { 1020 + "version": "4.57.1", 1021 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", 1022 + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", 1023 + "cpu": [ 1024 + "arm64" 1025 + ], 1026 + "license": "MIT", 1027 + "optional": true, 1028 + "os": [ 1029 + "darwin" 1030 + ] 1031 + }, 1032 + "node_modules/@rollup/rollup-darwin-x64": { 1033 + "version": "4.57.1", 1034 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", 1035 + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", 1036 + "cpu": [ 1037 + "x64" 1038 + ], 1039 + "license": "MIT", 1040 + "optional": true, 1041 + "os": [ 1042 + "darwin" 1043 + ] 1044 + }, 1045 + "node_modules/@rollup/rollup-freebsd-arm64": { 1046 + "version": "4.57.1", 1047 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", 1048 + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", 1049 + "cpu": [ 1050 + "arm64" 1051 + ], 1052 + "license": "MIT", 1053 + "optional": true, 1054 + "os": [ 1055 + "freebsd" 1056 + ] 1057 + }, 1058 + "node_modules/@rollup/rollup-freebsd-x64": { 1059 + "version": "4.57.1", 1060 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", 1061 + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", 1062 + "cpu": [ 1063 + "x64" 1064 + ], 1065 + "license": "MIT", 1066 + "optional": true, 1067 + "os": [ 1068 + "freebsd" 1069 + ] 1070 + }, 1071 + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 1072 + "version": "4.57.1", 1073 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", 1074 + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", 1075 + "cpu": [ 1076 + "arm" 1077 + ], 1078 + "license": "MIT", 1079 + "optional": true, 1080 + "os": [ 1081 + "linux" 1082 + ] 1083 + }, 1084 + "node_modules/@rollup/rollup-linux-arm-musleabihf": { 1085 + "version": "4.57.1", 1086 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", 1087 + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", 1088 + "cpu": [ 1089 + "arm" 1090 + ], 1091 + "license": "MIT", 1092 + "optional": true, 1093 + "os": [ 1094 + "linux" 1095 + ] 1096 + }, 1097 + "node_modules/@rollup/rollup-linux-arm64-gnu": { 1098 + "version": "4.57.1", 1099 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", 1100 + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", 1101 + "cpu": [ 1102 + "arm64" 1103 + ], 1104 + "license": "MIT", 1105 + "optional": true, 1106 + "os": [ 1107 + "linux" 1108 + ] 1109 + }, 1110 + "node_modules/@rollup/rollup-linux-arm64-musl": { 1111 + "version": "4.57.1", 1112 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", 1113 + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", 1114 + "cpu": [ 1115 + "arm64" 1116 + ], 1117 + "license": "MIT", 1118 + "optional": true, 1119 + "os": [ 1120 + "linux" 1121 + ] 1122 + }, 1123 + "node_modules/@rollup/rollup-linux-loong64-gnu": { 1124 + "version": "4.57.1", 1125 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", 1126 + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", 1127 + "cpu": [ 1128 + "loong64" 1129 + ], 1130 + "license": "MIT", 1131 + "optional": true, 1132 + "os": [ 1133 + "linux" 1134 + ] 1135 + }, 1136 + "node_modules/@rollup/rollup-linux-loong64-musl": { 1137 + "version": "4.57.1", 1138 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", 1139 + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", 1140 + "cpu": [ 1141 + "loong64" 1142 + ], 1143 + "license": "MIT", 1144 + "optional": true, 1145 + "os": [ 1146 + "linux" 1147 + ] 1148 + }, 1149 + "node_modules/@rollup/rollup-linux-ppc64-gnu": { 1150 + "version": "4.57.1", 1151 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", 1152 + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", 1153 + "cpu": [ 1154 + "ppc64" 1155 + ], 1156 + "license": "MIT", 1157 + "optional": true, 1158 + "os": [ 1159 + "linux" 1160 + ] 1161 + }, 1162 + "node_modules/@rollup/rollup-linux-ppc64-musl": { 1163 + "version": "4.57.1", 1164 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", 1165 + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", 1166 + "cpu": [ 1167 + "ppc64" 1168 + ], 1169 + "license": "MIT", 1170 + "optional": true, 1171 + "os": [ 1172 + "linux" 1173 + ] 1174 + }, 1175 + "node_modules/@rollup/rollup-linux-riscv64-gnu": { 1176 + "version": "4.57.1", 1177 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", 1178 + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", 1179 + "cpu": [ 1180 + "riscv64" 1181 + ], 1182 + "license": "MIT", 1183 + "optional": true, 1184 + "os": [ 1185 + "linux" 1186 + ] 1187 + }, 1188 + "node_modules/@rollup/rollup-linux-riscv64-musl": { 1189 + "version": "4.57.1", 1190 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", 1191 + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", 1192 + "cpu": [ 1193 + "riscv64" 1194 + ], 1195 + "license": "MIT", 1196 + "optional": true, 1197 + "os": [ 1198 + "linux" 1199 + ] 1200 + }, 1201 + "node_modules/@rollup/rollup-linux-s390x-gnu": { 1202 + "version": "4.57.1", 1203 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", 1204 + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", 1205 + "cpu": [ 1206 + "s390x" 1207 + ], 1208 + "license": "MIT", 1209 + "optional": true, 1210 + "os": [ 1211 + "linux" 1212 + ] 1213 + }, 1214 + "node_modules/@rollup/rollup-linux-x64-gnu": { 1215 + "version": "4.57.1", 1216 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", 1217 + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", 1218 + "cpu": [ 1219 + "x64" 1220 + ], 1221 + "license": "MIT", 1222 + "optional": true, 1223 + "os": [ 1224 + "linux" 1225 + ] 1226 + }, 1227 + "node_modules/@rollup/rollup-linux-x64-musl": { 1228 + "version": "4.57.1", 1229 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", 1230 + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", 1231 + "cpu": [ 1232 + "x64" 1233 + ], 1234 + "license": "MIT", 1235 + "optional": true, 1236 + "os": [ 1237 + "linux" 1238 + ] 1239 + }, 1240 + "node_modules/@rollup/rollup-openbsd-x64": { 1241 + "version": "4.57.1", 1242 + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", 1243 + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", 1244 + "cpu": [ 1245 + "x64" 1246 + ], 1247 + "license": "MIT", 1248 + "optional": true, 1249 + "os": [ 1250 + "openbsd" 1251 + ] 1252 + }, 1253 + "node_modules/@rollup/rollup-openharmony-arm64": { 1254 + "version": "4.57.1", 1255 + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", 1256 + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", 1257 + "cpu": [ 1258 + "arm64" 1259 + ], 1260 + "license": "MIT", 1261 + "optional": true, 1262 + "os": [ 1263 + "openharmony" 1264 + ] 1265 + }, 1266 + "node_modules/@rollup/rollup-win32-arm64-msvc": { 1267 + "version": "4.57.1", 1268 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", 1269 + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", 1270 + "cpu": [ 1271 + "arm64" 1272 + ], 1273 + "license": "MIT", 1274 + "optional": true, 1275 + "os": [ 1276 + "win32" 1277 + ] 1278 + }, 1279 + "node_modules/@rollup/rollup-win32-ia32-msvc": { 1280 + "version": "4.57.1", 1281 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", 1282 + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", 1283 + "cpu": [ 1284 + "ia32" 1285 + ], 1286 + "license": "MIT", 1287 + "optional": true, 1288 + "os": [ 1289 + "win32" 1290 + ] 1291 + }, 1292 + "node_modules/@rollup/rollup-win32-x64-gnu": { 1293 + "version": "4.57.1", 1294 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", 1295 + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", 1296 + "cpu": [ 1297 + "x64" 1298 + ], 1299 + "license": "MIT", 1300 + "optional": true, 1301 + "os": [ 1302 + "win32" 1303 + ] 1304 + }, 1305 + "node_modules/@rollup/rollup-win32-x64-msvc": { 1306 + "version": "4.57.1", 1307 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", 1308 + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", 1309 + "cpu": [ 1310 + "x64" 1311 + ], 1312 + "license": "MIT", 1313 + "optional": true, 1314 + "os": [ 1315 + "win32" 1316 + ] 1317 + }, 1318 + "node_modules/@tailwindcss/node": { 1319 + "version": "4.1.18", 1320 + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", 1321 + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", 1322 + "license": "MIT", 1323 + "dependencies": { 1324 + "@jridgewell/remapping": "^2.3.4", 1325 + "enhanced-resolve": "^5.18.3", 1326 + "jiti": "^2.6.1", 1327 + "lightningcss": "1.30.2", 1328 + "magic-string": "^0.30.21", 1329 + "source-map-js": "^1.2.1", 1330 + "tailwindcss": "4.1.18" 1331 + } 1332 + }, 1333 + "node_modules/@tailwindcss/oxide": { 1334 + "version": "4.1.18", 1335 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", 1336 + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", 1337 + "license": "MIT", 1338 + "engines": { 1339 + "node": ">= 10" 1340 + }, 1341 + "optionalDependencies": { 1342 + "@tailwindcss/oxide-android-arm64": "4.1.18", 1343 + "@tailwindcss/oxide-darwin-arm64": "4.1.18", 1344 + "@tailwindcss/oxide-darwin-x64": "4.1.18", 1345 + "@tailwindcss/oxide-freebsd-x64": "4.1.18", 1346 + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", 1347 + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", 1348 + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", 1349 + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", 1350 + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", 1351 + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", 1352 + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", 1353 + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" 1354 + } 1355 + }, 1356 + "node_modules/@tailwindcss/oxide-android-arm64": { 1357 + "version": "4.1.18", 1358 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", 1359 + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", 1360 + "cpu": [ 1361 + "arm64" 1362 + ], 1363 + "license": "MIT", 1364 + "optional": true, 1365 + "os": [ 1366 + "android" 1367 + ], 1368 + "engines": { 1369 + "node": ">= 10" 1370 + } 1371 + }, 1372 + "node_modules/@tailwindcss/oxide-darwin-arm64": { 1373 + "version": "4.1.18", 1374 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", 1375 + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", 1376 + "cpu": [ 1377 + "arm64" 1378 + ], 1379 + "license": "MIT", 1380 + "optional": true, 1381 + "os": [ 1382 + "darwin" 1383 + ], 1384 + "engines": { 1385 + "node": ">= 10" 1386 + } 1387 + }, 1388 + "node_modules/@tailwindcss/oxide-darwin-x64": { 1389 + "version": "4.1.18", 1390 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", 1391 + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", 1392 + "cpu": [ 1393 + "x64" 1394 + ], 1395 + "license": "MIT", 1396 + "optional": true, 1397 + "os": [ 1398 + "darwin" 1399 + ], 1400 + "engines": { 1401 + "node": ">= 10" 1402 + } 1403 + }, 1404 + "node_modules/@tailwindcss/oxide-freebsd-x64": { 1405 + "version": "4.1.18", 1406 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", 1407 + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", 1408 + "cpu": [ 1409 + "x64" 1410 + ], 1411 + "license": "MIT", 1412 + "optional": true, 1413 + "os": [ 1414 + "freebsd" 1415 + ], 1416 + "engines": { 1417 + "node": ">= 10" 1418 + } 1419 + }, 1420 + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { 1421 + "version": "4.1.18", 1422 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", 1423 + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", 1424 + "cpu": [ 1425 + "arm" 1426 + ], 1427 + "license": "MIT", 1428 + "optional": true, 1429 + "os": [ 1430 + "linux" 1431 + ], 1432 + "engines": { 1433 + "node": ">= 10" 1434 + } 1435 + }, 1436 + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { 1437 + "version": "4.1.18", 1438 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", 1439 + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", 1440 + "cpu": [ 1441 + "arm64" 1442 + ], 1443 + "license": "MIT", 1444 + "optional": true, 1445 + "os": [ 1446 + "linux" 1447 + ], 1448 + "engines": { 1449 + "node": ">= 10" 1450 + } 1451 + }, 1452 + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { 1453 + "version": "4.1.18", 1454 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", 1455 + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", 1456 + "cpu": [ 1457 + "arm64" 1458 + ], 1459 + "license": "MIT", 1460 + "optional": true, 1461 + "os": [ 1462 + "linux" 1463 + ], 1464 + "engines": { 1465 + "node": ">= 10" 1466 + } 1467 + }, 1468 + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { 1469 + "version": "4.1.18", 1470 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", 1471 + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", 1472 + "cpu": [ 1473 + "x64" 1474 + ], 1475 + "license": "MIT", 1476 + "optional": true, 1477 + "os": [ 1478 + "linux" 1479 + ], 1480 + "engines": { 1481 + "node": ">= 10" 1482 + } 1483 + }, 1484 + "node_modules/@tailwindcss/oxide-linux-x64-musl": { 1485 + "version": "4.1.18", 1486 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", 1487 + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", 1488 + "cpu": [ 1489 + "x64" 1490 + ], 1491 + "license": "MIT", 1492 + "optional": true, 1493 + "os": [ 1494 + "linux" 1495 + ], 1496 + "engines": { 1497 + "node": ">= 10" 1498 + } 1499 + }, 1500 + "node_modules/@tailwindcss/oxide-wasm32-wasi": { 1501 + "version": "4.1.18", 1502 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", 1503 + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", 1504 + "bundleDependencies": [ 1505 + "@napi-rs/wasm-runtime", 1506 + "@emnapi/core", 1507 + "@emnapi/runtime", 1508 + "@tybys/wasm-util", 1509 + "@emnapi/wasi-threads", 1510 + "tslib" 1511 + ], 1512 + "cpu": [ 1513 + "wasm32" 1514 + ], 1515 + "license": "MIT", 1516 + "optional": true, 1517 + "dependencies": { 1518 + "@emnapi/core": "^1.7.1", 1519 + "@emnapi/runtime": "^1.7.1", 1520 + "@emnapi/wasi-threads": "^1.1.0", 1521 + "@napi-rs/wasm-runtime": "^1.1.0", 1522 + "@tybys/wasm-util": "^0.10.1", 1523 + "tslib": "^2.4.0" 1524 + }, 1525 + "engines": { 1526 + "node": ">=14.0.0" 1527 + } 1528 + }, 1529 + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { 1530 + "version": "4.1.18", 1531 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", 1532 + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", 1533 + "cpu": [ 1534 + "arm64" 1535 + ], 1536 + "license": "MIT", 1537 + "optional": true, 1538 + "os": [ 1539 + "win32" 1540 + ], 1541 + "engines": { 1542 + "node": ">= 10" 1543 + } 1544 + }, 1545 + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { 1546 + "version": "4.1.18", 1547 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", 1548 + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", 1549 + "cpu": [ 1550 + "x64" 1551 + ], 1552 + "license": "MIT", 1553 + "optional": true, 1554 + "os": [ 1555 + "win32" 1556 + ], 1557 + "engines": { 1558 + "node": ">= 10" 1559 + } 1560 + }, 1561 + "node_modules/@tailwindcss/vite": { 1562 + "version": "4.1.18", 1563 + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", 1564 + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", 1565 + "license": "MIT", 1566 + "dependencies": { 1567 + "@tailwindcss/node": "4.1.18", 1568 + "@tailwindcss/oxide": "4.1.18", 1569 + "tailwindcss": "4.1.18" 1570 + }, 1571 + "peerDependencies": { 1572 + "vite": "^5.2.0 || ^6 || ^7" 1573 + } 1574 + }, 1575 + "node_modules/@tanstack/query-core": { 1576 + "version": "5.90.20", 1577 + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", 1578 + "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", 1579 + "license": "MIT", 1580 + "funding": { 1581 + "type": "github", 1582 + "url": "https://github.com/sponsors/tannerlinsley" 1583 + } 1584 + }, 1585 + "node_modules/@tanstack/react-query": { 1586 + "version": "5.90.20", 1587 + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz", 1588 + "integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==", 1589 + "license": "MIT", 1590 + "dependencies": { 1591 + "@tanstack/query-core": "5.90.20" 1592 + }, 1593 + "funding": { 1594 + "type": "github", 1595 + "url": "https://github.com/sponsors/tannerlinsley" 1596 + }, 1597 + "peerDependencies": { 1598 + "react": "^18 || ^19" 1599 + } 1600 + }, 1601 + "node_modules/@types/babel__core": { 1602 + "version": "7.20.5", 1603 + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", 1604 + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", 1605 + "dev": true, 1606 + "license": "MIT", 1607 + "dependencies": { 1608 + "@babel/parser": "^7.20.7", 1609 + "@babel/types": "^7.20.7", 1610 + "@types/babel__generator": "*", 1611 + "@types/babel__template": "*", 1612 + "@types/babel__traverse": "*" 1613 + } 1614 + }, 1615 + "node_modules/@types/babel__generator": { 1616 + "version": "7.27.0", 1617 + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", 1618 + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", 1619 + "dev": true, 1620 + "license": "MIT", 1621 + "dependencies": { 1622 + "@babel/types": "^7.0.0" 1623 + } 1624 + }, 1625 + "node_modules/@types/babel__template": { 1626 + "version": "7.4.4", 1627 + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", 1628 + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", 1629 + "dev": true, 1630 + "license": "MIT", 1631 + "dependencies": { 1632 + "@babel/parser": "^7.1.0", 1633 + "@babel/types": "^7.0.0" 1634 + } 1635 + }, 1636 + "node_modules/@types/babel__traverse": { 1637 + "version": "7.28.0", 1638 + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", 1639 + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", 1640 + "dev": true, 1641 + "license": "MIT", 1642 + "dependencies": { 1643 + "@babel/types": "^7.28.2" 1644 + } 1645 + }, 1646 + "node_modules/@types/estree": { 1647 + "version": "1.0.8", 1648 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 1649 + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 1650 + "license": "MIT" 1651 + }, 1652 + "node_modules/@types/json-schema": { 1653 + "version": "7.0.15", 1654 + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 1655 + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 1656 + "dev": true, 1657 + "license": "MIT" 1658 + }, 1659 + "node_modules/@types/node": { 1660 + "version": "24.10.10", 1661 + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.10.tgz", 1662 + "integrity": "sha512-+0/4J266CBGPUq/ELg7QUHhN25WYjE0wYTPSQJn1xeu8DOlIOPxXxrNGiLmfAWl7HMMgWFWXpt9IDjMWrF5Iow==", 1663 + "devOptional": true, 1664 + "license": "MIT", 1665 + "dependencies": { 1666 + "undici-types": "~7.16.0" 1667 + } 1668 + }, 1669 + "node_modules/@types/react": { 1670 + "version": "19.2.11", 1671 + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.11.tgz", 1672 + "integrity": "sha512-tORuanb01iEzWvMGVGv2ZDhYZVeRMrw453DCSAIn/5yvcSVnMoUMTyf33nQJLahYEnv9xqrTNbgz4qY5EfSh0g==", 1673 + "devOptional": true, 1674 + "license": "MIT", 1675 + "dependencies": { 1676 + "csstype": "^3.2.2" 1677 + } 1678 + }, 1679 + "node_modules/@types/react-dom": { 1680 + "version": "19.2.3", 1681 + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", 1682 + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", 1683 + "dev": true, 1684 + "license": "MIT", 1685 + "peerDependencies": { 1686 + "@types/react": "^19.2.0" 1687 + } 1688 + }, 1689 + "node_modules/@typescript-eslint/eslint-plugin": { 1690 + "version": "8.54.0", 1691 + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", 1692 + "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", 1693 + "dev": true, 1694 + "license": "MIT", 1695 + "dependencies": { 1696 + "@eslint-community/regexpp": "^4.12.2", 1697 + "@typescript-eslint/scope-manager": "8.54.0", 1698 + "@typescript-eslint/type-utils": "8.54.0", 1699 + "@typescript-eslint/utils": "8.54.0", 1700 + "@typescript-eslint/visitor-keys": "8.54.0", 1701 + "ignore": "^7.0.5", 1702 + "natural-compare": "^1.4.0", 1703 + "ts-api-utils": "^2.4.0" 1704 + }, 1705 + "engines": { 1706 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1707 + }, 1708 + "funding": { 1709 + "type": "opencollective", 1710 + "url": "https://opencollective.com/typescript-eslint" 1711 + }, 1712 + "peerDependencies": { 1713 + "@typescript-eslint/parser": "^8.54.0", 1714 + "eslint": "^8.57.0 || ^9.0.0", 1715 + "typescript": ">=4.8.4 <6.0.0" 1716 + } 1717 + }, 1718 + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { 1719 + "version": "7.0.5", 1720 + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", 1721 + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", 1722 + "dev": true, 1723 + "license": "MIT", 1724 + "engines": { 1725 + "node": ">= 4" 1726 + } 1727 + }, 1728 + "node_modules/@typescript-eslint/parser": { 1729 + "version": "8.54.0", 1730 + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", 1731 + "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", 1732 + "dev": true, 1733 + "license": "MIT", 1734 + "dependencies": { 1735 + "@typescript-eslint/scope-manager": "8.54.0", 1736 + "@typescript-eslint/types": "8.54.0", 1737 + "@typescript-eslint/typescript-estree": "8.54.0", 1738 + "@typescript-eslint/visitor-keys": "8.54.0", 1739 + "debug": "^4.4.3" 1740 + }, 1741 + "engines": { 1742 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1743 + }, 1744 + "funding": { 1745 + "type": "opencollective", 1746 + "url": "https://opencollective.com/typescript-eslint" 1747 + }, 1748 + "peerDependencies": { 1749 + "eslint": "^8.57.0 || ^9.0.0", 1750 + "typescript": ">=4.8.4 <6.0.0" 1751 + } 1752 + }, 1753 + "node_modules/@typescript-eslint/project-service": { 1754 + "version": "8.54.0", 1755 + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", 1756 + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", 1757 + "dev": true, 1758 + "license": "MIT", 1759 + "dependencies": { 1760 + "@typescript-eslint/tsconfig-utils": "^8.54.0", 1761 + "@typescript-eslint/types": "^8.54.0", 1762 + "debug": "^4.4.3" 1763 + }, 1764 + "engines": { 1765 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1766 + }, 1767 + "funding": { 1768 + "type": "opencollective", 1769 + "url": "https://opencollective.com/typescript-eslint" 1770 + }, 1771 + "peerDependencies": { 1772 + "typescript": ">=4.8.4 <6.0.0" 1773 + } 1774 + }, 1775 + "node_modules/@typescript-eslint/scope-manager": { 1776 + "version": "8.54.0", 1777 + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", 1778 + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", 1779 + "dev": true, 1780 + "license": "MIT", 1781 + "dependencies": { 1782 + "@typescript-eslint/types": "8.54.0", 1783 + "@typescript-eslint/visitor-keys": "8.54.0" 1784 + }, 1785 + "engines": { 1786 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1787 + }, 1788 + "funding": { 1789 + "type": "opencollective", 1790 + "url": "https://opencollective.com/typescript-eslint" 1791 + } 1792 + }, 1793 + "node_modules/@typescript-eslint/tsconfig-utils": { 1794 + "version": "8.54.0", 1795 + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", 1796 + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", 1797 + "dev": true, 1798 + "license": "MIT", 1799 + "engines": { 1800 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1801 + }, 1802 + "funding": { 1803 + "type": "opencollective", 1804 + "url": "https://opencollective.com/typescript-eslint" 1805 + }, 1806 + "peerDependencies": { 1807 + "typescript": ">=4.8.4 <6.0.0" 1808 + } 1809 + }, 1810 + "node_modules/@typescript-eslint/type-utils": { 1811 + "version": "8.54.0", 1812 + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", 1813 + "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", 1814 + "dev": true, 1815 + "license": "MIT", 1816 + "dependencies": { 1817 + "@typescript-eslint/types": "8.54.0", 1818 + "@typescript-eslint/typescript-estree": "8.54.0", 1819 + "@typescript-eslint/utils": "8.54.0", 1820 + "debug": "^4.4.3", 1821 + "ts-api-utils": "^2.4.0" 1822 + }, 1823 + "engines": { 1824 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1825 + }, 1826 + "funding": { 1827 + "type": "opencollective", 1828 + "url": "https://opencollective.com/typescript-eslint" 1829 + }, 1830 + "peerDependencies": { 1831 + "eslint": "^8.57.0 || ^9.0.0", 1832 + "typescript": ">=4.8.4 <6.0.0" 1833 + } 1834 + }, 1835 + "node_modules/@typescript-eslint/types": { 1836 + "version": "8.54.0", 1837 + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", 1838 + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", 1839 + "dev": true, 1840 + "license": "MIT", 1841 + "engines": { 1842 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1843 + }, 1844 + "funding": { 1845 + "type": "opencollective", 1846 + "url": "https://opencollective.com/typescript-eslint" 1847 + } 1848 + }, 1849 + "node_modules/@typescript-eslint/typescript-estree": { 1850 + "version": "8.54.0", 1851 + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", 1852 + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", 1853 + "dev": true, 1854 + "license": "MIT", 1855 + "dependencies": { 1856 + "@typescript-eslint/project-service": "8.54.0", 1857 + "@typescript-eslint/tsconfig-utils": "8.54.0", 1858 + "@typescript-eslint/types": "8.54.0", 1859 + "@typescript-eslint/visitor-keys": "8.54.0", 1860 + "debug": "^4.4.3", 1861 + "minimatch": "^9.0.5", 1862 + "semver": "^7.7.3", 1863 + "tinyglobby": "^0.2.15", 1864 + "ts-api-utils": "^2.4.0" 1865 + }, 1866 + "engines": { 1867 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1868 + }, 1869 + "funding": { 1870 + "type": "opencollective", 1871 + "url": "https://opencollective.com/typescript-eslint" 1872 + }, 1873 + "peerDependencies": { 1874 + "typescript": ">=4.8.4 <6.0.0" 1875 + } 1876 + }, 1877 + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { 1878 + "version": "2.0.2", 1879 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", 1880 + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", 1881 + "dev": true, 1882 + "license": "MIT", 1883 + "dependencies": { 1884 + "balanced-match": "^1.0.0" 1885 + } 1886 + }, 1887 + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { 1888 + "version": "9.0.5", 1889 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 1890 + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 1891 + "dev": true, 1892 + "license": "ISC", 1893 + "dependencies": { 1894 + "brace-expansion": "^2.0.1" 1895 + }, 1896 + "engines": { 1897 + "node": ">=16 || 14 >=14.17" 1898 + }, 1899 + "funding": { 1900 + "url": "https://github.com/sponsors/isaacs" 1901 + } 1902 + }, 1903 + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { 1904 + "version": "7.7.3", 1905 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", 1906 + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", 1907 + "dev": true, 1908 + "license": "ISC", 1909 + "bin": { 1910 + "semver": "bin/semver.js" 1911 + }, 1912 + "engines": { 1913 + "node": ">=10" 1914 + } 1915 + }, 1916 + "node_modules/@typescript-eslint/utils": { 1917 + "version": "8.54.0", 1918 + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", 1919 + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", 1920 + "dev": true, 1921 + "license": "MIT", 1922 + "dependencies": { 1923 + "@eslint-community/eslint-utils": "^4.9.1", 1924 + "@typescript-eslint/scope-manager": "8.54.0", 1925 + "@typescript-eslint/types": "8.54.0", 1926 + "@typescript-eslint/typescript-estree": "8.54.0" 1927 + }, 1928 + "engines": { 1929 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1930 + }, 1931 + "funding": { 1932 + "type": "opencollective", 1933 + "url": "https://opencollective.com/typescript-eslint" 1934 + }, 1935 + "peerDependencies": { 1936 + "eslint": "^8.57.0 || ^9.0.0", 1937 + "typescript": ">=4.8.4 <6.0.0" 1938 + } 1939 + }, 1940 + "node_modules/@typescript-eslint/visitor-keys": { 1941 + "version": "8.54.0", 1942 + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", 1943 + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", 1944 + "dev": true, 1945 + "license": "MIT", 1946 + "dependencies": { 1947 + "@typescript-eslint/types": "8.54.0", 1948 + "eslint-visitor-keys": "^4.2.1" 1949 + }, 1950 + "engines": { 1951 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1952 + }, 1953 + "funding": { 1954 + "type": "opencollective", 1955 + "url": "https://opencollective.com/typescript-eslint" 1956 + } 1957 + }, 1958 + "node_modules/@vitejs/plugin-react": { 1959 + "version": "5.1.3", 1960 + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.3.tgz", 1961 + "integrity": "sha512-NVUnA6gQCl8jfoYqKqQU5Clv0aPw14KkZYCsX6T9Lfu9slI0LOU10OTwFHS/WmptsMMpshNd/1tuWsHQ2Uk+cg==", 1962 + "dev": true, 1963 + "license": "MIT", 1964 + "dependencies": { 1965 + "@babel/core": "^7.29.0", 1966 + "@babel/plugin-transform-react-jsx-self": "^7.27.1", 1967 + "@babel/plugin-transform-react-jsx-source": "^7.27.1", 1968 + "@rolldown/pluginutils": "1.0.0-rc.2", 1969 + "@types/babel__core": "^7.20.5", 1970 + "react-refresh": "^0.18.0" 1971 + }, 1972 + "engines": { 1973 + "node": "^20.19.0 || >=22.12.0" 1974 + }, 1975 + "peerDependencies": { 1976 + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" 1977 + } 1978 + }, 1979 + "node_modules/acorn": { 1980 + "version": "8.15.0", 1981 + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", 1982 + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", 1983 + "dev": true, 1984 + "license": "MIT", 1985 + "bin": { 1986 + "acorn": "bin/acorn" 1987 + }, 1988 + "engines": { 1989 + "node": ">=0.4.0" 1990 + } 1991 + }, 1992 + "node_modules/acorn-jsx": { 1993 + "version": "5.3.2", 1994 + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 1995 + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 1996 + "dev": true, 1997 + "license": "MIT", 1998 + "peerDependencies": { 1999 + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 2000 + } 2001 + }, 2002 + "node_modules/ajv": { 2003 + "version": "6.12.6", 2004 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 2005 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 2006 + "dev": true, 2007 + "license": "MIT", 2008 + "dependencies": { 2009 + "fast-deep-equal": "^3.1.1", 2010 + "fast-json-stable-stringify": "^2.0.0", 2011 + "json-schema-traverse": "^0.4.1", 2012 + "uri-js": "^4.2.2" 2013 + }, 2014 + "funding": { 2015 + "type": "github", 2016 + "url": "https://github.com/sponsors/epoberezkin" 2017 + } 2018 + }, 2019 + "node_modules/ansi-styles": { 2020 + "version": "4.3.0", 2021 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 2022 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 2023 + "dev": true, 2024 + "license": "MIT", 2025 + "dependencies": { 2026 + "color-convert": "^2.0.1" 2027 + }, 2028 + "engines": { 2029 + "node": ">=8" 2030 + }, 2031 + "funding": { 2032 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 2033 + } 2034 + }, 2035 + "node_modules/argparse": { 2036 + "version": "2.0.1", 2037 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 2038 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 2039 + "dev": true, 2040 + "license": "Python-2.0" 2041 + }, 2042 + "node_modules/balanced-match": { 2043 + "version": "1.0.2", 2044 + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 2045 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 2046 + "dev": true, 2047 + "license": "MIT" 2048 + }, 2049 + "node_modules/baseline-browser-mapping": { 2050 + "version": "2.9.19", 2051 + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", 2052 + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", 2053 + "dev": true, 2054 + "license": "Apache-2.0", 2055 + "bin": { 2056 + "baseline-browser-mapping": "dist/cli.js" 2057 + } 2058 + }, 2059 + "node_modules/brace-expansion": { 2060 + "version": "1.1.12", 2061 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", 2062 + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", 2063 + "dev": true, 2064 + "license": "MIT", 2065 + "dependencies": { 2066 + "balanced-match": "^1.0.0", 2067 + "concat-map": "0.0.1" 2068 + } 2069 + }, 2070 + "node_modules/browserslist": { 2071 + "version": "4.28.1", 2072 + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", 2073 + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", 2074 + "dev": true, 2075 + "funding": [ 2076 + { 2077 + "type": "opencollective", 2078 + "url": "https://opencollective.com/browserslist" 2079 + }, 2080 + { 2081 + "type": "tidelift", 2082 + "url": "https://tidelift.com/funding/github/npm/browserslist" 2083 + }, 2084 + { 2085 + "type": "github", 2086 + "url": "https://github.com/sponsors/ai" 2087 + } 2088 + ], 2089 + "license": "MIT", 2090 + "dependencies": { 2091 + "baseline-browser-mapping": "^2.9.0", 2092 + "caniuse-lite": "^1.0.30001759", 2093 + "electron-to-chromium": "^1.5.263", 2094 + "node-releases": "^2.0.27", 2095 + "update-browserslist-db": "^1.2.0" 2096 + }, 2097 + "bin": { 2098 + "browserslist": "cli.js" 2099 + }, 2100 + "engines": { 2101 + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 2102 + } 2103 + }, 2104 + "node_modules/callsites": { 2105 + "version": "3.1.0", 2106 + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 2107 + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 2108 + "dev": true, 2109 + "license": "MIT", 2110 + "engines": { 2111 + "node": ">=6" 2112 + } 2113 + }, 2114 + "node_modules/caniuse-lite": { 2115 + "version": "1.0.30001767", 2116 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001767.tgz", 2117 + "integrity": "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ==", 2118 + "dev": true, 2119 + "funding": [ 2120 + { 2121 + "type": "opencollective", 2122 + "url": "https://opencollective.com/browserslist" 2123 + }, 2124 + { 2125 + "type": "tidelift", 2126 + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 2127 + }, 2128 + { 2129 + "type": "github", 2130 + "url": "https://github.com/sponsors/ai" 2131 + } 2132 + ], 2133 + "license": "CC-BY-4.0" 2134 + }, 2135 + "node_modules/chalk": { 2136 + "version": "4.1.2", 2137 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 2138 + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 2139 + "dev": true, 2140 + "license": "MIT", 2141 + "dependencies": { 2142 + "ansi-styles": "^4.1.0", 2143 + "supports-color": "^7.1.0" 2144 + }, 2145 + "engines": { 2146 + "node": ">=10" 2147 + }, 2148 + "funding": { 2149 + "url": "https://github.com/chalk/chalk?sponsor=1" 2150 + } 2151 + }, 2152 + "node_modules/color-convert": { 2153 + "version": "2.0.1", 2154 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 2155 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 2156 + "dev": true, 2157 + "license": "MIT", 2158 + "dependencies": { 2159 + "color-name": "~1.1.4" 2160 + }, 2161 + "engines": { 2162 + "node": ">=7.0.0" 2163 + } 2164 + }, 2165 + "node_modules/color-name": { 2166 + "version": "1.1.4", 2167 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 2168 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 2169 + "dev": true, 2170 + "license": "MIT" 2171 + }, 2172 + "node_modules/concat-map": { 2173 + "version": "0.0.1", 2174 + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 2175 + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 2176 + "dev": true, 2177 + "license": "MIT" 2178 + }, 2179 + "node_modules/convert-source-map": { 2180 + "version": "2.0.0", 2181 + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", 2182 + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", 2183 + "dev": true, 2184 + "license": "MIT" 2185 + }, 2186 + "node_modules/cookie": { 2187 + "version": "1.1.1", 2188 + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", 2189 + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", 2190 + "license": "MIT", 2191 + "engines": { 2192 + "node": ">=18" 2193 + }, 2194 + "funding": { 2195 + "type": "opencollective", 2196 + "url": "https://opencollective.com/express" 2197 + } 2198 + }, 2199 + "node_modules/cross-spawn": { 2200 + "version": "7.0.6", 2201 + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 2202 + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 2203 + "dev": true, 2204 + "license": "MIT", 2205 + "dependencies": { 2206 + "path-key": "^3.1.0", 2207 + "shebang-command": "^2.0.0", 2208 + "which": "^2.0.1" 2209 + }, 2210 + "engines": { 2211 + "node": ">= 8" 2212 + } 2213 + }, 2214 + "node_modules/csstype": { 2215 + "version": "3.2.3", 2216 + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", 2217 + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", 2218 + "devOptional": true, 2219 + "license": "MIT" 2220 + }, 2221 + "node_modules/debug": { 2222 + "version": "4.4.3", 2223 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", 2224 + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 2225 + "dev": true, 2226 + "license": "MIT", 2227 + "dependencies": { 2228 + "ms": "^2.1.3" 2229 + }, 2230 + "engines": { 2231 + "node": ">=6.0" 2232 + }, 2233 + "peerDependenciesMeta": { 2234 + "supports-color": { 2235 + "optional": true 2236 + } 2237 + } 2238 + }, 2239 + "node_modules/deep-is": { 2240 + "version": "0.1.4", 2241 + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 2242 + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 2243 + "dev": true, 2244 + "license": "MIT" 2245 + }, 2246 + "node_modules/detect-libc": { 2247 + "version": "2.1.2", 2248 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", 2249 + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", 2250 + "license": "Apache-2.0", 2251 + "engines": { 2252 + "node": ">=8" 2253 + } 2254 + }, 2255 + "node_modules/electron-to-chromium": { 2256 + "version": "1.5.286", 2257 + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", 2258 + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", 2259 + "dev": true, 2260 + "license": "ISC" 2261 + }, 2262 + "node_modules/enhanced-resolve": { 2263 + "version": "5.19.0", 2264 + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", 2265 + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", 2266 + "license": "MIT", 2267 + "dependencies": { 2268 + "graceful-fs": "^4.2.4", 2269 + "tapable": "^2.3.0" 2270 + }, 2271 + "engines": { 2272 + "node": ">=10.13.0" 2273 + } 2274 + }, 2275 + "node_modules/esbuild": { 2276 + "version": "0.27.2", 2277 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", 2278 + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", 2279 + "hasInstallScript": true, 2280 + "license": "MIT", 2281 + "bin": { 2282 + "esbuild": "bin/esbuild" 2283 + }, 2284 + "engines": { 2285 + "node": ">=18" 2286 + }, 2287 + "optionalDependencies": { 2288 + "@esbuild/aix-ppc64": "0.27.2", 2289 + "@esbuild/android-arm": "0.27.2", 2290 + "@esbuild/android-arm64": "0.27.2", 2291 + "@esbuild/android-x64": "0.27.2", 2292 + "@esbuild/darwin-arm64": "0.27.2", 2293 + "@esbuild/darwin-x64": "0.27.2", 2294 + "@esbuild/freebsd-arm64": "0.27.2", 2295 + "@esbuild/freebsd-x64": "0.27.2", 2296 + "@esbuild/linux-arm": "0.27.2", 2297 + "@esbuild/linux-arm64": "0.27.2", 2298 + "@esbuild/linux-ia32": "0.27.2", 2299 + "@esbuild/linux-loong64": "0.27.2", 2300 + "@esbuild/linux-mips64el": "0.27.2", 2301 + "@esbuild/linux-ppc64": "0.27.2", 2302 + "@esbuild/linux-riscv64": "0.27.2", 2303 + "@esbuild/linux-s390x": "0.27.2", 2304 + "@esbuild/linux-x64": "0.27.2", 2305 + "@esbuild/netbsd-arm64": "0.27.2", 2306 + "@esbuild/netbsd-x64": "0.27.2", 2307 + "@esbuild/openbsd-arm64": "0.27.2", 2308 + "@esbuild/openbsd-x64": "0.27.2", 2309 + "@esbuild/openharmony-arm64": "0.27.2", 2310 + "@esbuild/sunos-x64": "0.27.2", 2311 + "@esbuild/win32-arm64": "0.27.2", 2312 + "@esbuild/win32-ia32": "0.27.2", 2313 + "@esbuild/win32-x64": "0.27.2" 2314 + } 2315 + }, 2316 + "node_modules/escalade": { 2317 + "version": "3.2.0", 2318 + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 2319 + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 2320 + "dev": true, 2321 + "license": "MIT", 2322 + "engines": { 2323 + "node": ">=6" 2324 + } 2325 + }, 2326 + "node_modules/escape-string-regexp": { 2327 + "version": "4.0.0", 2328 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 2329 + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 2330 + "dev": true, 2331 + "license": "MIT", 2332 + "engines": { 2333 + "node": ">=10" 2334 + }, 2335 + "funding": { 2336 + "url": "https://github.com/sponsors/sindresorhus" 2337 + } 2338 + }, 2339 + "node_modules/eslint": { 2340 + "version": "9.39.2", 2341 + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", 2342 + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", 2343 + "dev": true, 2344 + "license": "MIT", 2345 + "dependencies": { 2346 + "@eslint-community/eslint-utils": "^4.8.0", 2347 + "@eslint-community/regexpp": "^4.12.1", 2348 + "@eslint/config-array": "^0.21.1", 2349 + "@eslint/config-helpers": "^0.4.2", 2350 + "@eslint/core": "^0.17.0", 2351 + "@eslint/eslintrc": "^3.3.1", 2352 + "@eslint/js": "9.39.2", 2353 + "@eslint/plugin-kit": "^0.4.1", 2354 + "@humanfs/node": "^0.16.6", 2355 + "@humanwhocodes/module-importer": "^1.0.1", 2356 + "@humanwhocodes/retry": "^0.4.2", 2357 + "@types/estree": "^1.0.6", 2358 + "ajv": "^6.12.4", 2359 + "chalk": "^4.0.0", 2360 + "cross-spawn": "^7.0.6", 2361 + "debug": "^4.3.2", 2362 + "escape-string-regexp": "^4.0.0", 2363 + "eslint-scope": "^8.4.0", 2364 + "eslint-visitor-keys": "^4.2.1", 2365 + "espree": "^10.4.0", 2366 + "esquery": "^1.5.0", 2367 + "esutils": "^2.0.2", 2368 + "fast-deep-equal": "^3.1.3", 2369 + "file-entry-cache": "^8.0.0", 2370 + "find-up": "^5.0.0", 2371 + "glob-parent": "^6.0.2", 2372 + "ignore": "^5.2.0", 2373 + "imurmurhash": "^0.1.4", 2374 + "is-glob": "^4.0.0", 2375 + "json-stable-stringify-without-jsonify": "^1.0.1", 2376 + "lodash.merge": "^4.6.2", 2377 + "minimatch": "^3.1.2", 2378 + "natural-compare": "^1.4.0", 2379 + "optionator": "^0.9.3" 2380 + }, 2381 + "bin": { 2382 + "eslint": "bin/eslint.js" 2383 + }, 2384 + "engines": { 2385 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2386 + }, 2387 + "funding": { 2388 + "url": "https://eslint.org/donate" 2389 + }, 2390 + "peerDependencies": { 2391 + "jiti": "*" 2392 + }, 2393 + "peerDependenciesMeta": { 2394 + "jiti": { 2395 + "optional": true 2396 + } 2397 + } 2398 + }, 2399 + "node_modules/eslint-plugin-react-hooks": { 2400 + "version": "7.0.1", 2401 + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", 2402 + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", 2403 + "dev": true, 2404 + "license": "MIT", 2405 + "dependencies": { 2406 + "@babel/core": "^7.24.4", 2407 + "@babel/parser": "^7.24.4", 2408 + "hermes-parser": "^0.25.1", 2409 + "zod": "^3.25.0 || ^4.0.0", 2410 + "zod-validation-error": "^3.5.0 || ^4.0.0" 2411 + }, 2412 + "engines": { 2413 + "node": ">=18" 2414 + }, 2415 + "peerDependencies": { 2416 + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" 2417 + } 2418 + }, 2419 + "node_modules/eslint-plugin-react-refresh": { 2420 + "version": "0.4.26", 2421 + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", 2422 + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", 2423 + "dev": true, 2424 + "license": "MIT", 2425 + "peerDependencies": { 2426 + "eslint": ">=8.40" 2427 + } 2428 + }, 2429 + "node_modules/eslint-scope": { 2430 + "version": "8.4.0", 2431 + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", 2432 + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", 2433 + "dev": true, 2434 + "license": "BSD-2-Clause", 2435 + "dependencies": { 2436 + "esrecurse": "^4.3.0", 2437 + "estraverse": "^5.2.0" 2438 + }, 2439 + "engines": { 2440 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2441 + }, 2442 + "funding": { 2443 + "url": "https://opencollective.com/eslint" 2444 + } 2445 + }, 2446 + "node_modules/eslint-visitor-keys": { 2447 + "version": "4.2.1", 2448 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", 2449 + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", 2450 + "dev": true, 2451 + "license": "Apache-2.0", 2452 + "engines": { 2453 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2454 + }, 2455 + "funding": { 2456 + "url": "https://opencollective.com/eslint" 2457 + } 2458 + }, 2459 + "node_modules/espree": { 2460 + "version": "10.4.0", 2461 + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", 2462 + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", 2463 + "dev": true, 2464 + "license": "BSD-2-Clause", 2465 + "dependencies": { 2466 + "acorn": "^8.15.0", 2467 + "acorn-jsx": "^5.3.2", 2468 + "eslint-visitor-keys": "^4.2.1" 2469 + }, 2470 + "engines": { 2471 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2472 + }, 2473 + "funding": { 2474 + "url": "https://opencollective.com/eslint" 2475 + } 2476 + }, 2477 + "node_modules/esquery": { 2478 + "version": "1.7.0", 2479 + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", 2480 + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", 2481 + "dev": true, 2482 + "license": "BSD-3-Clause", 2483 + "dependencies": { 2484 + "estraverse": "^5.1.0" 2485 + }, 2486 + "engines": { 2487 + "node": ">=0.10" 2488 + } 2489 + }, 2490 + "node_modules/esrecurse": { 2491 + "version": "4.3.0", 2492 + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 2493 + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 2494 + "dev": true, 2495 + "license": "BSD-2-Clause", 2496 + "dependencies": { 2497 + "estraverse": "^5.2.0" 2498 + }, 2499 + "engines": { 2500 + "node": ">=4.0" 2501 + } 2502 + }, 2503 + "node_modules/estraverse": { 2504 + "version": "5.3.0", 2505 + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 2506 + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 2507 + "dev": true, 2508 + "license": "BSD-2-Clause", 2509 + "engines": { 2510 + "node": ">=4.0" 2511 + } 2512 + }, 2513 + "node_modules/esutils": { 2514 + "version": "2.0.3", 2515 + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 2516 + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 2517 + "dev": true, 2518 + "license": "BSD-2-Clause", 2519 + "engines": { 2520 + "node": ">=0.10.0" 2521 + } 2522 + }, 2523 + "node_modules/fast-deep-equal": { 2524 + "version": "3.1.3", 2525 + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 2526 + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 2527 + "dev": true, 2528 + "license": "MIT" 2529 + }, 2530 + "node_modules/fast-json-stable-stringify": { 2531 + "version": "2.1.0", 2532 + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 2533 + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 2534 + "dev": true, 2535 + "license": "MIT" 2536 + }, 2537 + "node_modules/fast-levenshtein": { 2538 + "version": "2.0.6", 2539 + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 2540 + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 2541 + "dev": true, 2542 + "license": "MIT" 2543 + }, 2544 + "node_modules/fdir": { 2545 + "version": "6.5.0", 2546 + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", 2547 + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", 2548 + "license": "MIT", 2549 + "engines": { 2550 + "node": ">=12.0.0" 2551 + }, 2552 + "peerDependencies": { 2553 + "picomatch": "^3 || ^4" 2554 + }, 2555 + "peerDependenciesMeta": { 2556 + "picomatch": { 2557 + "optional": true 2558 + } 2559 + } 2560 + }, 2561 + "node_modules/file-entry-cache": { 2562 + "version": "8.0.0", 2563 + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", 2564 + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", 2565 + "dev": true, 2566 + "license": "MIT", 2567 + "dependencies": { 2568 + "flat-cache": "^4.0.0" 2569 + }, 2570 + "engines": { 2571 + "node": ">=16.0.0" 2572 + } 2573 + }, 2574 + "node_modules/find-up": { 2575 + "version": "5.0.0", 2576 + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 2577 + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 2578 + "dev": true, 2579 + "license": "MIT", 2580 + "dependencies": { 2581 + "locate-path": "^6.0.0", 2582 + "path-exists": "^4.0.0" 2583 + }, 2584 + "engines": { 2585 + "node": ">=10" 2586 + }, 2587 + "funding": { 2588 + "url": "https://github.com/sponsors/sindresorhus" 2589 + } 2590 + }, 2591 + "node_modules/flat-cache": { 2592 + "version": "4.0.1", 2593 + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", 2594 + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 2595 + "dev": true, 2596 + "license": "MIT", 2597 + "dependencies": { 2598 + "flatted": "^3.2.9", 2599 + "keyv": "^4.5.4" 2600 + }, 2601 + "engines": { 2602 + "node": ">=16" 2603 + } 2604 + }, 2605 + "node_modules/flatted": { 2606 + "version": "3.3.3", 2607 + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", 2608 + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", 2609 + "dev": true, 2610 + "license": "ISC" 2611 + }, 2612 + "node_modules/fsevents": { 2613 + "version": "2.3.3", 2614 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 2615 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 2616 + "hasInstallScript": true, 2617 + "license": "MIT", 2618 + "optional": true, 2619 + "os": [ 2620 + "darwin" 2621 + ], 2622 + "engines": { 2623 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 2624 + } 2625 + }, 2626 + "node_modules/gensync": { 2627 + "version": "1.0.0-beta.2", 2628 + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 2629 + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 2630 + "dev": true, 2631 + "license": "MIT", 2632 + "engines": { 2633 + "node": ">=6.9.0" 2634 + } 2635 + }, 2636 + "node_modules/glob-parent": { 2637 + "version": "6.0.2", 2638 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 2639 + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 2640 + "dev": true, 2641 + "license": "ISC", 2642 + "dependencies": { 2643 + "is-glob": "^4.0.3" 2644 + }, 2645 + "engines": { 2646 + "node": ">=10.13.0" 2647 + } 2648 + }, 2649 + "node_modules/globals": { 2650 + "version": "16.5.0", 2651 + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", 2652 + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", 2653 + "dev": true, 2654 + "license": "MIT", 2655 + "engines": { 2656 + "node": ">=18" 2657 + }, 2658 + "funding": { 2659 + "url": "https://github.com/sponsors/sindresorhus" 2660 + } 2661 + }, 2662 + "node_modules/graceful-fs": { 2663 + "version": "4.2.11", 2664 + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 2665 + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 2666 + "license": "ISC" 2667 + }, 2668 + "node_modules/has-flag": { 2669 + "version": "4.0.0", 2670 + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 2671 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 2672 + "dev": true, 2673 + "license": "MIT", 2674 + "engines": { 2675 + "node": ">=8" 2676 + } 2677 + }, 2678 + "node_modules/hermes-estree": { 2679 + "version": "0.25.1", 2680 + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", 2681 + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", 2682 + "dev": true, 2683 + "license": "MIT" 2684 + }, 2685 + "node_modules/hermes-parser": { 2686 + "version": "0.25.1", 2687 + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", 2688 + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", 2689 + "dev": true, 2690 + "license": "MIT", 2691 + "dependencies": { 2692 + "hermes-estree": "0.25.1" 2693 + } 2694 + }, 2695 + "node_modules/ignore": { 2696 + "version": "5.3.2", 2697 + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 2698 + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 2699 + "dev": true, 2700 + "license": "MIT", 2701 + "engines": { 2702 + "node": ">= 4" 2703 + } 2704 + }, 2705 + "node_modules/import-fresh": { 2706 + "version": "3.3.1", 2707 + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 2708 + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 2709 + "dev": true, 2710 + "license": "MIT", 2711 + "dependencies": { 2712 + "parent-module": "^1.0.0", 2713 + "resolve-from": "^4.0.0" 2714 + }, 2715 + "engines": { 2716 + "node": ">=6" 2717 + }, 2718 + "funding": { 2719 + "url": "https://github.com/sponsors/sindresorhus" 2720 + } 2721 + }, 2722 + "node_modules/imurmurhash": { 2723 + "version": "0.1.4", 2724 + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 2725 + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 2726 + "dev": true, 2727 + "license": "MIT", 2728 + "engines": { 2729 + "node": ">=0.8.19" 2730 + } 2731 + }, 2732 + "node_modules/is-extglob": { 2733 + "version": "2.1.1", 2734 + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 2735 + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 2736 + "dev": true, 2737 + "license": "MIT", 2738 + "engines": { 2739 + "node": ">=0.10.0" 2740 + } 2741 + }, 2742 + "node_modules/is-glob": { 2743 + "version": "4.0.3", 2744 + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 2745 + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 2746 + "dev": true, 2747 + "license": "MIT", 2748 + "dependencies": { 2749 + "is-extglob": "^2.1.1" 2750 + }, 2751 + "engines": { 2752 + "node": ">=0.10.0" 2753 + } 2754 + }, 2755 + "node_modules/isexe": { 2756 + "version": "2.0.0", 2757 + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 2758 + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 2759 + "dev": true, 2760 + "license": "ISC" 2761 + }, 2762 + "node_modules/jiti": { 2763 + "version": "2.6.1", 2764 + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", 2765 + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", 2766 + "license": "MIT", 2767 + "bin": { 2768 + "jiti": "lib/jiti-cli.mjs" 2769 + } 2770 + }, 2771 + "node_modules/js-tokens": { 2772 + "version": "4.0.0", 2773 + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 2774 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 2775 + "dev": true, 2776 + "license": "MIT" 2777 + }, 2778 + "node_modules/js-yaml": { 2779 + "version": "4.1.1", 2780 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", 2781 + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", 2782 + "dev": true, 2783 + "license": "MIT", 2784 + "dependencies": { 2785 + "argparse": "^2.0.1" 2786 + }, 2787 + "bin": { 2788 + "js-yaml": "bin/js-yaml.js" 2789 + } 2790 + }, 2791 + "node_modules/jsesc": { 2792 + "version": "3.1.0", 2793 + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", 2794 + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", 2795 + "dev": true, 2796 + "license": "MIT", 2797 + "bin": { 2798 + "jsesc": "bin/jsesc" 2799 + }, 2800 + "engines": { 2801 + "node": ">=6" 2802 + } 2803 + }, 2804 + "node_modules/json-buffer": { 2805 + "version": "3.0.1", 2806 + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 2807 + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 2808 + "dev": true, 2809 + "license": "MIT" 2810 + }, 2811 + "node_modules/json-schema-traverse": { 2812 + "version": "0.4.1", 2813 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 2814 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 2815 + "dev": true, 2816 + "license": "MIT" 2817 + }, 2818 + "node_modules/json-stable-stringify-without-jsonify": { 2819 + "version": "1.0.1", 2820 + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 2821 + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 2822 + "dev": true, 2823 + "license": "MIT" 2824 + }, 2825 + "node_modules/json5": { 2826 + "version": "2.2.3", 2827 + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 2828 + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 2829 + "dev": true, 2830 + "license": "MIT", 2831 + "bin": { 2832 + "json5": "lib/cli.js" 2833 + }, 2834 + "engines": { 2835 + "node": ">=6" 2836 + } 2837 + }, 2838 + "node_modules/keyv": { 2839 + "version": "4.5.4", 2840 + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 2841 + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 2842 + "dev": true, 2843 + "license": "MIT", 2844 + "dependencies": { 2845 + "json-buffer": "3.0.1" 2846 + } 2847 + }, 2848 + "node_modules/levn": { 2849 + "version": "0.4.1", 2850 + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 2851 + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 2852 + "dev": true, 2853 + "license": "MIT", 2854 + "dependencies": { 2855 + "prelude-ls": "^1.2.1", 2856 + "type-check": "~0.4.0" 2857 + }, 2858 + "engines": { 2859 + "node": ">= 0.8.0" 2860 + } 2861 + }, 2862 + "node_modules/lightningcss": { 2863 + "version": "1.30.2", 2864 + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", 2865 + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", 2866 + "license": "MPL-2.0", 2867 + "dependencies": { 2868 + "detect-libc": "^2.0.3" 2869 + }, 2870 + "engines": { 2871 + "node": ">= 12.0.0" 2872 + }, 2873 + "funding": { 2874 + "type": "opencollective", 2875 + "url": "https://opencollective.com/parcel" 2876 + }, 2877 + "optionalDependencies": { 2878 + "lightningcss-android-arm64": "1.30.2", 2879 + "lightningcss-darwin-arm64": "1.30.2", 2880 + "lightningcss-darwin-x64": "1.30.2", 2881 + "lightningcss-freebsd-x64": "1.30.2", 2882 + "lightningcss-linux-arm-gnueabihf": "1.30.2", 2883 + "lightningcss-linux-arm64-gnu": "1.30.2", 2884 + "lightningcss-linux-arm64-musl": "1.30.2", 2885 + "lightningcss-linux-x64-gnu": "1.30.2", 2886 + "lightningcss-linux-x64-musl": "1.30.2", 2887 + "lightningcss-win32-arm64-msvc": "1.30.2", 2888 + "lightningcss-win32-x64-msvc": "1.30.2" 2889 + } 2890 + }, 2891 + "node_modules/lightningcss-android-arm64": { 2892 + "version": "1.30.2", 2893 + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", 2894 + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", 2895 + "cpu": [ 2896 + "arm64" 2897 + ], 2898 + "license": "MPL-2.0", 2899 + "optional": true, 2900 + "os": [ 2901 + "android" 2902 + ], 2903 + "engines": { 2904 + "node": ">= 12.0.0" 2905 + }, 2906 + "funding": { 2907 + "type": "opencollective", 2908 + "url": "https://opencollective.com/parcel" 2909 + } 2910 + }, 2911 + "node_modules/lightningcss-darwin-arm64": { 2912 + "version": "1.30.2", 2913 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", 2914 + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", 2915 + "cpu": [ 2916 + "arm64" 2917 + ], 2918 + "license": "MPL-2.0", 2919 + "optional": true, 2920 + "os": [ 2921 + "darwin" 2922 + ], 2923 + "engines": { 2924 + "node": ">= 12.0.0" 2925 + }, 2926 + "funding": { 2927 + "type": "opencollective", 2928 + "url": "https://opencollective.com/parcel" 2929 + } 2930 + }, 2931 + "node_modules/lightningcss-darwin-x64": { 2932 + "version": "1.30.2", 2933 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", 2934 + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", 2935 + "cpu": [ 2936 + "x64" 2937 + ], 2938 + "license": "MPL-2.0", 2939 + "optional": true, 2940 + "os": [ 2941 + "darwin" 2942 + ], 2943 + "engines": { 2944 + "node": ">= 12.0.0" 2945 + }, 2946 + "funding": { 2947 + "type": "opencollective", 2948 + "url": "https://opencollective.com/parcel" 2949 + } 2950 + }, 2951 + "node_modules/lightningcss-freebsd-x64": { 2952 + "version": "1.30.2", 2953 + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", 2954 + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", 2955 + "cpu": [ 2956 + "x64" 2957 + ], 2958 + "license": "MPL-2.0", 2959 + "optional": true, 2960 + "os": [ 2961 + "freebsd" 2962 + ], 2963 + "engines": { 2964 + "node": ">= 12.0.0" 2965 + }, 2966 + "funding": { 2967 + "type": "opencollective", 2968 + "url": "https://opencollective.com/parcel" 2969 + } 2970 + }, 2971 + "node_modules/lightningcss-linux-arm-gnueabihf": { 2972 + "version": "1.30.2", 2973 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", 2974 + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", 2975 + "cpu": [ 2976 + "arm" 2977 + ], 2978 + "license": "MPL-2.0", 2979 + "optional": true, 2980 + "os": [ 2981 + "linux" 2982 + ], 2983 + "engines": { 2984 + "node": ">= 12.0.0" 2985 + }, 2986 + "funding": { 2987 + "type": "opencollective", 2988 + "url": "https://opencollective.com/parcel" 2989 + } 2990 + }, 2991 + "node_modules/lightningcss-linux-arm64-gnu": { 2992 + "version": "1.30.2", 2993 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", 2994 + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", 2995 + "cpu": [ 2996 + "arm64" 2997 + ], 2998 + "license": "MPL-2.0", 2999 + "optional": true, 3000 + "os": [ 3001 + "linux" 3002 + ], 3003 + "engines": { 3004 + "node": ">= 12.0.0" 3005 + }, 3006 + "funding": { 3007 + "type": "opencollective", 3008 + "url": "https://opencollective.com/parcel" 3009 + } 3010 + }, 3011 + "node_modules/lightningcss-linux-arm64-musl": { 3012 + "version": "1.30.2", 3013 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", 3014 + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", 3015 + "cpu": [ 3016 + "arm64" 3017 + ], 3018 + "license": "MPL-2.0", 3019 + "optional": true, 3020 + "os": [ 3021 + "linux" 3022 + ], 3023 + "engines": { 3024 + "node": ">= 12.0.0" 3025 + }, 3026 + "funding": { 3027 + "type": "opencollective", 3028 + "url": "https://opencollective.com/parcel" 3029 + } 3030 + }, 3031 + "node_modules/lightningcss-linux-x64-gnu": { 3032 + "version": "1.30.2", 3033 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", 3034 + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", 3035 + "cpu": [ 3036 + "x64" 3037 + ], 3038 + "license": "MPL-2.0", 3039 + "optional": true, 3040 + "os": [ 3041 + "linux" 3042 + ], 3043 + "engines": { 3044 + "node": ">= 12.0.0" 3045 + }, 3046 + "funding": { 3047 + "type": "opencollective", 3048 + "url": "https://opencollective.com/parcel" 3049 + } 3050 + }, 3051 + "node_modules/lightningcss-linux-x64-musl": { 3052 + "version": "1.30.2", 3053 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", 3054 + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", 3055 + "cpu": [ 3056 + "x64" 3057 + ], 3058 + "license": "MPL-2.0", 3059 + "optional": true, 3060 + "os": [ 3061 + "linux" 3062 + ], 3063 + "engines": { 3064 + "node": ">= 12.0.0" 3065 + }, 3066 + "funding": { 3067 + "type": "opencollective", 3068 + "url": "https://opencollective.com/parcel" 3069 + } 3070 + }, 3071 + "node_modules/lightningcss-win32-arm64-msvc": { 3072 + "version": "1.30.2", 3073 + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", 3074 + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", 3075 + "cpu": [ 3076 + "arm64" 3077 + ], 3078 + "license": "MPL-2.0", 3079 + "optional": true, 3080 + "os": [ 3081 + "win32" 3082 + ], 3083 + "engines": { 3084 + "node": ">= 12.0.0" 3085 + }, 3086 + "funding": { 3087 + "type": "opencollective", 3088 + "url": "https://opencollective.com/parcel" 3089 + } 3090 + }, 3091 + "node_modules/lightningcss-win32-x64-msvc": { 3092 + "version": "1.30.2", 3093 + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", 3094 + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", 3095 + "cpu": [ 3096 + "x64" 3097 + ], 3098 + "license": "MPL-2.0", 3099 + "optional": true, 3100 + "os": [ 3101 + "win32" 3102 + ], 3103 + "engines": { 3104 + "node": ">= 12.0.0" 3105 + }, 3106 + "funding": { 3107 + "type": "opencollective", 3108 + "url": "https://opencollective.com/parcel" 3109 + } 3110 + }, 3111 + "node_modules/locate-path": { 3112 + "version": "6.0.0", 3113 + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 3114 + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 3115 + "dev": true, 3116 + "license": "MIT", 3117 + "dependencies": { 3118 + "p-locate": "^5.0.0" 3119 + }, 3120 + "engines": { 3121 + "node": ">=10" 3122 + }, 3123 + "funding": { 3124 + "url": "https://github.com/sponsors/sindresorhus" 3125 + } 3126 + }, 3127 + "node_modules/lodash.merge": { 3128 + "version": "4.6.2", 3129 + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 3130 + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 3131 + "dev": true, 3132 + "license": "MIT" 3133 + }, 3134 + "node_modules/lru-cache": { 3135 + "version": "5.1.1", 3136 + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 3137 + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 3138 + "dev": true, 3139 + "license": "ISC", 3140 + "dependencies": { 3141 + "yallist": "^3.0.2" 3142 + } 3143 + }, 3144 + "node_modules/magic-string": { 3145 + "version": "0.30.21", 3146 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", 3147 + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", 3148 + "license": "MIT", 3149 + "dependencies": { 3150 + "@jridgewell/sourcemap-codec": "^1.5.5" 3151 + } 3152 + }, 3153 + "node_modules/minimatch": { 3154 + "version": "3.1.2", 3155 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 3156 + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 3157 + "dev": true, 3158 + "license": "ISC", 3159 + "dependencies": { 3160 + "brace-expansion": "^1.1.7" 3161 + }, 3162 + "engines": { 3163 + "node": "*" 3164 + } 3165 + }, 3166 + "node_modules/ms": { 3167 + "version": "2.1.3", 3168 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 3169 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 3170 + "dev": true, 3171 + "license": "MIT" 3172 + }, 3173 + "node_modules/nanoid": { 3174 + "version": "3.3.11", 3175 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 3176 + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 3177 + "funding": [ 3178 + { 3179 + "type": "github", 3180 + "url": "https://github.com/sponsors/ai" 3181 + } 3182 + ], 3183 + "license": "MIT", 3184 + "bin": { 3185 + "nanoid": "bin/nanoid.cjs" 3186 + }, 3187 + "engines": { 3188 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 3189 + } 3190 + }, 3191 + "node_modules/natural-compare": { 3192 + "version": "1.4.0", 3193 + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 3194 + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 3195 + "dev": true, 3196 + "license": "MIT" 3197 + }, 3198 + "node_modules/node-releases": { 3199 + "version": "2.0.27", 3200 + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", 3201 + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", 3202 + "dev": true, 3203 + "license": "MIT" 3204 + }, 3205 + "node_modules/optionator": { 3206 + "version": "0.9.4", 3207 + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 3208 + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 3209 + "dev": true, 3210 + "license": "MIT", 3211 + "dependencies": { 3212 + "deep-is": "^0.1.3", 3213 + "fast-levenshtein": "^2.0.6", 3214 + "levn": "^0.4.1", 3215 + "prelude-ls": "^1.2.1", 3216 + "type-check": "^0.4.0", 3217 + "word-wrap": "^1.2.5" 3218 + }, 3219 + "engines": { 3220 + "node": ">= 0.8.0" 3221 + } 3222 + }, 3223 + "node_modules/p-limit": { 3224 + "version": "3.1.0", 3225 + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 3226 + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 3227 + "dev": true, 3228 + "license": "MIT", 3229 + "dependencies": { 3230 + "yocto-queue": "^0.1.0" 3231 + }, 3232 + "engines": { 3233 + "node": ">=10" 3234 + }, 3235 + "funding": { 3236 + "url": "https://github.com/sponsors/sindresorhus" 3237 + } 3238 + }, 3239 + "node_modules/p-locate": { 3240 + "version": "5.0.0", 3241 + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 3242 + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 3243 + "dev": true, 3244 + "license": "MIT", 3245 + "dependencies": { 3246 + "p-limit": "^3.0.2" 3247 + }, 3248 + "engines": { 3249 + "node": ">=10" 3250 + }, 3251 + "funding": { 3252 + "url": "https://github.com/sponsors/sindresorhus" 3253 + } 3254 + }, 3255 + "node_modules/parent-module": { 3256 + "version": "1.0.1", 3257 + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 3258 + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 3259 + "dev": true, 3260 + "license": "MIT", 3261 + "dependencies": { 3262 + "callsites": "^3.0.0" 3263 + }, 3264 + "engines": { 3265 + "node": ">=6" 3266 + } 3267 + }, 3268 + "node_modules/path-exists": { 3269 + "version": "4.0.0", 3270 + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 3271 + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 3272 + "dev": true, 3273 + "license": "MIT", 3274 + "engines": { 3275 + "node": ">=8" 3276 + } 3277 + }, 3278 + "node_modules/path-key": { 3279 + "version": "3.1.1", 3280 + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 3281 + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 3282 + "dev": true, 3283 + "license": "MIT", 3284 + "engines": { 3285 + "node": ">=8" 3286 + } 3287 + }, 3288 + "node_modules/picocolors": { 3289 + "version": "1.1.1", 3290 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 3291 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 3292 + "license": "ISC" 3293 + }, 3294 + "node_modules/picomatch": { 3295 + "version": "4.0.3", 3296 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 3297 + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 3298 + "license": "MIT", 3299 + "engines": { 3300 + "node": ">=12" 3301 + }, 3302 + "funding": { 3303 + "url": "https://github.com/sponsors/jonschlinkert" 3304 + } 3305 + }, 3306 + "node_modules/postcss": { 3307 + "version": "8.5.6", 3308 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", 3309 + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", 3310 + "funding": [ 3311 + { 3312 + "type": "opencollective", 3313 + "url": "https://opencollective.com/postcss/" 3314 + }, 3315 + { 3316 + "type": "tidelift", 3317 + "url": "https://tidelift.com/funding/github/npm/postcss" 3318 + }, 3319 + { 3320 + "type": "github", 3321 + "url": "https://github.com/sponsors/ai" 3322 + } 3323 + ], 3324 + "license": "MIT", 3325 + "dependencies": { 3326 + "nanoid": "^3.3.11", 3327 + "picocolors": "^1.1.1", 3328 + "source-map-js": "^1.2.1" 3329 + }, 3330 + "engines": { 3331 + "node": "^10 || ^12 || >=14" 3332 + } 3333 + }, 3334 + "node_modules/prelude-ls": { 3335 + "version": "1.2.1", 3336 + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 3337 + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 3338 + "dev": true, 3339 + "license": "MIT", 3340 + "engines": { 3341 + "node": ">= 0.8.0" 3342 + } 3343 + }, 3344 + "node_modules/punycode": { 3345 + "version": "2.3.1", 3346 + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 3347 + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 3348 + "dev": true, 3349 + "license": "MIT", 3350 + "engines": { 3351 + "node": ">=6" 3352 + } 3353 + }, 3354 + "node_modules/react": { 3355 + "version": "19.2.4", 3356 + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", 3357 + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", 3358 + "license": "MIT", 3359 + "engines": { 3360 + "node": ">=0.10.0" 3361 + } 3362 + }, 3363 + "node_modules/react-dom": { 3364 + "version": "19.2.4", 3365 + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", 3366 + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", 3367 + "license": "MIT", 3368 + "dependencies": { 3369 + "scheduler": "^0.27.0" 3370 + }, 3371 + "peerDependencies": { 3372 + "react": "^19.2.4" 3373 + } 3374 + }, 3375 + "node_modules/react-refresh": { 3376 + "version": "0.18.0", 3377 + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", 3378 + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", 3379 + "dev": true, 3380 + "license": "MIT", 3381 + "engines": { 3382 + "node": ">=0.10.0" 3383 + } 3384 + }, 3385 + "node_modules/react-router": { 3386 + "version": "7.13.0", 3387 + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz", 3388 + "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==", 3389 + "license": "MIT", 3390 + "dependencies": { 3391 + "cookie": "^1.0.1", 3392 + "set-cookie-parser": "^2.6.0" 3393 + }, 3394 + "engines": { 3395 + "node": ">=20.0.0" 3396 + }, 3397 + "peerDependencies": { 3398 + "react": ">=18", 3399 + "react-dom": ">=18" 3400 + }, 3401 + "peerDependenciesMeta": { 3402 + "react-dom": { 3403 + "optional": true 3404 + } 3405 + } 3406 + }, 3407 + "node_modules/react-router-dom": { 3408 + "version": "7.13.0", 3409 + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz", 3410 + "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==", 3411 + "license": "MIT", 3412 + "dependencies": { 3413 + "react-router": "7.13.0" 3414 + }, 3415 + "engines": { 3416 + "node": ">=20.0.0" 3417 + }, 3418 + "peerDependencies": { 3419 + "react": ">=18", 3420 + "react-dom": ">=18" 3421 + } 3422 + }, 3423 + "node_modules/resolve-from": { 3424 + "version": "4.0.0", 3425 + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 3426 + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 3427 + "dev": true, 3428 + "license": "MIT", 3429 + "engines": { 3430 + "node": ">=4" 3431 + } 3432 + }, 3433 + "node_modules/rollup": { 3434 + "version": "4.57.1", 3435 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", 3436 + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", 3437 + "license": "MIT", 3438 + "dependencies": { 3439 + "@types/estree": "1.0.8" 3440 + }, 3441 + "bin": { 3442 + "rollup": "dist/bin/rollup" 3443 + }, 3444 + "engines": { 3445 + "node": ">=18.0.0", 3446 + "npm": ">=8.0.0" 3447 + }, 3448 + "optionalDependencies": { 3449 + "@rollup/rollup-android-arm-eabi": "4.57.1", 3450 + "@rollup/rollup-android-arm64": "4.57.1", 3451 + "@rollup/rollup-darwin-arm64": "4.57.1", 3452 + "@rollup/rollup-darwin-x64": "4.57.1", 3453 + "@rollup/rollup-freebsd-arm64": "4.57.1", 3454 + "@rollup/rollup-freebsd-x64": "4.57.1", 3455 + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", 3456 + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", 3457 + "@rollup/rollup-linux-arm64-gnu": "4.57.1", 3458 + "@rollup/rollup-linux-arm64-musl": "4.57.1", 3459 + "@rollup/rollup-linux-loong64-gnu": "4.57.1", 3460 + "@rollup/rollup-linux-loong64-musl": "4.57.1", 3461 + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", 3462 + "@rollup/rollup-linux-ppc64-musl": "4.57.1", 3463 + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", 3464 + "@rollup/rollup-linux-riscv64-musl": "4.57.1", 3465 + "@rollup/rollup-linux-s390x-gnu": "4.57.1", 3466 + "@rollup/rollup-linux-x64-gnu": "4.57.1", 3467 + "@rollup/rollup-linux-x64-musl": "4.57.1", 3468 + "@rollup/rollup-openbsd-x64": "4.57.1", 3469 + "@rollup/rollup-openharmony-arm64": "4.57.1", 3470 + "@rollup/rollup-win32-arm64-msvc": "4.57.1", 3471 + "@rollup/rollup-win32-ia32-msvc": "4.57.1", 3472 + "@rollup/rollup-win32-x64-gnu": "4.57.1", 3473 + "@rollup/rollup-win32-x64-msvc": "4.57.1", 3474 + "fsevents": "~2.3.2" 3475 + } 3476 + }, 3477 + "node_modules/scheduler": { 3478 + "version": "0.27.0", 3479 + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", 3480 + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", 3481 + "license": "MIT" 3482 + }, 3483 + "node_modules/semver": { 3484 + "version": "6.3.1", 3485 + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 3486 + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 3487 + "dev": true, 3488 + "license": "ISC", 3489 + "bin": { 3490 + "semver": "bin/semver.js" 3491 + } 3492 + }, 3493 + "node_modules/set-cookie-parser": { 3494 + "version": "2.7.2", 3495 + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", 3496 + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", 3497 + "license": "MIT" 3498 + }, 3499 + "node_modules/shebang-command": { 3500 + "version": "2.0.0", 3501 + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 3502 + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 3503 + "dev": true, 3504 + "license": "MIT", 3505 + "dependencies": { 3506 + "shebang-regex": "^3.0.0" 3507 + }, 3508 + "engines": { 3509 + "node": ">=8" 3510 + } 3511 + }, 3512 + "node_modules/shebang-regex": { 3513 + "version": "3.0.0", 3514 + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 3515 + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 3516 + "dev": true, 3517 + "license": "MIT", 3518 + "engines": { 3519 + "node": ">=8" 3520 + } 3521 + }, 3522 + "node_modules/source-map-js": { 3523 + "version": "1.2.1", 3524 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 3525 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 3526 + "license": "BSD-3-Clause", 3527 + "engines": { 3528 + "node": ">=0.10.0" 3529 + } 3530 + }, 3531 + "node_modules/strip-json-comments": { 3532 + "version": "3.1.1", 3533 + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 3534 + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 3535 + "dev": true, 3536 + "license": "MIT", 3537 + "engines": { 3538 + "node": ">=8" 3539 + }, 3540 + "funding": { 3541 + "url": "https://github.com/sponsors/sindresorhus" 3542 + } 3543 + }, 3544 + "node_modules/supports-color": { 3545 + "version": "7.2.0", 3546 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 3547 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 3548 + "dev": true, 3549 + "license": "MIT", 3550 + "dependencies": { 3551 + "has-flag": "^4.0.0" 3552 + }, 3553 + "engines": { 3554 + "node": ">=8" 3555 + } 3556 + }, 3557 + "node_modules/tailwindcss": { 3558 + "version": "4.1.18", 3559 + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", 3560 + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", 3561 + "license": "MIT" 3562 + }, 3563 + "node_modules/tapable": { 3564 + "version": "2.3.0", 3565 + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", 3566 + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", 3567 + "license": "MIT", 3568 + "engines": { 3569 + "node": ">=6" 3570 + }, 3571 + "funding": { 3572 + "type": "opencollective", 3573 + "url": "https://opencollective.com/webpack" 3574 + } 3575 + }, 3576 + "node_modules/tinyglobby": { 3577 + "version": "0.2.15", 3578 + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", 3579 + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", 3580 + "license": "MIT", 3581 + "dependencies": { 3582 + "fdir": "^6.5.0", 3583 + "picomatch": "^4.0.3" 3584 + }, 3585 + "engines": { 3586 + "node": ">=12.0.0" 3587 + }, 3588 + "funding": { 3589 + "url": "https://github.com/sponsors/SuperchupuDev" 3590 + } 3591 + }, 3592 + "node_modules/ts-api-utils": { 3593 + "version": "2.4.0", 3594 + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", 3595 + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", 3596 + "dev": true, 3597 + "license": "MIT", 3598 + "engines": { 3599 + "node": ">=18.12" 3600 + }, 3601 + "peerDependencies": { 3602 + "typescript": ">=4.8.4" 3603 + } 3604 + }, 3605 + "node_modules/type-check": { 3606 + "version": "0.4.0", 3607 + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 3608 + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 3609 + "dev": true, 3610 + "license": "MIT", 3611 + "dependencies": { 3612 + "prelude-ls": "^1.2.1" 3613 + }, 3614 + "engines": { 3615 + "node": ">= 0.8.0" 3616 + } 3617 + }, 3618 + "node_modules/typescript": { 3619 + "version": "5.9.3", 3620 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 3621 + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 3622 + "dev": true, 3623 + "license": "Apache-2.0", 3624 + "bin": { 3625 + "tsc": "bin/tsc", 3626 + "tsserver": "bin/tsserver" 3627 + }, 3628 + "engines": { 3629 + "node": ">=14.17" 3630 + } 3631 + }, 3632 + "node_modules/typescript-eslint": { 3633 + "version": "8.54.0", 3634 + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", 3635 + "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", 3636 + "dev": true, 3637 + "license": "MIT", 3638 + "dependencies": { 3639 + "@typescript-eslint/eslint-plugin": "8.54.0", 3640 + "@typescript-eslint/parser": "8.54.0", 3641 + "@typescript-eslint/typescript-estree": "8.54.0", 3642 + "@typescript-eslint/utils": "8.54.0" 3643 + }, 3644 + "engines": { 3645 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3646 + }, 3647 + "funding": { 3648 + "type": "opencollective", 3649 + "url": "https://opencollective.com/typescript-eslint" 3650 + }, 3651 + "peerDependencies": { 3652 + "eslint": "^8.57.0 || ^9.0.0", 3653 + "typescript": ">=4.8.4 <6.0.0" 3654 + } 3655 + }, 3656 + "node_modules/undici-types": { 3657 + "version": "7.16.0", 3658 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", 3659 + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", 3660 + "devOptional": true, 3661 + "license": "MIT" 3662 + }, 3663 + "node_modules/update-browserslist-db": { 3664 + "version": "1.2.3", 3665 + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", 3666 + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", 3667 + "dev": true, 3668 + "funding": [ 3669 + { 3670 + "type": "opencollective", 3671 + "url": "https://opencollective.com/browserslist" 3672 + }, 3673 + { 3674 + "type": "tidelift", 3675 + "url": "https://tidelift.com/funding/github/npm/browserslist" 3676 + }, 3677 + { 3678 + "type": "github", 3679 + "url": "https://github.com/sponsors/ai" 3680 + } 3681 + ], 3682 + "license": "MIT", 3683 + "dependencies": { 3684 + "escalade": "^3.2.0", 3685 + "picocolors": "^1.1.1" 3686 + }, 3687 + "bin": { 3688 + "update-browserslist-db": "cli.js" 3689 + }, 3690 + "peerDependencies": { 3691 + "browserslist": ">= 4.21.0" 3692 + } 3693 + }, 3694 + "node_modules/uri-js": { 3695 + "version": "4.4.1", 3696 + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 3697 + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 3698 + "dev": true, 3699 + "license": "BSD-2-Clause", 3700 + "dependencies": { 3701 + "punycode": "^2.1.0" 3702 + } 3703 + }, 3704 + "node_modules/vite": { 3705 + "version": "7.3.1", 3706 + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", 3707 + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", 3708 + "license": "MIT", 3709 + "dependencies": { 3710 + "esbuild": "^0.27.0", 3711 + "fdir": "^6.5.0", 3712 + "picomatch": "^4.0.3", 3713 + "postcss": "^8.5.6", 3714 + "rollup": "^4.43.0", 3715 + "tinyglobby": "^0.2.15" 3716 + }, 3717 + "bin": { 3718 + "vite": "bin/vite.js" 3719 + }, 3720 + "engines": { 3721 + "node": "^20.19.0 || >=22.12.0" 3722 + }, 3723 + "funding": { 3724 + "url": "https://github.com/vitejs/vite?sponsor=1" 3725 + }, 3726 + "optionalDependencies": { 3727 + "fsevents": "~2.3.3" 3728 + }, 3729 + "peerDependencies": { 3730 + "@types/node": "^20.19.0 || >=22.12.0", 3731 + "jiti": ">=1.21.0", 3732 + "less": "^4.0.0", 3733 + "lightningcss": "^1.21.0", 3734 + "sass": "^1.70.0", 3735 + "sass-embedded": "^1.70.0", 3736 + "stylus": ">=0.54.8", 3737 + "sugarss": "^5.0.0", 3738 + "terser": "^5.16.0", 3739 + "tsx": "^4.8.1", 3740 + "yaml": "^2.4.2" 3741 + }, 3742 + "peerDependenciesMeta": { 3743 + "@types/node": { 3744 + "optional": true 3745 + }, 3746 + "jiti": { 3747 + "optional": true 3748 + }, 3749 + "less": { 3750 + "optional": true 3751 + }, 3752 + "lightningcss": { 3753 + "optional": true 3754 + }, 3755 + "sass": { 3756 + "optional": true 3757 + }, 3758 + "sass-embedded": { 3759 + "optional": true 3760 + }, 3761 + "stylus": { 3762 + "optional": true 3763 + }, 3764 + "sugarss": { 3765 + "optional": true 3766 + }, 3767 + "terser": { 3768 + "optional": true 3769 + }, 3770 + "tsx": { 3771 + "optional": true 3772 + }, 3773 + "yaml": { 3774 + "optional": true 3775 + } 3776 + } 3777 + }, 3778 + "node_modules/which": { 3779 + "version": "2.0.2", 3780 + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 3781 + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 3782 + "dev": true, 3783 + "license": "ISC", 3784 + "dependencies": { 3785 + "isexe": "^2.0.0" 3786 + }, 3787 + "bin": { 3788 + "node-which": "bin/node-which" 3789 + }, 3790 + "engines": { 3791 + "node": ">= 8" 3792 + } 3793 + }, 3794 + "node_modules/word-wrap": { 3795 + "version": "1.2.5", 3796 + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 3797 + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 3798 + "dev": true, 3799 + "license": "MIT", 3800 + "engines": { 3801 + "node": ">=0.10.0" 3802 + } 3803 + }, 3804 + "node_modules/yallist": { 3805 + "version": "3.1.1", 3806 + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 3807 + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 3808 + "dev": true, 3809 + "license": "ISC" 3810 + }, 3811 + "node_modules/yocto-queue": { 3812 + "version": "0.1.0", 3813 + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 3814 + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 3815 + "dev": true, 3816 + "license": "MIT", 3817 + "engines": { 3818 + "node": ">=10" 3819 + }, 3820 + "funding": { 3821 + "url": "https://github.com/sponsors/sindresorhus" 3822 + } 3823 + }, 3824 + "node_modules/zod": { 3825 + "version": "4.3.6", 3826 + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", 3827 + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", 3828 + "dev": true, 3829 + "license": "MIT", 3830 + "funding": { 3831 + "url": "https://github.com/sponsors/colinhacks" 3832 + } 3833 + }, 3834 + "node_modules/zod-validation-error": { 3835 + "version": "4.0.2", 3836 + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", 3837 + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", 3838 + "dev": true, 3839 + "license": "MIT", 3840 + "engines": { 3841 + "node": ">=18.0.0" 3842 + }, 3843 + "peerDependencies": { 3844 + "zod": "^3.25.0 || ^4.0.0" 3845 + } 3846 + }, 3847 + "node_modules/zustand": { 3848 + "version": "5.0.11", 3849 + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz", 3850 + "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==", 3851 + "license": "MIT", 3852 + "engines": { 3853 + "node": ">=12.20.0" 3854 + }, 3855 + "peerDependencies": { 3856 + "@types/react": ">=18.0.0", 3857 + "immer": ">=9.0.6", 3858 + "react": ">=18.0.0", 3859 + "use-sync-external-store": ">=1.2.0" 3860 + }, 3861 + "peerDependenciesMeta": { 3862 + "@types/react": { 3863 + "optional": true 3864 + }, 3865 + "immer": { 3866 + "optional": true 3867 + }, 3868 + "react": { 3869 + "optional": true 3870 + }, 3871 + "use-sync-external-store": { 3872 + "optional": true 3873 + } 3874 + } 3875 + } 3876 + } 3877 + }
+35
gateway/admin-ui/package.json
··· 1 + { 2 + "name": "admin-ui", 3 + "private": true, 4 + "version": "0.0.0", 5 + "type": "module", 6 + "scripts": { 7 + "dev": "vite", 8 + "build": "tsc -b && vite build", 9 + "lint": "eslint .", 10 + "preview": "vite preview" 11 + }, 12 + "dependencies": { 13 + "@tailwindcss/vite": "^4.1.18", 14 + "@tanstack/react-query": "^5.90.20", 15 + "react": "^19.2.0", 16 + "react-dom": "^19.2.0", 17 + "react-router-dom": "^7.13.0", 18 + "tailwindcss": "^4.1.18", 19 + "zustand": "^5.0.11" 20 + }, 21 + "devDependencies": { 22 + "@eslint/js": "^9.39.1", 23 + "@types/node": "^24.10.1", 24 + "@types/react": "^19.2.5", 25 + "@types/react-dom": "^19.2.3", 26 + "@vitejs/plugin-react": "^5.1.1", 27 + "eslint": "^9.39.1", 28 + "eslint-plugin-react-hooks": "^7.0.1", 29 + "eslint-plugin-react-refresh": "^0.4.24", 30 + "globals": "^16.5.0", 31 + "typescript": "~5.9.3", 32 + "typescript-eslint": "^8.46.4", 33 + "vite": "^7.2.4" 34 + } 35 + }
+1
gateway/admin-ui/public/vite.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
+61
gateway/admin-ui/src/App.tsx
··· 1 + import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; 2 + import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 3 + import { useAuthStore } from './stores/auth'; 4 + import { Layout } from './components/Layout'; 5 + import { 6 + LoginPage, 7 + DashboardPage, 8 + AppsPage, 9 + OIDCClientsPage, 10 + SessionsPage, 11 + KeysPage, 12 + UsersPage, 13 + } from './pages'; 14 + 15 + const queryClient = new QueryClient({ 16 + defaultOptions: { 17 + queries: { 18 + staleTime: 30000, 19 + retry: 1, 20 + }, 21 + }, 22 + }); 23 + 24 + function ProtectedRoute({ children }: { children: React.ReactNode }) { 25 + const isAuthenticated = useAuthStore((s) => s.isAuthenticated); 26 + 27 + if (!isAuthenticated) { 28 + return <Navigate to="/login" replace />; 29 + } 30 + 31 + return <>{children}</>; 32 + } 33 + 34 + function App() { 35 + return ( 36 + <QueryClientProvider client={queryClient}> 37 + <BrowserRouter basename="/admin"> 38 + <Routes> 39 + <Route path="/login" element={<LoginPage />} /> 40 + <Route 41 + element={ 42 + <ProtectedRoute> 43 + <Layout /> 44 + </ProtectedRoute> 45 + } 46 + > 47 + <Route path="/dashboard" element={<DashboardPage />} /> 48 + <Route path="/apps" element={<AppsPage />} /> 49 + <Route path="/oidc-clients" element={<OIDCClientsPage />} /> 50 + <Route path="/sessions" element={<SessionsPage />} /> 51 + <Route path="/keys" element={<KeysPage />} /> 52 + <Route path="/users" element={<UsersPage />} /> 53 + <Route path="/" element={<Navigate to="/dashboard" replace />} /> 54 + </Route> 55 + </Routes> 56 + </BrowserRouter> 57 + </QueryClientProvider> 58 + ); 59 + } 60 + 61 + export default App;
+306
gateway/admin-ui/src/api/client.ts
··· 1 + /** 2 + * API Client for ATAuth Admin 3 + */ 4 + 5 + const API_BASE = import.meta.env.DEV ? '/admin/api' : '/admin'; 6 + 7 + interface ApiError { 8 + error: string; 9 + message: string; 10 + } 11 + 12 + class AdminApiClient { 13 + private token: string | null = null; 14 + 15 + setToken(token: string | null) { 16 + this.token = token; 17 + } 18 + 19 + getToken(): string | null { 20 + return this.token; 21 + } 22 + 23 + private async request<T>( 24 + path: string, 25 + options: RequestInit = {} 26 + ): Promise<T> { 27 + const headers: Record<string, string> = { 28 + 'Content-Type': 'application/json', 29 + ...(options.headers as Record<string, string>), 30 + }; 31 + 32 + if (this.token) { 33 + headers['Authorization'] = `Bearer ${this.token}`; 34 + } 35 + 36 + const response = await fetch(`${API_BASE}${path}`, { 37 + ...options, 38 + headers, 39 + }); 40 + 41 + if (!response.ok) { 42 + const error: ApiError = await response.json().catch(() => ({ 43 + error: 'unknown_error', 44 + message: response.statusText, 45 + })); 46 + throw new Error(error.message || error.error); 47 + } 48 + 49 + return response.json(); 50 + } 51 + 52 + // Stats 53 + async getStats() { 54 + return this.request<{ 55 + apps_count: number; 56 + oidc_clients_count: number; 57 + active_sessions_count: number; 58 + users_count: number; 59 + passkeys_count: number; 60 + mfa_enabled_count: number; 61 + verified_emails_count: number; 62 + }>('/stats'); 63 + } 64 + 65 + // Apps (Legacy) 66 + async getApps() { 67 + return this.request<{ 68 + apps: Array<{ 69 + id: string; 70 + name: string; 71 + token_ttl_seconds: number; 72 + callback_url: string; 73 + created_at: string; 74 + }>; 75 + }>('/apps'); 76 + } 77 + 78 + async createApp(data: { 79 + name: string; 80 + callback_url: string; 81 + token_ttl_seconds?: number; 82 + }) { 83 + return this.request<{ 84 + id: string; 85 + hmac_secret: string; 86 + }>('/apps', { 87 + method: 'POST', 88 + body: JSON.stringify(data), 89 + }); 90 + } 91 + 92 + async deleteApp(appId: string) { 93 + return this.request<{ success: boolean }>(`/apps/${appId}`, { 94 + method: 'DELETE', 95 + }); 96 + } 97 + 98 + async rotateAppSecret(appId: string) { 99 + return this.request<{ hmac_secret: string }>(`/apps/${appId}/rotate`, { 100 + method: 'POST', 101 + }); 102 + } 103 + 104 + // OIDC Clients 105 + async getOIDCClients() { 106 + return this.request<{ 107 + clients: Array<{ 108 + id: string; 109 + name: string; 110 + client_type: string; 111 + redirect_uris: string[]; 112 + allowed_scopes: string[]; 113 + grant_types: string[]; 114 + require_pkce: boolean; 115 + access_token_ttl_seconds: number; 116 + id_token_ttl_seconds: number; 117 + refresh_token_ttl_seconds: number; 118 + created_at: string; 119 + }>; 120 + }>('/oidc/clients'); 121 + } 122 + 123 + async createOIDCClient(data: { 124 + name: string; 125 + redirect_uris: string[]; 126 + allowed_scopes?: string[]; 127 + grant_types?: string[]; 128 + require_pkce?: boolean; 129 + access_token_ttl_seconds?: number; 130 + id_token_ttl_seconds?: number; 131 + refresh_token_ttl_seconds?: number; 132 + }) { 133 + return this.request<{ 134 + id: string; 135 + client_secret: string; 136 + }>('/oidc/clients', { 137 + method: 'POST', 138 + body: JSON.stringify(data), 139 + }); 140 + } 141 + 142 + async updateOIDCClient( 143 + clientId: string, 144 + data: { 145 + name?: string; 146 + redirect_uris?: string[]; 147 + allowed_scopes?: string[]; 148 + grant_types?: string[]; 149 + require_pkce?: boolean; 150 + access_token_ttl_seconds?: number; 151 + id_token_ttl_seconds?: number; 152 + refresh_token_ttl_seconds?: number; 153 + } 154 + ) { 155 + return this.request<{ success: boolean }>(`/oidc/clients/${clientId}`, { 156 + method: 'PUT', 157 + body: JSON.stringify(data), 158 + }); 159 + } 160 + 161 + async deleteOIDCClient(clientId: string) { 162 + return this.request<{ success: boolean }>(`/oidc/clients/${clientId}`, { 163 + method: 'DELETE', 164 + }); 165 + } 166 + 167 + async rotateOIDCClientSecret(clientId: string) { 168 + return this.request<{ client_secret: string }>( 169 + `/oidc/clients/${clientId}/rotate-secret`, 170 + { 171 + method: 'POST', 172 + } 173 + ); 174 + } 175 + 176 + // Sessions 177 + async getSessions(params?: { app_id?: string; did?: string; limit?: number }) { 178 + const query = new URLSearchParams(); 179 + if (params?.app_id) query.set('app_id', params.app_id); 180 + if (params?.did) query.set('did', params.did); 181 + if (params?.limit) query.set('limit', params.limit.toString()); 182 + 183 + const queryStr = query.toString(); 184 + return this.request<{ 185 + sessions: Array<{ 186 + id: string; 187 + did: string; 188 + handle: string; 189 + app_id: string; 190 + created_at: string; 191 + expires_at: string; 192 + connection_state: string; 193 + last_activity: string; 194 + }>; 195 + }>(`/sessions${queryStr ? `?${queryStr}` : ''}`); 196 + } 197 + 198 + async revokeSession(sessionId: string) { 199 + return this.request<{ success: boolean }>(`/sessions/${sessionId}`, { 200 + method: 'DELETE', 201 + }); 202 + } 203 + 204 + async revokeAllSessions(did: string, appId?: string) { 205 + return this.request<{ revoked: number }>('/sessions/revoke-all', { 206 + method: 'POST', 207 + body: JSON.stringify({ did, app_id: appId }), 208 + }); 209 + } 210 + 211 + // Keys 212 + async getKeys() { 213 + return this.request<{ 214 + keys: Array<{ 215 + kid: string; 216 + algorithm: string; 217 + is_active: boolean; 218 + use_for_signing: boolean; 219 + created_at: string; 220 + }>; 221 + }>('/keys'); 222 + } 223 + 224 + async rotateKeys() { 225 + return this.request<{ 226 + kid: string; 227 + message: string; 228 + }>('/keys/rotate', { 229 + method: 'POST', 230 + }); 231 + } 232 + 233 + async deleteKey(kid: string) { 234 + return this.request<{ success: boolean }>(`/keys/${kid}`, { 235 + method: 'DELETE', 236 + }); 237 + } 238 + 239 + // Users 240 + async getUsers(params?: { limit?: number; offset?: number }) { 241 + const query = new URLSearchParams(); 242 + if (params?.limit) query.set('limit', params.limit.toString()); 243 + if (params?.offset) query.set('offset', params.offset.toString()); 244 + 245 + const queryStr = query.toString(); 246 + return this.request<{ 247 + users: Array<{ 248 + did: string; 249 + handle: string; 250 + passkeys_count: number; 251 + mfa_enabled: boolean; 252 + emails_count: number; 253 + sessions_count: number; 254 + }>; 255 + }>(`/users${queryStr ? `?${queryStr}` : ''}`); 256 + } 257 + 258 + async getUser(did: string) { 259 + return this.request<{ 260 + user: { 261 + did: string; 262 + handle: string; 263 + passkeys: Array<{ 264 + id: string; 265 + name: string | null; 266 + device_type: string; 267 + backed_up: boolean; 268 + last_used_at: string | null; 269 + created_at: string; 270 + }>; 271 + mfa_enabled: boolean; 272 + emails: Array<{ 273 + email: string; 274 + verified: boolean; 275 + is_primary: boolean; 276 + }>; 277 + sessions: Array<{ 278 + id: string; 279 + app_id: string; 280 + created_at: string; 281 + expires_at: string; 282 + }>; 283 + }; 284 + }>(`/users/${encodeURIComponent(did)}`); 285 + } 286 + 287 + async revokeUserMFA(did: string) { 288 + return this.request<{ success: boolean }>( 289 + `/users/${encodeURIComponent(did)}/mfa`, 290 + { 291 + method: 'DELETE', 292 + } 293 + ); 294 + } 295 + 296 + async deleteUserPasskey(did: string, credentialId: string) { 297 + return this.request<{ success: boolean }>( 298 + `/users/${encodeURIComponent(did)}/passkeys/${encodeURIComponent(credentialId)}`, 299 + { 300 + method: 'DELETE', 301 + } 302 + ); 303 + } 304 + } 305 + 306 + export const api = new AdminApiClient();
+24
gateway/admin-ui/src/components/Layout/Header.tsx
··· 1 + import { useAuthStore } from '../../stores/auth'; 2 + 3 + interface HeaderProps { 4 + title: string; 5 + } 6 + 7 + export function Header({ title }: HeaderProps) { 8 + const logout = useAuthStore((s) => s.logout); 9 + 10 + return ( 11 + <header className="h-16 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between px-6"> 12 + <h2 className="text-xl font-semibold text-gray-900 dark:text-white"> 13 + {title} 14 + </h2> 15 + 16 + <button 17 + onClick={logout} 18 + className="text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors" 19 + > 20 + Sign Out 21 + </button> 22 + </header> 23 + ); 24 + }
+13
gateway/admin-ui/src/components/Layout/Layout.tsx
··· 1 + import { Outlet } from 'react-router-dom'; 2 + import { Sidebar } from './Sidebar'; 3 + 4 + export function Layout() { 5 + return ( 6 + <div className="flex h-screen overflow-hidden"> 7 + <Sidebar /> 8 + <main className="flex-1 overflow-auto bg-gray-100 dark:bg-gray-900"> 9 + <Outlet /> 10 + </main> 11 + </div> 12 + ); 13 + }
+93
gateway/admin-ui/src/components/Layout/Sidebar.tsx
··· 1 + import { NavLink } from 'react-router-dom'; 2 + 3 + const navItems = [ 4 + { to: '/dashboard', label: 'Dashboard', icon: HomeIcon }, 5 + { to: '/apps', label: 'Legacy Apps', icon: AppIcon }, 6 + { to: '/oidc-clients', label: 'OIDC Clients', icon: KeyIcon }, 7 + { to: '/sessions', label: 'Sessions', icon: UsersIcon }, 8 + { to: '/keys', label: 'Signing Keys', icon: LockIcon }, 9 + { to: '/users', label: 'Users', icon: UserIcon }, 10 + ]; 11 + 12 + export function Sidebar() { 13 + return ( 14 + <aside className="w-64 bg-gray-900 text-white flex flex-col"> 15 + <div className="p-4 border-b border-gray-700"> 16 + <h1 className="text-xl font-bold">ATAuth Admin</h1> 17 + <p className="text-sm text-gray-400">Identity Management</p> 18 + </div> 19 + 20 + <nav className="flex-1 py-4"> 21 + {navItems.map((item) => ( 22 + <NavLink 23 + key={item.to} 24 + to={item.to} 25 + className={({ isActive }) => 26 + `flex items-center gap-3 px-4 py-3 text-sm transition-colors ${ 27 + isActive 28 + ? 'bg-blue-600 text-white' 29 + : 'text-gray-300 hover:bg-gray-800 hover:text-white' 30 + }` 31 + } 32 + > 33 + <item.icon className="w-5 h-5" /> 34 + {item.label} 35 + </NavLink> 36 + ))} 37 + </nav> 38 + 39 + <div className="p-4 border-t border-gray-700 text-xs text-gray-500"> 40 + ATAuth Gateway v1.3.0 41 + </div> 42 + </aside> 43 + ); 44 + } 45 + 46 + // Simple icon components 47 + function HomeIcon({ className }: { className?: string }) { 48 + return ( 49 + <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"> 50 + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /> 51 + </svg> 52 + ); 53 + } 54 + 55 + function AppIcon({ className }: { className?: string }) { 56 + return ( 57 + <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"> 58 + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" /> 59 + </svg> 60 + ); 61 + } 62 + 63 + function KeyIcon({ className }: { className?: string }) { 64 + return ( 65 + <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"> 66 + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" /> 67 + </svg> 68 + ); 69 + } 70 + 71 + function UsersIcon({ className }: { className?: string }) { 72 + return ( 73 + <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"> 74 + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" /> 75 + </svg> 76 + ); 77 + } 78 + 79 + function LockIcon({ className }: { className?: string }) { 80 + return ( 81 + <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"> 82 + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /> 83 + </svg> 84 + ); 85 + } 86 + 87 + function UserIcon({ className }: { className?: string }) { 88 + return ( 89 + <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"> 90 + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /> 91 + </svg> 92 + ); 93 + }
+3
gateway/admin-ui/src/components/Layout/index.ts
··· 1 + export { Layout } from './Layout'; 2 + export { Sidebar } from './Sidebar'; 3 + export { Header } from './Header';
+22
gateway/admin-ui/src/index.css
··· 1 + @import "tailwindcss"; 2 + 3 + /* Custom theme variables */ 4 + :root { 5 + --color-primary: #3b82f6; 6 + --color-primary-dark: #2563eb; 7 + --color-success: #10b981; 8 + --color-warning: #f59e0b; 9 + --color-danger: #ef4444; 10 + } 11 + 12 + /* Base styles */ 13 + body { 14 + @apply bg-gray-50 text-gray-900 antialiased; 15 + } 16 + 17 + /* Dark mode */ 18 + @media (prefers-color-scheme: dark) { 19 + body { 20 + @apply bg-gray-900 text-gray-100; 21 + } 22 + }
+10
gateway/admin-ui/src/main.tsx
··· 1 + import { StrictMode } from 'react' 2 + import { createRoot } from 'react-dom/client' 3 + import './index.css' 4 + import App from './App.tsx' 5 + 6 + createRoot(document.getElementById('root')!).render( 7 + <StrictMode> 8 + <App /> 9 + </StrictMode>, 10 + )
+332
gateway/admin-ui/src/pages/AppsPage.tsx
··· 1 + import { useState } from 'react'; 2 + import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; 3 + import { Header } from '../components/Layout'; 4 + import { api } from '../api/client'; 5 + 6 + export function AppsPage() { 7 + const queryClient = useQueryClient(); 8 + const [showCreate, setShowCreate] = useState(false); 9 + const [newSecret, setNewSecret] = useState<{ appId: string; secret: string } | null>(null); 10 + 11 + const { data, isLoading, error } = useQuery({ 12 + queryKey: ['apps'], 13 + queryFn: () => api.getApps(), 14 + }); 15 + 16 + const deleteMutation = useMutation({ 17 + mutationFn: (appId: string) => api.deleteApp(appId), 18 + onSuccess: () => { 19 + queryClient.invalidateQueries({ queryKey: ['apps'] }); 20 + queryClient.invalidateQueries({ queryKey: ['stats'] }); 21 + }, 22 + }); 23 + 24 + const rotateMutation = useMutation({ 25 + mutationFn: (appId: string) => api.rotateAppSecret(appId), 26 + onSuccess: (data, appId) => { 27 + setNewSecret({ appId, secret: data.hmac_secret }); 28 + }, 29 + }); 30 + 31 + const handleDelete = (appId: string, appName: string) => { 32 + if (confirm(`Are you sure you want to delete "${appName}"? This cannot be undone.`)) { 33 + deleteMutation.mutate(appId); 34 + } 35 + }; 36 + 37 + const handleRotate = (appId: string, appName: string) => { 38 + if (confirm(`Rotate secret for "${appName}"? The old secret will stop working immediately.`)) { 39 + rotateMutation.mutate(appId); 40 + } 41 + }; 42 + 43 + return ( 44 + <div> 45 + <Header title="Legacy Apps" /> 46 + 47 + <div className="p-6"> 48 + <div className="flex justify-between items-center mb-6"> 49 + <p className="text-gray-600 dark:text-gray-400"> 50 + Manage legacy apps using HMAC-signed tokens 51 + </p> 52 + <button 53 + onClick={() => setShowCreate(true)} 54 + className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors" 55 + > 56 + Create App 57 + </button> 58 + </div> 59 + 60 + {showCreate && ( 61 + <CreateAppModal 62 + onClose={() => setShowCreate(false)} 63 + onCreated={(secret) => { 64 + setShowCreate(false); 65 + setNewSecret(secret); 66 + queryClient.invalidateQueries({ queryKey: ['apps'] }); 67 + queryClient.invalidateQueries({ queryKey: ['stats'] }); 68 + }} 69 + /> 70 + )} 71 + 72 + {newSecret && ( 73 + <SecretModal 74 + appId={newSecret.appId} 75 + secret={newSecret.secret} 76 + onClose={() => setNewSecret(null)} 77 + /> 78 + )} 79 + 80 + {isLoading ? ( 81 + <div className="text-gray-500">Loading apps...</div> 82 + ) : error ? ( 83 + <div className="p-4 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md"> 84 + Failed to load apps 85 + </div> 86 + ) : data?.apps.length === 0 ? ( 87 + <div className="text-center py-12 bg-white dark:bg-gray-800 rounded-lg"> 88 + <p className="text-gray-500 dark:text-gray-400">No apps registered yet</p> 89 + </div> 90 + ) : ( 91 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden"> 92 + <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700"> 93 + <thead className="bg-gray-50 dark:bg-gray-900"> 94 + <tr> 95 + <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> 96 + Name 97 + </th> 98 + <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> 99 + App ID 100 + </th> 101 + <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> 102 + Token TTL 103 + </th> 104 + <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> 105 + Created 106 + </th> 107 + <th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> 108 + Actions 109 + </th> 110 + </tr> 111 + </thead> 112 + <tbody className="divide-y divide-gray-200 dark:divide-gray-700"> 113 + {data?.apps.map((app) => ( 114 + <tr key={app.id}> 115 + <td className="px-6 py-4 whitespace-nowrap"> 116 + <div className="text-sm font-medium text-gray-900 dark:text-white"> 117 + {app.name} 118 + </div> 119 + <div className="text-sm text-gray-500 dark:text-gray-400"> 120 + {app.callback_url} 121 + </div> 122 + </td> 123 + <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 font-mono"> 124 + {app.id.substring(0, 8)}... 125 + </td> 126 + <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"> 127 + {formatDuration(app.token_ttl_seconds)} 128 + </td> 129 + <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"> 130 + {new Date(app.created_at).toLocaleDateString()} 131 + </td> 132 + <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2"> 133 + <button 134 + onClick={() => handleRotate(app.id, app.name)} 135 + className="text-yellow-600 hover:text-yellow-900 dark:hover:text-yellow-400" 136 + disabled={rotateMutation.isPending} 137 + > 138 + Rotate Secret 139 + </button> 140 + <button 141 + onClick={() => handleDelete(app.id, app.name)} 142 + className="text-red-600 hover:text-red-900 dark:hover:text-red-400" 143 + disabled={deleteMutation.isPending} 144 + > 145 + Delete 146 + </button> 147 + </td> 148 + </tr> 149 + ))} 150 + </tbody> 151 + </table> 152 + </div> 153 + )} 154 + </div> 155 + </div> 156 + ); 157 + } 158 + 159 + interface CreateAppModalProps { 160 + onClose: () => void; 161 + onCreated: (secret: { appId: string; secret: string }) => void; 162 + } 163 + 164 + function CreateAppModal({ onClose, onCreated }: CreateAppModalProps) { 165 + const [name, setName] = useState(''); 166 + const [callbackUrl, setCallbackUrl] = useState(''); 167 + const [ttl, setTtl] = useState('3600'); 168 + 169 + const createMutation = useMutation({ 170 + mutationFn: () => 171 + api.createApp({ 172 + name, 173 + callback_url: callbackUrl, 174 + token_ttl_seconds: parseInt(ttl, 10), 175 + }), 176 + onSuccess: (data) => { 177 + onCreated({ appId: data.id, secret: data.hmac_secret }); 178 + }, 179 + }); 180 + 181 + const handleSubmit = (e: React.FormEvent) => { 182 + e.preventDefault(); 183 + createMutation.mutate(); 184 + }; 185 + 186 + return ( 187 + <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"> 188 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md p-6"> 189 + <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4"> 190 + Create Legacy App 191 + </h3> 192 + 193 + <form onSubmit={handleSubmit} className="space-y-4"> 194 + <div> 195 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 196 + App Name 197 + </label> 198 + <input 199 + type="text" 200 + value={name} 201 + onChange={(e) => setName(e.target.value)} 202 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white" 203 + required 204 + /> 205 + </div> 206 + 207 + <div> 208 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 209 + Callback URL 210 + </label> 211 + <input 212 + type="url" 213 + value={callbackUrl} 214 + onChange={(e) => setCallbackUrl(e.target.value)} 215 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white" 216 + placeholder="https://myapp.com/auth/callback" 217 + required 218 + /> 219 + </div> 220 + 221 + <div> 222 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 223 + Token TTL (seconds) 224 + </label> 225 + <input 226 + type="number" 227 + value={ttl} 228 + onChange={(e) => setTtl(e.target.value)} 229 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white" 230 + min="60" 231 + required 232 + /> 233 + </div> 234 + 235 + {createMutation.error && ( 236 + <div className="p-3 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md text-sm"> 237 + {createMutation.error instanceof Error 238 + ? createMutation.error.message 239 + : 'Failed to create app'} 240 + </div> 241 + )} 242 + 243 + <div className="flex justify-end gap-3 pt-4"> 244 + <button 245 + type="button" 246 + onClick={onClose} 247 + className="px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md" 248 + > 249 + Cancel 250 + </button> 251 + <button 252 + type="submit" 253 + disabled={createMutation.isPending} 254 + className="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-md" 255 + > 256 + {createMutation.isPending ? 'Creating...' : 'Create'} 257 + </button> 258 + </div> 259 + </form> 260 + </div> 261 + </div> 262 + ); 263 + } 264 + 265 + interface SecretModalProps { 266 + appId: string; 267 + secret: string; 268 + onClose: () => void; 269 + } 270 + 271 + function SecretModal({ appId, secret, onClose }: SecretModalProps) { 272 + const [copied, setCopied] = useState(false); 273 + 274 + const handleCopy = async () => { 275 + await navigator.clipboard.writeText(secret); 276 + setCopied(true); 277 + setTimeout(() => setCopied(false), 2000); 278 + }; 279 + 280 + return ( 281 + <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"> 282 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md p-6"> 283 + <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2"> 284 + App Secret 285 + </h3> 286 + <p className="text-sm text-yellow-600 dark:text-yellow-400 mb-4"> 287 + Save this secret now! It won't be shown again. 288 + </p> 289 + 290 + <div className="mb-4"> 291 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 292 + App ID 293 + </label> 294 + <code className="block p-2 bg-gray-100 dark:bg-gray-900 rounded text-sm break-all"> 295 + {appId} 296 + </code> 297 + </div> 298 + 299 + <div className="mb-6"> 300 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 301 + HMAC Secret 302 + </label> 303 + <div className="relative"> 304 + <code className="block p-2 bg-gray-100 dark:bg-gray-900 rounded text-sm break-all pr-20"> 305 + {secret} 306 + </code> 307 + <button 308 + onClick={handleCopy} 309 + className="absolute right-2 top-1/2 -translate-y-1/2 px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600" 310 + > 311 + {copied ? 'Copied!' : 'Copy'} 312 + </button> 313 + </div> 314 + </div> 315 + 316 + <button 317 + onClick={onClose} 318 + className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md" 319 + > 320 + Done 321 + </button> 322 + </div> 323 + </div> 324 + ); 325 + } 326 + 327 + function formatDuration(seconds: number): string { 328 + if (seconds < 60) return `${seconds}s`; 329 + if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; 330 + if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`; 331 + return `${Math.floor(seconds / 86400)}d`; 332 + }
+130
gateway/admin-ui/src/pages/DashboardPage.tsx
··· 1 + import { useQuery } from '@tanstack/react-query'; 2 + import { Header } from '../components/Layout'; 3 + import { api } from '../api/client'; 4 + 5 + export function DashboardPage() { 6 + const { data: stats, isLoading, error } = useQuery({ 7 + queryKey: ['stats'], 8 + queryFn: () => api.getStats(), 9 + refetchInterval: 30000, // Refresh every 30 seconds 10 + }); 11 + 12 + return ( 13 + <div> 14 + <Header title="Dashboard" /> 15 + 16 + <div className="p-6"> 17 + {isLoading ? ( 18 + <div className="text-gray-500 dark:text-gray-400">Loading statistics...</div> 19 + ) : error ? ( 20 + <div className="p-4 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md"> 21 + Failed to load statistics 22 + </div> 23 + ) : stats ? ( 24 + <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> 25 + <StatCard 26 + title="Total Apps" 27 + value={stats.apps_count} 28 + subtitle="Legacy + OIDC" 29 + /> 30 + <StatCard 31 + title="OIDC Clients" 32 + value={stats.oidc_clients_count} 33 + subtitle="OAuth 2.0 / OIDC" 34 + /> 35 + <StatCard 36 + title="Active Sessions" 37 + value={stats.active_sessions_count} 38 + subtitle="Currently logged in" 39 + /> 40 + <StatCard 41 + title="Unique Users" 42 + value={stats.users_count} 43 + subtitle="Registered DIDs" 44 + /> 45 + <StatCard 46 + title="Passkeys" 47 + value={stats.passkeys_count} 48 + subtitle="WebAuthn credentials" 49 + /> 50 + <StatCard 51 + title="MFA Enabled" 52 + value={stats.mfa_enabled_count} 53 + subtitle="TOTP configured" 54 + /> 55 + <StatCard 56 + title="Verified Emails" 57 + value={stats.verified_emails_count} 58 + subtitle="Confirmed addresses" 59 + /> 60 + </div> 61 + ) : null} 62 + 63 + <div className="mt-8"> 64 + <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4"> 65 + Quick Actions 66 + </h3> 67 + <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> 68 + <QuickAction 69 + title="Register New App" 70 + description="Add a legacy HMAC-signed app" 71 + href="/apps" 72 + /> 73 + <QuickAction 74 + title="Create OIDC Client" 75 + description="Register an OpenID Connect client" 76 + href="/oidc-clients" 77 + /> 78 + <QuickAction 79 + title="Rotate Signing Keys" 80 + description="Generate new JWT signing keys" 81 + href="/keys" 82 + /> 83 + </div> 84 + </div> 85 + </div> 86 + </div> 87 + ); 88 + } 89 + 90 + interface StatCardProps { 91 + title: string; 92 + value: number; 93 + subtitle: string; 94 + } 95 + 96 + function StatCard({ title, value, subtitle }: StatCardProps) { 97 + return ( 98 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> 99 + <h4 className="text-sm font-medium text-gray-500 dark:text-gray-400"> 100 + {title} 101 + </h4> 102 + <p className="mt-2 text-3xl font-bold text-gray-900 dark:text-white"> 103 + {value.toLocaleString()} 104 + </p> 105 + <p className="mt-1 text-sm text-gray-500 dark:text-gray-400"> 106 + {subtitle} 107 + </p> 108 + </div> 109 + ); 110 + } 111 + 112 + interface QuickActionProps { 113 + title: string; 114 + description: string; 115 + href: string; 116 + } 117 + 118 + function QuickAction({ title, description, href }: QuickActionProps) { 119 + return ( 120 + <a 121 + href={href} 122 + className="block p-4 bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-md transition-shadow" 123 + > 124 + <h4 className="font-medium text-gray-900 dark:text-white">{title}</h4> 125 + <p className="mt-1 text-sm text-gray-500 dark:text-gray-400"> 126 + {description} 127 + </p> 128 + </a> 129 + ); 130 + }
+154
gateway/admin-ui/src/pages/KeysPage.tsx
··· 1 + import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; 2 + import { Header } from '../components/Layout'; 3 + import { api } from '../api/client'; 4 + 5 + export function KeysPage() { 6 + const queryClient = useQueryClient(); 7 + 8 + const { data, isLoading, error } = useQuery({ 9 + queryKey: ['keys'], 10 + queryFn: () => api.getKeys(), 11 + }); 12 + 13 + const rotateMutation = useMutation({ 14 + mutationFn: () => api.rotateKeys(), 15 + onSuccess: () => { 16 + queryClient.invalidateQueries({ queryKey: ['keys'] }); 17 + }, 18 + }); 19 + 20 + const deleteMutation = useMutation({ 21 + mutationFn: (kid: string) => api.deleteKey(kid), 22 + onSuccess: () => { 23 + queryClient.invalidateQueries({ queryKey: ['keys'] }); 24 + }, 25 + }); 26 + 27 + const handleRotate = () => { 28 + if (confirm('Generate a new signing key? The current key will remain valid but not be used for new tokens.')) { 29 + rotateMutation.mutate(); 30 + } 31 + }; 32 + 33 + const handleDelete = (kid: string, isActive: boolean) => { 34 + if (isActive) { 35 + alert('Cannot delete the active signing key. Rotate first to create a new key.'); 36 + return; 37 + } 38 + if (confirm(`Delete key ${kid}? Any tokens signed with this key will become invalid.`)) { 39 + deleteMutation.mutate(kid); 40 + } 41 + }; 42 + 43 + return ( 44 + <div> 45 + <Header title="Signing Keys" /> 46 + 47 + <div className="p-6"> 48 + <div className="flex justify-between items-center mb-6"> 49 + <div> 50 + <p className="text-gray-600 dark:text-gray-400"> 51 + Manage JWT signing keys for OIDC tokens 52 + </p> 53 + <p className="text-sm text-gray-500 dark:text-gray-500 mt-1"> 54 + Keys are used to sign ID tokens, access tokens, and other JWTs 55 + </p> 56 + </div> 57 + <button 58 + onClick={handleRotate} 59 + disabled={rotateMutation.isPending} 60 + className="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-md transition-colors" 61 + > 62 + {rotateMutation.isPending ? 'Generating...' : 'Rotate Keys'} 63 + </button> 64 + </div> 65 + 66 + {rotateMutation.isSuccess && ( 67 + <div className="mb-6 p-4 bg-green-50 dark:bg-green-900/30 text-green-700 dark:text-green-400 rounded-md"> 68 + New signing key generated: <code className="font-mono">{rotateMutation.data.kid}</code> 69 + </div> 70 + )} 71 + 72 + {isLoading ? ( 73 + <div className="text-gray-500">Loading keys...</div> 74 + ) : error ? ( 75 + <div className="p-4 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md"> 76 + Failed to load keys 77 + </div> 78 + ) : data?.keys.length === 0 ? ( 79 + <div className="text-center py-12 bg-white dark:bg-gray-800 rounded-lg"> 80 + <p className="text-gray-500 dark:text-gray-400">No signing keys configured</p> 81 + <p className="text-sm text-gray-400 mt-2"> 82 + Click "Rotate Keys" to generate an initial signing key 83 + </p> 84 + </div> 85 + ) : ( 86 + <div className="space-y-4"> 87 + {data?.keys.map((key) => ( 88 + <div 89 + key={key.kid} 90 + className={`bg-white dark:bg-gray-800 rounded-lg shadow p-6 ${ 91 + key.use_for_signing ? 'ring-2 ring-blue-500' : '' 92 + }`} 93 + > 94 + <div className="flex justify-between items-start"> 95 + <div> 96 + <div className="flex items-center gap-3"> 97 + <h3 className="text-lg font-mono text-gray-900 dark:text-white"> 98 + {key.kid} 99 + </h3> 100 + {key.use_for_signing && ( 101 + <span className="px-2 py-0.5 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400 text-xs font-medium rounded"> 102 + Active Signing Key 103 + </span> 104 + )} 105 + {key.is_active && !key.use_for_signing && ( 106 + <span className="px-2 py-0.5 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 text-xs font-medium rounded"> 107 + Valid for Verification 108 + </span> 109 + )} 110 + {!key.is_active && ( 111 + <span className="px-2 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 text-xs font-medium rounded"> 112 + Inactive 113 + </span> 114 + )} 115 + </div> 116 + <p className="text-sm text-gray-500 dark:text-gray-400 mt-1"> 117 + Algorithm: <span className="font-mono">{key.algorithm}</span> 118 + </p> 119 + <p className="text-sm text-gray-500 dark:text-gray-400"> 120 + Created: {new Date(key.created_at).toLocaleString()} 121 + </p> 122 + </div> 123 + <button 124 + onClick={() => handleDelete(key.kid, key.use_for_signing)} 125 + disabled={deleteMutation.isPending || key.use_for_signing} 126 + className={`px-3 py-1 text-sm rounded ${ 127 + key.use_for_signing 128 + ? 'text-gray-400 cursor-not-allowed' 129 + : 'text-red-600 hover:text-red-800 dark:hover:text-red-400' 130 + }`} 131 + > 132 + Delete 133 + </button> 134 + </div> 135 + </div> 136 + ))} 137 + </div> 138 + )} 139 + 140 + <div className="mt-8 p-4 bg-gray-50 dark:bg-gray-800/50 rounded-lg"> 141 + <h4 className="text-sm font-medium text-gray-900 dark:text-white mb-2"> 142 + Key Rotation Best Practices 143 + </h4> 144 + <ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1"> 145 + <li>- Rotate keys periodically (e.g., every 90 days)</li> 146 + <li>- Keep old keys active for verification until all existing tokens expire</li> 147 + <li>- Only delete old keys after their tokens have expired</li> 148 + <li>- The JWKS endpoint automatically publishes all active keys</li> 149 + </ul> 150 + </div> 151 + </div> 152 + </div> 153 + ); 154 + }
+86
gateway/admin-ui/src/pages/LoginPage.tsx
··· 1 + import { useState } from 'react'; 2 + import { useNavigate } from 'react-router-dom'; 3 + import { useAuthStore } from '../stores/auth'; 4 + import { api } from '../api/client'; 5 + 6 + export function LoginPage() { 7 + const [token, setToken] = useState(''); 8 + const [error, setError] = useState(''); 9 + const [loading, setLoading] = useState(false); 10 + const navigate = useNavigate(); 11 + const setAuthToken = useAuthStore((s) => s.setToken); 12 + 13 + const handleSubmit = async (e: React.FormEvent) => { 14 + e.preventDefault(); 15 + setError(''); 16 + setLoading(true); 17 + 18 + try { 19 + // Test the token by fetching stats 20 + api.setToken(token); 21 + await api.getStats(); 22 + setAuthToken(token); 23 + navigate('/dashboard'); 24 + } catch (err) { 25 + setError(err instanceof Error ? err.message : 'Invalid admin token'); 26 + api.setToken(null); 27 + } finally { 28 + setLoading(false); 29 + } 30 + }; 31 + 32 + return ( 33 + <div className="min-h-screen flex items-center justify-center bg-gray-100 dark:bg-gray-900 p-4"> 34 + <div className="w-full max-w-md"> 35 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8"> 36 + <div className="text-center mb-8"> 37 + <h1 className="text-2xl font-bold text-gray-900 dark:text-white"> 38 + ATAuth Admin 39 + </h1> 40 + <p className="text-gray-600 dark:text-gray-400 mt-2"> 41 + Enter your admin token to continue 42 + </p> 43 + </div> 44 + 45 + <form onSubmit={handleSubmit} className="space-y-6"> 46 + <div> 47 + <label 48 + htmlFor="token" 49 + className="block text-sm font-medium text-gray-700 dark:text-gray-300" 50 + > 51 + Admin Token 52 + </label> 53 + <input 54 + type="password" 55 + id="token" 56 + value={token} 57 + onChange={(e) => setToken(e.target.value)} 58 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" 59 + placeholder="Enter your ADMIN_TOKEN" 60 + required 61 + /> 62 + </div> 63 + 64 + {error && ( 65 + <div className="p-3 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-md"> 66 + <p className="text-sm text-red-600 dark:text-red-400">{error}</p> 67 + </div> 68 + )} 69 + 70 + <button 71 + type="submit" 72 + disabled={loading || !token} 73 + className="w-full py-2 px-4 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" 74 + > 75 + {loading ? 'Verifying...' : 'Sign In'} 76 + </button> 77 + </form> 78 + 79 + <p className="mt-6 text-center text-xs text-gray-500 dark:text-gray-400"> 80 + The admin token is set via the ADMIN_TOKEN environment variable on the gateway server. 81 + </p> 82 + </div> 83 + </div> 84 + </div> 85 + ); 86 + }
+589
gateway/admin-ui/src/pages/OIDCClientsPage.tsx
··· 1 + import { useState } from 'react'; 2 + import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; 3 + import { Header } from '../components/Layout'; 4 + import { api } from '../api/client'; 5 + 6 + export function OIDCClientsPage() { 7 + const queryClient = useQueryClient(); 8 + const [showCreate, setShowCreate] = useState(false); 9 + const [editingClient, setEditingClient] = useState<string | null>(null); 10 + const [newSecret, setNewSecret] = useState<{ clientId: string; secret: string } | null>(null); 11 + 12 + const { data, isLoading, error } = useQuery({ 13 + queryKey: ['oidc-clients'], 14 + queryFn: () => api.getOIDCClients(), 15 + }); 16 + 17 + const deleteMutation = useMutation({ 18 + mutationFn: (clientId: string) => api.deleteOIDCClient(clientId), 19 + onSuccess: () => { 20 + queryClient.invalidateQueries({ queryKey: ['oidc-clients'] }); 21 + queryClient.invalidateQueries({ queryKey: ['stats'] }); 22 + }, 23 + }); 24 + 25 + const rotateMutation = useMutation({ 26 + mutationFn: (clientId: string) => api.rotateOIDCClientSecret(clientId), 27 + onSuccess: (data, clientId) => { 28 + setNewSecret({ clientId, secret: data.client_secret }); 29 + }, 30 + }); 31 + 32 + const handleDelete = (clientId: string, clientName: string) => { 33 + if (confirm(`Delete OIDC client "${clientName}"? All associated tokens will be invalidated.`)) { 34 + deleteMutation.mutate(clientId); 35 + } 36 + }; 37 + 38 + const handleRotate = (clientId: string, clientName: string) => { 39 + if (confirm(`Rotate secret for "${clientName}"? The old secret will stop working.`)) { 40 + rotateMutation.mutate(clientId); 41 + } 42 + }; 43 + 44 + return ( 45 + <div> 46 + <Header title="OIDC Clients" /> 47 + 48 + <div className="p-6"> 49 + <div className="flex justify-between items-center mb-6"> 50 + <p className="text-gray-600 dark:text-gray-400"> 51 + Manage OpenID Connect clients for OAuth 2.0 authentication 52 + </p> 53 + <button 54 + onClick={() => setShowCreate(true)} 55 + className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors" 56 + > 57 + Create OIDC Client 58 + </button> 59 + </div> 60 + 61 + {showCreate && ( 62 + <CreateOIDCClientModal 63 + onClose={() => setShowCreate(false)} 64 + onCreated={(secret) => { 65 + setShowCreate(false); 66 + setNewSecret(secret); 67 + queryClient.invalidateQueries({ queryKey: ['oidc-clients'] }); 68 + queryClient.invalidateQueries({ queryKey: ['stats'] }); 69 + }} 70 + /> 71 + )} 72 + 73 + {editingClient && ( 74 + <EditOIDCClientModal 75 + clientId={editingClient} 76 + client={data?.clients.find((c) => c.id === editingClient)} 77 + onClose={() => setEditingClient(null)} 78 + onSaved={() => { 79 + setEditingClient(null); 80 + queryClient.invalidateQueries({ queryKey: ['oidc-clients'] }); 81 + }} 82 + /> 83 + )} 84 + 85 + {newSecret && ( 86 + <SecretModal 87 + clientId={newSecret.clientId} 88 + secret={newSecret.secret} 89 + onClose={() => setNewSecret(null)} 90 + /> 91 + )} 92 + 93 + {isLoading ? ( 94 + <div className="text-gray-500">Loading OIDC clients...</div> 95 + ) : error ? ( 96 + <div className="p-4 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md"> 97 + Failed to load OIDC clients 98 + </div> 99 + ) : data?.clients.length === 0 ? ( 100 + <div className="text-center py-12 bg-white dark:bg-gray-800 rounded-lg"> 101 + <p className="text-gray-500 dark:text-gray-400">No OIDC clients registered</p> 102 + <p className="text-sm text-gray-400 mt-2"> 103 + Create an OIDC client to enable OAuth 2.0 / OpenID Connect authentication 104 + </p> 105 + </div> 106 + ) : ( 107 + <div className="space-y-4"> 108 + {data?.clients.map((client) => ( 109 + <div 110 + key={client.id} 111 + className="bg-white dark:bg-gray-800 rounded-lg shadow p-6" 112 + > 113 + <div className="flex justify-between items-start"> 114 + <div> 115 + <h3 className="text-lg font-medium text-gray-900 dark:text-white"> 116 + {client.name} 117 + </h3> 118 + <p className="text-sm text-gray-500 dark:text-gray-400 font-mono mt-1"> 119 + Client ID: {client.id} 120 + </p> 121 + </div> 122 + <div className="flex gap-2"> 123 + <button 124 + onClick={() => setEditingClient(client.id)} 125 + className="px-3 py-1 text-sm text-blue-600 hover:text-blue-800 dark:hover:text-blue-400" 126 + > 127 + Edit 128 + </button> 129 + <button 130 + onClick={() => handleRotate(client.id, client.name)} 131 + className="px-3 py-1 text-sm text-yellow-600 hover:text-yellow-800 dark:hover:text-yellow-400" 132 + > 133 + Rotate Secret 134 + </button> 135 + <button 136 + onClick={() => handleDelete(client.id, client.name)} 137 + className="px-3 py-1 text-sm text-red-600 hover:text-red-800 dark:hover:text-red-400" 138 + > 139 + Delete 140 + </button> 141 + </div> 142 + </div> 143 + 144 + <div className="mt-4 grid grid-cols-2 gap-4 text-sm"> 145 + <div> 146 + <span className="text-gray-500 dark:text-gray-400">Redirect URIs:</span> 147 + <ul className="mt-1 space-y-1"> 148 + {client.redirect_uris.map((uri, i) => ( 149 + <li key={i} className="text-gray-900 dark:text-white font-mono text-xs"> 150 + {uri} 151 + </li> 152 + ))} 153 + </ul> 154 + </div> 155 + <div> 156 + <span className="text-gray-500 dark:text-gray-400">Scopes:</span> 157 + <div className="mt-1 flex flex-wrap gap-1"> 158 + {client.allowed_scopes.map((scope) => ( 159 + <span 160 + key={scope} 161 + className="px-2 py-0.5 bg-gray-100 dark:bg-gray-700 rounded text-xs" 162 + > 163 + {scope} 164 + </span> 165 + ))} 166 + </div> 167 + </div> 168 + </div> 169 + 170 + <div className="mt-4 flex gap-6 text-xs text-gray-500 dark:text-gray-400"> 171 + <span>PKCE: {client.require_pkce ? 'Required' : 'Optional'}</span> 172 + <span>Access Token: {formatDuration(client.access_token_ttl_seconds)}</span> 173 + <span>ID Token: {formatDuration(client.id_token_ttl_seconds)}</span> 174 + <span>Refresh Token: {formatDuration(client.refresh_token_ttl_seconds)}</span> 175 + </div> 176 + </div> 177 + ))} 178 + </div> 179 + )} 180 + </div> 181 + </div> 182 + ); 183 + } 184 + 185 + interface CreateOIDCClientModalProps { 186 + onClose: () => void; 187 + onCreated: (secret: { clientId: string; secret: string }) => void; 188 + } 189 + 190 + function CreateOIDCClientModal({ onClose, onCreated }: CreateOIDCClientModalProps) { 191 + const [name, setName] = useState(''); 192 + const [redirectUris, setRedirectUris] = useState(''); 193 + const [scopes, setScopes] = useState('openid profile email'); 194 + const [requirePkce, setRequirePkce] = useState(true); 195 + const [accessTtl, setAccessTtl] = useState('3600'); 196 + const [idTtl, setIdTtl] = useState('3600'); 197 + const [refreshTtl, setRefreshTtl] = useState('604800'); 198 + 199 + const createMutation = useMutation({ 200 + mutationFn: () => 201 + api.createOIDCClient({ 202 + name, 203 + redirect_uris: redirectUris.split('\n').map((u) => u.trim()).filter(Boolean), 204 + allowed_scopes: scopes.split(' ').filter(Boolean), 205 + require_pkce: requirePkce, 206 + access_token_ttl_seconds: parseInt(accessTtl, 10), 207 + id_token_ttl_seconds: parseInt(idTtl, 10), 208 + refresh_token_ttl_seconds: parseInt(refreshTtl, 10), 209 + }), 210 + onSuccess: (data) => { 211 + onCreated({ clientId: data.id, secret: data.client_secret }); 212 + }, 213 + }); 214 + 215 + const handleSubmit = (e: React.FormEvent) => { 216 + e.preventDefault(); 217 + createMutation.mutate(); 218 + }; 219 + 220 + return ( 221 + <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 overflow-auto py-8"> 222 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-lg p-6 m-4"> 223 + <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4"> 224 + Create OIDC Client 225 + </h3> 226 + 227 + <form onSubmit={handleSubmit} className="space-y-4"> 228 + <div> 229 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 230 + Client Name 231 + </label> 232 + <input 233 + type="text" 234 + value={name} 235 + onChange={(e) => setName(e.target.value)} 236 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white" 237 + required 238 + /> 239 + </div> 240 + 241 + <div> 242 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 243 + Redirect URIs (one per line) 244 + </label> 245 + <textarea 246 + value={redirectUris} 247 + onChange={(e) => setRedirectUris(e.target.value)} 248 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm" 249 + rows={3} 250 + placeholder="https://myapp.com/callback&#10;https://myapp.com/auth/callback" 251 + required 252 + /> 253 + </div> 254 + 255 + <div> 256 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 257 + Allowed Scopes (space-separated) 258 + </label> 259 + <input 260 + type="text" 261 + value={scopes} 262 + onChange={(e) => setScopes(e.target.value)} 263 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm" 264 + /> 265 + </div> 266 + 267 + <div className="flex items-center gap-2"> 268 + <input 269 + type="checkbox" 270 + id="requirePkce" 271 + checked={requirePkce} 272 + onChange={(e) => setRequirePkce(e.target.checked)} 273 + className="h-4 w-4 text-blue-600 rounded" 274 + /> 275 + <label htmlFor="requirePkce" className="text-sm text-gray-700 dark:text-gray-300"> 276 + Require PKCE (recommended) 277 + </label> 278 + </div> 279 + 280 + <div className="grid grid-cols-3 gap-4"> 281 + <div> 282 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 283 + Access Token TTL 284 + </label> 285 + <input 286 + type="number" 287 + value={accessTtl} 288 + onChange={(e) => setAccessTtl(e.target.value)} 289 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white" 290 + min="60" 291 + /> 292 + </div> 293 + <div> 294 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 295 + ID Token TTL 296 + </label> 297 + <input 298 + type="number" 299 + value={idTtl} 300 + onChange={(e) => setIdTtl(e.target.value)} 301 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white" 302 + min="60" 303 + /> 304 + </div> 305 + <div> 306 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 307 + Refresh Token TTL 308 + </label> 309 + <input 310 + type="number" 311 + value={refreshTtl} 312 + onChange={(e) => setRefreshTtl(e.target.value)} 313 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white" 314 + min="60" 315 + /> 316 + </div> 317 + </div> 318 + 319 + {createMutation.error && ( 320 + <div className="p-3 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md text-sm"> 321 + {createMutation.error instanceof Error 322 + ? createMutation.error.message 323 + : 'Failed to create client'} 324 + </div> 325 + )} 326 + 327 + <div className="flex justify-end gap-3 pt-4"> 328 + <button 329 + type="button" 330 + onClick={onClose} 331 + className="px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md" 332 + > 333 + Cancel 334 + </button> 335 + <button 336 + type="submit" 337 + disabled={createMutation.isPending} 338 + className="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-md" 339 + > 340 + {createMutation.isPending ? 'Creating...' : 'Create'} 341 + </button> 342 + </div> 343 + </form> 344 + </div> 345 + </div> 346 + ); 347 + } 348 + 349 + interface EditOIDCClientModalProps { 350 + clientId: string; 351 + client?: { 352 + name: string; 353 + redirect_uris: string[]; 354 + allowed_scopes: string[]; 355 + require_pkce: boolean; 356 + access_token_ttl_seconds: number; 357 + id_token_ttl_seconds: number; 358 + refresh_token_ttl_seconds: number; 359 + }; 360 + onClose: () => void; 361 + onSaved: () => void; 362 + } 363 + 364 + function EditOIDCClientModal({ clientId, client, onClose, onSaved }: EditOIDCClientModalProps) { 365 + const [name, setName] = useState(client?.name || ''); 366 + const [redirectUris, setRedirectUris] = useState(client?.redirect_uris.join('\n') || ''); 367 + const [scopes, setScopes] = useState(client?.allowed_scopes.join(' ') || ''); 368 + const [requirePkce, setRequirePkce] = useState(client?.require_pkce ?? true); 369 + const [accessTtl, setAccessTtl] = useState(String(client?.access_token_ttl_seconds || 3600)); 370 + const [idTtl, setIdTtl] = useState(String(client?.id_token_ttl_seconds || 3600)); 371 + const [refreshTtl, setRefreshTtl] = useState(String(client?.refresh_token_ttl_seconds || 604800)); 372 + 373 + const updateMutation = useMutation({ 374 + mutationFn: () => 375 + api.updateOIDCClient(clientId, { 376 + name, 377 + redirect_uris: redirectUris.split('\n').map((u) => u.trim()).filter(Boolean), 378 + allowed_scopes: scopes.split(' ').filter(Boolean), 379 + require_pkce: requirePkce, 380 + access_token_ttl_seconds: parseInt(accessTtl, 10), 381 + id_token_ttl_seconds: parseInt(idTtl, 10), 382 + refresh_token_ttl_seconds: parseInt(refreshTtl, 10), 383 + }), 384 + onSuccess: onSaved, 385 + }); 386 + 387 + const handleSubmit = (e: React.FormEvent) => { 388 + e.preventDefault(); 389 + updateMutation.mutate(); 390 + }; 391 + 392 + if (!client) return null; 393 + 394 + return ( 395 + <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 overflow-auto py-8"> 396 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-lg p-6 m-4"> 397 + <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4"> 398 + Edit OIDC Client 399 + </h3> 400 + 401 + <form onSubmit={handleSubmit} className="space-y-4"> 402 + <div> 403 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 404 + Client Name 405 + </label> 406 + <input 407 + type="text" 408 + value={name} 409 + onChange={(e) => setName(e.target.value)} 410 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white" 411 + required 412 + /> 413 + </div> 414 + 415 + <div> 416 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 417 + Redirect URIs (one per line) 418 + </label> 419 + <textarea 420 + value={redirectUris} 421 + onChange={(e) => setRedirectUris(e.target.value)} 422 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm" 423 + rows={3} 424 + required 425 + /> 426 + </div> 427 + 428 + <div> 429 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 430 + Allowed Scopes (space-separated) 431 + </label> 432 + <input 433 + type="text" 434 + value={scopes} 435 + onChange={(e) => setScopes(e.target.value)} 436 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm" 437 + /> 438 + </div> 439 + 440 + <div className="flex items-center gap-2"> 441 + <input 442 + type="checkbox" 443 + id="editRequirePkce" 444 + checked={requirePkce} 445 + onChange={(e) => setRequirePkce(e.target.checked)} 446 + className="h-4 w-4 text-blue-600 rounded" 447 + /> 448 + <label htmlFor="editRequirePkce" className="text-sm text-gray-700 dark:text-gray-300"> 449 + Require PKCE 450 + </label> 451 + </div> 452 + 453 + <div className="grid grid-cols-3 gap-4"> 454 + <div> 455 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 456 + Access Token TTL 457 + </label> 458 + <input 459 + type="number" 460 + value={accessTtl} 461 + onChange={(e) => setAccessTtl(e.target.value)} 462 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white" 463 + min="60" 464 + /> 465 + </div> 466 + <div> 467 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 468 + ID Token TTL 469 + </label> 470 + <input 471 + type="number" 472 + value={idTtl} 473 + onChange={(e) => setIdTtl(e.target.value)} 474 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white" 475 + min="60" 476 + /> 477 + </div> 478 + <div> 479 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300"> 480 + Refresh Token TTL 481 + </label> 482 + <input 483 + type="number" 484 + value={refreshTtl} 485 + onChange={(e) => setRefreshTtl(e.target.value)} 486 + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white" 487 + min="60" 488 + /> 489 + </div> 490 + </div> 491 + 492 + {updateMutation.error && ( 493 + <div className="p-3 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md text-sm"> 494 + {updateMutation.error instanceof Error 495 + ? updateMutation.error.message 496 + : 'Failed to update client'} 497 + </div> 498 + )} 499 + 500 + <div className="flex justify-end gap-3 pt-4"> 501 + <button 502 + type="button" 503 + onClick={onClose} 504 + className="px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md" 505 + > 506 + Cancel 507 + </button> 508 + <button 509 + type="submit" 510 + disabled={updateMutation.isPending} 511 + className="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-md" 512 + > 513 + {updateMutation.isPending ? 'Saving...' : 'Save Changes'} 514 + </button> 515 + </div> 516 + </form> 517 + </div> 518 + </div> 519 + ); 520 + } 521 + 522 + interface SecretModalProps { 523 + clientId: string; 524 + secret: string; 525 + onClose: () => void; 526 + } 527 + 528 + function SecretModal({ clientId, secret, onClose }: SecretModalProps) { 529 + const [copied, setCopied] = useState(false); 530 + 531 + const handleCopy = async () => { 532 + await navigator.clipboard.writeText(secret); 533 + setCopied(true); 534 + setTimeout(() => setCopied(false), 2000); 535 + }; 536 + 537 + return ( 538 + <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"> 539 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md p-6"> 540 + <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2"> 541 + Client Secret 542 + </h3> 543 + <p className="text-sm text-yellow-600 dark:text-yellow-400 mb-4"> 544 + Save this secret now! It won't be shown again. 545 + </p> 546 + 547 + <div className="mb-4"> 548 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 549 + Client ID 550 + </label> 551 + <code className="block p-2 bg-gray-100 dark:bg-gray-900 rounded text-sm break-all"> 552 + {clientId} 553 + </code> 554 + </div> 555 + 556 + <div className="mb-6"> 557 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 558 + Client Secret 559 + </label> 560 + <div className="relative"> 561 + <code className="block p-2 bg-gray-100 dark:bg-gray-900 rounded text-sm break-all pr-20"> 562 + {secret} 563 + </code> 564 + <button 565 + onClick={handleCopy} 566 + className="absolute right-2 top-1/2 -translate-y-1/2 px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600" 567 + > 568 + {copied ? 'Copied!' : 'Copy'} 569 + </button> 570 + </div> 571 + </div> 572 + 573 + <button 574 + onClick={onClose} 575 + className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md" 576 + > 577 + Done 578 + </button> 579 + </div> 580 + </div> 581 + ); 582 + } 583 + 584 + function formatDuration(seconds: number): string { 585 + if (seconds < 60) return `${seconds}s`; 586 + if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; 587 + if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`; 588 + return `${Math.floor(seconds / 86400)}d`; 589 + }
+211
gateway/admin-ui/src/pages/SessionsPage.tsx
··· 1 + import { useState } from 'react'; 2 + import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; 3 + import { Header } from '../components/Layout'; 4 + import { api } from '../api/client'; 5 + 6 + export function SessionsPage() { 7 + const queryClient = useQueryClient(); 8 + const [filterDid, setFilterDid] = useState(''); 9 + const [filterAppId, setFilterAppId] = useState(''); 10 + 11 + const { data, isLoading, error, refetch } = useQuery({ 12 + queryKey: ['sessions', filterDid, filterAppId], 13 + queryFn: () => 14 + api.getSessions({ 15 + did: filterDid || undefined, 16 + app_id: filterAppId || undefined, 17 + limit: 100, 18 + }), 19 + refetchInterval: 30000, 20 + }); 21 + 22 + const revokeMutation = useMutation({ 23 + mutationFn: (sessionId: string) => api.revokeSession(sessionId), 24 + onSuccess: () => { 25 + queryClient.invalidateQueries({ queryKey: ['sessions'] }); 26 + queryClient.invalidateQueries({ queryKey: ['stats'] }); 27 + }, 28 + }); 29 + 30 + const revokeAllMutation = useMutation({ 31 + mutationFn: ({ did, appId }: { did: string; appId?: string }) => 32 + api.revokeAllSessions(did, appId), 33 + onSuccess: () => { 34 + queryClient.invalidateQueries({ queryKey: ['sessions'] }); 35 + queryClient.invalidateQueries({ queryKey: ['stats'] }); 36 + }, 37 + }); 38 + 39 + const handleRevoke = (sessionId: string) => { 40 + if (confirm('Revoke this session?')) { 41 + revokeMutation.mutate(sessionId); 42 + } 43 + }; 44 + 45 + const handleRevokeAll = () => { 46 + if (!filterDid) { 47 + alert('Enter a DID to revoke all sessions for that user'); 48 + return; 49 + } 50 + if (confirm(`Revoke all sessions for ${filterDid}?`)) { 51 + revokeAllMutation.mutate({ 52 + did: filterDid, 53 + appId: filterAppId || undefined, 54 + }); 55 + } 56 + }; 57 + 58 + return ( 59 + <div> 60 + <Header title="Sessions" /> 61 + 62 + <div className="p-6"> 63 + <div className="mb-6"> 64 + <p className="text-gray-600 dark:text-gray-400 mb-4"> 65 + View and manage active user sessions 66 + </p> 67 + 68 + <div className="flex gap-4 items-end"> 69 + <div className="flex-1"> 70 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 71 + Filter by DID 72 + </label> 73 + <input 74 + type="text" 75 + value={filterDid} 76 + onChange={(e) => setFilterDid(e.target.value)} 77 + className="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white" 78 + placeholder="did:plc:..." 79 + /> 80 + </div> 81 + <div className="flex-1"> 82 + <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 83 + Filter by App ID 84 + </label> 85 + <input 86 + type="text" 87 + value={filterAppId} 88 + onChange={(e) => setFilterAppId(e.target.value)} 89 + className="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white" 90 + placeholder="app-id" 91 + /> 92 + </div> 93 + <button 94 + onClick={() => refetch()} 95 + className="px-4 py-2 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-md" 96 + > 97 + Refresh 98 + </button> 99 + {filterDid && ( 100 + <button 101 + onClick={handleRevokeAll} 102 + disabled={revokeAllMutation.isPending} 103 + className="px-4 py-2 bg-red-600 hover:bg-red-700 disabled:bg-red-400 text-white rounded-md" 104 + > 105 + Revoke All 106 + </button> 107 + )} 108 + </div> 109 + </div> 110 + 111 + {isLoading ? ( 112 + <div className="text-gray-500">Loading sessions...</div> 113 + ) : error ? ( 114 + <div className="p-4 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md"> 115 + Failed to load sessions 116 + </div> 117 + ) : data?.sessions.length === 0 ? ( 118 + <div className="text-center py-12 bg-white dark:bg-gray-800 rounded-lg"> 119 + <p className="text-gray-500 dark:text-gray-400">No active sessions found</p> 120 + </div> 121 + ) : ( 122 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden"> 123 + <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700"> 124 + <thead className="bg-gray-50 dark:bg-gray-900"> 125 + <tr> 126 + <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> 127 + User 128 + </th> 129 + <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> 130 + App 131 + </th> 132 + <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> 133 + Status 134 + </th> 135 + <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> 136 + Created 137 + </th> 138 + <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> 139 + Expires 140 + </th> 141 + <th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"> 142 + Actions 143 + </th> 144 + </tr> 145 + </thead> 146 + <tbody className="divide-y divide-gray-200 dark:divide-gray-700"> 147 + {data?.sessions.map((session) => ( 148 + <tr key={session.id}> 149 + <td className="px-6 py-4 whitespace-nowrap"> 150 + <div className="text-sm font-medium text-gray-900 dark:text-white"> 151 + @{session.handle} 152 + </div> 153 + <div className="text-xs text-gray-500 dark:text-gray-400 font-mono"> 154 + {session.did.substring(0, 20)}... 155 + </div> 156 + </td> 157 + <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 font-mono"> 158 + {session.app_id.substring(0, 8)}... 159 + </td> 160 + <td className="px-6 py-4 whitespace-nowrap"> 161 + <StatusBadge status={session.connection_state} /> 162 + </td> 163 + <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"> 164 + {formatDateTime(session.created_at)} 165 + </td> 166 + <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"> 167 + {formatDateTime(session.expires_at)} 168 + </td> 169 + <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> 170 + <button 171 + onClick={() => handleRevoke(session.id)} 172 + className="text-red-600 hover:text-red-900 dark:hover:text-red-400" 173 + disabled={revokeMutation.isPending} 174 + > 175 + Revoke 176 + </button> 177 + </td> 178 + </tr> 179 + ))} 180 + </tbody> 181 + </table> 182 + </div> 183 + )} 184 + </div> 185 + </div> 186 + ); 187 + } 188 + 189 + function StatusBadge({ status }: { status: string }) { 190 + const colors: Record<string, string> = { 191 + active: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400', 192 + pending: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400', 193 + connected: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400', 194 + disconnected: 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400', 195 + }; 196 + 197 + return ( 198 + <span 199 + className={`inline-flex px-2 py-0.5 text-xs font-medium rounded-full ${ 200 + colors[status] || colors.pending 201 + }`} 202 + > 203 + {status} 204 + </span> 205 + ); 206 + } 207 + 208 + function formatDateTime(dateStr: string): string { 209 + const date = new Date(dateStr); 210 + return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); 211 + }
+324
gateway/admin-ui/src/pages/UsersPage.tsx
··· 1 + import { useState } from 'react'; 2 + import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; 3 + import { Header } from '../components/Layout'; 4 + import { api } from '../api/client'; 5 + 6 + export function UsersPage() { 7 + const queryClient = useQueryClient(); 8 + const [selectedUser, setSelectedUser] = useState<string | null>(null); 9 + 10 + const { data, isLoading, error } = useQuery({ 11 + queryKey: ['users'], 12 + queryFn: () => api.getUsers({ limit: 100 }), 13 + }); 14 + 15 + const { data: userDetails, isLoading: detailsLoading } = useQuery({ 16 + queryKey: ['user', selectedUser], 17 + queryFn: () => (selectedUser ? api.getUser(selectedUser) : null), 18 + enabled: !!selectedUser, 19 + }); 20 + 21 + return ( 22 + <div> 23 + <Header title="Users" /> 24 + 25 + <div className="p-6"> 26 + <p className="text-gray-600 dark:text-gray-400 mb-6"> 27 + View and manage user accounts, passkeys, MFA, and sessions 28 + </p> 29 + 30 + {isLoading ? ( 31 + <div className="text-gray-500">Loading users...</div> 32 + ) : error ? ( 33 + <div className="p-4 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md"> 34 + Failed to load users 35 + </div> 36 + ) : data?.users.length === 0 ? ( 37 + <div className="text-center py-12 bg-white dark:bg-gray-800 rounded-lg"> 38 + <p className="text-gray-500 dark:text-gray-400">No users found</p> 39 + </div> 40 + ) : ( 41 + <div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> 42 + <div className="lg:col-span-1"> 43 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden"> 44 + <div className="px-4 py-3 border-b border-gray-200 dark:border-gray-700"> 45 + <h3 className="text-sm font-medium text-gray-900 dark:text-white"> 46 + Users ({data?.users.length}) 47 + </h3> 48 + </div> 49 + <div className="divide-y divide-gray-200 dark:divide-gray-700 max-h-[600px] overflow-auto"> 50 + {data?.users.map((user) => ( 51 + <button 52 + key={user.did} 53 + onClick={() => setSelectedUser(user.did)} 54 + className={`w-full px-4 py-3 text-left hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors ${ 55 + selectedUser === user.did 56 + ? 'bg-blue-50 dark:bg-blue-900/30' 57 + : '' 58 + }`} 59 + > 60 + <div className="font-medium text-gray-900 dark:text-white"> 61 + @{user.handle} 62 + </div> 63 + <div className="text-xs text-gray-500 dark:text-gray-400 font-mono mt-0.5"> 64 + {user.did.substring(0, 30)}... 65 + </div> 66 + <div className="flex gap-3 mt-2 text-xs text-gray-500 dark:text-gray-400"> 67 + {user.passkeys_count > 0 && ( 68 + <span title="Passkeys">{user.passkeys_count} passkeys</span> 69 + )} 70 + {user.mfa_enabled && ( 71 + <span className="text-green-600 dark:text-green-400">MFA</span> 72 + )} 73 + {user.sessions_count > 0 && ( 74 + <span>{user.sessions_count} sessions</span> 75 + )} 76 + </div> 77 + </button> 78 + ))} 79 + </div> 80 + </div> 81 + </div> 82 + 83 + <div className="lg:col-span-2"> 84 + {selectedUser ? ( 85 + detailsLoading ? ( 86 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> 87 + <div className="text-gray-500">Loading user details...</div> 88 + </div> 89 + ) : userDetails?.user ? ( 90 + <UserDetails 91 + user={userDetails.user} 92 + onRefresh={() => 93 + queryClient.invalidateQueries({ queryKey: ['user', selectedUser] }) 94 + } 95 + /> 96 + ) : null 97 + ) : ( 98 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 text-center"> 99 + <p className="text-gray-500 dark:text-gray-400"> 100 + Select a user to view details 101 + </p> 102 + </div> 103 + )} 104 + </div> 105 + </div> 106 + )} 107 + </div> 108 + </div> 109 + ); 110 + } 111 + 112 + interface UserDetailsProps { 113 + user: { 114 + did: string; 115 + handle: string; 116 + passkeys: Array<{ 117 + id: string; 118 + name: string | null; 119 + device_type: string; 120 + backed_up: boolean; 121 + last_used_at: string | null; 122 + created_at: string; 123 + }>; 124 + mfa_enabled: boolean; 125 + emails: Array<{ 126 + email: string; 127 + verified: boolean; 128 + is_primary: boolean; 129 + }>; 130 + sessions: Array<{ 131 + id: string; 132 + app_id: string; 133 + created_at: string; 134 + expires_at: string; 135 + }>; 136 + }; 137 + onRefresh: () => void; 138 + } 139 + 140 + function UserDetails({ user, onRefresh }: UserDetailsProps) { 141 + const queryClient = useQueryClient(); 142 + 143 + const revokeMfaMutation = useMutation({ 144 + mutationFn: () => api.revokeUserMFA(user.did), 145 + onSuccess: () => { 146 + onRefresh(); 147 + queryClient.invalidateQueries({ queryKey: ['stats'] }); 148 + }, 149 + }); 150 + 151 + const deletePasskeyMutation = useMutation({ 152 + mutationFn: (credentialId: string) => api.deleteUserPasskey(user.did, credentialId), 153 + onSuccess: () => { 154 + onRefresh(); 155 + queryClient.invalidateQueries({ queryKey: ['stats'] }); 156 + }, 157 + }); 158 + 159 + const handleRevokeMfa = () => { 160 + if (confirm('Disable MFA for this user? They will need to set it up again.')) { 161 + revokeMfaMutation.mutate(); 162 + } 163 + }; 164 + 165 + const handleDeletePasskey = (credentialId: string, name: string | null) => { 166 + if (confirm(`Delete passkey "${name || credentialId.substring(0, 8)}"?`)) { 167 + deletePasskeyMutation.mutate(credentialId); 168 + } 169 + }; 170 + 171 + return ( 172 + <div className="space-y-6"> 173 + {/* User Info */} 174 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> 175 + <h3 className="text-lg font-medium text-gray-900 dark:text-white mb-4"> 176 + User Information 177 + </h3> 178 + <dl className="grid grid-cols-2 gap-4"> 179 + <div> 180 + <dt className="text-sm text-gray-500 dark:text-gray-400">Handle</dt> 181 + <dd className="text-gray-900 dark:text-white">@{user.handle}</dd> 182 + </div> 183 + <div> 184 + <dt className="text-sm text-gray-500 dark:text-gray-400">DID</dt> 185 + <dd className="text-gray-900 dark:text-white font-mono text-sm break-all"> 186 + {user.did} 187 + </dd> 188 + </div> 189 + </dl> 190 + </div> 191 + 192 + {/* Passkeys */} 193 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> 194 + <div className="flex justify-between items-center mb-4"> 195 + <h3 className="text-lg font-medium text-gray-900 dark:text-white"> 196 + Passkeys ({user.passkeys.length}) 197 + </h3> 198 + </div> 199 + {user.passkeys.length === 0 ? ( 200 + <p className="text-gray-500 dark:text-gray-400">No passkeys registered</p> 201 + ) : ( 202 + <div className="space-y-3"> 203 + {user.passkeys.map((passkey) => ( 204 + <div 205 + key={passkey.id} 206 + className="flex justify-between items-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg" 207 + > 208 + <div> 209 + <div className="font-medium text-gray-900 dark:text-white"> 210 + {passkey.name || `Passkey ${passkey.id.substring(0, 8)}...`} 211 + </div> 212 + <div className="text-sm text-gray-500 dark:text-gray-400"> 213 + {passkey.device_type} • {passkey.backed_up ? 'Backed up' : 'Not backed up'} 214 + {passkey.last_used_at && ( 215 + <> • Last used: {new Date(passkey.last_used_at).toLocaleDateString()}</> 216 + )} 217 + </div> 218 + </div> 219 + <button 220 + onClick={() => handleDeletePasskey(passkey.id, passkey.name)} 221 + className="text-red-600 hover:text-red-800 dark:hover:text-red-400 text-sm" 222 + disabled={deletePasskeyMutation.isPending} 223 + > 224 + Delete 225 + </button> 226 + </div> 227 + ))} 228 + </div> 229 + )} 230 + </div> 231 + 232 + {/* MFA */} 233 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> 234 + <div className="flex justify-between items-center mb-4"> 235 + <h3 className="text-lg font-medium text-gray-900 dark:text-white"> 236 + Two-Factor Authentication 237 + </h3> 238 + {user.mfa_enabled && ( 239 + <button 240 + onClick={handleRevokeMfa} 241 + className="text-red-600 hover:text-red-800 dark:hover:text-red-400 text-sm" 242 + disabled={revokeMfaMutation.isPending} 243 + > 244 + Disable MFA 245 + </button> 246 + )} 247 + </div> 248 + <p className="text-gray-500 dark:text-gray-400"> 249 + {user.mfa_enabled ? ( 250 + <span className="flex items-center gap-2"> 251 + <span className="w-2 h-2 bg-green-500 rounded-full"></span> 252 + TOTP Enabled 253 + </span> 254 + ) : ( 255 + 'Not configured' 256 + )} 257 + </p> 258 + </div> 259 + 260 + {/* Emails */} 261 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> 262 + <h3 className="text-lg font-medium text-gray-900 dark:text-white mb-4"> 263 + Email Addresses ({user.emails.length}) 264 + </h3> 265 + {user.emails.length === 0 ? ( 266 + <p className="text-gray-500 dark:text-gray-400">No emails registered</p> 267 + ) : ( 268 + <div className="space-y-2"> 269 + {user.emails.map((email) => ( 270 + <div 271 + key={email.email} 272 + className="flex items-center gap-3 p-2 bg-gray-50 dark:bg-gray-700 rounded" 273 + > 274 + <span className="text-gray-900 dark:text-white">{email.email}</span> 275 + {email.is_primary && ( 276 + <span className="px-2 py-0.5 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400 text-xs rounded"> 277 + Primary 278 + </span> 279 + )} 280 + {email.verified ? ( 281 + <span className="px-2 py-0.5 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 text-xs rounded"> 282 + Verified 283 + </span> 284 + ) : ( 285 + <span className="px-2 py-0.5 bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-400 text-xs rounded"> 286 + Pending 287 + </span> 288 + )} 289 + </div> 290 + ))} 291 + </div> 292 + )} 293 + </div> 294 + 295 + {/* Sessions */} 296 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> 297 + <h3 className="text-lg font-medium text-gray-900 dark:text-white mb-4"> 298 + Active Sessions ({user.sessions.length}) 299 + </h3> 300 + {user.sessions.length === 0 ? ( 301 + <p className="text-gray-500 dark:text-gray-400">No active sessions</p> 302 + ) : ( 303 + <div className="space-y-2"> 304 + {user.sessions.map((session) => ( 305 + <div 306 + key={session.id} 307 + className="flex justify-between items-center p-2 bg-gray-50 dark:bg-gray-700 rounded" 308 + > 309 + <div className="text-sm"> 310 + <span className="text-gray-900 dark:text-white font-mono"> 311 + {session.app_id.substring(0, 12)}... 312 + </span> 313 + <span className="text-gray-500 dark:text-gray-400 ml-2"> 314 + Expires: {new Date(session.expires_at).toLocaleDateString()} 315 + </span> 316 + </div> 317 + </div> 318 + ))} 319 + </div> 320 + )} 321 + </div> 322 + </div> 323 + ); 324 + }
+7
gateway/admin-ui/src/pages/index.ts
··· 1 + export { LoginPage } from './LoginPage'; 2 + export { DashboardPage } from './DashboardPage'; 3 + export { AppsPage } from './AppsPage'; 4 + export { OIDCClientsPage } from './OIDCClientsPage'; 5 + export { SessionsPage } from './SessionsPage'; 6 + export { KeysPage } from './KeysPage'; 7 + export { UsersPage } from './UsersPage';
+38
gateway/admin-ui/src/stores/auth.ts
··· 1 + import { create } from 'zustand'; 2 + import { persist } from 'zustand/middleware'; 3 + import { api } from '../api/client'; 4 + 5 + interface AuthState { 6 + token: string | null; 7 + isAuthenticated: boolean; 8 + setToken: (token: string) => void; 9 + logout: () => void; 10 + } 11 + 12 + export const useAuthStore = create<AuthState>()( 13 + persist( 14 + (set) => ({ 15 + token: null, 16 + isAuthenticated: false, 17 + 18 + setToken: (token: string) => { 19 + api.setToken(token); 20 + set({ token, isAuthenticated: true }); 21 + }, 22 + 23 + logout: () => { 24 + api.setToken(null); 25 + set({ token: null, isAuthenticated: false }); 26 + }, 27 + }), 28 + { 29 + name: 'atauth-admin-auth', 30 + onRehydrateStorage: () => (state) => { 31 + // Restore token to API client on rehydration 32 + if (state?.token) { 33 + api.setToken(state.token); 34 + } 35 + }, 36 + } 37 + ) 38 + );
+28
gateway/admin-ui/tsconfig.app.json
··· 1 + { 2 + "compilerOptions": { 3 + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 + "target": "ES2022", 5 + "useDefineForClassFields": true, 6 + "lib": ["ES2022", "DOM", "DOM.Iterable"], 7 + "module": "ESNext", 8 + "types": ["vite/client"], 9 + "skipLibCheck": true, 10 + 11 + /* Bundler mode */ 12 + "moduleResolution": "bundler", 13 + "allowImportingTsExtensions": true, 14 + "verbatimModuleSyntax": true, 15 + "moduleDetection": "force", 16 + "noEmit": true, 17 + "jsx": "react-jsx", 18 + 19 + /* Linting */ 20 + "strict": true, 21 + "noUnusedLocals": true, 22 + "noUnusedParameters": true, 23 + "erasableSyntaxOnly": true, 24 + "noFallthroughCasesInSwitch": true, 25 + "noUncheckedSideEffectImports": true 26 + }, 27 + "include": ["src"] 28 + }
+7
gateway/admin-ui/tsconfig.json
··· 1 + { 2 + "files": [], 3 + "references": [ 4 + { "path": "./tsconfig.app.json" }, 5 + { "path": "./tsconfig.node.json" } 6 + ] 7 + }
+26
gateway/admin-ui/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 + "target": "ES2023", 5 + "lib": ["ES2023"], 6 + "module": "ESNext", 7 + "types": ["node"], 8 + "skipLibCheck": true, 9 + 10 + /* Bundler mode */ 11 + "moduleResolution": "bundler", 12 + "allowImportingTsExtensions": true, 13 + "verbatimModuleSyntax": true, 14 + "moduleDetection": "force", 15 + "noEmit": true, 16 + 17 + /* Linting */ 18 + "strict": true, 19 + "noUnusedLocals": true, 20 + "noUnusedParameters": true, 21 + "erasableSyntaxOnly": true, 22 + "noFallthroughCasesInSwitch": true, 23 + "noUncheckedSideEffectImports": true 24 + }, 25 + "include": ["vite.config.ts"] 26 + }
+22
gateway/admin-ui/vite.config.ts
··· 1 + import { defineConfig } from 'vite' 2 + import react from '@vitejs/plugin-react' 3 + import tailwindcss from '@tailwindcss/vite' 4 + 5 + // https://vite.dev/config/ 6 + export default defineConfig({ 7 + plugins: [react(), tailwindcss()], 8 + base: '/admin/', 9 + build: { 10 + outDir: '../public/admin', 11 + emptyOutDir: true, 12 + }, 13 + server: { 14 + proxy: { 15 + '/admin/api': { 16 + target: 'http://localhost:3100', 17 + changeOrigin: true, 18 + rewrite: (path) => path.replace(/^\/admin\/api/, '/admin'), 19 + }, 20 + }, 21 + }, 22 + })
+66
gateway/docker-compose.yaml
··· 1 + version: '3.8' 2 + 3 + services: 4 + gateway: 5 + build: 6 + context: . 7 + dockerfile: Dockerfile 8 + image: atauth-gateway:latest 9 + container_name: atauth-gateway 10 + restart: unless-stopped 11 + ports: 12 + - "3100:3100" 13 + volumes: 14 + - atauth-data:/app/data 15 + environment: 16 + # Server 17 + - PORT=3100 18 + - HOST=0.0.0.0 19 + - NODE_ENV=production 20 + 21 + # OAuth client configuration 22 + - OAUTH_CLIENT_ID=https://auth.example.com/client-metadata.json 23 + - OAUTH_REDIRECT_URI=https://auth.example.com/auth/callback 24 + 25 + # CORS 26 + - CORS_ORIGINS=https://example.com,http://localhost:3000 27 + 28 + # Admin token (generate with: openssl rand -hex 32) 29 + - ADMIN_TOKEN=${ADMIN_TOKEN:-changeme} 30 + 31 + # OIDC configuration 32 + - OIDC_ENABLED=true 33 + - OIDC_ISSUER=https://auth.example.com 34 + - OIDC_KEY_SECRET=${OIDC_KEY_SECRET:-change-me-in-production-32-bytes!} 35 + - OIDC_KEY_ALGORITHM=ES256 36 + 37 + # Passkey/WebAuthn 38 + - PASSKEY_ENABLED=true 39 + - WEBAUTHN_RP_NAME=ATAuth 40 + - WEBAUTHN_RP_ID=auth.example.com 41 + - WEBAUTHN_ORIGIN=https://auth.example.com 42 + 43 + # MFA 44 + - MFA_ENABLED=true 45 + - MFA_TOTP_ISSUER=ATAuth 46 + - MFA_ENCRYPTION_KEY=${MFA_ENCRYPTION_KEY:-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef} 47 + 48 + # Email (disabled by default) 49 + - EMAIL_ENABLED=false 50 + # - EMAIL_PROVIDER=smtp 51 + # - EMAIL_FROM=ATAuth <noreply@example.com> 52 + # - SMTP_HOST=smtp.example.com 53 + # - SMTP_PORT=587 54 + # - SMTP_USER=noreply@example.com 55 + # - SMTP_PASS=your-password 56 + 57 + healthcheck: 58 + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3100/health"] 59 + interval: 30s 60 + timeout: 3s 61 + retries: 3 62 + start_period: 5s 63 + 64 + volumes: 65 + atauth-data: 66 + driver: local
+16
gateway/helm/atauth-gateway/Chart.yaml
··· 1 + apiVersion: v2 2 + name: atauth-gateway 3 + description: ATAuth Gateway - AT Protocol OAuth gateway with OIDC provider support 4 + type: application 5 + version: 0.1.0 6 + appVersion: "1.3.0" 7 + keywords: 8 + - atproto 9 + - oauth 10 + - oidc 11 + - identity 12 + - authentication 13 + maintainers: 14 + - name: ATAuth 15 + sources: 16 + - https://github.com/Arcnode-xyz/atauth
+71
gateway/helm/atauth-gateway/templates/_helpers.tpl
··· 1 + {{/* 2 + Expand the name of the chart. 3 + */}} 4 + {{- define "atauth-gateway.name" -}} 5 + {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 + {{- end }} 7 + 8 + {{/* 9 + Create a default fully qualified app name. 10 + */}} 11 + {{- define "atauth-gateway.fullname" -}} 12 + {{- if .Values.fullnameOverride }} 13 + {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 14 + {{- else }} 15 + {{- $name := default .Chart.Name .Values.nameOverride }} 16 + {{- if contains $name .Release.Name }} 17 + {{- .Release.Name | trunc 63 | trimSuffix "-" }} 18 + {{- else }} 19 + {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 20 + {{- end }} 21 + {{- end }} 22 + {{- end }} 23 + 24 + {{/* 25 + Create chart name and version as used by the chart label. 26 + */}} 27 + {{- define "atauth-gateway.chart" -}} 28 + {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 29 + {{- end }} 30 + 31 + {{/* 32 + Common labels 33 + */}} 34 + {{- define "atauth-gateway.labels" -}} 35 + helm.sh/chart: {{ include "atauth-gateway.chart" . }} 36 + {{ include "atauth-gateway.selectorLabels" . }} 37 + {{- if .Chart.AppVersion }} 38 + app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 39 + {{- end }} 40 + app.kubernetes.io/managed-by: {{ .Release.Service }} 41 + {{- end }} 42 + 43 + {{/* 44 + Selector labels 45 + */}} 46 + {{- define "atauth-gateway.selectorLabels" -}} 47 + app.kubernetes.io/name: {{ include "atauth-gateway.name" . }} 48 + app.kubernetes.io/instance: {{ .Release.Name }} 49 + {{- end }} 50 + 51 + {{/* 52 + Create the name of the service account to use 53 + */}} 54 + {{- define "atauth-gateway.serviceAccountName" -}} 55 + {{- if .Values.serviceAccount.create }} 56 + {{- default (include "atauth-gateway.fullname" .) .Values.serviceAccount.name }} 57 + {{- else }} 58 + {{- default "default" .Values.serviceAccount.name }} 59 + {{- end }} 60 + {{- end }} 61 + 62 + {{/* 63 + Secret name 64 + */}} 65 + {{- define "atauth-gateway.secretName" -}} 66 + {{- if .Values.externalSecrets.enabled }} 67 + {{- .Values.externalSecrets.secretName }} 68 + {{- else }} 69 + {{- include "atauth-gateway.fullname" . }}-secrets 70 + {{- end }} 71 + {{- end }}
+32
gateway/helm/atauth-gateway/templates/configmap.yaml
··· 1 + apiVersion: v1 2 + kind: ConfigMap 3 + metadata: 4 + name: {{ include "atauth-gateway.fullname" . }}-config 5 + labels: 6 + {{- include "atauth-gateway.labels" . | nindent 4 }} 7 + data: 8 + PORT: {{ .Values.config.port | quote }} 9 + HOST: {{ .Values.config.host | quote }} 10 + NODE_ENV: "production" 11 + 12 + OAUTH_CLIENT_ID: {{ .Values.config.oauthClientId | quote }} 13 + OAUTH_REDIRECT_URI: {{ .Values.config.oauthRedirectUri | quote }} 14 + CORS_ORIGINS: {{ .Values.config.corsOrigins | quote }} 15 + 16 + OIDC_ENABLED: {{ .Values.config.oidc.enabled | quote }} 17 + OIDC_ISSUER: {{ .Values.config.oidc.issuer | quote }} 18 + OIDC_KEY_ALGORITHM: {{ .Values.config.oidc.keyAlgorithm | quote }} 19 + 20 + PASSKEY_ENABLED: {{ .Values.config.passkey.enabled | quote }} 21 + WEBAUTHN_RP_NAME: {{ .Values.config.passkey.rpName | quote }} 22 + WEBAUTHN_RP_ID: {{ .Values.config.passkey.rpId | quote }} 23 + WEBAUTHN_ORIGIN: {{ .Values.config.passkey.origin | quote }} 24 + 25 + MFA_ENABLED: {{ .Values.config.mfa.enabled | quote }} 26 + MFA_TOTP_ISSUER: {{ .Values.config.mfa.totpIssuer | quote }} 27 + MFA_BACKUP_CODES_COUNT: {{ .Values.config.mfa.backupCodesCount | quote }} 28 + 29 + EMAIL_ENABLED: {{ .Values.config.email.enabled | quote }} 30 + EMAIL_PROVIDER: {{ .Values.config.email.provider | quote }} 31 + EMAIL_FROM: {{ .Values.config.email.from | quote }} 32 + EMAIL_CODE_EXPIRY: {{ .Values.config.email.codeExpiry | quote }}
+87
gateway/helm/atauth-gateway/templates/deployment.yaml
··· 1 + apiVersion: apps/v1 2 + kind: Deployment 3 + metadata: 4 + name: {{ include "atauth-gateway.fullname" . }} 5 + labels: 6 + {{- include "atauth-gateway.labels" . | nindent 4 }} 7 + spec: 8 + replicas: {{ .Values.replicaCount }} 9 + selector: 10 + matchLabels: 11 + {{- include "atauth-gateway.selectorLabels" . | nindent 6 }} 12 + strategy: 13 + type: Recreate 14 + template: 15 + metadata: 16 + annotations: 17 + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} 18 + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} 19 + labels: 20 + {{- include "atauth-gateway.selectorLabels" . | nindent 8 }} 21 + spec: 22 + {{- with .Values.imagePullSecrets }} 23 + imagePullSecrets: 24 + {{- toYaml . | nindent 8 }} 25 + {{- end }} 26 + serviceAccountName: {{ include "atauth-gateway.serviceAccountName" . }} 27 + securityContext: 28 + {{- toYaml .Values.podSecurityContext | nindent 8 }} 29 + containers: 30 + - name: {{ .Chart.Name }} 31 + securityContext: 32 + {{- toYaml .Values.securityContext | nindent 12 }} 33 + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 34 + imagePullPolicy: {{ .Values.image.pullPolicy }} 35 + ports: 36 + - name: http 37 + containerPort: {{ .Values.config.port }} 38 + protocol: TCP 39 + envFrom: 40 + - configMapRef: 41 + name: {{ include "atauth-gateway.fullname" . }}-config 42 + - secretRef: 43 + name: {{ include "atauth-gateway.secretName" . }} 44 + env: 45 + - name: DB_PATH 46 + value: /app/data/gateway.db 47 + {{- if .Values.persistence.enabled }} 48 + volumeMounts: 49 + - name: data 50 + mountPath: /app/data 51 + {{- end }} 52 + resources: 53 + {{- toYaml .Values.resources | nindent 12 }} 54 + livenessProbe: 55 + httpGet: 56 + path: /health 57 + port: http 58 + initialDelaySeconds: 10 59 + periodSeconds: 30 60 + timeoutSeconds: 5 61 + failureThreshold: 3 62 + readinessProbe: 63 + httpGet: 64 + path: /health 65 + port: http 66 + initialDelaySeconds: 5 67 + periodSeconds: 10 68 + timeoutSeconds: 3 69 + failureThreshold: 3 70 + {{- if .Values.persistence.enabled }} 71 + volumes: 72 + - name: data 73 + persistentVolumeClaim: 74 + claimName: {{ .Values.persistence.existingClaim | default (printf "%s-data" (include "atauth-gateway.fullname" .)) }} 75 + {{- end }} 76 + {{- with .Values.nodeSelector }} 77 + nodeSelector: 78 + {{- toYaml . | nindent 8 }} 79 + {{- end }} 80 + {{- with .Values.affinity }} 81 + affinity: 82 + {{- toYaml . | nindent 8 }} 83 + {{- end }} 84 + {{- with .Values.tolerations }} 85 + tolerations: 86 + {{- toYaml . | nindent 8 }} 87 + {{- end }}
+41
gateway/helm/atauth-gateway/templates/ingress.yaml
··· 1 + {{- if .Values.ingress.enabled -}} 2 + apiVersion: networking.k8s.io/v1 3 + kind: Ingress 4 + metadata: 5 + name: {{ include "atauth-gateway.fullname" . }} 6 + labels: 7 + {{- include "atauth-gateway.labels" . | nindent 4 }} 8 + {{- with .Values.ingress.annotations }} 9 + annotations: 10 + {{- toYaml . | nindent 4 }} 11 + {{- end }} 12 + spec: 13 + {{- if .Values.ingress.className }} 14 + ingressClassName: {{ .Values.ingress.className }} 15 + {{- end }} 16 + {{- if .Values.ingress.tls }} 17 + tls: 18 + {{- range .Values.ingress.tls }} 19 + - hosts: 20 + {{- range .hosts }} 21 + - {{ . | quote }} 22 + {{- end }} 23 + secretName: {{ .secretName }} 24 + {{- end }} 25 + {{- end }} 26 + rules: 27 + {{- range .Values.ingress.hosts }} 28 + - host: {{ .host | quote }} 29 + http: 30 + paths: 31 + {{- range .paths }} 32 + - path: {{ .path }} 33 + pathType: {{ .pathType }} 34 + backend: 35 + service: 36 + name: {{ include "atauth-gateway.fullname" $ }} 37 + port: 38 + name: http 39 + {{- end }} 40 + {{- end }} 41 + {{- end }}
+17
gateway/helm/atauth-gateway/templates/pvc.yaml
··· 1 + {{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} 2 + apiVersion: v1 3 + kind: PersistentVolumeClaim 4 + metadata: 5 + name: {{ include "atauth-gateway.fullname" . }}-data 6 + labels: 7 + {{- include "atauth-gateway.labels" . | nindent 4 }} 8 + spec: 9 + accessModes: 10 + - {{ .Values.persistence.accessMode }} 11 + resources: 12 + requests: 13 + storage: {{ .Values.persistence.size }} 14 + {{- if .Values.persistence.storageClass }} 15 + storageClassName: {{ .Values.persistence.storageClass | quote }} 16 + {{- end }} 17 + {{- end }}
+29
gateway/helm/atauth-gateway/templates/secret.yaml
··· 1 + {{- if not .Values.externalSecrets.enabled }} 2 + apiVersion: v1 3 + kind: Secret 4 + metadata: 5 + name: {{ include "atauth-gateway.fullname" . }}-secrets 6 + labels: 7 + {{- include "atauth-gateway.labels" . | nindent 4 }} 8 + type: Opaque 9 + stringData: 10 + {{- if .Values.secrets.adminToken }} 11 + ADMIN_TOKEN: {{ .Values.secrets.adminToken | quote }} 12 + {{- end }} 13 + {{- if .Values.secrets.oidcKeySecret }} 14 + OIDC_KEY_SECRET: {{ .Values.secrets.oidcKeySecret | quote }} 15 + {{- end }} 16 + {{- if .Values.secrets.mfaEncryptionKey }} 17 + MFA_ENCRYPTION_KEY: {{ .Values.secrets.mfaEncryptionKey | quote }} 18 + {{- end }} 19 + {{- if and .Values.config.email.enabled .Values.secrets.smtp.host }} 20 + SMTP_HOST: {{ .Values.secrets.smtp.host | quote }} 21 + SMTP_PORT: {{ .Values.secrets.smtp.port | quote }} 22 + SMTP_SECURE: {{ .Values.secrets.smtp.secure | quote }} 23 + SMTP_USER: {{ .Values.secrets.smtp.user | quote }} 24 + SMTP_PASS: {{ .Values.secrets.smtp.pass | quote }} 25 + {{- end }} 26 + {{- if .Values.secrets.emailApiKey }} 27 + EMAIL_API_KEY: {{ .Values.secrets.emailApiKey | quote }} 28 + {{- end }} 29 + {{- end }}
+15
gateway/helm/atauth-gateway/templates/service.yaml
··· 1 + apiVersion: v1 2 + kind: Service 3 + metadata: 4 + name: {{ include "atauth-gateway.fullname" . }} 5 + labels: 6 + {{- include "atauth-gateway.labels" . | nindent 4 }} 7 + spec: 8 + type: {{ .Values.service.type }} 9 + ports: 10 + - port: {{ .Values.service.port }} 11 + targetPort: http 12 + protocol: TCP 13 + name: http 14 + selector: 15 + {{- include "atauth-gateway.selectorLabels" . | nindent 4 }}
+12
gateway/helm/atauth-gateway/templates/serviceaccount.yaml
··· 1 + {{- if .Values.serviceAccount.create -}} 2 + apiVersion: v1 3 + kind: ServiceAccount 4 + metadata: 5 + name: {{ include "atauth-gateway.serviceAccountName" . }} 6 + labels: 7 + {{- include "atauth-gateway.labels" . | nindent 4 }} 8 + {{- with .Values.serviceAccount.annotations }} 9 + annotations: 10 + {{- toYaml . | nindent 4 }} 11 + {{- end }} 12 + {{- end }}
+149
gateway/helm/atauth-gateway/values.yaml
··· 1 + # ATAuth Gateway Helm Values 2 + 3 + # Image configuration 4 + image: 5 + repository: atauth-gateway 6 + tag: latest 7 + pullPolicy: IfNotPresent 8 + 9 + # Image pull secrets (if using private registry) 10 + imagePullSecrets: [] 11 + 12 + # Number of replicas (use 1 for SQLite) 13 + replicaCount: 1 14 + 15 + # Service account 16 + serviceAccount: 17 + create: true 18 + name: "" 19 + annotations: {} 20 + 21 + # Pod security context 22 + podSecurityContext: 23 + runAsNonRoot: true 24 + runAsUser: 1001 25 + runAsGroup: 1001 26 + fsGroup: 1001 27 + 28 + # Container security context 29 + securityContext: 30 + allowPrivilegeEscalation: false 31 + readOnlyRootFilesystem: false 32 + capabilities: 33 + drop: 34 + - ALL 35 + 36 + # Service configuration 37 + service: 38 + type: ClusterIP 39 + port: 80 40 + 41 + # Ingress configuration 42 + ingress: 43 + enabled: false 44 + className: "" 45 + annotations: {} 46 + # cert-manager.io/cluster-issuer: letsencrypt-prod 47 + hosts: 48 + - host: auth.example.com 49 + paths: 50 + - path: / 51 + pathType: Prefix 52 + tls: [] 53 + # - secretName: atauth-gateway-tls 54 + # hosts: 55 + # - auth.example.com 56 + 57 + # Resource limits 58 + resources: 59 + requests: 60 + memory: "128Mi" 61 + cpu: "100m" 62 + limits: 63 + memory: "512Mi" 64 + cpu: "500m" 65 + 66 + # Persistent storage for SQLite database 67 + persistence: 68 + enabled: true 69 + storageClass: "" 70 + accessMode: ReadWriteOnce 71 + size: 1Gi 72 + existingClaim: "" 73 + 74 + # Node selector 75 + nodeSelector: {} 76 + 77 + # Tolerations 78 + tolerations: [] 79 + 80 + # Affinity rules 81 + affinity: {} 82 + 83 + # ATAuth Gateway configuration 84 + config: 85 + # Server 86 + port: 3100 87 + host: "0.0.0.0" 88 + 89 + # OAuth client configuration (for AT Protocol) 90 + oauthClientId: "https://auth.example.com/client-metadata.json" 91 + oauthRedirectUri: "https://auth.example.com/auth/callback" 92 + 93 + # CORS origins (comma-separated) 94 + corsOrigins: "https://example.com" 95 + 96 + # OIDC configuration 97 + oidc: 98 + enabled: true 99 + issuer: "https://auth.example.com" 100 + keyAlgorithm: "ES256" 101 + 102 + # Passkey/WebAuthn configuration 103 + passkey: 104 + enabled: true 105 + rpName: "ATAuth" 106 + rpId: "auth.example.com" 107 + origin: "https://auth.example.com" 108 + 109 + # MFA configuration 110 + mfa: 111 + enabled: true 112 + totpIssuer: "ATAuth" 113 + backupCodesCount: 10 114 + 115 + # Email configuration 116 + email: 117 + enabled: false 118 + provider: "smtp" 119 + from: "ATAuth <noreply@example.com>" 120 + codeExpiry: 900 121 + 122 + # Secrets (set via --set or external secret management) 123 + secrets: 124 + # Admin token for API access 125 + # Generate with: openssl rand -hex 32 126 + adminToken: "" 127 + 128 + # OIDC key encryption secret 129 + oidcKeySecret: "" 130 + 131 + # MFA encryption key (32 bytes hex) 132 + mfaEncryptionKey: "" 133 + 134 + # SMTP configuration (if email.enabled) 135 + smtp: 136 + host: "" 137 + port: "587" 138 + secure: "false" 139 + user: "" 140 + pass: "" 141 + 142 + # Email API key (alternative to SMTP) 143 + emailApiKey: "" 144 + 145 + # External secrets (use existing secrets) 146 + externalSecrets: 147 + # Use an existing secret for sensitive values 148 + enabled: false 149 + secretName: ""
+43
gateway/k8s/base/configmap.yaml
··· 1 + apiVersion: v1 2 + kind: ConfigMap 3 + metadata: 4 + name: atauth-gateway-config 5 + namespace: atauth 6 + labels: 7 + app.kubernetes.io/name: atauth-gateway 8 + app.kubernetes.io/component: config 9 + data: 10 + # Server configuration 11 + PORT: "3100" 12 + HOST: "0.0.0.0" 13 + NODE_ENV: "production" 14 + 15 + # OAuth client configuration (for AT Protocol) 16 + # Update these to match your deployment 17 + OAUTH_CLIENT_ID: "https://auth.example.com/client-metadata.json" 18 + OAUTH_REDIRECT_URI: "https://auth.example.com/auth/callback" 19 + 20 + # CORS origins (comma-separated) 21 + CORS_ORIGINS: "https://example.com,https://app.example.com" 22 + 23 + # OIDC configuration 24 + OIDC_ENABLED: "true" 25 + OIDC_ISSUER: "https://auth.example.com" 26 + OIDC_KEY_ALGORITHM: "ES256" 27 + 28 + # Passkey/WebAuthn configuration 29 + PASSKEY_ENABLED: "true" 30 + WEBAUTHN_RP_NAME: "ATAuth" 31 + WEBAUTHN_RP_ID: "auth.example.com" 32 + WEBAUTHN_ORIGIN: "https://auth.example.com" 33 + 34 + # MFA configuration 35 + MFA_ENABLED: "true" 36 + MFA_TOTP_ISSUER: "ATAuth" 37 + MFA_BACKUP_CODES_COUNT: "10" 38 + 39 + # Email configuration (disabled by default) 40 + EMAIL_ENABLED: "false" 41 + EMAIL_PROVIDER: "smtp" 42 + EMAIL_FROM: "ATAuth <noreply@example.com>" 43 + EMAIL_CODE_EXPIRY: "900"
+97
gateway/k8s/base/deployment.yaml
··· 1 + apiVersion: apps/v1 2 + kind: Deployment 3 + metadata: 4 + name: atauth-gateway 5 + namespace: atauth 6 + labels: 7 + app.kubernetes.io/name: atauth-gateway 8 + app.kubernetes.io/component: server 9 + spec: 10 + replicas: 1 11 + selector: 12 + matchLabels: 13 + app.kubernetes.io/name: atauth-gateway 14 + strategy: 15 + type: Recreate # Use Recreate due to SQLite file lock 16 + template: 17 + metadata: 18 + labels: 19 + app.kubernetes.io/name: atauth-gateway 20 + spec: 21 + securityContext: 22 + runAsNonRoot: true 23 + runAsUser: 1001 24 + runAsGroup: 1001 25 + fsGroup: 1001 26 + 27 + containers: 28 + - name: gateway 29 + image: atauth-gateway:latest 30 + imagePullPolicy: IfNotPresent 31 + 32 + ports: 33 + - name: http 34 + containerPort: 3100 35 + protocol: TCP 36 + 37 + envFrom: 38 + - configMapRef: 39 + name: atauth-gateway-config 40 + - secretRef: 41 + name: atauth-gateway-secrets 42 + 43 + env: 44 + - name: DB_PATH 45 + value: /app/data/gateway.db 46 + 47 + volumeMounts: 48 + - name: data 49 + mountPath: /app/data 50 + 51 + resources: 52 + requests: 53 + memory: "128Mi" 54 + cpu: "100m" 55 + limits: 56 + memory: "512Mi" 57 + cpu: "500m" 58 + 59 + livenessProbe: 60 + httpGet: 61 + path: /health 62 + port: http 63 + initialDelaySeconds: 10 64 + periodSeconds: 30 65 + timeoutSeconds: 5 66 + failureThreshold: 3 67 + 68 + readinessProbe: 69 + httpGet: 70 + path: /health 71 + port: http 72 + initialDelaySeconds: 5 73 + periodSeconds: 10 74 + timeoutSeconds: 3 75 + failureThreshold: 3 76 + 77 + securityContext: 78 + allowPrivilegeEscalation: false 79 + readOnlyRootFilesystem: false 80 + capabilities: 81 + drop: 82 + - ALL 83 + 84 + volumes: 85 + - name: data 86 + persistentVolumeClaim: 87 + claimName: atauth-gateway-data 88 + 89 + # Optional: Node selector for specific nodes 90 + # nodeSelector: 91 + # kubernetes.io/os: linux 92 + 93 + # Optional: Tolerations 94 + # tolerations: 95 + # - key: "node-role.kubernetes.io/control-plane" 96 + # operator: "Exists" 97 + # effect: "NoSchedule"
+37
gateway/k8s/base/ingress.yaml
··· 1 + apiVersion: networking.k8s.io/v1 2 + kind: Ingress 3 + metadata: 4 + name: atauth-gateway 5 + namespace: atauth 6 + labels: 7 + app.kubernetes.io/name: atauth-gateway 8 + app.kubernetes.io/component: ingress 9 + annotations: 10 + # Traefik annotations (for k3s default) 11 + # traefik.ingress.kubernetes.io/router.entrypoints: websecure 12 + # traefik.ingress.kubernetes.io/router.tls: "true" 13 + 14 + # Nginx annotations (if using nginx-ingress) 15 + # nginx.ingress.kubernetes.io/ssl-redirect: "true" 16 + # nginx.ingress.kubernetes.io/proxy-body-size: "10m" 17 + 18 + # cert-manager for automatic TLS 19 + # cert-manager.io/cluster-issuer: letsencrypt-prod 20 + spec: 21 + # Uncomment for TLS 22 + # tls: 23 + # - hosts: 24 + # - auth.example.com 25 + # secretName: atauth-gateway-tls 26 + 27 + rules: 28 + - host: auth.example.com 29 + http: 30 + paths: 31 + - path: / 32 + pathType: Prefix 33 + backend: 34 + service: 35 + name: atauth-gateway 36 + port: 37 + name: http
+15
gateway/k8s/base/kustomization.yaml
··· 1 + apiVersion: kustomize.config.k8s.io/v1beta1 2 + kind: Kustomization 3 + 4 + resources: 5 + - namespace.yaml 6 + - configmap.yaml 7 + - secret.yaml 8 + - pvc.yaml 9 + - deployment.yaml 10 + - service.yaml 11 + - ingress.yaml 12 + 13 + commonLabels: 14 + app.kubernetes.io/part-of: atauth 15 + app.kubernetes.io/managed-by: kustomize
+7
gateway/k8s/base/namespace.yaml
··· 1 + apiVersion: v1 2 + kind: Namespace 3 + metadata: 4 + name: atauth 5 + labels: 6 + app.kubernetes.io/name: atauth 7 + app.kubernetes.io/component: identity
+16
gateway/k8s/base/pvc.yaml
··· 1 + apiVersion: v1 2 + kind: PersistentVolumeClaim 3 + metadata: 4 + name: atauth-gateway-data 5 + namespace: atauth 6 + labels: 7 + app.kubernetes.io/name: atauth-gateway 8 + app.kubernetes.io/component: storage 9 + spec: 10 + accessModes: 11 + - ReadWriteOnce 12 + resources: 13 + requests: 14 + storage: 1Gi 15 + # Uncomment and set your storage class 16 + # storageClassName: local-path
+31
gateway/k8s/base/secret.yaml
··· 1 + apiVersion: v1 2 + kind: Secret 3 + metadata: 4 + name: atauth-gateway-secrets 5 + namespace: atauth 6 + labels: 7 + app.kubernetes.io/name: atauth-gateway 8 + app.kubernetes.io/component: secrets 9 + type: Opaque 10 + stringData: 11 + # Admin token for API access 12 + # Generate with: openssl rand -hex 32 13 + ADMIN_TOKEN: "CHANGE_ME_generate_with_openssl_rand_hex_32" 14 + 15 + # OIDC key encryption secret (32 bytes) 16 + # Generate with: openssl rand -hex 32 17 + OIDC_KEY_SECRET: "CHANGE_ME_generate_with_openssl_rand_hex_32" 18 + 19 + # MFA encryption key (32 bytes hex = 64 characters) 20 + # Generate with: openssl rand -hex 32 21 + MFA_ENCRYPTION_KEY: "CHANGE_ME_generate_with_openssl_rand_hex_32" 22 + 23 + # SMTP credentials (if EMAIL_ENABLED=true) 24 + # SMTP_HOST: "smtp.example.com" 25 + # SMTP_PORT: "587" 26 + # SMTP_SECURE: "false" 27 + # SMTP_USER: "noreply@example.com" 28 + # SMTP_PASS: "your-smtp-password" 29 + 30 + # Or use API-based email provider 31 + # EMAIL_API_KEY: "your-api-key"
+17
gateway/k8s/base/service.yaml
··· 1 + apiVersion: v1 2 + kind: Service 3 + metadata: 4 + name: atauth-gateway 5 + namespace: atauth 6 + labels: 7 + app.kubernetes.io/name: atauth-gateway 8 + app.kubernetes.io/component: server 9 + spec: 10 + type: ClusterIP 11 + selector: 12 + app.kubernetes.io/name: atauth-gateway 13 + ports: 14 + - name: http 15 + port: 80 16 + targetPort: http 17 + protocol: TCP
+11
gateway/k8s/kustomization.yaml
··· 1 + # Root kustomization - use overlays for deployment 2 + # 3 + # Deploy staging: kubectl apply -k k8s/overlays/staging 4 + # Deploy production: kubectl apply -k k8s/overlays/production 5 + # 6 + # This file points to production for backward compatibility 7 + apiVersion: kustomize.config.k8s.io/v1beta1 8 + kind: Kustomization 9 + 10 + resources: 11 + - overlays/production
+134
gateway/k8s/overlays/production/backup-cronjob.yaml
··· 1 + --- 2 + # CronJob to backup SQLite database before deployments 3 + apiVersion: batch/v1 4 + kind: CronJob 5 + metadata: 6 + name: atauth-gateway-backup 7 + namespace: atauth 8 + spec: 9 + schedule: "0 */6 * * *" # Every 6 hours 10 + concurrencyPolicy: Forbid 11 + successfulJobsHistoryLimit: 3 12 + failedJobsHistoryLimit: 1 13 + jobTemplate: 14 + spec: 15 + template: 16 + spec: 17 + securityContext: 18 + runAsNonRoot: true 19 + runAsUser: 1001 20 + fsGroup: 1001 21 + containers: 22 + - name: backup 23 + image: alpine:3.19 24 + command: 25 + - /bin/sh 26 + - -c 27 + - | 28 + set -e 29 + TIMESTAMP=$(date +%Y%m%d-%H%M%S) 30 + BACKUP_FILE="/backups/gateway-${TIMESTAMP}.db" 31 + 32 + echo "Starting backup at ${TIMESTAMP}" 33 + 34 + # Copy database (SQLite safe copy) 35 + cp /data/gateway.db "${BACKUP_FILE}" 36 + 37 + # Verify backup 38 + sqlite3 "${BACKUP_FILE}" "PRAGMA integrity_check;" 39 + 40 + # Keep only last 10 backups 41 + ls -t /backups/gateway-*.db | tail -n +11 | xargs -r rm 42 + 43 + echo "Backup complete: ${BACKUP_FILE}" 44 + ls -la /backups/ 45 + volumeMounts: 46 + - name: data 47 + mountPath: /data 48 + readOnly: true 49 + - name: backups 50 + mountPath: /backups 51 + resources: 52 + requests: 53 + memory: "32Mi" 54 + cpu: "10m" 55 + limits: 56 + memory: "64Mi" 57 + cpu: "100m" 58 + restartPolicy: OnFailure 59 + volumes: 60 + - name: data 61 + persistentVolumeClaim: 62 + claimName: atauth-gateway-data 63 + - name: backups 64 + persistentVolumeClaim: 65 + claimName: atauth-gateway-backups 66 + --- 67 + # PVC for backups 68 + apiVersion: v1 69 + kind: PersistentVolumeClaim 70 + metadata: 71 + name: atauth-gateway-backups 72 + namespace: atauth 73 + spec: 74 + accessModes: 75 + - ReadWriteOnce 76 + resources: 77 + requests: 78 + storage: 2Gi 79 + --- 80 + # Job to run backup before deployment (triggered manually or by CI) 81 + apiVersion: batch/v1 82 + kind: Job 83 + metadata: 84 + name: atauth-pre-deploy-backup 85 + namespace: atauth 86 + annotations: 87 + # This job is meant to be created fresh each time 88 + helm.sh/hook: pre-upgrade 89 + helm.sh/hook-delete-policy: before-hook-creation 90 + spec: 91 + ttlSecondsAfterFinished: 600 92 + template: 93 + spec: 94 + securityContext: 95 + runAsNonRoot: true 96 + runAsUser: 1001 97 + fsGroup: 1001 98 + containers: 99 + - name: backup 100 + image: alpine:3.19 101 + command: 102 + - /bin/sh 103 + - -c 104 + - | 105 + set -e 106 + TIMESTAMP=$(date +%Y%m%d-%H%M%S) 107 + BACKUP_FILE="/backups/pre-deploy-${TIMESTAMP}.db" 108 + 109 + echo "Creating pre-deployment backup" 110 + cp /data/gateway.db "${BACKUP_FILE}" 111 + sqlite3 "${BACKUP_FILE}" "PRAGMA integrity_check;" 112 + 113 + echo "Pre-deploy backup complete: ${BACKUP_FILE}" 114 + volumeMounts: 115 + - name: data 116 + mountPath: /data 117 + readOnly: true 118 + - name: backups 119 + mountPath: /backups 120 + resources: 121 + requests: 122 + memory: "32Mi" 123 + cpu: "10m" 124 + limits: 125 + memory: "64Mi" 126 + cpu: "100m" 127 + restartPolicy: Never 128 + volumes: 129 + - name: data 130 + persistentVolumeClaim: 131 + claimName: atauth-gateway-data 132 + - name: backups 133 + persistentVolumeClaim: 134 + claimName: atauth-gateway-backups
+28
gateway/k8s/overlays/production/kustomization.yaml
··· 1 + apiVersion: kustomize.config.k8s.io/v1beta1 2 + kind: Kustomization 3 + 4 + namespace: atauth 5 + 6 + resources: 7 + - ../../base 8 + - backup-cronjob.yaml 9 + 10 + commonLabels: 11 + environment: production 12 + 13 + images: 14 + - name: atauth-gateway 15 + newName: gitea.cloudforest-basilisk.ts.net/arcnode.xyz/atauth-gateway 16 + newTag: latest # Use specific version tags in production! 17 + 18 + # Production-specific patches 19 + patches: 20 + # Enable pod disruption budget for HA 21 + - target: 22 + kind: Deployment 23 + name: atauth-gateway 24 + patch: | 25 + - op: add 26 + path: /metadata/annotations 27 + value: 28 + reloader.stakater.com/auto: "true"
+88
gateway/k8s/overlays/staging/kustomization.yaml
··· 1 + apiVersion: kustomize.config.k8s.io/v1beta1 2 + kind: Kustomization 3 + 4 + namespace: atauth-staging 5 + 6 + resources: 7 + - ../../base 8 + 9 + nameSuffix: -staging 10 + 11 + commonLabels: 12 + environment: staging 13 + 14 + # Use staging image tag 15 + images: 16 + - name: atauth-gateway 17 + newName: gitea.cloudforest-basilisk.ts.net/arcnode.xyz/atauth-gateway 18 + newTag: staging 19 + 20 + patches: 21 + # Rename namespace 22 + - target: 23 + kind: Namespace 24 + name: atauth 25 + patch: | 26 + - op: replace 27 + path: /metadata/name 28 + value: atauth-staging 29 + 30 + # Reduce resources for staging 31 + - target: 32 + kind: Deployment 33 + name: atauth-gateway 34 + patch: | 35 + - op: replace 36 + path: /spec/template/spec/containers/0/resources/requests/memory 37 + value: "64Mi" 38 + - op: replace 39 + path: /spec/template/spec/containers/0/resources/requests/cpu 40 + value: "50m" 41 + - op: replace 42 + path: /spec/template/spec/containers/0/resources/limits/memory 43 + value: "256Mi" 44 + - op: replace 45 + path: /spec/template/spec/containers/0/resources/limits/cpu 46 + value: "250m" 47 + 48 + # Smaller PVC for staging 49 + - target: 50 + kind: PersistentVolumeClaim 51 + name: atauth-gateway-data 52 + patch: | 53 + - op: replace 54 + path: /spec/resources/requests/storage 55 + value: "512Mi" 56 + 57 + # Update ingress host for staging 58 + - target: 59 + kind: Ingress 60 + name: atauth-gateway 61 + patch: | 62 + - op: replace 63 + path: /spec/rules/0/host 64 + value: auth-staging.cloudforest-basilisk.ts.net 65 + 66 + # Update ConfigMap with staging URLs 67 + - target: 68 + kind: ConfigMap 69 + name: atauth-gateway-config 70 + patch: | 71 + - op: replace 72 + path: /data/OIDC_ISSUER 73 + value: "https://auth-staging.cloudforest-basilisk.ts.net" 74 + - op: replace 75 + path: /data/OAUTH_CLIENT_ID 76 + value: "https://auth-staging.cloudforest-basilisk.ts.net/client-metadata.json" 77 + - op: replace 78 + path: /data/OAUTH_REDIRECT_URI 79 + value: "https://auth-staging.cloudforest-basilisk.ts.net/auth/callback" 80 + - op: replace 81 + path: /data/WEBAUTHN_RP_ID 82 + value: "auth-staging.cloudforest-basilisk.ts.net" 83 + - op: replace 84 + path: /data/WEBAUTHN_ORIGIN 85 + value: "https://auth-staging.cloudforest-basilisk.ts.net" 86 + - op: replace 87 + path: /data/CORS_ORIGINS 88 + value: "https://staging.example.com,http://localhost:3000"
+508 -6
gateway/package-lock.json
··· 1 1 { 2 2 "name": "atauth-gateway", 3 - "version": "1.2.0", 3 + "version": "1.3.0", 4 4 "lockfileVersion": 3, 5 5 "requires": true, 6 6 "packages": { 7 7 "": { 8 8 "name": "atauth-gateway", 9 - "version": "1.2.0", 9 + "version": "1.3.0", 10 10 "license": "MIT", 11 11 "dependencies": { 12 12 "@atproto/oauth-client-node": "^0.3.0", 13 + "@simplewebauthn/server": "^10.0.1", 13 14 "better-sqlite3": "^11.6.0", 14 15 "cors": "^2.8.5", 15 16 "express": "^5.0.0", 16 17 "helmet": "^8.0.0", 18 + "nodemailer": "^6.9.9", 19 + "otpauth": "^9.2.1", 20 + "qrcode": "^1.5.3", 17 21 "uuid": "^9.0.0" 18 22 }, 19 23 "devDependencies": { ··· 21 25 "@types/cors": "^2.8.17", 22 26 "@types/express": "^5.0.0", 23 27 "@types/node": "^22.0.0", 28 + "@types/nodemailer": "^7.0.9", 29 + "@types/qrcode": "^1.5.6", 24 30 "@types/uuid": "^9.0.7", 25 31 "eslint": "^9.0.0", 26 32 "globals": "^15.0.0", ··· 457 463 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 458 464 } 459 465 }, 466 + "node_modules/@hexagon/base64": { 467 + "version": "1.1.28", 468 + "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz", 469 + "integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==", 470 + "license": "MIT" 471 + }, 460 472 "node_modules/@humanfs/core": { 461 473 "version": "0.19.1", 462 474 "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", ··· 509 521 "url": "https://github.com/sponsors/nzakas" 510 522 } 511 523 }, 524 + "node_modules/@levischuck/tiny-cbor": { 525 + "version": "0.2.11", 526 + "resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz", 527 + "integrity": "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==", 528 + "license": "MIT" 529 + }, 530 + "node_modules/@peculiar/asn1-android": { 531 + "version": "2.6.0", 532 + "resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.6.0.tgz", 533 + "integrity": "sha512-cBRCKtYPF7vJGN76/yG8VbxRcHLPF3HnkoHhKOZeHpoVtbMYfY9ROKtH3DtYUY9m8uI1Mh47PRhHf2hSK3xcSQ==", 534 + "license": "MIT", 535 + "dependencies": { 536 + "@peculiar/asn1-schema": "^2.6.0", 537 + "asn1js": "^3.0.6", 538 + "tslib": "^2.8.1" 539 + } 540 + }, 541 + "node_modules/@peculiar/asn1-ecc": { 542 + "version": "2.6.0", 543 + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.0.tgz", 544 + "integrity": "sha512-FF3LMGq6SfAOwUG2sKpPXblibn6XnEIKa+SryvUl5Pik+WR9rmRA3OCiwz8R3lVXnYnyRkSZsSLdml8H3UiOcw==", 545 + "license": "MIT", 546 + "dependencies": { 547 + "@peculiar/asn1-schema": "^2.6.0", 548 + "@peculiar/asn1-x509": "^2.6.0", 549 + "asn1js": "^3.0.6", 550 + "tslib": "^2.8.1" 551 + } 552 + }, 553 + "node_modules/@peculiar/asn1-rsa": { 554 + "version": "2.6.0", 555 + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.0.tgz", 556 + "integrity": "sha512-Nu4C19tsrTsCp9fDrH+sdcOKoVfdfoQQ7S3VqjJU6vedR7tY3RLkQ5oguOIB3zFW33USDUuYZnPEQYySlgha4w==", 557 + "license": "MIT", 558 + "dependencies": { 559 + "@peculiar/asn1-schema": "^2.6.0", 560 + "@peculiar/asn1-x509": "^2.6.0", 561 + "asn1js": "^3.0.6", 562 + "tslib": "^2.8.1" 563 + } 564 + }, 565 + "node_modules/@peculiar/asn1-schema": { 566 + "version": "2.6.0", 567 + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", 568 + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", 569 + "license": "MIT", 570 + "dependencies": { 571 + "asn1js": "^3.0.6", 572 + "pvtsutils": "^1.3.6", 573 + "tslib": "^2.8.1" 574 + } 575 + }, 576 + "node_modules/@peculiar/asn1-x509": { 577 + "version": "2.6.0", 578 + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.0.tgz", 579 + "integrity": "sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA==", 580 + "license": "MIT", 581 + "dependencies": { 582 + "@peculiar/asn1-schema": "^2.6.0", 583 + "asn1js": "^3.0.6", 584 + "pvtsutils": "^1.3.6", 585 + "tslib": "^2.8.1" 586 + } 587 + }, 588 + "node_modules/@simplewebauthn/server": { 589 + "version": "10.0.1", 590 + "resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-10.0.1.tgz", 591 + "integrity": "sha512-djNWcRn+H+6zvihBFJSpG3fzb0NQS9c/Mw5dYOtZ9H+oDw8qn9Htqxt4cpqRvSOAfwqP7rOvE9rwqVaoGGc3hg==", 592 + "license": "MIT", 593 + "dependencies": { 594 + "@hexagon/base64": "^1.1.27", 595 + "@levischuck/tiny-cbor": "^0.2.2", 596 + "@peculiar/asn1-android": "^2.3.10", 597 + "@peculiar/asn1-ecc": "^2.3.8", 598 + "@peculiar/asn1-rsa": "^2.3.8", 599 + "@peculiar/asn1-schema": "^2.3.8", 600 + "@peculiar/asn1-x509": "^2.3.8", 601 + "@simplewebauthn/types": "^10.0.0", 602 + "cross-fetch": "^4.0.0" 603 + }, 604 + "engines": { 605 + "node": ">=20.0.0" 606 + } 607 + }, 608 + "node_modules/@simplewebauthn/types": { 609 + "version": "10.0.0", 610 + "resolved": "https://registry.npmjs.org/@simplewebauthn/types/-/types-10.0.0.tgz", 611 + "integrity": "sha512-SFXke7xkgPRowY2E+8djKbdEznTVnD5R6GO7GPTthpHrokLvNKw8C3lFZypTxLI7KkCfGPfhtqB3d7OVGGa9jQ==", 612 + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", 613 + "license": "MIT" 614 + }, 512 615 "node_modules/@types/better-sqlite3": { 513 616 "version": "7.6.13", 514 617 "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", ··· 604 707 "license": "MIT", 605 708 "dependencies": { 606 709 "undici-types": "~6.21.0" 710 + } 711 + }, 712 + "node_modules/@types/nodemailer": { 713 + "version": "7.0.9", 714 + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.9.tgz", 715 + "integrity": "sha512-vI8oF1M+8JvQhsId0Pc38BdUP2evenIIys7c7p+9OZXSPOH5c1dyINP1jT8xQ2xPuBUXmIC87s+91IZMDjH8Ow==", 716 + "dev": true, 717 + "license": "MIT", 718 + "dependencies": { 719 + "@types/node": "*" 720 + } 721 + }, 722 + "node_modules/@types/qrcode": { 723 + "version": "1.5.6", 724 + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", 725 + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", 726 + "dev": true, 727 + "license": "MIT", 728 + "dependencies": { 729 + "@types/node": "*" 607 730 } 608 731 }, 609 732 "node_modules/@types/qs": { ··· 957 1080 "url": "https://github.com/sponsors/epoberezkin" 958 1081 } 959 1082 }, 1083 + "node_modules/ansi-regex": { 1084 + "version": "5.0.1", 1085 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1086 + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1087 + "license": "MIT", 1088 + "engines": { 1089 + "node": ">=8" 1090 + } 1091 + }, 960 1092 "node_modules/ansi-styles": { 961 1093 "version": "4.3.0", 962 1094 "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 963 1095 "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 964 - "dev": true, 965 1096 "license": "MIT", 966 1097 "dependencies": { 967 1098 "color-convert": "^2.0.1" ··· 979 1110 "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 980 1111 "dev": true, 981 1112 "license": "Python-2.0" 1113 + }, 1114 + "node_modules/asn1js": { 1115 + "version": "3.0.7", 1116 + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", 1117 + "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", 1118 + "license": "BSD-3-Clause", 1119 + "dependencies": { 1120 + "pvtsutils": "^1.3.6", 1121 + "pvutils": "^1.1.3", 1122 + "tslib": "^2.8.1" 1123 + }, 1124 + "engines": { 1125 + "node": ">=12.0.0" 1126 + } 982 1127 }, 983 1128 "node_modules/balanced-match": { 984 1129 "version": "1.0.2", ··· 1145 1290 "node": ">=6" 1146 1291 } 1147 1292 }, 1293 + "node_modules/camelcase": { 1294 + "version": "5.3.1", 1295 + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 1296 + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", 1297 + "license": "MIT", 1298 + "engines": { 1299 + "node": ">=6" 1300 + } 1301 + }, 1148 1302 "node_modules/chalk": { 1149 1303 "version": "4.1.2", 1150 1304 "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", ··· 1168 1322 "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", 1169 1323 "license": "ISC" 1170 1324 }, 1325 + "node_modules/cliui": { 1326 + "version": "6.0.0", 1327 + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", 1328 + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", 1329 + "license": "ISC", 1330 + "dependencies": { 1331 + "string-width": "^4.2.0", 1332 + "strip-ansi": "^6.0.0", 1333 + "wrap-ansi": "^6.2.0" 1334 + } 1335 + }, 1171 1336 "node_modules/color-convert": { 1172 1337 "version": "2.0.1", 1173 1338 "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1174 1339 "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1175 - "dev": true, 1176 1340 "license": "MIT", 1177 1341 "dependencies": { 1178 1342 "color-name": "~1.1.4" ··· 1185 1349 "version": "1.1.4", 1186 1350 "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1187 1351 "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1188 - "dev": true, 1189 1352 "license": "MIT" 1190 1353 }, 1191 1354 "node_modules/concat-map": { ··· 1259 1422 "node": ">= 0.10" 1260 1423 } 1261 1424 }, 1425 + "node_modules/cross-fetch": { 1426 + "version": "4.1.0", 1427 + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", 1428 + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", 1429 + "license": "MIT", 1430 + "dependencies": { 1431 + "node-fetch": "^2.7.0" 1432 + } 1433 + }, 1262 1434 "node_modules/cross-spawn": { 1263 1435 "version": "7.0.6", 1264 1436 "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", ··· 1289 1461 "supports-color": { 1290 1462 "optional": true 1291 1463 } 1464 + } 1465 + }, 1466 + "node_modules/decamelize": { 1467 + "version": "1.2.0", 1468 + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 1469 + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", 1470 + "license": "MIT", 1471 + "engines": { 1472 + "node": ">=0.10.0" 1292 1473 } 1293 1474 }, 1294 1475 "node_modules/decompress-response": { ··· 1340 1521 "node": ">=8" 1341 1522 } 1342 1523 }, 1524 + "node_modules/dijkstrajs": { 1525 + "version": "1.0.3", 1526 + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", 1527 + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", 1528 + "license": "MIT" 1529 + }, 1343 1530 "node_modules/dunder-proto": { 1344 1531 "version": "1.0.1", 1345 1532 "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", ··· 1358 1545 "version": "1.1.1", 1359 1546 "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 1360 1547 "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 1548 + "license": "MIT" 1549 + }, 1550 + "node_modules/emoji-regex": { 1551 + "version": "8.0.0", 1552 + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1553 + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1554 + "license": "MIT" 1555 + }, 1556 + "node_modules/encode-utf8": { 1557 + "version": "1.0.3", 1558 + "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", 1559 + "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==", 1361 1560 "license": "MIT" 1362 1561 }, 1363 1562 "node_modules/encodeurl": { ··· 1834 2033 "url": "https://github.com/sponsors/ljharb" 1835 2034 } 1836 2035 }, 2036 + "node_modules/get-caller-file": { 2037 + "version": "2.0.5", 2038 + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 2039 + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 2040 + "license": "ISC", 2041 + "engines": { 2042 + "node": "6.* || 8.* || >= 10.*" 2043 + } 2044 + }, 1837 2045 "node_modules/get-intrinsic": { 1838 2046 "version": "1.3.0", 1839 2047 "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", ··· 2095 2303 "node": ">=0.10.0" 2096 2304 } 2097 2305 }, 2306 + "node_modules/is-fullwidth-code-point": { 2307 + "version": "3.0.0", 2308 + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 2309 + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 2310 + "license": "MIT", 2311 + "engines": { 2312 + "node": ">=8" 2313 + } 2314 + }, 2098 2315 "node_modules/is-glob": { 2099 2316 "version": "4.0.3", 2100 2317 "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", ··· 2169 2386 "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 2170 2387 "dev": true, 2171 2388 "license": "MIT" 2389 + }, 2390 + "node_modules/jssha": { 2391 + "version": "3.3.1", 2392 + "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz", 2393 + "integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==", 2394 + "license": "BSD-3-Clause", 2395 + "engines": { 2396 + "node": "*" 2397 + } 2172 2398 }, 2173 2399 "node_modules/keyv": { 2174 2400 "version": "4.5.4", ··· 2364 2590 "node": ">=10" 2365 2591 } 2366 2592 }, 2593 + "node_modules/node-fetch": { 2594 + "version": "2.7.0", 2595 + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 2596 + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 2597 + "license": "MIT", 2598 + "dependencies": { 2599 + "whatwg-url": "^5.0.0" 2600 + }, 2601 + "engines": { 2602 + "node": "4.x || >=6.0.0" 2603 + }, 2604 + "peerDependencies": { 2605 + "encoding": "^0.1.0" 2606 + }, 2607 + "peerDependenciesMeta": { 2608 + "encoding": { 2609 + "optional": true 2610 + } 2611 + } 2612 + }, 2613 + "node_modules/nodemailer": { 2614 + "version": "6.9.9", 2615 + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.9.tgz", 2616 + "integrity": "sha512-dexTll8zqQoVJEZPwQAKzxxtFn0qTnjdQTchoU6Re9BUUGBJiOy3YMn/0ShTW6J5M0dfQ1NeDeRTTl4oIWgQMA==", 2617 + "license": "MIT-0", 2618 + "engines": { 2619 + "node": ">=6.0.0" 2620 + } 2621 + }, 2367 2622 "node_modules/object-assign": { 2368 2623 "version": "4.1.1", 2369 2624 "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", ··· 2424 2679 "node": ">= 0.8.0" 2425 2680 } 2426 2681 }, 2682 + "node_modules/otpauth": { 2683 + "version": "9.2.1", 2684 + "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.2.1.tgz", 2685 + "integrity": "sha512-/MRvcm63pzK20NCsIOe8Btun42/yWNylPbUo/h5dMpSRJpoAJstWodEUjm4zUDeT1+Vbqif2E8IcP4trl1U4gQ==", 2686 + "license": "MIT", 2687 + "dependencies": { 2688 + "jssha": "~3.3.1" 2689 + }, 2690 + "funding": { 2691 + "url": "https://github.com/hectorm/otpauth?sponsor=1" 2692 + } 2693 + }, 2427 2694 "node_modules/p-limit": { 2428 2695 "version": "3.1.0", 2429 2696 "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", ··· 2456 2723 "url": "https://github.com/sponsors/sindresorhus" 2457 2724 } 2458 2725 }, 2726 + "node_modules/p-try": { 2727 + "version": "2.2.0", 2728 + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 2729 + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 2730 + "license": "MIT", 2731 + "engines": { 2732 + "node": ">=6" 2733 + } 2734 + }, 2459 2735 "node_modules/parent-module": { 2460 2736 "version": "1.0.1", 2461 2737 "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", ··· 2482 2758 "version": "4.0.0", 2483 2759 "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 2484 2760 "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 2485 - "dev": true, 2486 2761 "license": "MIT", 2487 2762 "engines": { 2488 2763 "node": ">=8" ··· 2521 2796 "url": "https://github.com/sponsors/jonschlinkert" 2522 2797 } 2523 2798 }, 2799 + "node_modules/pngjs": { 2800 + "version": "5.0.0", 2801 + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", 2802 + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", 2803 + "license": "MIT", 2804 + "engines": { 2805 + "node": ">=10.13.0" 2806 + } 2807 + }, 2524 2808 "node_modules/prebuild-install": { 2525 2809 "version": "7.1.3", 2526 2810 "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", ··· 2599 2883 "node": ">=6" 2600 2884 } 2601 2885 }, 2886 + "node_modules/pvtsutils": { 2887 + "version": "1.3.6", 2888 + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", 2889 + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", 2890 + "license": "MIT", 2891 + "dependencies": { 2892 + "tslib": "^2.8.1" 2893 + } 2894 + }, 2895 + "node_modules/pvutils": { 2896 + "version": "1.1.5", 2897 + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", 2898 + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", 2899 + "license": "MIT", 2900 + "engines": { 2901 + "node": ">=16.0.0" 2902 + } 2903 + }, 2904 + "node_modules/qrcode": { 2905 + "version": "1.5.3", 2906 + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", 2907 + "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==", 2908 + "license": "MIT", 2909 + "dependencies": { 2910 + "dijkstrajs": "^1.0.1", 2911 + "encode-utf8": "^1.0.3", 2912 + "pngjs": "^5.0.0", 2913 + "yargs": "^15.3.1" 2914 + }, 2915 + "bin": { 2916 + "qrcode": "bin/qrcode" 2917 + }, 2918 + "engines": { 2919 + "node": ">=10.13.0" 2920 + } 2921 + }, 2602 2922 "node_modules/qs": { 2603 2923 "version": "6.14.0", 2604 2924 "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", ··· 2676 2996 "node": ">= 6" 2677 2997 } 2678 2998 }, 2999 + "node_modules/require-directory": { 3000 + "version": "2.1.1", 3001 + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 3002 + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 3003 + "license": "MIT", 3004 + "engines": { 3005 + "node": ">=0.10.0" 3006 + } 3007 + }, 3008 + "node_modules/require-main-filename": { 3009 + "version": "2.0.0", 3010 + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 3011 + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", 3012 + "license": "ISC" 3013 + }, 2679 3014 "node_modules/resolve-from": { 2680 3015 "version": "4.0.0", 2681 3016 "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", ··· 2786 3121 "engines": { 2787 3122 "node": ">= 18" 2788 3123 } 3124 + }, 3125 + "node_modules/set-blocking": { 3126 + "version": "2.0.0", 3127 + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 3128 + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", 3129 + "license": "ISC" 2789 3130 }, 2790 3131 "node_modules/setprototypeof": { 2791 3132 "version": "1.2.0", ··· 2951 3292 "safe-buffer": "~5.2.0" 2952 3293 } 2953 3294 }, 3295 + "node_modules/string-width": { 3296 + "version": "4.2.3", 3297 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 3298 + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 3299 + "license": "MIT", 3300 + "dependencies": { 3301 + "emoji-regex": "^8.0.0", 3302 + "is-fullwidth-code-point": "^3.0.0", 3303 + "strip-ansi": "^6.0.1" 3304 + }, 3305 + "engines": { 3306 + "node": ">=8" 3307 + } 3308 + }, 3309 + "node_modules/strip-ansi": { 3310 + "version": "6.0.1", 3311 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 3312 + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 3313 + "license": "MIT", 3314 + "dependencies": { 3315 + "ansi-regex": "^5.0.1" 3316 + }, 3317 + "engines": { 3318 + "node": ">=8" 3319 + } 3320 + }, 2954 3321 "node_modules/strip-json-comments": { 2955 3322 "version": "3.1.1", 2956 3323 "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", ··· 3030 3397 "engines": { 3031 3398 "node": ">=0.6" 3032 3399 } 3400 + }, 3401 + "node_modules/tr46": { 3402 + "version": "0.0.3", 3403 + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 3404 + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", 3405 + "license": "MIT" 3033 3406 }, 3034 3407 "node_modules/ts-api-utils": { 3035 3408 "version": "2.1.0", ··· 3225 3598 "node": ">= 0.8" 3226 3599 } 3227 3600 }, 3601 + "node_modules/webidl-conversions": { 3602 + "version": "3.0.1", 3603 + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 3604 + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", 3605 + "license": "BSD-2-Clause" 3606 + }, 3607 + "node_modules/whatwg-url": { 3608 + "version": "5.0.0", 3609 + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 3610 + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 3611 + "license": "MIT", 3612 + "dependencies": { 3613 + "tr46": "~0.0.3", 3614 + "webidl-conversions": "^3.0.0" 3615 + } 3616 + }, 3228 3617 "node_modules/which": { 3229 3618 "version": "2.0.2", 3230 3619 "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", ··· 3241 3630 "node": ">= 8" 3242 3631 } 3243 3632 }, 3633 + "node_modules/which-module": { 3634 + "version": "2.0.1", 3635 + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", 3636 + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", 3637 + "license": "ISC" 3638 + }, 3244 3639 "node_modules/word-wrap": { 3245 3640 "version": "1.2.5", 3246 3641 "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", ··· 3251 3646 "node": ">=0.10.0" 3252 3647 } 3253 3648 }, 3649 + "node_modules/wrap-ansi": { 3650 + "version": "6.2.0", 3651 + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", 3652 + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", 3653 + "license": "MIT", 3654 + "dependencies": { 3655 + "ansi-styles": "^4.0.0", 3656 + "string-width": "^4.1.0", 3657 + "strip-ansi": "^6.0.0" 3658 + }, 3659 + "engines": { 3660 + "node": ">=8" 3661 + } 3662 + }, 3254 3663 "node_modules/wrappy": { 3255 3664 "version": "1.0.2", 3256 3665 "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 3257 3666 "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 3258 3667 "license": "ISC" 3668 + }, 3669 + "node_modules/y18n": { 3670 + "version": "4.0.3", 3671 + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", 3672 + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", 3673 + "license": "ISC" 3674 + }, 3675 + "node_modules/yargs": { 3676 + "version": "15.4.1", 3677 + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", 3678 + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", 3679 + "license": "MIT", 3680 + "dependencies": { 3681 + "cliui": "^6.0.0", 3682 + "decamelize": "^1.2.0", 3683 + "find-up": "^4.1.0", 3684 + "get-caller-file": "^2.0.1", 3685 + "require-directory": "^2.1.1", 3686 + "require-main-filename": "^2.0.0", 3687 + "set-blocking": "^2.0.0", 3688 + "string-width": "^4.2.0", 3689 + "which-module": "^2.0.0", 3690 + "y18n": "^4.0.0", 3691 + "yargs-parser": "^18.1.2" 3692 + }, 3693 + "engines": { 3694 + "node": ">=8" 3695 + } 3696 + }, 3697 + "node_modules/yargs-parser": { 3698 + "version": "18.1.3", 3699 + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", 3700 + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", 3701 + "license": "ISC", 3702 + "dependencies": { 3703 + "camelcase": "^5.0.0", 3704 + "decamelize": "^1.2.0" 3705 + }, 3706 + "engines": { 3707 + "node": ">=6" 3708 + } 3709 + }, 3710 + "node_modules/yargs/node_modules/find-up": { 3711 + "version": "4.1.0", 3712 + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 3713 + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 3714 + "license": "MIT", 3715 + "dependencies": { 3716 + "locate-path": "^5.0.0", 3717 + "path-exists": "^4.0.0" 3718 + }, 3719 + "engines": { 3720 + "node": ">=8" 3721 + } 3722 + }, 3723 + "node_modules/yargs/node_modules/locate-path": { 3724 + "version": "5.0.0", 3725 + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 3726 + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 3727 + "license": "MIT", 3728 + "dependencies": { 3729 + "p-locate": "^4.1.0" 3730 + }, 3731 + "engines": { 3732 + "node": ">=8" 3733 + } 3734 + }, 3735 + "node_modules/yargs/node_modules/p-limit": { 3736 + "version": "2.3.0", 3737 + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 3738 + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 3739 + "license": "MIT", 3740 + "dependencies": { 3741 + "p-try": "^2.0.0" 3742 + }, 3743 + "engines": { 3744 + "node": ">=6" 3745 + }, 3746 + "funding": { 3747 + "url": "https://github.com/sponsors/sindresorhus" 3748 + } 3749 + }, 3750 + "node_modules/yargs/node_modules/p-locate": { 3751 + "version": "4.1.0", 3752 + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 3753 + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 3754 + "license": "MIT", 3755 + "dependencies": { 3756 + "p-limit": "^2.2.0" 3757 + }, 3758 + "engines": { 3759 + "node": ">=8" 3760 + } 3259 3761 }, 3260 3762 "node_modules/yocto-queue": { 3261 3763 "version": "0.1.0",
+8 -2
gateway/package.json
··· 27 27 }, 28 28 "dependencies": { 29 29 "@atproto/oauth-client-node": "^0.3.0", 30 + "@simplewebauthn/server": "^10.0.1", 30 31 "better-sqlite3": "^11.6.0", 31 32 "cors": "^2.8.5", 32 33 "express": "^5.0.0", 33 34 "helmet": "^8.0.0", 35 + "nodemailer": "^6.9.9", 36 + "otpauth": "^9.2.1", 37 + "qrcode": "^1.5.3", 34 38 "uuid": "^9.0.0" 35 39 }, 36 40 "devDependencies": { ··· 38 42 "@types/cors": "^2.8.17", 39 43 "@types/express": "^5.0.0", 40 44 "@types/node": "^22.0.0", 45 + "@types/nodemailer": "^7.0.9", 46 + "@types/qrcode": "^1.5.6", 41 47 "@types/uuid": "^9.0.7", 42 - "typescript-eslint": "^8.0.0", 43 48 "eslint": "^9.0.0", 44 49 "globals": "^15.0.0", 45 50 "tsx": "^4.19.0", 46 - "typescript": "^5.7.0" 51 + "typescript": "^5.7.0", 52 + "typescript-eslint": "^8.0.0" 47 53 }, 48 54 "engines": { 49 55 "node": ">=18.0.0"
+1
gateway/public/admin/assets/index-BTKH7v1n.css
··· 1 + @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-lg:32rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wider:.05em;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.top-1\/2{top:50%}.right-2{right:calc(var(--spacing)*2)}.z-50{z-index:50}.m-4{margin:calc(var(--spacing)*4)}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.ml-2{margin-left:calc(var(--spacing)*2)}.block{display:block}.flex{display:flex}.grid{display:grid}.inline-flex{display:inline-flex}.h-2{height:calc(var(--spacing)*2)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-16{height:calc(var(--spacing)*16)}.h-screen{height:100vh}.max-h-\[600px\]{max-height:600px}.min-h-screen{min-height:100vh}.w-2{width:calc(var(--spacing)*2)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-64{width:calc(var(--spacing)*64)}.w-full{width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.min-w-full{min-width:100%}.flex-1{flex:1}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.cursor-not-allowed{cursor:not-allowed}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-700{border-color:var(--color-gray-700)}.border-red-200{border-color:var(--color-red-200)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-900{background-color:var(--color-gray-900)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-500{background-color:var(--color-green-500)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-600{background-color:var(--color-red-600)}.bg-white{background-color:var(--color-white)}.bg-yellow-100{background-color:var(--color-yellow-100)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.pt-4{padding-top:calc(var(--spacing)*4)}.pr-20{padding-right:calc(var(--spacing)*20)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-800{color:var(--color-blue-800)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-red-600{color:var(--color-red-600)}.text-white{color:var(--color-white)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.uppercase{text-transform:uppercase}.placeholder-gray-400::placeholder{color:var(--color-gray-400)}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-blue-500{--tw-ring-color:var(--color-blue-500)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-gray-300:hover{background-color:var(--color-gray-300)}.hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-red-800:hover{color:var(--color-red-800)}.hover\:text-red-900:hover{color:var(--color-red-900)}.hover\:text-white:hover{color:var(--color-white)}.hover\:text-yellow-800:hover{color:var(--color-yellow-800)}.hover\:text-yellow-900:hover{color:var(--color-yellow-900)}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:border-transparent:focus{border-color:#0000}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:bg-blue-400:disabled{background-color:var(--color-blue-400)}.disabled\:bg-red-400:disabled{background-color:var(--color-red-400)}@media(min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(min-width:64rem){.lg\:col-span-1{grid-column:span 1/span 1}.lg\:col-span-2{grid-column:span 2/span 2}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(prefers-color-scheme:dark){:where(.dark\:divide-gray-700>:not(:last-child)){border-color:var(--color-gray-700)}.dark\:border-gray-600{border-color:var(--color-gray-600)}.dark\:border-gray-700{border-color:var(--color-gray-700)}.dark\:border-red-800{border-color:var(--color-red-800)}.dark\:bg-blue-900\/30{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark\:bg-blue-900\/30{background-color:color-mix(in oklab,var(--color-blue-900)30%,transparent)}}.dark\:bg-gray-700{background-color:var(--color-gray-700)}.dark\:bg-gray-800{background-color:var(--color-gray-800)}.dark\:bg-gray-800\/50{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.dark\:bg-gray-800\/50{background-color:color-mix(in oklab,var(--color-gray-800)50%,transparent)}}.dark\:bg-gray-900{background-color:var(--color-gray-900)}.dark\:bg-gray-900\/30{background-color:#1018284d}@supports (color:color-mix(in lab,red,red)){.dark\:bg-gray-900\/30{background-color:color-mix(in oklab,var(--color-gray-900)30%,transparent)}}.dark\:bg-green-900\/30{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark\:bg-green-900\/30{background-color:color-mix(in oklab,var(--color-green-900)30%,transparent)}}.dark\:bg-red-900\/30{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark\:bg-red-900\/30{background-color:color-mix(in oklab,var(--color-red-900)30%,transparent)}}.dark\:bg-yellow-900\/30{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark\:bg-yellow-900\/30{background-color:color-mix(in oklab,var(--color-yellow-900)30%,transparent)}}.dark\:text-blue-400{color:var(--color-blue-400)}.dark\:text-gray-300{color:var(--color-gray-300)}.dark\:text-gray-400{color:var(--color-gray-400)}.dark\:text-gray-500{color:var(--color-gray-500)}.dark\:text-green-400{color:var(--color-green-400)}.dark\:text-red-400{color:var(--color-red-400)}.dark\:text-white{color:var(--color-white)}.dark\:text-yellow-400{color:var(--color-yellow-400)}@media(hover:hover){.dark\:hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.dark\:hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.dark\:hover\:text-blue-400:hover{color:var(--color-blue-400)}.dark\:hover\:text-red-400:hover{color:var(--color-red-400)}.dark\:hover\:text-white:hover{color:var(--color-white)}.dark\:hover\:text-yellow-400:hover{color:var(--color-yellow-400)}}}}:root{--color-primary:#3b82f6;--color-primary-dark:#2563eb;--color-success:#10b981;--color-warning:#f59e0b;--color-danger:#ef4444}body{background-color:var(--color-gray-50);color:var(--color-gray-900);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media(prefers-color-scheme:dark){body{background-color:var(--color-gray-900);color:var(--color-gray-100)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}
+15
gateway/public/admin/assets/index-D_DjgfgI.js
··· 1 + (function(){const s=document.createElement("link").relList;if(s&&s.supports&&s.supports("modulepreload"))return;for(const d of document.querySelectorAll('link[rel="modulepreload"]'))c(d);new MutationObserver(d=>{for(const m of d)if(m.type==="childList")for(const y of m.addedNodes)y.tagName==="LINK"&&y.rel==="modulepreload"&&c(y)}).observe(document,{childList:!0,subtree:!0});function r(d){const m={};return d.integrity&&(m.integrity=d.integrity),d.referrerPolicy&&(m.referrerPolicy=d.referrerPolicy),d.crossOrigin==="use-credentials"?m.credentials="include":d.crossOrigin==="anonymous"?m.credentials="omit":m.credentials="same-origin",m}function c(d){if(d.ep)return;d.ep=!0;const m=r(d);fetch(d.href,m)}})();function ag(n){return n&&n.__esModule&&Object.prototype.hasOwnProperty.call(n,"default")?n.default:n}var Kr={exports:{}},Xn={};var Ah;function lg(){if(Ah)return Xn;Ah=1;var n=Symbol.for("react.transitional.element"),s=Symbol.for("react.fragment");function r(c,d,m){var y=null;if(m!==void 0&&(y=""+m),d.key!==void 0&&(y=""+d.key),"key"in d){m={};for(var g in d)g!=="key"&&(m[g]=d[g])}else m=d;return d=m.ref,{$$typeof:n,type:c,key:y,ref:d!==void 0?d:null,props:m}}return Xn.Fragment=s,Xn.jsx=r,Xn.jsxs=r,Xn}var Rh;function ng(){return Rh||(Rh=1,Kr.exports=lg()),Kr.exports}var f=ng(),Xr={exports:{}},ee={};var _h;function ig(){if(_h)return ee;_h=1;var n=Symbol.for("react.transitional.element"),s=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),c=Symbol.for("react.strict_mode"),d=Symbol.for("react.profiler"),m=Symbol.for("react.consumer"),y=Symbol.for("react.context"),g=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),v=Symbol.for("react.memo"),E=Symbol.for("react.lazy"),T=Symbol.for("react.activity"),R=Symbol.iterator;function U(S){return S===null||typeof S!="object"?null:(S=R&&S[R]||S["@@iterator"],typeof S=="function"?S:null)}var k={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},B=Object.assign,q={};function Q(S,H,G){this.props=S,this.context=H,this.refs=q,this.updater=G||k}Q.prototype.isReactComponent={},Q.prototype.setState=function(S,H){if(typeof S!="object"&&typeof S!="function"&&S!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,S,H,"setState")},Q.prototype.forceUpdate=function(S){this.updater.enqueueForceUpdate(this,S,"forceUpdate")};function L(){}L.prototype=Q.prototype;function K(S,H,G){this.props=S,this.context=H,this.refs=q,this.updater=G||k}var X=K.prototype=new L;X.constructor=K,B(X,Q.prototype),X.isPureReactComponent=!0;var P=Array.isArray;function me(){}var $={H:null,A:null,T:null,S:null},oe=Object.prototype.hasOwnProperty;function Re(S,H,G){var V=G.ref;return{$$typeof:n,type:S,key:H,ref:V!==void 0?V:null,props:G}}function Ke(S,H){return Re(S.type,H,S.props)}function st(S){return typeof S=="object"&&S!==null&&S.$$typeof===n}function Le(S){var H={"=":"=0",":":"=2"};return"$"+S.replace(/[=:]/g,function(G){return H[G]})}var xt=/\/+/g;function Gt(S,H){return typeof S=="object"&&S!==null&&S.key!=null?Le(""+S.key):H.toString(36)}function Mt(S){switch(S.status){case"fulfilled":return S.value;case"rejected":throw S.reason;default:switch(typeof S.status=="string"?S.then(me,me):(S.status="pending",S.then(function(H){S.status==="pending"&&(S.status="fulfilled",S.value=H)},function(H){S.status==="pending"&&(S.status="rejected",S.reason=H)})),S.status){case"fulfilled":return S.value;case"rejected":throw S.reason}}throw S}function M(S,H,G,V,te){var ne=typeof S;(ne==="undefined"||ne==="boolean")&&(S=null);var ye=!1;if(S===null)ye=!0;else switch(ne){case"bigint":case"string":case"number":ye=!0;break;case"object":switch(S.$$typeof){case n:case s:ye=!0;break;case E:return ye=S._init,M(ye(S._payload),H,G,V,te)}}if(ye)return te=te(S),ye=V===""?"."+Gt(S,0):V,P(te)?(G="",ye!=null&&(G=ye.replace(xt,"$&/")+"/"),M(te,H,G,"",function(Wl){return Wl})):te!=null&&(st(te)&&(te=Ke(te,G+(te.key==null||S&&S.key===te.key?"":(""+te.key).replace(xt,"$&/")+"/")+ye)),H.push(te)),1;ye=0;var We=V===""?".":V+":";if(P(S))for(var Me=0;Me<S.length;Me++)V=S[Me],ne=We+Gt(V,Me),ye+=M(V,H,G,ne,te);else if(Me=U(S),typeof Me=="function")for(S=Me.call(S),Me=0;!(V=S.next()).done;)V=V.value,ne=We+Gt(V,Me++),ye+=M(V,H,G,ne,te);else if(ne==="object"){if(typeof S.then=="function")return M(Mt(S),H,G,V,te);throw H=String(S),Error("Objects are not valid as a React child (found: "+(H==="[object Object]"?"object with keys {"+Object.keys(S).join(", ")+"}":H)+"). If you meant to render a collection of children, use an array instead.")}return ye}function Y(S,H,G){if(S==null)return S;var V=[],te=0;return M(S,V,"","",function(ne){return H.call(G,ne,te++)}),V}function I(S){if(S._status===-1){var H=S._result;H=H(),H.then(function(G){(S._status===0||S._status===-1)&&(S._status=1,S._result=G)},function(G){(S._status===0||S._status===-1)&&(S._status=2,S._result=G)}),S._status===-1&&(S._status=0,S._result=H)}if(S._status===1)return S._result.default;throw S._result}var pe=typeof reportError=="function"?reportError:function(S){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var H=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof S=="object"&&S!==null&&typeof S.message=="string"?String(S.message):String(S),error:S});if(!window.dispatchEvent(H))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",S);return}console.error(S)},Ee={map:Y,forEach:function(S,H,G){Y(S,function(){H.apply(this,arguments)},G)},count:function(S){var H=0;return Y(S,function(){H++}),H},toArray:function(S){return Y(S,function(H){return H})||[]},only:function(S){if(!st(S))throw Error("React.Children.only expected to receive a single React element child.");return S}};return ee.Activity=T,ee.Children=Ee,ee.Component=Q,ee.Fragment=r,ee.Profiler=d,ee.PureComponent=K,ee.StrictMode=c,ee.Suspense=p,ee.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=$,ee.__COMPILER_RUNTIME={__proto__:null,c:function(S){return $.H.useMemoCache(S)}},ee.cache=function(S){return function(){return S.apply(null,arguments)}},ee.cacheSignal=function(){return null},ee.cloneElement=function(S,H,G){if(S==null)throw Error("The argument must be a React element, but you passed "+S+".");var V=B({},S.props),te=S.key;if(H!=null)for(ne in H.key!==void 0&&(te=""+H.key),H)!oe.call(H,ne)||ne==="key"||ne==="__self"||ne==="__source"||ne==="ref"&&H.ref===void 0||(V[ne]=H[ne]);var ne=arguments.length-2;if(ne===1)V.children=G;else if(1<ne){for(var ye=Array(ne),We=0;We<ne;We++)ye[We]=arguments[We+2];V.children=ye}return Re(S.type,te,V)},ee.createContext=function(S){return S={$$typeof:y,_currentValue:S,_currentValue2:S,_threadCount:0,Provider:null,Consumer:null},S.Provider=S,S.Consumer={$$typeof:m,_context:S},S},ee.createElement=function(S,H,G){var V,te={},ne=null;if(H!=null)for(V in H.key!==void 0&&(ne=""+H.key),H)oe.call(H,V)&&V!=="key"&&V!=="__self"&&V!=="__source"&&(te[V]=H[V]);var ye=arguments.length-2;if(ye===1)te.children=G;else if(1<ye){for(var We=Array(ye),Me=0;Me<ye;Me++)We[Me]=arguments[Me+2];te.children=We}if(S&&S.defaultProps)for(V in ye=S.defaultProps,ye)te[V]===void 0&&(te[V]=ye[V]);return Re(S,ne,te)},ee.createRef=function(){return{current:null}},ee.forwardRef=function(S){return{$$typeof:g,render:S}},ee.isValidElement=st,ee.lazy=function(S){return{$$typeof:E,_payload:{_status:-1,_result:S},_init:I}},ee.memo=function(S,H){return{$$typeof:v,type:S,compare:H===void 0?null:H}},ee.startTransition=function(S){var H=$.T,G={};$.T=G;try{var V=S(),te=$.S;te!==null&&te(G,V),typeof V=="object"&&V!==null&&typeof V.then=="function"&&V.then(me,pe)}catch(ne){pe(ne)}finally{H!==null&&G.types!==null&&(H.types=G.types),$.T=H}},ee.unstable_useCacheRefresh=function(){return $.H.useCacheRefresh()},ee.use=function(S){return $.H.use(S)},ee.useActionState=function(S,H,G){return $.H.useActionState(S,H,G)},ee.useCallback=function(S,H){return $.H.useCallback(S,H)},ee.useContext=function(S){return $.H.useContext(S)},ee.useDebugValue=function(){},ee.useDeferredValue=function(S,H){return $.H.useDeferredValue(S,H)},ee.useEffect=function(S,H){return $.H.useEffect(S,H)},ee.useEffectEvent=function(S){return $.H.useEffectEvent(S)},ee.useId=function(){return $.H.useId()},ee.useImperativeHandle=function(S,H,G){return $.H.useImperativeHandle(S,H,G)},ee.useInsertionEffect=function(S,H){return $.H.useInsertionEffect(S,H)},ee.useLayoutEffect=function(S,H){return $.H.useLayoutEffect(S,H)},ee.useMemo=function(S,H){return $.H.useMemo(S,H)},ee.useOptimistic=function(S,H){return $.H.useOptimistic(S,H)},ee.useReducer=function(S,H,G){return $.H.useReducer(S,H,G)},ee.useRef=function(S){return $.H.useRef(S)},ee.useState=function(S){return $.H.useState(S)},ee.useSyncExternalStore=function(S,H,G){return $.H.useSyncExternalStore(S,H,G)},ee.useTransition=function(){return $.H.useTransition()},ee.version="19.2.4",ee}var Dh;function oc(){return Dh||(Dh=1,Xr.exports=ig()),Xr.exports}var N=oc();const gu=ag(N);var Zr={exports:{}},Zn={},Vr={exports:{}},Jr={};var Mh;function ug(){return Mh||(Mh=1,(function(n){function s(M,Y){var I=M.length;M.push(Y);e:for(;0<I;){var pe=I-1>>>1,Ee=M[pe];if(0<d(Ee,Y))M[pe]=Y,M[I]=Ee,I=pe;else break e}}function r(M){return M.length===0?null:M[0]}function c(M){if(M.length===0)return null;var Y=M[0],I=M.pop();if(I!==Y){M[0]=I;e:for(var pe=0,Ee=M.length,S=Ee>>>1;pe<S;){var H=2*(pe+1)-1,G=M[H],V=H+1,te=M[V];if(0>d(G,I))V<Ee&&0>d(te,G)?(M[pe]=te,M[V]=I,pe=V):(M[pe]=G,M[H]=I,pe=H);else if(V<Ee&&0>d(te,I))M[pe]=te,M[V]=I,pe=V;else break e}}return Y}function d(M,Y){var I=M.sortIndex-Y.sortIndex;return I!==0?I:M.id-Y.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var m=performance;n.unstable_now=function(){return m.now()}}else{var y=Date,g=y.now();n.unstable_now=function(){return y.now()-g}}var p=[],v=[],E=1,T=null,R=3,U=!1,k=!1,B=!1,q=!1,Q=typeof setTimeout=="function"?setTimeout:null,L=typeof clearTimeout=="function"?clearTimeout:null,K=typeof setImmediate<"u"?setImmediate:null;function X(M){for(var Y=r(v);Y!==null;){if(Y.callback===null)c(v);else if(Y.startTime<=M)c(v),Y.sortIndex=Y.expirationTime,s(p,Y);else break;Y=r(v)}}function P(M){if(B=!1,X(M),!k)if(r(p)!==null)k=!0,me||(me=!0,Le());else{var Y=r(v);Y!==null&&Mt(P,Y.startTime-M)}}var me=!1,$=-1,oe=5,Re=-1;function Ke(){return q?!0:!(n.unstable_now()-Re<oe)}function st(){if(q=!1,me){var M=n.unstable_now();Re=M;var Y=!0;try{e:{k=!1,B&&(B=!1,L($),$=-1),U=!0;var I=R;try{t:{for(X(M),T=r(p);T!==null&&!(T.expirationTime>M&&Ke());){var pe=T.callback;if(typeof pe=="function"){T.callback=null,R=T.priorityLevel;var Ee=pe(T.expirationTime<=M);if(M=n.unstable_now(),typeof Ee=="function"){T.callback=Ee,X(M),Y=!0;break t}T===r(p)&&c(p),X(M)}else c(p);T=r(p)}if(T!==null)Y=!0;else{var S=r(v);S!==null&&Mt(P,S.startTime-M),Y=!1}}break e}finally{T=null,R=I,U=!1}Y=void 0}}finally{Y?Le():me=!1}}}var Le;if(typeof K=="function")Le=function(){K(st)};else if(typeof MessageChannel<"u"){var xt=new MessageChannel,Gt=xt.port2;xt.port1.onmessage=st,Le=function(){Gt.postMessage(null)}}else Le=function(){Q(st,0)};function Mt(M,Y){$=Q(function(){M(n.unstable_now())},Y)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(M){M.callback=null},n.unstable_forceFrameRate=function(M){0>M||125<M?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):oe=0<M?Math.floor(1e3/M):5},n.unstable_getCurrentPriorityLevel=function(){return R},n.unstable_next=function(M){switch(R){case 1:case 2:case 3:var Y=3;break;default:Y=R}var I=R;R=Y;try{return M()}finally{R=I}},n.unstable_requestPaint=function(){q=!0},n.unstable_runWithPriority=function(M,Y){switch(M){case 1:case 2:case 3:case 4:case 5:break;default:M=3}var I=R;R=M;try{return Y()}finally{R=I}},n.unstable_scheduleCallback=function(M,Y,I){var pe=n.unstable_now();switch(typeof I=="object"&&I!==null?(I=I.delay,I=typeof I=="number"&&0<I?pe+I:pe):I=pe,M){case 1:var Ee=-1;break;case 2:Ee=250;break;case 5:Ee=1073741823;break;case 4:Ee=1e4;break;default:Ee=5e3}return Ee=I+Ee,M={id:E++,callback:Y,priorityLevel:M,startTime:I,expirationTime:Ee,sortIndex:-1},I>pe?(M.sortIndex=I,s(v,M),r(p)===null&&M===r(v)&&(B?(L($),$=-1):B=!0,Mt(P,I-pe))):(M.sortIndex=Ee,s(p,M),k||U||(k=!0,me||(me=!0,Le()))),M},n.unstable_shouldYield=Ke,n.unstable_wrapCallback=function(M){var Y=R;return function(){var I=R;R=Y;try{return M.apply(this,arguments)}finally{R=I}}}})(Jr)),Jr}var zh;function sg(){return zh||(zh=1,Vr.exports=ug()),Vr.exports}var Fr={exports:{}},$e={};var wh;function rg(){if(wh)return $e;wh=1;var n=oc();function s(p){var v="https://react.dev/errors/"+p;if(1<arguments.length){v+="?args[]="+encodeURIComponent(arguments[1]);for(var E=2;E<arguments.length;E++)v+="&args[]="+encodeURIComponent(arguments[E])}return"Minified React error #"+p+"; visit "+v+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function r(){}var c={d:{f:r,r:function(){throw Error(s(522))},D:r,C:r,L:r,m:r,X:r,S:r,M:r},p:0,findDOMNode:null},d=Symbol.for("react.portal");function m(p,v,E){var T=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:d,key:T==null?null:""+T,children:p,containerInfo:v,implementation:E}}var y=n.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function g(p,v){if(p==="font")return"";if(typeof v=="string")return v==="use-credentials"?v:""}return $e.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=c,$e.createPortal=function(p,v){var E=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!v||v.nodeType!==1&&v.nodeType!==9&&v.nodeType!==11)throw Error(s(299));return m(p,v,null,E)},$e.flushSync=function(p){var v=y.T,E=c.p;try{if(y.T=null,c.p=2,p)return p()}finally{y.T=v,c.p=E,c.d.f()}},$e.preconnect=function(p,v){typeof p=="string"&&(v?(v=v.crossOrigin,v=typeof v=="string"?v==="use-credentials"?v:"":void 0):v=null,c.d.C(p,v))},$e.prefetchDNS=function(p){typeof p=="string"&&c.d.D(p)},$e.preinit=function(p,v){if(typeof p=="string"&&v&&typeof v.as=="string"){var E=v.as,T=g(E,v.crossOrigin),R=typeof v.integrity=="string"?v.integrity:void 0,U=typeof v.fetchPriority=="string"?v.fetchPriority:void 0;E==="style"?c.d.S(p,typeof v.precedence=="string"?v.precedence:void 0,{crossOrigin:T,integrity:R,fetchPriority:U}):E==="script"&&c.d.X(p,{crossOrigin:T,integrity:R,fetchPriority:U,nonce:typeof v.nonce=="string"?v.nonce:void 0})}},$e.preinitModule=function(p,v){if(typeof p=="string")if(typeof v=="object"&&v!==null){if(v.as==null||v.as==="script"){var E=g(v.as,v.crossOrigin);c.d.M(p,{crossOrigin:E,integrity:typeof v.integrity=="string"?v.integrity:void 0,nonce:typeof v.nonce=="string"?v.nonce:void 0})}}else v==null&&c.d.M(p)},$e.preload=function(p,v){if(typeof p=="string"&&typeof v=="object"&&v!==null&&typeof v.as=="string"){var E=v.as,T=g(E,v.crossOrigin);c.d.L(p,E,{crossOrigin:T,integrity:typeof v.integrity=="string"?v.integrity:void 0,nonce:typeof v.nonce=="string"?v.nonce:void 0,type:typeof v.type=="string"?v.type:void 0,fetchPriority:typeof v.fetchPriority=="string"?v.fetchPriority:void 0,referrerPolicy:typeof v.referrerPolicy=="string"?v.referrerPolicy:void 0,imageSrcSet:typeof v.imageSrcSet=="string"?v.imageSrcSet:void 0,imageSizes:typeof v.imageSizes=="string"?v.imageSizes:void 0,media:typeof v.media=="string"?v.media:void 0})}},$e.preloadModule=function(p,v){if(typeof p=="string")if(v){var E=g(v.as,v.crossOrigin);c.d.m(p,{as:typeof v.as=="string"&&v.as!=="script"?v.as:void 0,crossOrigin:E,integrity:typeof v.integrity=="string"?v.integrity:void 0})}else c.d.m(p)},$e.requestFormReset=function(p){c.d.r(p)},$e.unstable_batchedUpdates=function(p,v){return p(v)},$e.useFormState=function(p,v,E){return y.H.useFormState(p,v,E)},$e.useFormStatus=function(){return y.H.useHostTransitionStatus()},$e.version="19.2.4",$e}var Uh;function cg(){if(Uh)return Fr.exports;Uh=1;function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(s){console.error(s)}}return n(),Fr.exports=rg(),Fr.exports}var kh;function og(){if(kh)return Zn;kh=1;var n=sg(),s=oc(),r=cg();function c(e){var t="https://react.dev/errors/"+e;if(1<arguments.length){t+="?args[]="+encodeURIComponent(arguments[1]);for(var a=2;a<arguments.length;a++)t+="&args[]="+encodeURIComponent(arguments[a])}return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function d(e){return!(!e||e.nodeType!==1&&e.nodeType!==9&&e.nodeType!==11)}function m(e){var t=e,a=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do t=e,(t.flags&4098)!==0&&(a=t.return),e=t.return;while(e)}return t.tag===3?a:null}function y(e){if(e.tag===13){var t=e.memoizedState;if(t===null&&(e=e.alternate,e!==null&&(t=e.memoizedState)),t!==null)return t.dehydrated}return null}function g(e){if(e.tag===31){var t=e.memoizedState;if(t===null&&(e=e.alternate,e!==null&&(t=e.memoizedState)),t!==null)return t.dehydrated}return null}function p(e){if(m(e)!==e)throw Error(c(188))}function v(e){var t=e.alternate;if(!t){if(t=m(e),t===null)throw Error(c(188));return t!==e?null:e}for(var a=e,l=t;;){var i=a.return;if(i===null)break;var u=i.alternate;if(u===null){if(l=i.return,l!==null){a=l;continue}break}if(i.child===u.child){for(u=i.child;u;){if(u===a)return p(i),e;if(u===l)return p(i),t;u=u.sibling}throw Error(c(188))}if(a.return!==l.return)a=i,l=u;else{for(var o=!1,h=i.child;h;){if(h===a){o=!0,a=i,l=u;break}if(h===l){o=!0,l=i,a=u;break}h=h.sibling}if(!o){for(h=u.child;h;){if(h===a){o=!0,a=u,l=i;break}if(h===l){o=!0,l=u,a=i;break}h=h.sibling}if(!o)throw Error(c(189))}}if(a.alternate!==l)throw Error(c(190))}if(a.tag!==3)throw Error(c(188));return a.stateNode.current===a?e:t}function E(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e;for(e=e.child;e!==null;){if(t=E(e),t!==null)return t;e=e.sibling}return null}var T=Object.assign,R=Symbol.for("react.element"),U=Symbol.for("react.transitional.element"),k=Symbol.for("react.portal"),B=Symbol.for("react.fragment"),q=Symbol.for("react.strict_mode"),Q=Symbol.for("react.profiler"),L=Symbol.for("react.consumer"),K=Symbol.for("react.context"),X=Symbol.for("react.forward_ref"),P=Symbol.for("react.suspense"),me=Symbol.for("react.suspense_list"),$=Symbol.for("react.memo"),oe=Symbol.for("react.lazy"),Re=Symbol.for("react.activity"),Ke=Symbol.for("react.memo_cache_sentinel"),st=Symbol.iterator;function Le(e){return e===null||typeof e!="object"?null:(e=st&&e[st]||e["@@iterator"],typeof e=="function"?e:null)}var xt=Symbol.for("react.client.reference");function Gt(e){if(e==null)return null;if(typeof e=="function")return e.$$typeof===xt?null:e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case B:return"Fragment";case Q:return"Profiler";case q:return"StrictMode";case P:return"Suspense";case me:return"SuspenseList";case Re:return"Activity"}if(typeof e=="object")switch(e.$$typeof){case k:return"Portal";case K:return e.displayName||"Context";case L:return(e._context.displayName||"Context")+".Consumer";case X:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case $:return t=e.displayName||null,t!==null?t:Gt(e.type)||"Memo";case oe:t=e._payload,e=e._init;try{return Gt(e(t))}catch{}}return null}var Mt=Array.isArray,M=s.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,Y=r.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,I={pending:!1,data:null,method:null,action:null},pe=[],Ee=-1;function S(e){return{current:e}}function H(e){0>Ee||(e.current=pe[Ee],pe[Ee]=null,Ee--)}function G(e,t){Ee++,pe[Ee]=e.current,e.current=t}var V=S(null),te=S(null),ne=S(null),ye=S(null);function We(e,t){switch(G(ne,t),G(te,e),G(V,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Wd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Wd(t),e=Id(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}H(V),G(V,e)}function Me(){H(V),H(te),H(ne)}function Wl(e){e.memoizedState!==null&&G(ye,e);var t=V.current,a=Id(t,e.type);t!==a&&(G(te,e),G(V,a))}function In(e){te.current===e&&(H(V),H(te)),ye.current===e&&(H(ye),Qn._currentValue=I)}var Cu,Nc;function Ha(e){if(Cu===void 0)try{throw Error()}catch(a){var t=a.stack.trim().match(/\n( *(at )?)/);Cu=t&&t[1]||"",Nc=-1<a.stack.indexOf(` 2 + at`)?" (<anonymous>)":-1<a.stack.indexOf("@")?"@unknown:0:0":""}return` 3 + `+Cu+e+Nc}var Ou=!1;function Au(e,t){if(!e||Ou)return"";Ou=!0;var a=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var l={DetermineComponentFrameRoot:function(){try{if(t){var w=function(){throw Error()};if(Object.defineProperty(w.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(w,[])}catch(_){var A=_}Reflect.construct(e,[],w)}else{try{w.call()}catch(_){A=_}e.call(w.prototype)}}else{try{throw Error()}catch(_){A=_}(w=e())&&typeof w.catch=="function"&&w.catch(function(){})}}catch(_){if(_&&A&&typeof _.stack=="string")return[_.stack,A.stack]}return[null,null]}};l.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var i=Object.getOwnPropertyDescriptor(l.DetermineComponentFrameRoot,"name");i&&i.configurable&&Object.defineProperty(l.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var u=l.DetermineComponentFrameRoot(),o=u[0],h=u[1];if(o&&h){var b=o.split(` 4 + `),O=h.split(` 5 + `);for(i=l=0;l<b.length&&!b[l].includes("DetermineComponentFrameRoot");)l++;for(;i<O.length&&!O[i].includes("DetermineComponentFrameRoot");)i++;if(l===b.length||i===O.length)for(l=b.length-1,i=O.length-1;1<=l&&0<=i&&b[l]!==O[i];)i--;for(;1<=l&&0<=i;l--,i--)if(b[l]!==O[i]){if(l!==1||i!==1)do if(l--,i--,0>i||b[l]!==O[i]){var D=` 6 + `+b[l].replace(" at new "," at ");return e.displayName&&D.includes("<anonymous>")&&(D=D.replace("<anonymous>",e.displayName)),D}while(1<=l&&0<=i);break}}}finally{Ou=!1,Error.prepareStackTrace=a}return(a=e?e.displayName||e.name:"")?Ha(a):""}function z0(e,t){switch(e.tag){case 26:case 27:case 5:return Ha(e.type);case 16:return Ha("Lazy");case 13:return e.child!==t&&t!==null?Ha("Suspense Fallback"):Ha("Suspense");case 19:return Ha("SuspenseList");case 0:case 15:return Au(e.type,!1);case 11:return Au(e.type.render,!1);case 1:return Au(e.type,!0);case 31:return Ha("Activity");default:return""}}function Cc(e){try{var t="",a=null;do t+=z0(e,a),a=e,e=e.return;while(e);return t}catch(l){return` 7 + Error generating stack: `+l.message+` 8 + `+l.stack}}var Ru=Object.prototype.hasOwnProperty,_u=n.unstable_scheduleCallback,Du=n.unstable_cancelCallback,w0=n.unstable_shouldYield,U0=n.unstable_requestPaint,rt=n.unstable_now,k0=n.unstable_getCurrentPriorityLevel,Oc=n.unstable_ImmediatePriority,Ac=n.unstable_UserBlockingPriority,Pn=n.unstable_NormalPriority,q0=n.unstable_LowPriority,Rc=n.unstable_IdlePriority,H0=n.log,L0=n.unstable_setDisableYieldValue,Il=null,ct=null;function oa(e){if(typeof H0=="function"&&L0(e),ct&&typeof ct.setStrictMode=="function")try{ct.setStrictMode(Il,e)}catch{}}var ot=Math.clz32?Math.clz32:Y0,B0=Math.log,Q0=Math.LN2;function Y0(e){return e>>>=0,e===0?32:31-(B0(e)/Q0|0)|0}var ei=256,ti=262144,ai=4194304;function La(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function li(e,t,a){var l=e.pendingLanes;if(l===0)return 0;var i=0,u=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var h=l&134217727;return h!==0?(l=h&~u,l!==0?i=La(l):(o&=h,o!==0?i=La(o):a||(a=h&~e,a!==0&&(i=La(a))))):(h=l&~u,h!==0?i=La(h):o!==0?i=La(o):a||(a=l&~e,a!==0&&(i=La(a)))),i===0?0:t!==0&&t!==i&&(t&u)===0&&(u=i&-i,a=t&-t,u>=a||u===32&&(a&4194048)!==0)?t:i}function Pl(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function G0(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function _c(){var e=ai;return ai<<=1,(ai&62914560)===0&&(ai=4194304),e}function Mu(e){for(var t=[],a=0;31>a;a++)t.push(e);return t}function en(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function K0(e,t,a,l,i,u){var o=e.pendingLanes;e.pendingLanes=a,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=a,e.entangledLanes&=a,e.errorRecoveryDisabledLanes&=a,e.shellSuspendCounter=0;var h=e.entanglements,b=e.expirationTimes,O=e.hiddenUpdates;for(a=o&~a;0<a;){var D=31-ot(a),w=1<<D;h[D]=0,b[D]=-1;var A=O[D];if(A!==null)for(O[D]=null,D=0;D<A.length;D++){var _=A[D];_!==null&&(_.lane&=-536870913)}a&=~w}l!==0&&Dc(e,l,0),u!==0&&i===0&&e.tag!==0&&(e.suspendedLanes|=u&~(o&~t))}function Dc(e,t,a){e.pendingLanes|=t,e.suspendedLanes&=~t;var l=31-ot(t);e.entangledLanes|=t,e.entanglements[l]=e.entanglements[l]|1073741824|a&261930}function Mc(e,t){var a=e.entangledLanes|=t;for(e=e.entanglements;a;){var l=31-ot(a),i=1<<l;i&t|e[l]&t&&(e[l]|=t),a&=~i}}function zc(e,t){var a=t&-t;return a=(a&42)!==0?1:zu(a),(a&(e.suspendedLanes|t))!==0?0:a}function zu(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function wu(e){return e&=-e,2<e?8<e?(e&134217727)!==0?32:268435456:8:2}function wc(){var e=Y.p;return e!==0?e:(e=window.event,e===void 0?32:Sh(e.type))}function Uc(e,t){var a=Y.p;try{return Y.p=e,t()}finally{Y.p=a}}var fa=Math.random().toString(36).slice(2),Xe="__reactFiber$"+fa,et="__reactProps$"+fa,rl="__reactContainer$"+fa,Uu="__reactEvents$"+fa,X0="__reactListeners$"+fa,Z0="__reactHandles$"+fa,kc="__reactResources$"+fa,tn="__reactMarker$"+fa;function ku(e){delete e[Xe],delete e[et],delete e[Uu],delete e[X0],delete e[Z0]}function cl(e){var t=e[Xe];if(t)return t;for(var a=e.parentNode;a;){if(t=a[rl]||a[Xe]){if(a=t.alternate,t.child!==null||a!==null&&a.child!==null)for(e=ih(e);e!==null;){if(a=e[Xe])return a;e=ih(e)}return t}e=a,a=e.parentNode}return null}function ol(e){if(e=e[Xe]||e[rl]){var t=e.tag;if(t===5||t===6||t===13||t===31||t===26||t===27||t===3)return e}return null}function an(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e.stateNode;throw Error(c(33))}function fl(e){var t=e[kc];return t||(t=e[kc]={hoistableStyles:new Map,hoistableScripts:new Map}),t}function Ye(e){e[tn]=!0}var qc=new Set,Hc={};function Ba(e,t){dl(e,t),dl(e+"Capture",t)}function dl(e,t){for(Hc[e]=t,e=0;e<t.length;e++)qc.add(t[e])}var V0=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD][:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$"),Lc={},Bc={};function J0(e){return Ru.call(Bc,e)?!0:Ru.call(Lc,e)?!1:V0.test(e)?Bc[e]=!0:(Lc[e]=!0,!1)}function ni(e,t,a){if(J0(t))if(a===null)e.removeAttribute(t);else{switch(typeof a){case"undefined":case"function":case"symbol":e.removeAttribute(t);return;case"boolean":var l=t.toLowerCase().slice(0,5);if(l!=="data-"&&l!=="aria-"){e.removeAttribute(t);return}}e.setAttribute(t,""+a)}}function ii(e,t,a){if(a===null)e.removeAttribute(t);else{switch(typeof a){case"undefined":case"function":case"symbol":case"boolean":e.removeAttribute(t);return}e.setAttribute(t,""+a)}}function Kt(e,t,a,l){if(l===null)e.removeAttribute(a);else{switch(typeof l){case"undefined":case"function":case"symbol":case"boolean":e.removeAttribute(a);return}e.setAttributeNS(t,a,""+l)}}function St(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Qc(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function F0(e,t,a){var l=Object.getOwnPropertyDescriptor(e.constructor.prototype,t);if(!e.hasOwnProperty(t)&&typeof l<"u"&&typeof l.get=="function"&&typeof l.set=="function"){var i=l.get,u=l.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return i.call(this)},set:function(o){a=""+o,u.call(this,o)}}),Object.defineProperty(e,t,{enumerable:l.enumerable}),{getValue:function(){return a},setValue:function(o){a=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function qu(e){if(!e._valueTracker){var t=Qc(e)?"checked":"value";e._valueTracker=F0(e,t,""+e[t])}}function Yc(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var a=t.getValue(),l="";return e&&(l=Qc(e)?e.checked?"true":"false":e.value),e=l,e!==a?(t.setValue(e),!0):!1}function ui(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var $0=/[\n"\\]/g;function Et(e){return e.replace($0,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function Hu(e,t,a,l,i,u,o,h){e.name="",o!=null&&typeof o!="function"&&typeof o!="symbol"&&typeof o!="boolean"?e.type=o:e.removeAttribute("type"),t!=null?o==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+St(t)):e.value!==""+St(t)&&(e.value=""+St(t)):o!=="submit"&&o!=="reset"||e.removeAttribute("value"),t!=null?Lu(e,o,St(t)):a!=null?Lu(e,o,St(a)):l!=null&&e.removeAttribute("value"),i==null&&u!=null&&(e.defaultChecked=!!u),i!=null&&(e.checked=i&&typeof i!="function"&&typeof i!="symbol"),h!=null&&typeof h!="function"&&typeof h!="symbol"&&typeof h!="boolean"?e.name=""+St(h):e.removeAttribute("name")}function Gc(e,t,a,l,i,u,o,h){if(u!=null&&typeof u!="function"&&typeof u!="symbol"&&typeof u!="boolean"&&(e.type=u),t!=null||a!=null){if(!(u!=="submit"&&u!=="reset"||t!=null)){qu(e);return}a=a!=null?""+St(a):"",t=t!=null?""+St(t):a,h||t===e.value||(e.value=t),e.defaultValue=t}l=l??i,l=typeof l!="function"&&typeof l!="symbol"&&!!l,e.checked=h?e.checked:!!l,e.defaultChecked=!!l,o!=null&&typeof o!="function"&&typeof o!="symbol"&&typeof o!="boolean"&&(e.name=o),qu(e)}function Lu(e,t,a){t==="number"&&ui(e.ownerDocument)===e||e.defaultValue===""+a||(e.defaultValue=""+a)}function hl(e,t,a,l){if(e=e.options,t){t={};for(var i=0;i<a.length;i++)t["$"+a[i]]=!0;for(a=0;a<e.length;a++)i=t.hasOwnProperty("$"+e[a].value),e[a].selected!==i&&(e[a].selected=i),i&&l&&(e[a].defaultSelected=!0)}else{for(a=""+St(a),t=null,i=0;i<e.length;i++){if(e[i].value===a){e[i].selected=!0,l&&(e[i].defaultSelected=!0);return}t!==null||e[i].disabled||(t=e[i])}t!==null&&(t.selected=!0)}}function Kc(e,t,a){if(t!=null&&(t=""+St(t),t!==e.value&&(e.value=t),a==null)){e.defaultValue!==t&&(e.defaultValue=t);return}e.defaultValue=a!=null?""+St(a):""}function Xc(e,t,a,l){if(t==null){if(l!=null){if(a!=null)throw Error(c(92));if(Mt(l)){if(1<l.length)throw Error(c(93));l=l[0]}a=l}a==null&&(a=""),t=a}a=St(t),e.defaultValue=a,l=e.textContent,l===a&&l!==""&&l!==null&&(e.value=l),qu(e)}function ml(e,t){if(t){var a=e.firstChild;if(a&&a===e.lastChild&&a.nodeType===3){a.nodeValue=t;return}}e.textContent=t}var W0=new Set("animationIterationCount aspectRatio borderImageOutset borderImageSlice borderImageWidth boxFlex boxFlexGroup boxOrdinalGroup columnCount columns flex flexGrow flexPositive flexShrink flexNegative flexOrder gridArea gridRow gridRowEnd gridRowSpan gridRowStart gridColumn gridColumnEnd gridColumnSpan gridColumnStart fontWeight lineClamp lineHeight opacity order orphans scale tabSize widows zIndex zoom fillOpacity floodOpacity stopOpacity strokeDasharray strokeDashoffset strokeMiterlimit strokeOpacity strokeWidth MozAnimationIterationCount MozBoxFlex MozBoxFlexGroup MozLineClamp msAnimationIterationCount msFlex msZoom msFlexGrow msFlexNegative msFlexOrder msFlexPositive msFlexShrink msGridColumn msGridColumnSpan msGridRow msGridRowSpan WebkitAnimationIterationCount WebkitBoxFlex WebKitBoxFlexGroup WebkitBoxOrdinalGroup WebkitColumnCount WebkitColumns WebkitFlex WebkitFlexGrow WebkitFlexPositive WebkitFlexShrink WebkitLineClamp".split(" "));function Zc(e,t,a){var l=t.indexOf("--")===0;a==null||typeof a=="boolean"||a===""?l?e.setProperty(t,""):t==="float"?e.cssFloat="":e[t]="":l?e.setProperty(t,a):typeof a!="number"||a===0||W0.has(t)?t==="float"?e.cssFloat=a:e[t]=(""+a).trim():e[t]=a+"px"}function Vc(e,t,a){if(t!=null&&typeof t!="object")throw Error(c(62));if(e=e.style,a!=null){for(var l in a)!a.hasOwnProperty(l)||t!=null&&t.hasOwnProperty(l)||(l.indexOf("--")===0?e.setProperty(l,""):l==="float"?e.cssFloat="":e[l]="");for(var i in t)l=t[i],t.hasOwnProperty(i)&&a[i]!==l&&Zc(e,i,l)}else for(var u in t)t.hasOwnProperty(u)&&Zc(e,u,t[u])}function Bu(e){if(e.indexOf("-")===-1)return!1;switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var I0=new Map([["acceptCharset","accept-charset"],["htmlFor","for"],["httpEquiv","http-equiv"],["crossOrigin","crossorigin"],["accentHeight","accent-height"],["alignmentBaseline","alignment-baseline"],["arabicForm","arabic-form"],["baselineShift","baseline-shift"],["capHeight","cap-height"],["clipPath","clip-path"],["clipRule","clip-rule"],["colorInterpolation","color-interpolation"],["colorInterpolationFilters","color-interpolation-filters"],["colorProfile","color-profile"],["colorRendering","color-rendering"],["dominantBaseline","dominant-baseline"],["enableBackground","enable-background"],["fillOpacity","fill-opacity"],["fillRule","fill-rule"],["floodColor","flood-color"],["floodOpacity","flood-opacity"],["fontFamily","font-family"],["fontSize","font-size"],["fontSizeAdjust","font-size-adjust"],["fontStretch","font-stretch"],["fontStyle","font-style"],["fontVariant","font-variant"],["fontWeight","font-weight"],["glyphName","glyph-name"],["glyphOrientationHorizontal","glyph-orientation-horizontal"],["glyphOrientationVertical","glyph-orientation-vertical"],["horizAdvX","horiz-adv-x"],["horizOriginX","horiz-origin-x"],["imageRendering","image-rendering"],["letterSpacing","letter-spacing"],["lightingColor","lighting-color"],["markerEnd","marker-end"],["markerMid","marker-mid"],["markerStart","marker-start"],["overlinePosition","overline-position"],["overlineThickness","overline-thickness"],["paintOrder","paint-order"],["panose-1","panose-1"],["pointerEvents","pointer-events"],["renderingIntent","rendering-intent"],["shapeRendering","shape-rendering"],["stopColor","stop-color"],["stopOpacity","stop-opacity"],["strikethroughPosition","strikethrough-position"],["strikethroughThickness","strikethrough-thickness"],["strokeDasharray","stroke-dasharray"],["strokeDashoffset","stroke-dashoffset"],["strokeLinecap","stroke-linecap"],["strokeLinejoin","stroke-linejoin"],["strokeMiterlimit","stroke-miterlimit"],["strokeOpacity","stroke-opacity"],["strokeWidth","stroke-width"],["textAnchor","text-anchor"],["textDecoration","text-decoration"],["textRendering","text-rendering"],["transformOrigin","transform-origin"],["underlinePosition","underline-position"],["underlineThickness","underline-thickness"],["unicodeBidi","unicode-bidi"],["unicodeRange","unicode-range"],["unitsPerEm","units-per-em"],["vAlphabetic","v-alphabetic"],["vHanging","v-hanging"],["vIdeographic","v-ideographic"],["vMathematical","v-mathematical"],["vectorEffect","vector-effect"],["vertAdvY","vert-adv-y"],["vertOriginX","vert-origin-x"],["vertOriginY","vert-origin-y"],["wordSpacing","word-spacing"],["writingMode","writing-mode"],["xmlnsXlink","xmlns:xlink"],["xHeight","x-height"]]),P0=/^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;function si(e){return P0.test(""+e)?"javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')":e}function Xt(){}var Qu=null;function Yu(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var yl=null,gl=null;function Jc(e){var t=ol(e);if(t&&(e=t.stateNode)){var a=e[et]||null;e:switch(e=t.stateNode,t.type){case"input":if(Hu(e,a.value,a.defaultValue,a.defaultValue,a.checked,a.defaultChecked,a.type,a.name),t=a.name,a.type==="radio"&&t!=null){for(a=e;a.parentNode;)a=a.parentNode;for(a=a.querySelectorAll('input[name="'+Et(""+t)+'"][type="radio"]'),t=0;t<a.length;t++){var l=a[t];if(l!==e&&l.form===e.form){var i=l[et]||null;if(!i)throw Error(c(90));Hu(l,i.value,i.defaultValue,i.defaultValue,i.checked,i.defaultChecked,i.type,i.name)}}for(t=0;t<a.length;t++)l=a[t],l.form===e.form&&Yc(l)}break e;case"textarea":Kc(e,a.value,a.defaultValue);break e;case"select":t=a.value,t!=null&&hl(e,!!a.multiple,t,!1)}}}var Gu=!1;function Fc(e,t,a){if(Gu)return e(t,a);Gu=!0;try{var l=e(t);return l}finally{if(Gu=!1,(yl!==null||gl!==null)&&(Ji(),yl&&(t=yl,e=gl,gl=yl=null,Jc(t),e)))for(t=0;t<e.length;t++)Jc(e[t])}}function ln(e,t){var a=e.stateNode;if(a===null)return null;var l=a[et]||null;if(l===null)return null;a=l[t];e:switch(t){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":case"onMouseEnter":(l=!l.disabled)||(e=e.type,l=!(e==="button"||e==="input"||e==="select"||e==="textarea")),e=!l;break e;default:e=!1}if(e)return null;if(a&&typeof a!="function")throw Error(c(231,t,typeof a));return a}var Zt=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Ku=!1;if(Zt)try{var nn={};Object.defineProperty(nn,"passive",{get:function(){Ku=!0}}),window.addEventListener("test",nn,nn),window.removeEventListener("test",nn,nn)}catch{Ku=!1}var da=null,Xu=null,ri=null;function $c(){if(ri)return ri;var e,t=Xu,a=t.length,l,i="value"in da?da.value:da.textContent,u=i.length;for(e=0;e<a&&t[e]===i[e];e++);var o=a-e;for(l=1;l<=o&&t[a-l]===i[u-l];l++);return ri=i.slice(e,1<l?1-l:void 0)}function ci(e){var t=e.keyCode;return"charCode"in e?(e=e.charCode,e===0&&t===13&&(e=13)):e=t,e===10&&(e=13),32<=e||e===13?e:0}function oi(){return!0}function Wc(){return!1}function tt(e){function t(a,l,i,u,o){this._reactName=a,this._targetInst=i,this.type=l,this.nativeEvent=u,this.target=o,this.currentTarget=null;for(var h in e)e.hasOwnProperty(h)&&(a=e[h],this[h]=a?a(u):u[h]);return this.isDefaultPrevented=(u.defaultPrevented!=null?u.defaultPrevented:u.returnValue===!1)?oi:Wc,this.isPropagationStopped=Wc,this}return T(t.prototype,{preventDefault:function(){this.defaultPrevented=!0;var a=this.nativeEvent;a&&(a.preventDefault?a.preventDefault():typeof a.returnValue!="unknown"&&(a.returnValue=!1),this.isDefaultPrevented=oi)},stopPropagation:function(){var a=this.nativeEvent;a&&(a.stopPropagation?a.stopPropagation():typeof a.cancelBubble!="unknown"&&(a.cancelBubble=!0),this.isPropagationStopped=oi)},persist:function(){},isPersistent:oi}),t}var Qa={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},fi=tt(Qa),un=T({},Qa,{view:0,detail:0}),em=tt(un),Zu,Vu,sn,di=T({},un,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:Fu,button:0,buttons:0,relatedTarget:function(e){return e.relatedTarget===void 0?e.fromElement===e.srcElement?e.toElement:e.fromElement:e.relatedTarget},movementX:function(e){return"movementX"in e?e.movementX:(e!==sn&&(sn&&e.type==="mousemove"?(Zu=e.screenX-sn.screenX,Vu=e.screenY-sn.screenY):Vu=Zu=0,sn=e),Zu)},movementY:function(e){return"movementY"in e?e.movementY:Vu}}),Ic=tt(di),tm=T({},di,{dataTransfer:0}),am=tt(tm),lm=T({},un,{relatedTarget:0}),Ju=tt(lm),nm=T({},Qa,{animationName:0,elapsedTime:0,pseudoElement:0}),im=tt(nm),um=T({},Qa,{clipboardData:function(e){return"clipboardData"in e?e.clipboardData:window.clipboardData}}),sm=tt(um),rm=T({},Qa,{data:0}),Pc=tt(rm),cm={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},om={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},fm={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function dm(e){var t=this.nativeEvent;return t.getModifierState?t.getModifierState(e):(e=fm[e])?!!t[e]:!1}function Fu(){return dm}var hm=T({},un,{key:function(e){if(e.key){var t=cm[e.key]||e.key;if(t!=="Unidentified")return t}return e.type==="keypress"?(e=ci(e),e===13?"Enter":String.fromCharCode(e)):e.type==="keydown"||e.type==="keyup"?om[e.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,repeat:0,locale:0,getModifierState:Fu,charCode:function(e){return e.type==="keypress"?ci(e):0},keyCode:function(e){return e.type==="keydown"||e.type==="keyup"?e.keyCode:0},which:function(e){return e.type==="keypress"?ci(e):e.type==="keydown"||e.type==="keyup"?e.keyCode:0}}),mm=tt(hm),ym=T({},di,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0}),eo=tt(ym),gm=T({},un,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,ctrlKey:0,shiftKey:0,getModifierState:Fu}),vm=tt(gm),pm=T({},Qa,{propertyName:0,elapsedTime:0,pseudoElement:0}),bm=tt(pm),xm=T({},di,{deltaX:function(e){return"deltaX"in e?e.deltaX:"wheelDeltaX"in e?-e.wheelDeltaX:0},deltaY:function(e){return"deltaY"in e?e.deltaY:"wheelDeltaY"in e?-e.wheelDeltaY:"wheelDelta"in e?-e.wheelDelta:0},deltaZ:0,deltaMode:0}),Sm=tt(xm),Em=T({},Qa,{newState:0,oldState:0}),jm=tt(Em),Tm=[9,13,27,32],$u=Zt&&"CompositionEvent"in window,rn=null;Zt&&"documentMode"in document&&(rn=document.documentMode);var Nm=Zt&&"TextEvent"in window&&!rn,to=Zt&&(!$u||rn&&8<rn&&11>=rn),ao=" ",lo=!1;function no(e,t){switch(e){case"keyup":return Tm.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function io(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var vl=!1;function Cm(e,t){switch(e){case"compositionend":return io(t);case"keypress":return t.which!==32?null:(lo=!0,ao);case"textInput":return e=t.data,e===ao&&lo?null:e;default:return null}}function Om(e,t){if(vl)return e==="compositionend"||!$u&&no(e,t)?(e=$c(),ri=Xu=da=null,vl=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1<t.char.length)return t.char;if(t.which)return String.fromCharCode(t.which)}return null;case"compositionend":return to&&t.locale!=="ko"?null:t.data;default:return null}}var Am={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function uo(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t==="input"?!!Am[e.type]:t==="textarea"}function so(e,t,a,l){yl?gl?gl.push(l):gl=[l]:yl=l,t=tu(t,"onChange"),0<t.length&&(a=new fi("onChange","change",null,a,l),e.push({event:a,listeners:t}))}var cn=null,on=null;function Rm(e){Xd(e,0)}function hi(e){var t=an(e);if(Yc(t))return e}function ro(e,t){if(e==="change")return t}var co=!1;if(Zt){var Wu;if(Zt){var Iu="oninput"in document;if(!Iu){var oo=document.createElement("div");oo.setAttribute("oninput","return;"),Iu=typeof oo.oninput=="function"}Wu=Iu}else Wu=!1;co=Wu&&(!document.documentMode||9<document.documentMode)}function fo(){cn&&(cn.detachEvent("onpropertychange",ho),on=cn=null)}function ho(e){if(e.propertyName==="value"&&hi(on)){var t=[];so(t,on,e,Yu(e)),Fc(Rm,t)}}function _m(e,t,a){e==="focusin"?(fo(),cn=t,on=a,cn.attachEvent("onpropertychange",ho)):e==="focusout"&&fo()}function Dm(e){if(e==="selectionchange"||e==="keyup"||e==="keydown")return hi(on)}function Mm(e,t){if(e==="click")return hi(t)}function zm(e,t){if(e==="input"||e==="change")return hi(t)}function wm(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var ft=typeof Object.is=="function"?Object.is:wm;function fn(e,t){if(ft(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;var a=Object.keys(e),l=Object.keys(t);if(a.length!==l.length)return!1;for(l=0;l<a.length;l++){var i=a[l];if(!Ru.call(t,i)||!ft(e[i],t[i]))return!1}return!0}function mo(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function yo(e,t){var a=mo(e);e=0;for(var l;a;){if(a.nodeType===3){if(l=e+a.textContent.length,e<=t&&l>=t)return{node:a,offset:t-e};e=l}e:{for(;a;){if(a.nextSibling){a=a.nextSibling;break e}a=a.parentNode}a=void 0}a=mo(a)}}function go(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?go(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function vo(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=ui(e.document);t instanceof e.HTMLIFrameElement;){try{var a=typeof t.contentWindow.location.href=="string"}catch{a=!1}if(a)e=t.contentWindow;else break;t=ui(e.document)}return t}function Pu(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var Um=Zt&&"documentMode"in document&&11>=document.documentMode,pl=null,es=null,dn=null,ts=!1;function po(e,t,a){var l=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;ts||pl==null||pl!==ui(l)||(l=pl,"selectionStart"in l&&Pu(l)?l={start:l.selectionStart,end:l.selectionEnd}:(l=(l.ownerDocument&&l.ownerDocument.defaultView||window).getSelection(),l={anchorNode:l.anchorNode,anchorOffset:l.anchorOffset,focusNode:l.focusNode,focusOffset:l.focusOffset}),dn&&fn(dn,l)||(dn=l,l=tu(es,"onSelect"),0<l.length&&(t=new fi("onSelect","select",null,t,a),e.push({event:t,listeners:l}),t.target=pl)))}function Ya(e,t){var a={};return a[e.toLowerCase()]=t.toLowerCase(),a["Webkit"+e]="webkit"+t,a["Moz"+e]="moz"+t,a}var bl={animationend:Ya("Animation","AnimationEnd"),animationiteration:Ya("Animation","AnimationIteration"),animationstart:Ya("Animation","AnimationStart"),transitionrun:Ya("Transition","TransitionRun"),transitionstart:Ya("Transition","TransitionStart"),transitioncancel:Ya("Transition","TransitionCancel"),transitionend:Ya("Transition","TransitionEnd")},as={},bo={};Zt&&(bo=document.createElement("div").style,"AnimationEvent"in window||(delete bl.animationend.animation,delete bl.animationiteration.animation,delete bl.animationstart.animation),"TransitionEvent"in window||delete bl.transitionend.transition);function Ga(e){if(as[e])return as[e];if(!bl[e])return e;var t=bl[e],a;for(a in t)if(t.hasOwnProperty(a)&&a in bo)return as[e]=t[a];return e}var xo=Ga("animationend"),So=Ga("animationiteration"),Eo=Ga("animationstart"),km=Ga("transitionrun"),qm=Ga("transitionstart"),Hm=Ga("transitioncancel"),jo=Ga("transitionend"),To=new Map,ls="abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");ls.push("scrollEnd");function zt(e,t){To.set(e,t),Ba(t,[e])}var mi=typeof reportError=="function"?reportError:function(e){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof e=="object"&&e!==null&&typeof e.message=="string"?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",e);return}console.error(e)},jt=[],xl=0,ns=0;function yi(){for(var e=xl,t=ns=xl=0;t<e;){var a=jt[t];jt[t++]=null;var l=jt[t];jt[t++]=null;var i=jt[t];jt[t++]=null;var u=jt[t];if(jt[t++]=null,l!==null&&i!==null){var o=l.pending;o===null?i.next=i:(i.next=o.next,o.next=i),l.pending=i}u!==0&&No(a,i,u)}}function gi(e,t,a,l){jt[xl++]=e,jt[xl++]=t,jt[xl++]=a,jt[xl++]=l,ns|=l,e.lanes|=l,e=e.alternate,e!==null&&(e.lanes|=l)}function is(e,t,a,l){return gi(e,t,a,l),vi(e)}function Ka(e,t){return gi(e,null,null,t),vi(e)}function No(e,t,a){e.lanes|=a;var l=e.alternate;l!==null&&(l.lanes|=a);for(var i=!1,u=e.return;u!==null;)u.childLanes|=a,l=u.alternate,l!==null&&(l.childLanes|=a),u.tag===22&&(e=u.stateNode,e===null||e._visibility&1||(i=!0)),e=u,u=u.return;return e.tag===3?(u=e.stateNode,i&&t!==null&&(i=31-ot(a),e=u.hiddenUpdates,l=e[i],l===null?e[i]=[t]:l.push(t),t.lane=a|536870912),u):null}function vi(e){if(50<wn)throw wn=0,mr=null,Error(c(185));for(var t=e.return;t!==null;)e=t,t=e.return;return e.tag===3?e.stateNode:null}var Sl={};function Lm(e,t,a,l){this.tag=e,this.key=a,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.refCleanup=this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=l,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function dt(e,t,a,l){return new Lm(e,t,a,l)}function us(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Vt(e,t){var a=e.alternate;return a===null?(a=dt(e.tag,t,e.key,e.mode),a.elementType=e.elementType,a.type=e.type,a.stateNode=e.stateNode,a.alternate=e,e.alternate=a):(a.pendingProps=t,a.type=e.type,a.flags=0,a.subtreeFlags=0,a.deletions=null),a.flags=e.flags&65011712,a.childLanes=e.childLanes,a.lanes=e.lanes,a.child=e.child,a.memoizedProps=e.memoizedProps,a.memoizedState=e.memoizedState,a.updateQueue=e.updateQueue,t=e.dependencies,a.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},a.sibling=e.sibling,a.index=e.index,a.ref=e.ref,a.refCleanup=e.refCleanup,a}function Co(e,t){e.flags&=65011714;var a=e.alternate;return a===null?(e.childLanes=0,e.lanes=t,e.child=null,e.subtreeFlags=0,e.memoizedProps=null,e.memoizedState=null,e.updateQueue=null,e.dependencies=null,e.stateNode=null):(e.childLanes=a.childLanes,e.lanes=a.lanes,e.child=a.child,e.subtreeFlags=0,e.deletions=null,e.memoizedProps=a.memoizedProps,e.memoizedState=a.memoizedState,e.updateQueue=a.updateQueue,e.type=a.type,t=a.dependencies,e.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext}),e}function pi(e,t,a,l,i,u){var o=0;if(l=e,typeof e=="function")us(e)&&(o=1);else if(typeof e=="string")o=Ky(e,a,V.current)?26:e==="html"||e==="head"||e==="body"?27:5;else e:switch(e){case Re:return e=dt(31,a,t,i),e.elementType=Re,e.lanes=u,e;case B:return Xa(a.children,i,u,t);case q:o=8,i|=24;break;case Q:return e=dt(12,a,t,i|2),e.elementType=Q,e.lanes=u,e;case P:return e=dt(13,a,t,i),e.elementType=P,e.lanes=u,e;case me:return e=dt(19,a,t,i),e.elementType=me,e.lanes=u,e;default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case K:o=10;break e;case L:o=9;break e;case X:o=11;break e;case $:o=14;break e;case oe:o=16,l=null;break e}o=29,a=Error(c(130,e===null?"null":typeof e,"")),l=null}return t=dt(o,a,t,i),t.elementType=e,t.type=l,t.lanes=u,t}function Xa(e,t,a,l){return e=dt(7,e,l,t),e.lanes=a,e}function ss(e,t,a){return e=dt(6,e,null,t),e.lanes=a,e}function Oo(e){var t=dt(18,null,null,0);return t.stateNode=e,t}function rs(e,t,a){return t=dt(4,e.children!==null?e.children:[],e.key,t),t.lanes=a,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}var Ao=new WeakMap;function Tt(e,t){if(typeof e=="object"&&e!==null){var a=Ao.get(e);return a!==void 0?a:(t={value:e,source:t,stack:Cc(t)},Ao.set(e,t),t)}return{value:e,source:t,stack:Cc(t)}}var El=[],jl=0,bi=null,hn=0,Nt=[],Ct=0,ha=null,Ht=1,Lt="";function Jt(e,t){El[jl++]=hn,El[jl++]=bi,bi=e,hn=t}function Ro(e,t,a){Nt[Ct++]=Ht,Nt[Ct++]=Lt,Nt[Ct++]=ha,ha=e;var l=Ht;e=Lt;var i=32-ot(l)-1;l&=~(1<<i),a+=1;var u=32-ot(t)+i;if(30<u){var o=i-i%5;u=(l&(1<<o)-1).toString(32),l>>=o,i-=o,Ht=1<<32-ot(t)+i|a<<i|l,Lt=u+e}else Ht=1<<u|a<<i|l,Lt=e}function cs(e){e.return!==null&&(Jt(e,1),Ro(e,1,0))}function os(e){for(;e===bi;)bi=El[--jl],El[jl]=null,hn=El[--jl],El[jl]=null;for(;e===ha;)ha=Nt[--Ct],Nt[Ct]=null,Lt=Nt[--Ct],Nt[Ct]=null,Ht=Nt[--Ct],Nt[Ct]=null}function _o(e,t){Nt[Ct++]=Ht,Nt[Ct++]=Lt,Nt[Ct++]=ha,Ht=t.id,Lt=t.overflow,ha=e}var Ze=null,Ne=null,ce=!1,ma=null,Ot=!1,fs=Error(c(519));function ya(e){var t=Error(c(418,1<arguments.length&&arguments[1]!==void 0&&arguments[1]?"text":"HTML",""));throw mn(Tt(t,e)),fs}function Do(e){var t=e.stateNode,a=e.type,l=e.memoizedProps;switch(t[Xe]=e,t[et]=l,a){case"dialog":ue("cancel",t),ue("close",t);break;case"iframe":case"object":case"embed":ue("load",t);break;case"video":case"audio":for(a=0;a<kn.length;a++)ue(kn[a],t);break;case"source":ue("error",t);break;case"img":case"image":case"link":ue("error",t),ue("load",t);break;case"details":ue("toggle",t);break;case"input":ue("invalid",t),Gc(t,l.value,l.defaultValue,l.checked,l.defaultChecked,l.type,l.name,!0);break;case"select":ue("invalid",t);break;case"textarea":ue("invalid",t),Xc(t,l.value,l.defaultValue,l.children)}a=l.children,typeof a!="string"&&typeof a!="number"&&typeof a!="bigint"||t.textContent===""+a||l.suppressHydrationWarning===!0||Fd(t.textContent,a)?(l.popover!=null&&(ue("beforetoggle",t),ue("toggle",t)),l.onScroll!=null&&ue("scroll",t),l.onScrollEnd!=null&&ue("scrollend",t),l.onClick!=null&&(t.onclick=Xt),t=!0):t=!1,t||ya(e,!0)}function Mo(e){for(Ze=e.return;Ze;)switch(Ze.tag){case 5:case 31:case 13:Ot=!1;return;case 27:case 3:Ot=!0;return;default:Ze=Ze.return}}function Tl(e){if(e!==Ze)return!1;if(!ce)return Mo(e),ce=!0,!1;var t=e.tag,a;if((a=t!==3&&t!==27)&&((a=t===5)&&(a=e.type,a=!(a!=="form"&&a!=="button")||Rr(e.type,e.memoizedProps)),a=!a),a&&Ne&&ya(e),Mo(e),t===13){if(e=e.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(c(317));Ne=nh(e)}else if(t===31){if(e=e.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(c(317));Ne=nh(e)}else t===27?(t=Ne,Ra(e.type)?(e=wr,wr=null,Ne=e):Ne=t):Ne=Ze?Rt(e.stateNode.nextSibling):null;return!0}function Za(){Ne=Ze=null,ce=!1}function ds(){var e=ma;return e!==null&&(it===null?it=e:it.push.apply(it,e),ma=null),e}function mn(e){ma===null?ma=[e]:ma.push(e)}var hs=S(null),Va=null,Ft=null;function ga(e,t,a){G(hs,t._currentValue),t._currentValue=a}function $t(e){e._currentValue=hs.current,H(hs)}function ms(e,t,a){for(;e!==null;){var l=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,l!==null&&(l.childLanes|=t)):l!==null&&(l.childLanes&t)!==t&&(l.childLanes|=t),e===a)break;e=e.return}}function ys(e,t,a,l){var i=e.child;for(i!==null&&(i.return=e);i!==null;){var u=i.dependencies;if(u!==null){var o=i.child;u=u.firstContext;e:for(;u!==null;){var h=u;u=i;for(var b=0;b<t.length;b++)if(h.context===t[b]){u.lanes|=a,h=u.alternate,h!==null&&(h.lanes|=a),ms(u.return,a,e),l||(o=null);break e}u=h.next}}else if(i.tag===18){if(o=i.return,o===null)throw Error(c(341));o.lanes|=a,u=o.alternate,u!==null&&(u.lanes|=a),ms(o,a,e),o=null}else o=i.child;if(o!==null)o.return=i;else for(o=i;o!==null;){if(o===e){o=null;break}if(i=o.sibling,i!==null){i.return=o.return,o=i;break}o=o.return}i=o}}function Nl(e,t,a,l){e=null;for(var i=t,u=!1;i!==null;){if(!u){if((i.flags&524288)!==0)u=!0;else if((i.flags&262144)!==0)break}if(i.tag===10){var o=i.alternate;if(o===null)throw Error(c(387));if(o=o.memoizedProps,o!==null){var h=i.type;ft(i.pendingProps.value,o.value)||(e!==null?e.push(h):e=[h])}}else if(i===ye.current){if(o=i.alternate,o===null)throw Error(c(387));o.memoizedState.memoizedState!==i.memoizedState.memoizedState&&(e!==null?e.push(Qn):e=[Qn])}i=i.return}e!==null&&ys(t,e,a,l),t.flags|=262144}function xi(e){for(e=e.firstContext;e!==null;){if(!ft(e.context._currentValue,e.memoizedValue))return!0;e=e.next}return!1}function Ja(e){Va=e,Ft=null,e=e.dependencies,e!==null&&(e.firstContext=null)}function Ve(e){return zo(Va,e)}function Si(e,t){return Va===null&&Ja(e),zo(e,t)}function zo(e,t){var a=t._currentValue;if(t={context:t,memoizedValue:a,next:null},Ft===null){if(e===null)throw Error(c(308));Ft=t,e.dependencies={lanes:0,firstContext:t},e.flags|=524288}else Ft=Ft.next=t;return a}var Bm=typeof AbortController<"u"?AbortController:function(){var e=[],t=this.signal={aborted:!1,addEventListener:function(a,l){e.push(l)}};this.abort=function(){t.aborted=!0,e.forEach(function(a){return a()})}},Qm=n.unstable_scheduleCallback,Ym=n.unstable_NormalPriority,Ue={$$typeof:K,Consumer:null,Provider:null,_currentValue:null,_currentValue2:null,_threadCount:0};function gs(){return{controller:new Bm,data:new Map,refCount:0}}function yn(e){e.refCount--,e.refCount===0&&Qm(Ym,function(){e.controller.abort()})}var gn=null,vs=0,Cl=0,Ol=null;function Gm(e,t){if(gn===null){var a=gn=[];vs=0,Cl=xr(),Ol={status:"pending",value:void 0,then:function(l){a.push(l)}}}return vs++,t.then(wo,wo),t}function wo(){if(--vs===0&&gn!==null){Ol!==null&&(Ol.status="fulfilled");var e=gn;gn=null,Cl=0,Ol=null;for(var t=0;t<e.length;t++)(0,e[t])()}}function Km(e,t){var a=[],l={status:"pending",value:null,reason:null,then:function(i){a.push(i)}};return e.then(function(){l.status="fulfilled",l.value=t;for(var i=0;i<a.length;i++)(0,a[i])(t)},function(i){for(l.status="rejected",l.reason=i,i=0;i<a.length;i++)(0,a[i])(void 0)}),l}var Uo=M.S;M.S=function(e,t){bd=rt(),typeof t=="object"&&t!==null&&typeof t.then=="function"&&Gm(e,t),Uo!==null&&Uo(e,t)};var Fa=S(null);function ps(){var e=Fa.current;return e!==null?e:je.pooledCache}function Ei(e,t){t===null?G(Fa,Fa.current):G(Fa,t.pool)}function ko(){var e=ps();return e===null?null:{parent:Ue._currentValue,pool:e}}var Al=Error(c(460)),bs=Error(c(474)),ji=Error(c(542)),Ti={then:function(){}};function qo(e){return e=e.status,e==="fulfilled"||e==="rejected"}function Ho(e,t,a){switch(a=e[a],a===void 0?e.push(t):a!==t&&(t.then(Xt,Xt),t=a),t.status){case"fulfilled":return t.value;case"rejected":throw e=t.reason,Bo(e),e;default:if(typeof t.status=="string")t.then(Xt,Xt);else{if(e=je,e!==null&&100<e.shellSuspendCounter)throw Error(c(482));e=t,e.status="pending",e.then(function(l){if(t.status==="pending"){var i=t;i.status="fulfilled",i.value=l}},function(l){if(t.status==="pending"){var i=t;i.status="rejected",i.reason=l}})}switch(t.status){case"fulfilled":return t.value;case"rejected":throw e=t.reason,Bo(e),e}throw Wa=t,Al}}function $a(e){try{var t=e._init;return t(e._payload)}catch(a){throw a!==null&&typeof a=="object"&&typeof a.then=="function"?(Wa=a,Al):a}}var Wa=null;function Lo(){if(Wa===null)throw Error(c(459));var e=Wa;return Wa=null,e}function Bo(e){if(e===Al||e===ji)throw Error(c(483))}var Rl=null,vn=0;function Ni(e){var t=vn;return vn+=1,Rl===null&&(Rl=[]),Ho(Rl,e,t)}function pn(e,t){t=t.props.ref,e.ref=t!==void 0?t:null}function Ci(e,t){throw t.$$typeof===R?Error(c(525)):(e=Object.prototype.toString.call(t),Error(c(31,e==="[object Object]"?"object with keys {"+Object.keys(t).join(", ")+"}":e)))}function Qo(e){function t(j,x){if(e){var C=j.deletions;C===null?(j.deletions=[x],j.flags|=16):C.push(x)}}function a(j,x){if(!e)return null;for(;x!==null;)t(j,x),x=x.sibling;return null}function l(j){for(var x=new Map;j!==null;)j.key!==null?x.set(j.key,j):x.set(j.index,j),j=j.sibling;return x}function i(j,x){return j=Vt(j,x),j.index=0,j.sibling=null,j}function u(j,x,C){return j.index=C,e?(C=j.alternate,C!==null?(C=C.index,C<x?(j.flags|=67108866,x):C):(j.flags|=67108866,x)):(j.flags|=1048576,x)}function o(j){return e&&j.alternate===null&&(j.flags|=67108866),j}function h(j,x,C,z){return x===null||x.tag!==6?(x=ss(C,j.mode,z),x.return=j,x):(x=i(x,C),x.return=j,x)}function b(j,x,C,z){var F=C.type;return F===B?D(j,x,C.props.children,z,C.key):x!==null&&(x.elementType===F||typeof F=="object"&&F!==null&&F.$$typeof===oe&&$a(F)===x.type)?(x=i(x,C.props),pn(x,C),x.return=j,x):(x=pi(C.type,C.key,C.props,null,j.mode,z),pn(x,C),x.return=j,x)}function O(j,x,C,z){return x===null||x.tag!==4||x.stateNode.containerInfo!==C.containerInfo||x.stateNode.implementation!==C.implementation?(x=rs(C,j.mode,z),x.return=j,x):(x=i(x,C.children||[]),x.return=j,x)}function D(j,x,C,z,F){return x===null||x.tag!==7?(x=Xa(C,j.mode,z,F),x.return=j,x):(x=i(x,C),x.return=j,x)}function w(j,x,C){if(typeof x=="string"&&x!==""||typeof x=="number"||typeof x=="bigint")return x=ss(""+x,j.mode,C),x.return=j,x;if(typeof x=="object"&&x!==null){switch(x.$$typeof){case U:return C=pi(x.type,x.key,x.props,null,j.mode,C),pn(C,x),C.return=j,C;case k:return x=rs(x,j.mode,C),x.return=j,x;case oe:return x=$a(x),w(j,x,C)}if(Mt(x)||Le(x))return x=Xa(x,j.mode,C,null),x.return=j,x;if(typeof x.then=="function")return w(j,Ni(x),C);if(x.$$typeof===K)return w(j,Si(j,x),C);Ci(j,x)}return null}function A(j,x,C,z){var F=x!==null?x.key:null;if(typeof C=="string"&&C!==""||typeof C=="number"||typeof C=="bigint")return F!==null?null:h(j,x,""+C,z);if(typeof C=="object"&&C!==null){switch(C.$$typeof){case U:return C.key===F?b(j,x,C,z):null;case k:return C.key===F?O(j,x,C,z):null;case oe:return C=$a(C),A(j,x,C,z)}if(Mt(C)||Le(C))return F!==null?null:D(j,x,C,z,null);if(typeof C.then=="function")return A(j,x,Ni(C),z);if(C.$$typeof===K)return A(j,x,Si(j,C),z);Ci(j,C)}return null}function _(j,x,C,z,F){if(typeof z=="string"&&z!==""||typeof z=="number"||typeof z=="bigint")return j=j.get(C)||null,h(x,j,""+z,F);if(typeof z=="object"&&z!==null){switch(z.$$typeof){case U:return j=j.get(z.key===null?C:z.key)||null,b(x,j,z,F);case k:return j=j.get(z.key===null?C:z.key)||null,O(x,j,z,F);case oe:return z=$a(z),_(j,x,C,z,F)}if(Mt(z)||Le(z))return j=j.get(C)||null,D(x,j,z,F,null);if(typeof z.then=="function")return _(j,x,C,Ni(z),F);if(z.$$typeof===K)return _(j,x,C,Si(x,z),F);Ci(x,z)}return null}function Z(j,x,C,z){for(var F=null,fe=null,J=x,le=x=0,re=null;J!==null&&le<C.length;le++){J.index>le?(re=J,J=null):re=J.sibling;var de=A(j,J,C[le],z);if(de===null){J===null&&(J=re);break}e&&J&&de.alternate===null&&t(j,J),x=u(de,x,le),fe===null?F=de:fe.sibling=de,fe=de,J=re}if(le===C.length)return a(j,J),ce&&Jt(j,le),F;if(J===null){for(;le<C.length;le++)J=w(j,C[le],z),J!==null&&(x=u(J,x,le),fe===null?F=J:fe.sibling=J,fe=J);return ce&&Jt(j,le),F}for(J=l(J);le<C.length;le++)re=_(J,j,le,C[le],z),re!==null&&(e&&re.alternate!==null&&J.delete(re.key===null?le:re.key),x=u(re,x,le),fe===null?F=re:fe.sibling=re,fe=re);return e&&J.forEach(function(wa){return t(j,wa)}),ce&&Jt(j,le),F}function W(j,x,C,z){if(C==null)throw Error(c(151));for(var F=null,fe=null,J=x,le=x=0,re=null,de=C.next();J!==null&&!de.done;le++,de=C.next()){J.index>le?(re=J,J=null):re=J.sibling;var wa=A(j,J,de.value,z);if(wa===null){J===null&&(J=re);break}e&&J&&wa.alternate===null&&t(j,J),x=u(wa,x,le),fe===null?F=wa:fe.sibling=wa,fe=wa,J=re}if(de.done)return a(j,J),ce&&Jt(j,le),F;if(J===null){for(;!de.done;le++,de=C.next())de=w(j,de.value,z),de!==null&&(x=u(de,x,le),fe===null?F=de:fe.sibling=de,fe=de);return ce&&Jt(j,le),F}for(J=l(J);!de.done;le++,de=C.next())de=_(J,j,le,de.value,z),de!==null&&(e&&de.alternate!==null&&J.delete(de.key===null?le:de.key),x=u(de,x,le),fe===null?F=de:fe.sibling=de,fe=de);return e&&J.forEach(function(tg){return t(j,tg)}),ce&&Jt(j,le),F}function Se(j,x,C,z){if(typeof C=="object"&&C!==null&&C.type===B&&C.key===null&&(C=C.props.children),typeof C=="object"&&C!==null){switch(C.$$typeof){case U:e:{for(var F=C.key;x!==null;){if(x.key===F){if(F=C.type,F===B){if(x.tag===7){a(j,x.sibling),z=i(x,C.props.children),z.return=j,j=z;break e}}else if(x.elementType===F||typeof F=="object"&&F!==null&&F.$$typeof===oe&&$a(F)===x.type){a(j,x.sibling),z=i(x,C.props),pn(z,C),z.return=j,j=z;break e}a(j,x);break}else t(j,x);x=x.sibling}C.type===B?(z=Xa(C.props.children,j.mode,z,C.key),z.return=j,j=z):(z=pi(C.type,C.key,C.props,null,j.mode,z),pn(z,C),z.return=j,j=z)}return o(j);case k:e:{for(F=C.key;x!==null;){if(x.key===F)if(x.tag===4&&x.stateNode.containerInfo===C.containerInfo&&x.stateNode.implementation===C.implementation){a(j,x.sibling),z=i(x,C.children||[]),z.return=j,j=z;break e}else{a(j,x);break}else t(j,x);x=x.sibling}z=rs(C,j.mode,z),z.return=j,j=z}return o(j);case oe:return C=$a(C),Se(j,x,C,z)}if(Mt(C))return Z(j,x,C,z);if(Le(C)){if(F=Le(C),typeof F!="function")throw Error(c(150));return C=F.call(C),W(j,x,C,z)}if(typeof C.then=="function")return Se(j,x,Ni(C),z);if(C.$$typeof===K)return Se(j,x,Si(j,C),z);Ci(j,C)}return typeof C=="string"&&C!==""||typeof C=="number"||typeof C=="bigint"?(C=""+C,x!==null&&x.tag===6?(a(j,x.sibling),z=i(x,C),z.return=j,j=z):(a(j,x),z=ss(C,j.mode,z),z.return=j,j=z),o(j)):a(j,x)}return function(j,x,C,z){try{vn=0;var F=Se(j,x,C,z);return Rl=null,F}catch(J){if(J===Al||J===ji)throw J;var fe=dt(29,J,null,j.mode);return fe.lanes=z,fe.return=j,fe}}}var Ia=Qo(!0),Yo=Qo(!1),va=!1;function xs(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ss(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function pa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function ba(e,t,a){var l=e.updateQueue;if(l===null)return null;if(l=l.shared,(he&2)!==0){var i=l.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),l.pending=t,t=vi(e),No(e,null,a),t}return gi(e,l,t,a),vi(e)}function bn(e,t,a){if(t=t.updateQueue,t!==null&&(t=t.shared,(a&4194048)!==0)){var l=t.lanes;l&=e.pendingLanes,a|=l,t.lanes=a,Mc(e,a)}}function Es(e,t){var a=e.updateQueue,l=e.alternate;if(l!==null&&(l=l.updateQueue,a===l)){var i=null,u=null;if(a=a.firstBaseUpdate,a!==null){do{var o={lane:a.lane,tag:a.tag,payload:a.payload,callback:null,next:null};u===null?i=u=o:u=u.next=o,a=a.next}while(a!==null);u===null?i=u=t:u=u.next=t}else i=u=t;a={baseState:l.baseState,firstBaseUpdate:i,lastBaseUpdate:u,shared:l.shared,callbacks:l.callbacks},e.updateQueue=a;return}e=a.lastBaseUpdate,e===null?a.firstBaseUpdate=t:e.next=t,a.lastBaseUpdate=t}var js=!1;function xn(){if(js){var e=Ol;if(e!==null)throw e}}function Sn(e,t,a,l){js=!1;var i=e.updateQueue;va=!1;var u=i.firstBaseUpdate,o=i.lastBaseUpdate,h=i.shared.pending;if(h!==null){i.shared.pending=null;var b=h,O=b.next;b.next=null,o===null?u=O:o.next=O,o=b;var D=e.alternate;D!==null&&(D=D.updateQueue,h=D.lastBaseUpdate,h!==o&&(h===null?D.firstBaseUpdate=O:h.next=O,D.lastBaseUpdate=b))}if(u!==null){var w=i.baseState;o=0,D=O=b=null,h=u;do{var A=h.lane&-536870913,_=A!==h.lane;if(_?(se&A)===A:(l&A)===A){A!==0&&A===Cl&&(js=!0),D!==null&&(D=D.next={lane:0,tag:h.tag,payload:h.payload,callback:null,next:null});e:{var Z=e,W=h;A=t;var Se=a;switch(W.tag){case 1:if(Z=W.payload,typeof Z=="function"){w=Z.call(Se,w,A);break e}w=Z;break e;case 3:Z.flags=Z.flags&-65537|128;case 0:if(Z=W.payload,A=typeof Z=="function"?Z.call(Se,w,A):Z,A==null)break e;w=T({},w,A);break e;case 2:va=!0}}A=h.callback,A!==null&&(e.flags|=64,_&&(e.flags|=8192),_=i.callbacks,_===null?i.callbacks=[A]:_.push(A))}else _={lane:A,tag:h.tag,payload:h.payload,callback:h.callback,next:null},D===null?(O=D=_,b=w):D=D.next=_,o|=A;if(h=h.next,h===null){if(h=i.shared.pending,h===null)break;_=h,h=_.next,_.next=null,i.lastBaseUpdate=_,i.shared.pending=null}}while(!0);D===null&&(b=w),i.baseState=b,i.firstBaseUpdate=O,i.lastBaseUpdate=D,u===null&&(i.shared.lanes=0),Ta|=o,e.lanes=o,e.memoizedState=w}}function Go(e,t){if(typeof e!="function")throw Error(c(191,e));e.call(t)}function Ko(e,t){var a=e.callbacks;if(a!==null)for(e.callbacks=null,e=0;e<a.length;e++)Go(a[e],t)}var _l=S(null),Oi=S(0);function Xo(e,t){e=ia,G(Oi,e),G(_l,t),ia=e|t.baseLanes}function Ts(){G(Oi,ia),G(_l,_l.current)}function Ns(){ia=Oi.current,H(_l),H(Oi)}var ht=S(null),At=null;function xa(e){var t=e.alternate;G(ze,ze.current&1),G(ht,e),At===null&&(t===null||_l.current!==null||t.memoizedState!==null)&&(At=e)}function Cs(e){G(ze,ze.current),G(ht,e),At===null&&(At=e)}function Zo(e){e.tag===22?(G(ze,ze.current),G(ht,e),At===null&&(At=e)):Sa()}function Sa(){G(ze,ze.current),G(ht,ht.current)}function mt(e){H(ht),At===e&&(At=null),H(ze)}var ze=S(0);function Ai(e){for(var t=e;t!==null;){if(t.tag===13){var a=t.memoizedState;if(a!==null&&(a=a.dehydrated,a===null||Mr(a)||zr(a)))return t}else if(t.tag===19&&(t.memoizedProps.revealOrder==="forwards"||t.memoizedProps.revealOrder==="backwards"||t.memoizedProps.revealOrder==="unstable_legacy-backwards"||t.memoizedProps.revealOrder==="together")){if((t.flags&128)!==0)return t}else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}var Wt=0,ae=null,be=null,ke=null,Ri=!1,Dl=!1,Pa=!1,_i=0,En=0,Ml=null,Xm=0;function _e(){throw Error(c(321))}function Os(e,t){if(t===null)return!1;for(var a=0;a<t.length&&a<e.length;a++)if(!ft(e[a],t[a]))return!1;return!0}function As(e,t,a,l,i,u){return Wt=u,ae=t,t.memoizedState=null,t.updateQueue=null,t.lanes=0,M.H=e===null||e.memoizedState===null?_f:Gs,Pa=!1,u=a(l,i),Pa=!1,Dl&&(u=Jo(t,a,l,i)),Vo(e),u}function Vo(e){M.H=Nn;var t=be!==null&&be.next!==null;if(Wt=0,ke=be=ae=null,Ri=!1,En=0,Ml=null,t)throw Error(c(300));e===null||qe||(e=e.dependencies,e!==null&&xi(e)&&(qe=!0))}function Jo(e,t,a,l){ae=e;var i=0;do{if(Dl&&(Ml=null),En=0,Dl=!1,25<=i)throw Error(c(301));if(i+=1,ke=be=null,e.updateQueue!=null){var u=e.updateQueue;u.lastEffect=null,u.events=null,u.stores=null,u.memoCache!=null&&(u.memoCache.index=0)}M.H=Df,u=t(a,l)}while(Dl);return u}function Zm(){var e=M.H,t=e.useState()[0];return t=typeof t.then=="function"?jn(t):t,e=e.useState()[0],(be!==null?be.memoizedState:null)!==e&&(ae.flags|=1024),t}function Rs(){var e=_i!==0;return _i=0,e}function _s(e,t,a){t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~a}function Ds(e){if(Ri){for(e=e.memoizedState;e!==null;){var t=e.queue;t!==null&&(t.pending=null),e=e.next}Ri=!1}Wt=0,ke=be=ae=null,Dl=!1,En=_i=0,Ml=null}function Ie(){var e={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return ke===null?ae.memoizedState=ke=e:ke=ke.next=e,ke}function we(){if(be===null){var e=ae.alternate;e=e!==null?e.memoizedState:null}else e=be.next;var t=ke===null?ae.memoizedState:ke.next;if(t!==null)ke=t,be=e;else{if(e===null)throw ae.alternate===null?Error(c(467)):Error(c(310));be=e,e={memoizedState:be.memoizedState,baseState:be.baseState,baseQueue:be.baseQueue,queue:be.queue,next:null},ke===null?ae.memoizedState=ke=e:ke=ke.next=e}return ke}function Di(){return{lastEffect:null,events:null,stores:null,memoCache:null}}function jn(e){var t=En;return En+=1,Ml===null&&(Ml=[]),e=Ho(Ml,e,t),t=ae,(ke===null?t.memoizedState:ke.next)===null&&(t=t.alternate,M.H=t===null||t.memoizedState===null?_f:Gs),e}function Mi(e){if(e!==null&&typeof e=="object"){if(typeof e.then=="function")return jn(e);if(e.$$typeof===K)return Ve(e)}throw Error(c(438,String(e)))}function Ms(e){var t=null,a=ae.updateQueue;if(a!==null&&(t=a.memoCache),t==null){var l=ae.alternate;l!==null&&(l=l.updateQueue,l!==null&&(l=l.memoCache,l!=null&&(t={data:l.data.map(function(i){return i.slice()}),index:0})))}if(t==null&&(t={data:[],index:0}),a===null&&(a=Di(),ae.updateQueue=a),a.memoCache=t,a=t.data[t.index],a===void 0)for(a=t.data[t.index]=Array(e),l=0;l<e;l++)a[l]=Ke;return t.index++,a}function It(e,t){return typeof t=="function"?t(e):t}function zi(e){var t=we();return zs(t,be,e)}function zs(e,t,a){var l=e.queue;if(l===null)throw Error(c(311));l.lastRenderedReducer=a;var i=e.baseQueue,u=l.pending;if(u!==null){if(i!==null){var o=i.next;i.next=u.next,u.next=o}t.baseQueue=i=u,l.pending=null}if(u=e.baseState,i===null)e.memoizedState=u;else{t=i.next;var h=o=null,b=null,O=t,D=!1;do{var w=O.lane&-536870913;if(w!==O.lane?(se&w)===w:(Wt&w)===w){var A=O.revertLane;if(A===0)b!==null&&(b=b.next={lane:0,revertLane:0,gesture:null,action:O.action,hasEagerState:O.hasEagerState,eagerState:O.eagerState,next:null}),w===Cl&&(D=!0);else if((Wt&A)===A){O=O.next,A===Cl&&(D=!0);continue}else w={lane:0,revertLane:O.revertLane,gesture:null,action:O.action,hasEagerState:O.hasEagerState,eagerState:O.eagerState,next:null},b===null?(h=b=w,o=u):b=b.next=w,ae.lanes|=A,Ta|=A;w=O.action,Pa&&a(u,w),u=O.hasEagerState?O.eagerState:a(u,w)}else A={lane:w,revertLane:O.revertLane,gesture:O.gesture,action:O.action,hasEagerState:O.hasEagerState,eagerState:O.eagerState,next:null},b===null?(h=b=A,o=u):b=b.next=A,ae.lanes|=w,Ta|=w;O=O.next}while(O!==null&&O!==t);if(b===null?o=u:b.next=h,!ft(u,e.memoizedState)&&(qe=!0,D&&(a=Ol,a!==null)))throw a;e.memoizedState=u,e.baseState=o,e.baseQueue=b,l.lastRenderedState=u}return i===null&&(l.lanes=0),[e.memoizedState,l.dispatch]}function ws(e){var t=we(),a=t.queue;if(a===null)throw Error(c(311));a.lastRenderedReducer=e;var l=a.dispatch,i=a.pending,u=t.memoizedState;if(i!==null){a.pending=null;var o=i=i.next;do u=e(u,o.action),o=o.next;while(o!==i);ft(u,t.memoizedState)||(qe=!0),t.memoizedState=u,t.baseQueue===null&&(t.baseState=u),a.lastRenderedState=u}return[u,l]}function Fo(e,t,a){var l=ae,i=we(),u=ce;if(u){if(a===void 0)throw Error(c(407));a=a()}else a=t();var o=!ft((be||i).memoizedState,a);if(o&&(i.memoizedState=a,qe=!0),i=i.queue,qs(Io.bind(null,l,i,e),[e]),i.getSnapshot!==t||o||ke!==null&&ke.memoizedState.tag&1){if(l.flags|=2048,zl(9,{destroy:void 0},Wo.bind(null,l,i,a,t),null),je===null)throw Error(c(349));u||(Wt&127)!==0||$o(l,t,a)}return a}function $o(e,t,a){e.flags|=16384,e={getSnapshot:t,value:a},t=ae.updateQueue,t===null?(t=Di(),ae.updateQueue=t,t.stores=[e]):(a=t.stores,a===null?t.stores=[e]:a.push(e))}function Wo(e,t,a,l){t.value=a,t.getSnapshot=l,Po(t)&&ef(e)}function Io(e,t,a){return a(function(){Po(t)&&ef(e)})}function Po(e){var t=e.getSnapshot;e=e.value;try{var a=t();return!ft(e,a)}catch{return!0}}function ef(e){var t=Ka(e,2);t!==null&&ut(t,e,2)}function Us(e){var t=Ie();if(typeof e=="function"){var a=e;if(e=a(),Pa){oa(!0);try{a()}finally{oa(!1)}}}return t.memoizedState=t.baseState=e,t.queue={pending:null,lanes:0,dispatch:null,lastRenderedReducer:It,lastRenderedState:e},t}function tf(e,t,a,l){return e.baseState=a,zs(e,be,typeof l=="function"?l:It)}function Vm(e,t,a,l,i){if(ki(e))throw Error(c(485));if(e=t.action,e!==null){var u={payload:i,action:e,next:null,isTransition:!0,status:"pending",value:null,reason:null,listeners:[],then:function(o){u.listeners.push(o)}};M.T!==null?a(!0):u.isTransition=!1,l(u),a=t.pending,a===null?(u.next=t.pending=u,af(t,u)):(u.next=a.next,t.pending=a.next=u)}}function af(e,t){var a=t.action,l=t.payload,i=e.state;if(t.isTransition){var u=M.T,o={};M.T=o;try{var h=a(i,l),b=M.S;b!==null&&b(o,h),lf(e,t,h)}catch(O){ks(e,t,O)}finally{u!==null&&o.types!==null&&(u.types=o.types),M.T=u}}else try{u=a(i,l),lf(e,t,u)}catch(O){ks(e,t,O)}}function lf(e,t,a){a!==null&&typeof a=="object"&&typeof a.then=="function"?a.then(function(l){nf(e,t,l)},function(l){return ks(e,t,l)}):nf(e,t,a)}function nf(e,t,a){t.status="fulfilled",t.value=a,uf(t),e.state=a,t=e.pending,t!==null&&(a=t.next,a===t?e.pending=null:(a=a.next,t.next=a,af(e,a)))}function ks(e,t,a){var l=e.pending;if(e.pending=null,l!==null){l=l.next;do t.status="rejected",t.reason=a,uf(t),t=t.next;while(t!==l)}e.action=null}function uf(e){e=e.listeners;for(var t=0;t<e.length;t++)(0,e[t])()}function sf(e,t){return t}function rf(e,t){if(ce){var a=je.formState;if(a!==null){e:{var l=ae;if(ce){if(Ne){t:{for(var i=Ne,u=Ot;i.nodeType!==8;){if(!u){i=null;break t}if(i=Rt(i.nextSibling),i===null){i=null;break t}}u=i.data,i=u==="F!"||u==="F"?i:null}if(i){Ne=Rt(i.nextSibling),l=i.data==="F!";break e}}ya(l)}l=!1}l&&(t=a[0])}}return a=Ie(),a.memoizedState=a.baseState=t,l={pending:null,lanes:0,dispatch:null,lastRenderedReducer:sf,lastRenderedState:t},a.queue=l,a=Of.bind(null,ae,l),l.dispatch=a,l=Us(!1),u=Ys.bind(null,ae,!1,l.queue),l=Ie(),i={state:t,dispatch:null,action:e,pending:null},l.queue=i,a=Vm.bind(null,ae,i,u,a),i.dispatch=a,l.memoizedState=e,[t,a,!1]}function cf(e){var t=we();return of(t,be,e)}function of(e,t,a){if(t=zs(e,t,sf)[0],e=zi(It)[0],typeof t=="object"&&t!==null&&typeof t.then=="function")try{var l=jn(t)}catch(o){throw o===Al?ji:o}else l=t;t=we();var i=t.queue,u=i.dispatch;return a!==t.memoizedState&&(ae.flags|=2048,zl(9,{destroy:void 0},Jm.bind(null,i,a),null)),[l,u,e]}function Jm(e,t){e.action=t}function ff(e){var t=we(),a=be;if(a!==null)return of(t,a,e);we(),t=t.memoizedState,a=we();var l=a.queue.dispatch;return a.memoizedState=e,[t,l,!1]}function zl(e,t,a,l){return e={tag:e,create:a,deps:l,inst:t,next:null},t=ae.updateQueue,t===null&&(t=Di(),ae.updateQueue=t),a=t.lastEffect,a===null?t.lastEffect=e.next=e:(l=a.next,a.next=e,e.next=l,t.lastEffect=e),e}function df(){return we().memoizedState}function wi(e,t,a,l){var i=Ie();ae.flags|=e,i.memoizedState=zl(1|t,{destroy:void 0},a,l===void 0?null:l)}function Ui(e,t,a,l){var i=we();l=l===void 0?null:l;var u=i.memoizedState.inst;be!==null&&l!==null&&Os(l,be.memoizedState.deps)?i.memoizedState=zl(t,u,a,l):(ae.flags|=e,i.memoizedState=zl(1|t,u,a,l))}function hf(e,t){wi(8390656,8,e,t)}function qs(e,t){Ui(2048,8,e,t)}function Fm(e){ae.flags|=4;var t=ae.updateQueue;if(t===null)t=Di(),ae.updateQueue=t,t.events=[e];else{var a=t.events;a===null?t.events=[e]:a.push(e)}}function mf(e){var t=we().memoizedState;return Fm({ref:t,nextImpl:e}),function(){if((he&2)!==0)throw Error(c(440));return t.impl.apply(void 0,arguments)}}function yf(e,t){return Ui(4,2,e,t)}function gf(e,t){return Ui(4,4,e,t)}function vf(e,t){if(typeof t=="function"){e=e();var a=t(e);return function(){typeof a=="function"?a():t(null)}}if(t!=null)return e=e(),t.current=e,function(){t.current=null}}function pf(e,t,a){a=a!=null?a.concat([e]):null,Ui(4,4,vf.bind(null,t,e),a)}function Hs(){}function bf(e,t){var a=we();t=t===void 0?null:t;var l=a.memoizedState;return t!==null&&Os(t,l[1])?l[0]:(a.memoizedState=[e,t],e)}function xf(e,t){var a=we();t=t===void 0?null:t;var l=a.memoizedState;if(t!==null&&Os(t,l[1]))return l[0];if(l=e(),Pa){oa(!0);try{e()}finally{oa(!1)}}return a.memoizedState=[l,t],l}function Ls(e,t,a){return a===void 0||(Wt&1073741824)!==0&&(se&261930)===0?e.memoizedState=t:(e.memoizedState=a,e=Sd(),ae.lanes|=e,Ta|=e,a)}function Sf(e,t,a,l){return ft(a,t)?a:_l.current!==null?(e=Ls(e,a,l),ft(e,t)||(qe=!0),e):(Wt&42)===0||(Wt&1073741824)!==0&&(se&261930)===0?(qe=!0,e.memoizedState=a):(e=Sd(),ae.lanes|=e,Ta|=e,t)}function Ef(e,t,a,l,i){var u=Y.p;Y.p=u!==0&&8>u?u:8;var o=M.T,h={};M.T=h,Ys(e,!1,t,a);try{var b=i(),O=M.S;if(O!==null&&O(h,b),b!==null&&typeof b=="object"&&typeof b.then=="function"){var D=Km(b,l);Tn(e,t,D,vt(e))}else Tn(e,t,l,vt(e))}catch(w){Tn(e,t,{then:function(){},status:"rejected",reason:w},vt())}finally{Y.p=u,o!==null&&h.types!==null&&(o.types=h.types),M.T=o}}function $m(){}function Bs(e,t,a,l){if(e.tag!==5)throw Error(c(476));var i=jf(e).queue;Ef(e,i,t,I,a===null?$m:function(){return Tf(e),a(l)})}function jf(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:I,baseState:I,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:It,lastRenderedState:I},next:null};var a={};return t.next={memoizedState:a,baseState:a,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:It,lastRenderedState:a},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Tf(e){var t=jf(e);t.next===null&&(t=e.alternate.memoizedState),Tn(e,t.next.queue,{},vt())}function Qs(){return Ve(Qn)}function Nf(){return we().memoizedState}function Cf(){return we().memoizedState}function Wm(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var a=vt();e=pa(a);var l=ba(t,e,a);l!==null&&(ut(l,t,a),bn(l,t,a)),t={cache:gs()},e.payload=t;return}t=t.return}}function Im(e,t,a){var l=vt();a={lane:l,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},ki(e)?Af(t,a):(a=is(e,t,a,l),a!==null&&(ut(a,e,l),Rf(a,t,l)))}function Of(e,t,a){var l=vt();Tn(e,t,a,l)}function Tn(e,t,a,l){var i={lane:l,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null};if(ki(e))Af(t,i);else{var u=e.alternate;if(e.lanes===0&&(u===null||u.lanes===0)&&(u=t.lastRenderedReducer,u!==null))try{var o=t.lastRenderedState,h=u(o,a);if(i.hasEagerState=!0,i.eagerState=h,ft(h,o))return gi(e,t,i,0),je===null&&yi(),!1}catch{}if(a=is(e,t,i,l),a!==null)return ut(a,e,l),Rf(a,t,l),!0}return!1}function Ys(e,t,a,l){if(l={lane:2,revertLane:xr(),gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},ki(e)){if(t)throw Error(c(479))}else t=is(e,a,l,2),t!==null&&ut(t,e,2)}function ki(e){var t=e.alternate;return e===ae||t!==null&&t===ae}function Af(e,t){Dl=Ri=!0;var a=e.pending;a===null?t.next=t:(t.next=a.next,a.next=t),e.pending=t}function Rf(e,t,a){if((a&4194048)!==0){var l=t.lanes;l&=e.pendingLanes,a|=l,t.lanes=a,Mc(e,a)}}var Nn={readContext:Ve,use:Mi,useCallback:_e,useContext:_e,useEffect:_e,useImperativeHandle:_e,useLayoutEffect:_e,useInsertionEffect:_e,useMemo:_e,useReducer:_e,useRef:_e,useState:_e,useDebugValue:_e,useDeferredValue:_e,useTransition:_e,useSyncExternalStore:_e,useId:_e,useHostTransitionStatus:_e,useFormState:_e,useActionState:_e,useOptimistic:_e,useMemoCache:_e,useCacheRefresh:_e};Nn.useEffectEvent=_e;var _f={readContext:Ve,use:Mi,useCallback:function(e,t){return Ie().memoizedState=[e,t===void 0?null:t],e},useContext:Ve,useEffect:hf,useImperativeHandle:function(e,t,a){a=a!=null?a.concat([e]):null,wi(4194308,4,vf.bind(null,t,e),a)},useLayoutEffect:function(e,t){return wi(4194308,4,e,t)},useInsertionEffect:function(e,t){wi(4,2,e,t)},useMemo:function(e,t){var a=Ie();t=t===void 0?null:t;var l=e();if(Pa){oa(!0);try{e()}finally{oa(!1)}}return a.memoizedState=[l,t],l},useReducer:function(e,t,a){var l=Ie();if(a!==void 0){var i=a(t);if(Pa){oa(!0);try{a(t)}finally{oa(!1)}}}else i=t;return l.memoizedState=l.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},l.queue=e,e=e.dispatch=Im.bind(null,ae,e),[l.memoizedState,e]},useRef:function(e){var t=Ie();return e={current:e},t.memoizedState=e},useState:function(e){e=Us(e);var t=e.queue,a=Of.bind(null,ae,t);return t.dispatch=a,[e.memoizedState,a]},useDebugValue:Hs,useDeferredValue:function(e,t){var a=Ie();return Ls(a,e,t)},useTransition:function(){var e=Us(!1);return e=Ef.bind(null,ae,e.queue,!0,!1),Ie().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,a){var l=ae,i=Ie();if(ce){if(a===void 0)throw Error(c(407));a=a()}else{if(a=t(),je===null)throw Error(c(349));(se&127)!==0||$o(l,t,a)}i.memoizedState=a;var u={value:a,getSnapshot:t};return i.queue=u,hf(Io.bind(null,l,u,e),[e]),l.flags|=2048,zl(9,{destroy:void 0},Wo.bind(null,l,u,a,t),null),a},useId:function(){var e=Ie(),t=je.identifierPrefix;if(ce){var a=Lt,l=Ht;a=(l&~(1<<32-ot(l)-1)).toString(32)+a,t="_"+t+"R_"+a,a=_i++,0<a&&(t+="H"+a.toString(32)),t+="_"}else a=Xm++,t="_"+t+"r_"+a.toString(32)+"_";return e.memoizedState=t},useHostTransitionStatus:Qs,useFormState:rf,useActionState:rf,useOptimistic:function(e){var t=Ie();t.memoizedState=t.baseState=e;var a={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return t.queue=a,t=Ys.bind(null,ae,!0,a),a.dispatch=t,[e,t]},useMemoCache:Ms,useCacheRefresh:function(){return Ie().memoizedState=Wm.bind(null,ae)},useEffectEvent:function(e){var t=Ie(),a={impl:e};return t.memoizedState=a,function(){if((he&2)!==0)throw Error(c(440));return a.impl.apply(void 0,arguments)}}},Gs={readContext:Ve,use:Mi,useCallback:bf,useContext:Ve,useEffect:qs,useImperativeHandle:pf,useInsertionEffect:yf,useLayoutEffect:gf,useMemo:xf,useReducer:zi,useRef:df,useState:function(){return zi(It)},useDebugValue:Hs,useDeferredValue:function(e,t){var a=we();return Sf(a,be.memoizedState,e,t)},useTransition:function(){var e=zi(It)[0],t=we().memoizedState;return[typeof e=="boolean"?e:jn(e),t]},useSyncExternalStore:Fo,useId:Nf,useHostTransitionStatus:Qs,useFormState:cf,useActionState:cf,useOptimistic:function(e,t){var a=we();return tf(a,be,e,t)},useMemoCache:Ms,useCacheRefresh:Cf};Gs.useEffectEvent=mf;var Df={readContext:Ve,use:Mi,useCallback:bf,useContext:Ve,useEffect:qs,useImperativeHandle:pf,useInsertionEffect:yf,useLayoutEffect:gf,useMemo:xf,useReducer:ws,useRef:df,useState:function(){return ws(It)},useDebugValue:Hs,useDeferredValue:function(e,t){var a=we();return be===null?Ls(a,e,t):Sf(a,be.memoizedState,e,t)},useTransition:function(){var e=ws(It)[0],t=we().memoizedState;return[typeof e=="boolean"?e:jn(e),t]},useSyncExternalStore:Fo,useId:Nf,useHostTransitionStatus:Qs,useFormState:ff,useActionState:ff,useOptimistic:function(e,t){var a=we();return be!==null?tf(a,be,e,t):(a.baseState=e,[e,a.queue.dispatch])},useMemoCache:Ms,useCacheRefresh:Cf};Df.useEffectEvent=mf;function Ks(e,t,a,l){t=e.memoizedState,a=a(l,t),a=a==null?t:T({},t,a),e.memoizedState=a,e.lanes===0&&(e.updateQueue.baseState=a)}var Xs={enqueueSetState:function(e,t,a){e=e._reactInternals;var l=vt(),i=pa(l);i.payload=t,a!=null&&(i.callback=a),t=ba(e,i,l),t!==null&&(ut(t,e,l),bn(t,e,l))},enqueueReplaceState:function(e,t,a){e=e._reactInternals;var l=vt(),i=pa(l);i.tag=1,i.payload=t,a!=null&&(i.callback=a),t=ba(e,i,l),t!==null&&(ut(t,e,l),bn(t,e,l))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var a=vt(),l=pa(a);l.tag=2,t!=null&&(l.callback=t),t=ba(e,l,a),t!==null&&(ut(t,e,a),bn(t,e,a))}};function Mf(e,t,a,l,i,u,o){return e=e.stateNode,typeof e.shouldComponentUpdate=="function"?e.shouldComponentUpdate(l,u,o):t.prototype&&t.prototype.isPureReactComponent?!fn(a,l)||!fn(i,u):!0}function zf(e,t,a,l){e=t.state,typeof t.componentWillReceiveProps=="function"&&t.componentWillReceiveProps(a,l),typeof t.UNSAFE_componentWillReceiveProps=="function"&&t.UNSAFE_componentWillReceiveProps(a,l),t.state!==e&&Xs.enqueueReplaceState(t,t.state,null)}function el(e,t){var a=t;if("ref"in t){a={};for(var l in t)l!=="ref"&&(a[l]=t[l])}if(e=e.defaultProps){a===t&&(a=T({},a));for(var i in e)a[i]===void 0&&(a[i]=e[i])}return a}function wf(e){mi(e)}function Uf(e){console.error(e)}function kf(e){mi(e)}function qi(e,t){try{var a=e.onUncaughtError;a(t.value,{componentStack:t.stack})}catch(l){setTimeout(function(){throw l})}}function qf(e,t,a){try{var l=e.onCaughtError;l(a.value,{componentStack:a.stack,errorBoundary:t.tag===1?t.stateNode:null})}catch(i){setTimeout(function(){throw i})}}function Zs(e,t,a){return a=pa(a),a.tag=3,a.payload={element:null},a.callback=function(){qi(e,t)},a}function Hf(e){return e=pa(e),e.tag=3,e}function Lf(e,t,a,l){var i=a.type.getDerivedStateFromError;if(typeof i=="function"){var u=l.value;e.payload=function(){return i(u)},e.callback=function(){qf(t,a,l)}}var o=a.stateNode;o!==null&&typeof o.componentDidCatch=="function"&&(e.callback=function(){qf(t,a,l),typeof i!="function"&&(Na===null?Na=new Set([this]):Na.add(this));var h=l.stack;this.componentDidCatch(l.value,{componentStack:h!==null?h:""})})}function Pm(e,t,a,l,i){if(a.flags|=32768,l!==null&&typeof l=="object"&&typeof l.then=="function"){if(t=a.alternate,t!==null&&Nl(t,a,i,!0),a=ht.current,a!==null){switch(a.tag){case 31:case 13:return At===null?Fi():a.alternate===null&&De===0&&(De=3),a.flags&=-257,a.flags|=65536,a.lanes=i,l===Ti?a.flags|=16384:(t=a.updateQueue,t===null?a.updateQueue=new Set([l]):t.add(l),vr(e,l,i)),!1;case 22:return a.flags|=65536,l===Ti?a.flags|=16384:(t=a.updateQueue,t===null?(t={transitions:null,markerInstances:null,retryQueue:new Set([l])},a.updateQueue=t):(a=t.retryQueue,a===null?t.retryQueue=new Set([l]):a.add(l)),vr(e,l,i)),!1}throw Error(c(435,a.tag))}return vr(e,l,i),Fi(),!1}if(ce)return t=ht.current,t!==null?((t.flags&65536)===0&&(t.flags|=256),t.flags|=65536,t.lanes=i,l!==fs&&(e=Error(c(422),{cause:l}),mn(Tt(e,a)))):(l!==fs&&(t=Error(c(423),{cause:l}),mn(Tt(t,a))),e=e.current.alternate,e.flags|=65536,i&=-i,e.lanes|=i,l=Tt(l,a),i=Zs(e.stateNode,l,i),Es(e,i),De!==4&&(De=2)),!1;var u=Error(c(520),{cause:l});if(u=Tt(u,a),zn===null?zn=[u]:zn.push(u),De!==4&&(De=2),t===null)return!0;l=Tt(l,a),a=t;do{switch(a.tag){case 3:return a.flags|=65536,e=i&-i,a.lanes|=e,e=Zs(a.stateNode,l,e),Es(a,e),!1;case 1:if(t=a.type,u=a.stateNode,(a.flags&128)===0&&(typeof t.getDerivedStateFromError=="function"||u!==null&&typeof u.componentDidCatch=="function"&&(Na===null||!Na.has(u))))return a.flags|=65536,i&=-i,a.lanes|=i,i=Hf(i),Lf(i,e,a,l),Es(a,i),!1}a=a.return}while(a!==null);return!1}var Vs=Error(c(461)),qe=!1;function Je(e,t,a,l){t.child=e===null?Yo(t,null,a,l):Ia(t,e.child,a,l)}function Bf(e,t,a,l,i){a=a.render;var u=t.ref;if("ref"in l){var o={};for(var h in l)h!=="ref"&&(o[h]=l[h])}else o=l;return Ja(t),l=As(e,t,a,o,u,i),h=Rs(),e!==null&&!qe?(_s(e,t,i),Pt(e,t,i)):(ce&&h&&cs(t),t.flags|=1,Je(e,t,l,i),t.child)}function Qf(e,t,a,l,i){if(e===null){var u=a.type;return typeof u=="function"&&!us(u)&&u.defaultProps===void 0&&a.compare===null?(t.tag=15,t.type=u,Yf(e,t,u,l,i)):(e=pi(a.type,null,l,t,t.mode,i),e.ref=t.ref,e.return=t,t.child=e)}if(u=e.child,!tr(e,i)){var o=u.memoizedProps;if(a=a.compare,a=a!==null?a:fn,a(o,l)&&e.ref===t.ref)return Pt(e,t,i)}return t.flags|=1,e=Vt(u,l),e.ref=t.ref,e.return=t,t.child=e}function Yf(e,t,a,l,i){if(e!==null){var u=e.memoizedProps;if(fn(u,l)&&e.ref===t.ref)if(qe=!1,t.pendingProps=l=u,tr(e,i))(e.flags&131072)!==0&&(qe=!0);else return t.lanes=e.lanes,Pt(e,t,i)}return Js(e,t,a,l,i)}function Gf(e,t,a,l){var i=l.children,u=e!==null?e.memoizedState:null;if(e===null&&t.stateNode===null&&(t.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),l.mode==="hidden"){if((t.flags&128)!==0){if(u=u!==null?u.baseLanes|a:a,e!==null){for(l=t.child=e.child,i=0;l!==null;)i=i|l.lanes|l.childLanes,l=l.sibling;l=i&~u}else l=0,t.child=null;return Kf(e,t,u,a,l)}if((a&536870912)!==0)t.memoizedState={baseLanes:0,cachePool:null},e!==null&&Ei(t,u!==null?u.cachePool:null),u!==null?Xo(t,u):Ts(),Zo(t);else return l=t.lanes=536870912,Kf(e,t,u!==null?u.baseLanes|a:a,a,l)}else u!==null?(Ei(t,u.cachePool),Xo(t,u),Sa(),t.memoizedState=null):(e!==null&&Ei(t,null),Ts(),Sa());return Je(e,t,i,a),t.child}function Cn(e,t){return e!==null&&e.tag===22||t.stateNode!==null||(t.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),t.sibling}function Kf(e,t,a,l,i){var u=ps();return u=u===null?null:{parent:Ue._currentValue,pool:u},t.memoizedState={baseLanes:a,cachePool:u},e!==null&&Ei(t,null),Ts(),Zo(t),e!==null&&Nl(e,t,l,!0),t.childLanes=i,null}function Hi(e,t){return t=Bi({mode:t.mode,children:t.children},e.mode),t.ref=e.ref,e.child=t,t.return=e,t}function Xf(e,t,a){return Ia(t,e.child,null,a),e=Hi(t,t.pendingProps),e.flags|=2,mt(t),t.memoizedState=null,e}function ey(e,t,a){var l=t.pendingProps,i=(t.flags&128)!==0;if(t.flags&=-129,e===null){if(ce){if(l.mode==="hidden")return e=Hi(t,l),t.lanes=536870912,Cn(null,e);if(Cs(t),(e=Ne)?(e=lh(e,Ot),e=e!==null&&e.data==="&"?e:null,e!==null&&(t.memoizedState={dehydrated:e,treeContext:ha!==null?{id:Ht,overflow:Lt}:null,retryLane:536870912,hydrationErrors:null},a=Oo(e),a.return=t,t.child=a,Ze=t,Ne=null)):e=null,e===null)throw ya(t);return t.lanes=536870912,null}return Hi(t,l)}var u=e.memoizedState;if(u!==null){var o=u.dehydrated;if(Cs(t),i)if(t.flags&256)t.flags&=-257,t=Xf(e,t,a);else if(t.memoizedState!==null)t.child=e.child,t.flags|=128,t=null;else throw Error(c(558));else if(qe||Nl(e,t,a,!1),i=(a&e.childLanes)!==0,qe||i){if(l=je,l!==null&&(o=zc(l,a),o!==0&&o!==u.retryLane))throw u.retryLane=o,Ka(e,o),ut(l,e,o),Vs;Fi(),t=Xf(e,t,a)}else e=u.treeContext,Ne=Rt(o.nextSibling),Ze=t,ce=!0,ma=null,Ot=!1,e!==null&&_o(t,e),t=Hi(t,l),t.flags|=4096;return t}return e=Vt(e.child,{mode:l.mode,children:l.children}),e.ref=t.ref,t.child=e,e.return=t,e}function Li(e,t){var a=t.ref;if(a===null)e!==null&&e.ref!==null&&(t.flags|=4194816);else{if(typeof a!="function"&&typeof a!="object")throw Error(c(284));(e===null||e.ref!==a)&&(t.flags|=4194816)}}function Js(e,t,a,l,i){return Ja(t),a=As(e,t,a,l,void 0,i),l=Rs(),e!==null&&!qe?(_s(e,t,i),Pt(e,t,i)):(ce&&l&&cs(t),t.flags|=1,Je(e,t,a,i),t.child)}function Zf(e,t,a,l,i,u){return Ja(t),t.updateQueue=null,a=Jo(t,l,a,i),Vo(e),l=Rs(),e!==null&&!qe?(_s(e,t,u),Pt(e,t,u)):(ce&&l&&cs(t),t.flags|=1,Je(e,t,a,u),t.child)}function Vf(e,t,a,l,i){if(Ja(t),t.stateNode===null){var u=Sl,o=a.contextType;typeof o=="object"&&o!==null&&(u=Ve(o)),u=new a(l,u),t.memoizedState=u.state!==null&&u.state!==void 0?u.state:null,u.updater=Xs,t.stateNode=u,u._reactInternals=t,u=t.stateNode,u.props=l,u.state=t.memoizedState,u.refs={},xs(t),o=a.contextType,u.context=typeof o=="object"&&o!==null?Ve(o):Sl,u.state=t.memoizedState,o=a.getDerivedStateFromProps,typeof o=="function"&&(Ks(t,a,o,l),u.state=t.memoizedState),typeof a.getDerivedStateFromProps=="function"||typeof u.getSnapshotBeforeUpdate=="function"||typeof u.UNSAFE_componentWillMount!="function"&&typeof u.componentWillMount!="function"||(o=u.state,typeof u.componentWillMount=="function"&&u.componentWillMount(),typeof u.UNSAFE_componentWillMount=="function"&&u.UNSAFE_componentWillMount(),o!==u.state&&Xs.enqueueReplaceState(u,u.state,null),Sn(t,l,u,i),xn(),u.state=t.memoizedState),typeof u.componentDidMount=="function"&&(t.flags|=4194308),l=!0}else if(e===null){u=t.stateNode;var h=t.memoizedProps,b=el(a,h);u.props=b;var O=u.context,D=a.contextType;o=Sl,typeof D=="object"&&D!==null&&(o=Ve(D));var w=a.getDerivedStateFromProps;D=typeof w=="function"||typeof u.getSnapshotBeforeUpdate=="function",h=t.pendingProps!==h,D||typeof u.UNSAFE_componentWillReceiveProps!="function"&&typeof u.componentWillReceiveProps!="function"||(h||O!==o)&&zf(t,u,l,o),va=!1;var A=t.memoizedState;u.state=A,Sn(t,l,u,i),xn(),O=t.memoizedState,h||A!==O||va?(typeof w=="function"&&(Ks(t,a,w,l),O=t.memoizedState),(b=va||Mf(t,a,b,l,A,O,o))?(D||typeof u.UNSAFE_componentWillMount!="function"&&typeof u.componentWillMount!="function"||(typeof u.componentWillMount=="function"&&u.componentWillMount(),typeof u.UNSAFE_componentWillMount=="function"&&u.UNSAFE_componentWillMount()),typeof u.componentDidMount=="function"&&(t.flags|=4194308)):(typeof u.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=l,t.memoizedState=O),u.props=l,u.state=O,u.context=o,l=b):(typeof u.componentDidMount=="function"&&(t.flags|=4194308),l=!1)}else{u=t.stateNode,Ss(e,t),o=t.memoizedProps,D=el(a,o),u.props=D,w=t.pendingProps,A=u.context,O=a.contextType,b=Sl,typeof O=="object"&&O!==null&&(b=Ve(O)),h=a.getDerivedStateFromProps,(O=typeof h=="function"||typeof u.getSnapshotBeforeUpdate=="function")||typeof u.UNSAFE_componentWillReceiveProps!="function"&&typeof u.componentWillReceiveProps!="function"||(o!==w||A!==b)&&zf(t,u,l,b),va=!1,A=t.memoizedState,u.state=A,Sn(t,l,u,i),xn();var _=t.memoizedState;o!==w||A!==_||va||e!==null&&e.dependencies!==null&&xi(e.dependencies)?(typeof h=="function"&&(Ks(t,a,h,l),_=t.memoizedState),(D=va||Mf(t,a,D,l,A,_,b)||e!==null&&e.dependencies!==null&&xi(e.dependencies))?(O||typeof u.UNSAFE_componentWillUpdate!="function"&&typeof u.componentWillUpdate!="function"||(typeof u.componentWillUpdate=="function"&&u.componentWillUpdate(l,_,b),typeof u.UNSAFE_componentWillUpdate=="function"&&u.UNSAFE_componentWillUpdate(l,_,b)),typeof u.componentDidUpdate=="function"&&(t.flags|=4),typeof u.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof u.componentDidUpdate!="function"||o===e.memoizedProps&&A===e.memoizedState||(t.flags|=4),typeof u.getSnapshotBeforeUpdate!="function"||o===e.memoizedProps&&A===e.memoizedState||(t.flags|=1024),t.memoizedProps=l,t.memoizedState=_),u.props=l,u.state=_,u.context=b,l=D):(typeof u.componentDidUpdate!="function"||o===e.memoizedProps&&A===e.memoizedState||(t.flags|=4),typeof u.getSnapshotBeforeUpdate!="function"||o===e.memoizedProps&&A===e.memoizedState||(t.flags|=1024),l=!1)}return u=l,Li(e,t),l=(t.flags&128)!==0,u||l?(u=t.stateNode,a=l&&typeof a.getDerivedStateFromError!="function"?null:u.render(),t.flags|=1,e!==null&&l?(t.child=Ia(t,e.child,null,i),t.child=Ia(t,null,a,i)):Je(e,t,a,i),t.memoizedState=u.state,e=t.child):e=Pt(e,t,i),e}function Jf(e,t,a,l){return Za(),t.flags|=256,Je(e,t,a,l),t.child}var Fs={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function $s(e){return{baseLanes:e,cachePool:ko()}}function Ws(e,t,a){return e=e!==null?e.childLanes&~a:0,t&&(e|=gt),e}function Ff(e,t,a){var l=t.pendingProps,i=!1,u=(t.flags&128)!==0,o;if((o=u)||(o=e!==null&&e.memoizedState===null?!1:(ze.current&2)!==0),o&&(i=!0,t.flags&=-129),o=(t.flags&32)!==0,t.flags&=-33,e===null){if(ce){if(i?xa(t):Sa(),(e=Ne)?(e=lh(e,Ot),e=e!==null&&e.data!=="&"?e:null,e!==null&&(t.memoizedState={dehydrated:e,treeContext:ha!==null?{id:Ht,overflow:Lt}:null,retryLane:536870912,hydrationErrors:null},a=Oo(e),a.return=t,t.child=a,Ze=t,Ne=null)):e=null,e===null)throw ya(t);return zr(e)?t.lanes=32:t.lanes=536870912,null}var h=l.children;return l=l.fallback,i?(Sa(),i=t.mode,h=Bi({mode:"hidden",children:h},i),l=Xa(l,i,a,null),h.return=t,l.return=t,h.sibling=l,t.child=h,l=t.child,l.memoizedState=$s(a),l.childLanes=Ws(e,o,a),t.memoizedState=Fs,Cn(null,l)):(xa(t),Is(t,h))}var b=e.memoizedState;if(b!==null&&(h=b.dehydrated,h!==null)){if(u)t.flags&256?(xa(t),t.flags&=-257,t=Ps(e,t,a)):t.memoizedState!==null?(Sa(),t.child=e.child,t.flags|=128,t=null):(Sa(),h=l.fallback,i=t.mode,l=Bi({mode:"visible",children:l.children},i),h=Xa(h,i,a,null),h.flags|=2,l.return=t,h.return=t,l.sibling=h,t.child=l,Ia(t,e.child,null,a),l=t.child,l.memoizedState=$s(a),l.childLanes=Ws(e,o,a),t.memoizedState=Fs,t=Cn(null,l));else if(xa(t),zr(h)){if(o=h.nextSibling&&h.nextSibling.dataset,o)var O=o.dgst;o=O,l=Error(c(419)),l.stack="",l.digest=o,mn({value:l,source:null,stack:null}),t=Ps(e,t,a)}else if(qe||Nl(e,t,a,!1),o=(a&e.childLanes)!==0,qe||o){if(o=je,o!==null&&(l=zc(o,a),l!==0&&l!==b.retryLane))throw b.retryLane=l,Ka(e,l),ut(o,e,l),Vs;Mr(h)||Fi(),t=Ps(e,t,a)}else Mr(h)?(t.flags|=192,t.child=e.child,t=null):(e=b.treeContext,Ne=Rt(h.nextSibling),Ze=t,ce=!0,ma=null,Ot=!1,e!==null&&_o(t,e),t=Is(t,l.children),t.flags|=4096);return t}return i?(Sa(),h=l.fallback,i=t.mode,b=e.child,O=b.sibling,l=Vt(b,{mode:"hidden",children:l.children}),l.subtreeFlags=b.subtreeFlags&65011712,O!==null?h=Vt(O,h):(h=Xa(h,i,a,null),h.flags|=2),h.return=t,l.return=t,l.sibling=h,t.child=l,Cn(null,l),l=t.child,h=e.child.memoizedState,h===null?h=$s(a):(i=h.cachePool,i!==null?(b=Ue._currentValue,i=i.parent!==b?{parent:b,pool:b}:i):i=ko(),h={baseLanes:h.baseLanes|a,cachePool:i}),l.memoizedState=h,l.childLanes=Ws(e,o,a),t.memoizedState=Fs,Cn(e.child,l)):(xa(t),a=e.child,e=a.sibling,a=Vt(a,{mode:"visible",children:l.children}),a.return=t,a.sibling=null,e!==null&&(o=t.deletions,o===null?(t.deletions=[e],t.flags|=16):o.push(e)),t.child=a,t.memoizedState=null,a)}function Is(e,t){return t=Bi({mode:"visible",children:t},e.mode),t.return=e,e.child=t}function Bi(e,t){return e=dt(22,e,null,t),e.lanes=0,e}function Ps(e,t,a){return Ia(t,e.child,null,a),e=Is(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function $f(e,t,a){e.lanes|=t;var l=e.alternate;l!==null&&(l.lanes|=t),ms(e.return,t,a)}function er(e,t,a,l,i,u){var o=e.memoizedState;o===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:l,tail:a,tailMode:i,treeForkCount:u}:(o.isBackwards=t,o.rendering=null,o.renderingStartTime=0,o.last=l,o.tail=a,o.tailMode=i,o.treeForkCount=u)}function Wf(e,t,a){var l=t.pendingProps,i=l.revealOrder,u=l.tail;l=l.children;var o=ze.current,h=(o&2)!==0;if(h?(o=o&1|2,t.flags|=128):o&=1,G(ze,o),Je(e,t,l,a),l=ce?hn:0,!h&&e!==null&&(e.flags&128)!==0)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&$f(e,a,t);else if(e.tag===19)$f(e,a,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}switch(i){case"forwards":for(a=t.child,i=null;a!==null;)e=a.alternate,e!==null&&Ai(e)===null&&(i=a),a=a.sibling;a=i,a===null?(i=t.child,t.child=null):(i=a.sibling,a.sibling=null),er(t,!1,i,a,u,l);break;case"backwards":case"unstable_legacy-backwards":for(a=null,i=t.child,t.child=null;i!==null;){if(e=i.alternate,e!==null&&Ai(e)===null){t.child=i;break}e=i.sibling,i.sibling=a,a=i,i=e}er(t,!0,a,null,u,l);break;case"together":er(t,!1,null,null,void 0,l);break;default:t.memoizedState=null}return t.child}function Pt(e,t,a){if(e!==null&&(t.dependencies=e.dependencies),Ta|=t.lanes,(a&t.childLanes)===0)if(e!==null){if(Nl(e,t,a,!1),(a&t.childLanes)===0)return null}else return null;if(e!==null&&t.child!==e.child)throw Error(c(153));if(t.child!==null){for(e=t.child,a=Vt(e,e.pendingProps),t.child=a,a.return=t;e.sibling!==null;)e=e.sibling,a=a.sibling=Vt(e,e.pendingProps),a.return=t;a.sibling=null}return t.child}function tr(e,t){return(e.lanes&t)!==0?!0:(e=e.dependencies,!!(e!==null&&xi(e)))}function ty(e,t,a){switch(t.tag){case 3:We(t,t.stateNode.containerInfo),ga(t,Ue,e.memoizedState.cache),Za();break;case 27:case 5:Wl(t);break;case 4:We(t,t.stateNode.containerInfo);break;case 10:ga(t,t.type,t.memoizedProps.value);break;case 31:if(t.memoizedState!==null)return t.flags|=128,Cs(t),null;break;case 13:var l=t.memoizedState;if(l!==null)return l.dehydrated!==null?(xa(t),t.flags|=128,null):(a&t.child.childLanes)!==0?Ff(e,t,a):(xa(t),e=Pt(e,t,a),e!==null?e.sibling:null);xa(t);break;case 19:var i=(e.flags&128)!==0;if(l=(a&t.childLanes)!==0,l||(Nl(e,t,a,!1),l=(a&t.childLanes)!==0),i){if(l)return Wf(e,t,a);t.flags|=128}if(i=t.memoizedState,i!==null&&(i.rendering=null,i.tail=null,i.lastEffect=null),G(ze,ze.current),l)break;return null;case 22:return t.lanes=0,Gf(e,t,a,t.pendingProps);case 24:ga(t,Ue,e.memoizedState.cache)}return Pt(e,t,a)}function If(e,t,a){if(e!==null)if(e.memoizedProps!==t.pendingProps)qe=!0;else{if(!tr(e,a)&&(t.flags&128)===0)return qe=!1,ty(e,t,a);qe=(e.flags&131072)!==0}else qe=!1,ce&&(t.flags&1048576)!==0&&Ro(t,hn,t.index);switch(t.lanes=0,t.tag){case 16:e:{var l=t.pendingProps;if(e=$a(t.elementType),t.type=e,typeof e=="function")us(e)?(l=el(e,l),t.tag=1,t=Vf(null,t,e,l,a)):(t.tag=0,t=Js(null,t,e,l,a));else{if(e!=null){var i=e.$$typeof;if(i===X){t.tag=11,t=Bf(null,t,e,l,a);break e}else if(i===$){t.tag=14,t=Qf(null,t,e,l,a);break e}}throw t=Gt(e)||e,Error(c(306,t,""))}}return t;case 0:return Js(e,t,t.type,t.pendingProps,a);case 1:return l=t.type,i=el(l,t.pendingProps),Vf(e,t,l,i,a);case 3:e:{if(We(t,t.stateNode.containerInfo),e===null)throw Error(c(387));l=t.pendingProps;var u=t.memoizedState;i=u.element,Ss(e,t),Sn(t,l,null,a);var o=t.memoizedState;if(l=o.cache,ga(t,Ue,l),l!==u.cache&&ys(t,[Ue],a,!0),xn(),l=o.element,u.isDehydrated)if(u={element:l,isDehydrated:!1,cache:o.cache},t.updateQueue.baseState=u,t.memoizedState=u,t.flags&256){t=Jf(e,t,l,a);break e}else if(l!==i){i=Tt(Error(c(424)),t),mn(i),t=Jf(e,t,l,a);break e}else for(e=t.stateNode.containerInfo,e.nodeType===9?e=e.body:e=e.nodeName==="HTML"?e.ownerDocument.body:e,Ne=Rt(e.firstChild),Ze=t,ce=!0,ma=null,Ot=!0,a=Yo(t,null,l,a),t.child=a;a;)a.flags=a.flags&-3|4096,a=a.sibling;else{if(Za(),l===i){t=Pt(e,t,a);break e}Je(e,t,l,a)}t=t.child}return t;case 26:return Li(e,t),e===null?(a=ch(t.type,null,t.pendingProps,null))?t.memoizedState=a:ce||(a=t.type,e=t.pendingProps,l=au(ne.current).createElement(a),l[Xe]=t,l[et]=e,Fe(l,a,e),Ye(l),t.stateNode=l):t.memoizedState=ch(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return Wl(t),e===null&&ce&&(l=t.stateNode=uh(t.type,t.pendingProps,ne.current),Ze=t,Ot=!0,i=Ne,Ra(t.type)?(wr=i,Ne=Rt(l.firstChild)):Ne=i),Je(e,t,t.pendingProps.children,a),Li(e,t),e===null&&(t.flags|=4194304),t.child;case 5:return e===null&&ce&&((i=l=Ne)&&(l=Dy(l,t.type,t.pendingProps,Ot),l!==null?(t.stateNode=l,Ze=t,Ne=Rt(l.firstChild),Ot=!1,i=!0):i=!1),i||ya(t)),Wl(t),i=t.type,u=t.pendingProps,o=e!==null?e.memoizedProps:null,l=u.children,Rr(i,u)?l=null:o!==null&&Rr(i,o)&&(t.flags|=32),t.memoizedState!==null&&(i=As(e,t,Zm,null,null,a),Qn._currentValue=i),Li(e,t),Je(e,t,l,a),t.child;case 6:return e===null&&ce&&((e=a=Ne)&&(a=My(a,t.pendingProps,Ot),a!==null?(t.stateNode=a,Ze=t,Ne=null,e=!0):e=!1),e||ya(t)),null;case 13:return Ff(e,t,a);case 4:return We(t,t.stateNode.containerInfo),l=t.pendingProps,e===null?t.child=Ia(t,null,l,a):Je(e,t,l,a),t.child;case 11:return Bf(e,t,t.type,t.pendingProps,a);case 7:return Je(e,t,t.pendingProps,a),t.child;case 8:return Je(e,t,t.pendingProps.children,a),t.child;case 12:return Je(e,t,t.pendingProps.children,a),t.child;case 10:return l=t.pendingProps,ga(t,t.type,l.value),Je(e,t,l.children,a),t.child;case 9:return i=t.type._context,l=t.pendingProps.children,Ja(t),i=Ve(i),l=l(i),t.flags|=1,Je(e,t,l,a),t.child;case 14:return Qf(e,t,t.type,t.pendingProps,a);case 15:return Yf(e,t,t.type,t.pendingProps,a);case 19:return Wf(e,t,a);case 31:return ey(e,t,a);case 22:return Gf(e,t,a,t.pendingProps);case 24:return Ja(t),l=Ve(Ue),e===null?(i=ps(),i===null&&(i=je,u=gs(),i.pooledCache=u,u.refCount++,u!==null&&(i.pooledCacheLanes|=a),i=u),t.memoizedState={parent:l,cache:i},xs(t),ga(t,Ue,i)):((e.lanes&a)!==0&&(Ss(e,t),Sn(t,null,null,a),xn()),i=e.memoizedState,u=t.memoizedState,i.parent!==l?(i={parent:l,cache:l},t.memoizedState=i,t.lanes===0&&(t.memoizedState=t.updateQueue.baseState=i),ga(t,Ue,l)):(l=u.cache,ga(t,Ue,l),l!==i.cache&&ys(t,[Ue],a,!0))),Je(e,t,t.pendingProps.children,a),t.child;case 29:throw t.pendingProps}throw Error(c(156,t.tag))}function ea(e){e.flags|=4}function ar(e,t,a,l,i){if((t=(e.mode&32)!==0)&&(t=!1),t){if(e.flags|=16777216,(i&335544128)===i)if(e.stateNode.complete)e.flags|=8192;else if(Nd())e.flags|=8192;else throw Wa=Ti,bs}else e.flags&=-16777217}function Pf(e,t){if(t.type!=="stylesheet"||(t.state.loading&4)!==0)e.flags&=-16777217;else if(e.flags|=16777216,!mh(t))if(Nd())e.flags|=8192;else throw Wa=Ti,bs}function Qi(e,t){t!==null&&(e.flags|=4),e.flags&16384&&(t=e.tag!==22?_c():536870912,e.lanes|=t,ql|=t)}function On(e,t){if(!ce)switch(e.tailMode){case"hidden":t=e.tail;for(var a=null;t!==null;)t.alternate!==null&&(a=t),t=t.sibling;a===null?e.tail=null:a.sibling=null;break;case"collapsed":a=e.tail;for(var l=null;a!==null;)a.alternate!==null&&(l=a),a=a.sibling;l===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:l.sibling=null}}function Ce(e){var t=e.alternate!==null&&e.alternate.child===e.child,a=0,l=0;if(t)for(var i=e.child;i!==null;)a|=i.lanes|i.childLanes,l|=i.subtreeFlags&65011712,l|=i.flags&65011712,i.return=e,i=i.sibling;else for(i=e.child;i!==null;)a|=i.lanes|i.childLanes,l|=i.subtreeFlags,l|=i.flags,i.return=e,i=i.sibling;return e.subtreeFlags|=l,e.childLanes=a,t}function ay(e,t,a){var l=t.pendingProps;switch(os(t),t.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Ce(t),null;case 1:return Ce(t),null;case 3:return a=t.stateNode,l=null,e!==null&&(l=e.memoizedState.cache),t.memoizedState.cache!==l&&(t.flags|=2048),$t(Ue),Me(),a.pendingContext&&(a.context=a.pendingContext,a.pendingContext=null),(e===null||e.child===null)&&(Tl(t)?ea(t):e===null||e.memoizedState.isDehydrated&&(t.flags&256)===0||(t.flags|=1024,ds())),Ce(t),null;case 26:var i=t.type,u=t.memoizedState;return e===null?(ea(t),u!==null?(Ce(t),Pf(t,u)):(Ce(t),ar(t,i,null,l,a))):u?u!==e.memoizedState?(ea(t),Ce(t),Pf(t,u)):(Ce(t),t.flags&=-16777217):(e=e.memoizedProps,e!==l&&ea(t),Ce(t),ar(t,i,e,l,a)),null;case 27:if(In(t),a=ne.current,i=t.type,e!==null&&t.stateNode!=null)e.memoizedProps!==l&&ea(t);else{if(!l){if(t.stateNode===null)throw Error(c(166));return Ce(t),null}e=V.current,Tl(t)?Do(t):(e=uh(i,l,a),t.stateNode=e,ea(t))}return Ce(t),null;case 5:if(In(t),i=t.type,e!==null&&t.stateNode!=null)e.memoizedProps!==l&&ea(t);else{if(!l){if(t.stateNode===null)throw Error(c(166));return Ce(t),null}if(u=V.current,Tl(t))Do(t);else{var o=au(ne.current);switch(u){case 1:u=o.createElementNS("http://www.w3.org/2000/svg",i);break;case 2:u=o.createElementNS("http://www.w3.org/1998/Math/MathML",i);break;default:switch(i){case"svg":u=o.createElementNS("http://www.w3.org/2000/svg",i);break;case"math":u=o.createElementNS("http://www.w3.org/1998/Math/MathML",i);break;case"script":u=o.createElement("div"),u.innerHTML="<script><\/script>",u=u.removeChild(u.firstChild);break;case"select":u=typeof l.is=="string"?o.createElement("select",{is:l.is}):o.createElement("select"),l.multiple?u.multiple=!0:l.size&&(u.size=l.size);break;default:u=typeof l.is=="string"?o.createElement(i,{is:l.is}):o.createElement(i)}}u[Xe]=t,u[et]=l;e:for(o=t.child;o!==null;){if(o.tag===5||o.tag===6)u.appendChild(o.stateNode);else if(o.tag!==4&&o.tag!==27&&o.child!==null){o.child.return=o,o=o.child;continue}if(o===t)break e;for(;o.sibling===null;){if(o.return===null||o.return===t)break e;o=o.return}o.sibling.return=o.return,o=o.sibling}t.stateNode=u;e:switch(Fe(u,i,l),i){case"button":case"input":case"select":case"textarea":l=!!l.autoFocus;break e;case"img":l=!0;break e;default:l=!1}l&&ea(t)}}return Ce(t),ar(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,a),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==l&&ea(t);else{if(typeof l!="string"&&t.stateNode===null)throw Error(c(166));if(e=ne.current,Tl(t)){if(e=t.stateNode,a=t.memoizedProps,l=null,i=Ze,i!==null)switch(i.tag){case 27:case 5:l=i.memoizedProps}e[Xe]=t,e=!!(e.nodeValue===a||l!==null&&l.suppressHydrationWarning===!0||Fd(e.nodeValue,a)),e||ya(t,!0)}else e=au(e).createTextNode(l),e[Xe]=t,t.stateNode=e}return Ce(t),null;case 31:if(a=t.memoizedState,e===null||e.memoizedState!==null){if(l=Tl(t),a!==null){if(e===null){if(!l)throw Error(c(318));if(e=t.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(c(557));e[Xe]=t}else Za(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Ce(t),e=!1}else a=ds(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),e=!0;if(!e)return t.flags&256?(mt(t),t):(mt(t),null);if((t.flags&128)!==0)throw Error(c(558))}return Ce(t),null;case 13:if(l=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(i=Tl(t),l!==null&&l.dehydrated!==null){if(e===null){if(!i)throw Error(c(318));if(i=t.memoizedState,i=i!==null?i.dehydrated:null,!i)throw Error(c(317));i[Xe]=t}else Za(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Ce(t),i=!1}else i=ds(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=i),i=!0;if(!i)return t.flags&256?(mt(t),t):(mt(t),null)}return mt(t),(t.flags&128)!==0?(t.lanes=a,t):(a=l!==null,e=e!==null&&e.memoizedState!==null,a&&(l=t.child,i=null,l.alternate!==null&&l.alternate.memoizedState!==null&&l.alternate.memoizedState.cachePool!==null&&(i=l.alternate.memoizedState.cachePool.pool),u=null,l.memoizedState!==null&&l.memoizedState.cachePool!==null&&(u=l.memoizedState.cachePool.pool),u!==i&&(l.flags|=2048)),a!==e&&a&&(t.child.flags|=8192),Qi(t,t.updateQueue),Ce(t),null);case 4:return Me(),e===null&&Tr(t.stateNode.containerInfo),Ce(t),null;case 10:return $t(t.type),Ce(t),null;case 19:if(H(ze),l=t.memoizedState,l===null)return Ce(t),null;if(i=(t.flags&128)!==0,u=l.rendering,u===null)if(i)On(l,!1);else{if(De!==0||e!==null&&(e.flags&128)!==0)for(e=t.child;e!==null;){if(u=Ai(e),u!==null){for(t.flags|=128,On(l,!1),e=u.updateQueue,t.updateQueue=e,Qi(t,e),t.subtreeFlags=0,e=a,a=t.child;a!==null;)Co(a,e),a=a.sibling;return G(ze,ze.current&1|2),ce&&Jt(t,l.treeForkCount),t.child}e=e.sibling}l.tail!==null&&rt()>Zi&&(t.flags|=128,i=!0,On(l,!1),t.lanes=4194304)}else{if(!i)if(e=Ai(u),e!==null){if(t.flags|=128,i=!0,e=e.updateQueue,t.updateQueue=e,Qi(t,e),On(l,!0),l.tail===null&&l.tailMode==="hidden"&&!u.alternate&&!ce)return Ce(t),null}else 2*rt()-l.renderingStartTime>Zi&&a!==536870912&&(t.flags|=128,i=!0,On(l,!1),t.lanes=4194304);l.isBackwards?(u.sibling=t.child,t.child=u):(e=l.last,e!==null?e.sibling=u:t.child=u,l.last=u)}return l.tail!==null?(e=l.tail,l.rendering=e,l.tail=e.sibling,l.renderingStartTime=rt(),e.sibling=null,a=ze.current,G(ze,i?a&1|2:a&1),ce&&Jt(t,l.treeForkCount),e):(Ce(t),null);case 22:case 23:return mt(t),Ns(),l=t.memoizedState!==null,e!==null?e.memoizedState!==null!==l&&(t.flags|=8192):l&&(t.flags|=8192),l?(a&536870912)!==0&&(t.flags&128)===0&&(Ce(t),t.subtreeFlags&6&&(t.flags|=8192)):Ce(t),a=t.updateQueue,a!==null&&Qi(t,a.retryQueue),a=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),l=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(l=t.memoizedState.cachePool.pool),l!==a&&(t.flags|=2048),e!==null&&H(Fa),null;case 24:return a=null,e!==null&&(a=e.memoizedState.cache),t.memoizedState.cache!==a&&(t.flags|=2048),$t(Ue),Ce(t),null;case 25:return null;case 30:return null}throw Error(c(156,t.tag))}function ly(e,t){switch(os(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return $t(Ue),Me(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return In(t),null;case 31:if(t.memoizedState!==null){if(mt(t),t.alternate===null)throw Error(c(340));Za()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(mt(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(c(340));Za()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return H(ze),null;case 4:return Me(),null;case 10:return $t(t.type),null;case 22:case 23:return mt(t),Ns(),e!==null&&H(Fa),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return $t(Ue),null;case 25:return null;default:return null}}function ed(e,t){switch(os(t),t.tag){case 3:$t(Ue),Me();break;case 26:case 27:case 5:In(t);break;case 4:Me();break;case 31:t.memoizedState!==null&&mt(t);break;case 13:mt(t);break;case 19:H(ze);break;case 10:$t(t.type);break;case 22:case 23:mt(t),Ns(),e!==null&&H(Fa);break;case 24:$t(Ue)}}function An(e,t){try{var a=t.updateQueue,l=a!==null?a.lastEffect:null;if(l!==null){var i=l.next;a=i;do{if((a.tag&e)===e){l=void 0;var u=a.create,o=a.inst;l=u(),o.destroy=l}a=a.next}while(a!==i)}}catch(h){ve(t,t.return,h)}}function Ea(e,t,a){try{var l=t.updateQueue,i=l!==null?l.lastEffect:null;if(i!==null){var u=i.next;l=u;do{if((l.tag&e)===e){var o=l.inst,h=o.destroy;if(h!==void 0){o.destroy=void 0,i=t;var b=a,O=h;try{O()}catch(D){ve(i,b,D)}}}l=l.next}while(l!==u)}}catch(D){ve(t,t.return,D)}}function td(e){var t=e.updateQueue;if(t!==null){var a=e.stateNode;try{Ko(t,a)}catch(l){ve(e,e.return,l)}}}function ad(e,t,a){a.props=el(e.type,e.memoizedProps),a.state=e.memoizedState;try{a.componentWillUnmount()}catch(l){ve(e,t,l)}}function Rn(e,t){try{var a=e.ref;if(a!==null){switch(e.tag){case 26:case 27:case 5:var l=e.stateNode;break;case 30:l=e.stateNode;break;default:l=e.stateNode}typeof a=="function"?e.refCleanup=a(l):a.current=l}}catch(i){ve(e,t,i)}}function Bt(e,t){var a=e.ref,l=e.refCleanup;if(a!==null)if(typeof l=="function")try{l()}catch(i){ve(e,t,i)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof a=="function")try{a(null)}catch(i){ve(e,t,i)}else a.current=null}function ld(e){var t=e.type,a=e.memoizedProps,l=e.stateNode;try{e:switch(t){case"button":case"input":case"select":case"textarea":a.autoFocus&&l.focus();break e;case"img":a.src?l.src=a.src:a.srcSet&&(l.srcset=a.srcSet)}}catch(i){ve(e,e.return,i)}}function lr(e,t,a){try{var l=e.stateNode;Ny(l,e.type,a,t),l[et]=t}catch(i){ve(e,e.return,i)}}function nd(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Ra(e.type)||e.tag===4}function nr(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||nd(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Ra(e.type)||e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function ir(e,t,a){var l=e.tag;if(l===5||l===6)e=e.stateNode,t?(a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a).insertBefore(e,t):(t=a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a,t.appendChild(e),a=a._reactRootContainer,a!=null||t.onclick!==null||(t.onclick=Xt));else if(l!==4&&(l===27&&Ra(e.type)&&(a=e.stateNode,t=null),e=e.child,e!==null))for(ir(e,t,a),e=e.sibling;e!==null;)ir(e,t,a),e=e.sibling}function Yi(e,t,a){var l=e.tag;if(l===5||l===6)e=e.stateNode,t?a.insertBefore(e,t):a.appendChild(e);else if(l!==4&&(l===27&&Ra(e.type)&&(a=e.stateNode),e=e.child,e!==null))for(Yi(e,t,a),e=e.sibling;e!==null;)Yi(e,t,a),e=e.sibling}function id(e){var t=e.stateNode,a=e.memoizedProps;try{for(var l=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Fe(t,l,a),t[Xe]=e,t[et]=a}catch(u){ve(e,e.return,u)}}var ta=!1,He=!1,ur=!1,ud=typeof WeakSet=="function"?WeakSet:Set,Ge=null;function ny(e,t){if(e=e.containerInfo,Or=cu,e=vo(e),Pu(e)){if("selectionStart"in e)var a={start:e.selectionStart,end:e.selectionEnd};else e:{a=(a=e.ownerDocument)&&a.defaultView||window;var l=a.getSelection&&a.getSelection();if(l&&l.rangeCount!==0){a=l.anchorNode;var i=l.anchorOffset,u=l.focusNode;l=l.focusOffset;try{a.nodeType,u.nodeType}catch{a=null;break e}var o=0,h=-1,b=-1,O=0,D=0,w=e,A=null;t:for(;;){for(var _;w!==a||i!==0&&w.nodeType!==3||(h=o+i),w!==u||l!==0&&w.nodeType!==3||(b=o+l),w.nodeType===3&&(o+=w.nodeValue.length),(_=w.firstChild)!==null;)A=w,w=_;for(;;){if(w===e)break t;if(A===a&&++O===i&&(h=o),A===u&&++D===l&&(b=o),(_=w.nextSibling)!==null)break;w=A,A=w.parentNode}w=_}a=h===-1||b===-1?null:{start:h,end:b}}else a=null}a=a||{start:0,end:0}}else a=null;for(Ar={focusedElem:e,selectionRange:a},cu=!1,Ge=t;Ge!==null;)if(t=Ge,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,Ge=e;else for(;Ge!==null;){switch(t=Ge,u=t.alternate,e=t.flags,t.tag){case 0:if((e&4)!==0&&(e=t.updateQueue,e=e!==null?e.events:null,e!==null))for(a=0;a<e.length;a++)i=e[a],i.ref.impl=i.nextImpl;break;case 11:case 15:break;case 1:if((e&1024)!==0&&u!==null){e=void 0,a=t,i=u.memoizedProps,u=u.memoizedState,l=a.stateNode;try{var Z=el(a.type,i);e=l.getSnapshotBeforeUpdate(Z,u),l.__reactInternalSnapshotBeforeUpdate=e}catch(W){ve(a,a.return,W)}}break;case 3:if((e&1024)!==0){if(e=t.stateNode.containerInfo,a=e.nodeType,a===9)Dr(e);else if(a===1)switch(e.nodeName){case"HEAD":case"HTML":case"BODY":Dr(e);break;default:e.textContent=""}}break;case 5:case 26:case 27:case 6:case 4:case 17:break;default:if((e&1024)!==0)throw Error(c(163))}if(e=t.sibling,e!==null){e.return=t.return,Ge=e;break}Ge=t.return}}function sd(e,t,a){var l=a.flags;switch(a.tag){case 0:case 11:case 15:la(e,a),l&4&&An(5,a);break;case 1:if(la(e,a),l&4)if(e=a.stateNode,t===null)try{e.componentDidMount()}catch(o){ve(a,a.return,o)}else{var i=el(a.type,t.memoizedProps);t=t.memoizedState;try{e.componentDidUpdate(i,t,e.__reactInternalSnapshotBeforeUpdate)}catch(o){ve(a,a.return,o)}}l&64&&td(a),l&512&&Rn(a,a.return);break;case 3:if(la(e,a),l&64&&(e=a.updateQueue,e!==null)){if(t=null,a.child!==null)switch(a.child.tag){case 27:case 5:t=a.child.stateNode;break;case 1:t=a.child.stateNode}try{Ko(e,t)}catch(o){ve(a,a.return,o)}}break;case 27:t===null&&l&4&&id(a);case 26:case 5:la(e,a),t===null&&l&4&&ld(a),l&512&&Rn(a,a.return);break;case 12:la(e,a);break;case 31:la(e,a),l&4&&od(e,a);break;case 13:la(e,a),l&4&&fd(e,a),l&64&&(e=a.memoizedState,e!==null&&(e=e.dehydrated,e!==null&&(a=hy.bind(null,a),zy(e,a))));break;case 22:if(l=a.memoizedState!==null||ta,!l){t=t!==null&&t.memoizedState!==null||He,i=ta;var u=He;ta=l,(He=t)&&!u?na(e,a,(a.subtreeFlags&8772)!==0):la(e,a),ta=i,He=u}break;case 30:break;default:la(e,a)}}function rd(e){var t=e.alternate;t!==null&&(e.alternate=null,rd(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&ku(t)),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}var Oe=null,at=!1;function aa(e,t,a){for(a=a.child;a!==null;)cd(e,t,a),a=a.sibling}function cd(e,t,a){if(ct&&typeof ct.onCommitFiberUnmount=="function")try{ct.onCommitFiberUnmount(Il,a)}catch{}switch(a.tag){case 26:He||Bt(a,t),aa(e,t,a),a.memoizedState?a.memoizedState.count--:a.stateNode&&(a=a.stateNode,a.parentNode.removeChild(a));break;case 27:He||Bt(a,t);var l=Oe,i=at;Ra(a.type)&&(Oe=a.stateNode,at=!1),aa(e,t,a),Hn(a.stateNode),Oe=l,at=i;break;case 5:He||Bt(a,t);case 6:if(l=Oe,i=at,Oe=null,aa(e,t,a),Oe=l,at=i,Oe!==null)if(at)try{(Oe.nodeType===9?Oe.body:Oe.nodeName==="HTML"?Oe.ownerDocument.body:Oe).removeChild(a.stateNode)}catch(u){ve(a,t,u)}else try{Oe.removeChild(a.stateNode)}catch(u){ve(a,t,u)}break;case 18:Oe!==null&&(at?(e=Oe,th(e.nodeType===9?e.body:e.nodeName==="HTML"?e.ownerDocument.body:e,a.stateNode),Xl(e)):th(Oe,a.stateNode));break;case 4:l=Oe,i=at,Oe=a.stateNode.containerInfo,at=!0,aa(e,t,a),Oe=l,at=i;break;case 0:case 11:case 14:case 15:Ea(2,a,t),He||Ea(4,a,t),aa(e,t,a);break;case 1:He||(Bt(a,t),l=a.stateNode,typeof l.componentWillUnmount=="function"&&ad(a,t,l)),aa(e,t,a);break;case 21:aa(e,t,a);break;case 22:He=(l=He)||a.memoizedState!==null,aa(e,t,a),He=l;break;default:aa(e,t,a)}}function od(e,t){if(t.memoizedState===null&&(e=t.alternate,e!==null&&(e=e.memoizedState,e!==null))){e=e.dehydrated;try{Xl(e)}catch(a){ve(t,t.return,a)}}}function fd(e,t){if(t.memoizedState===null&&(e=t.alternate,e!==null&&(e=e.memoizedState,e!==null&&(e=e.dehydrated,e!==null))))try{Xl(e)}catch(a){ve(t,t.return,a)}}function iy(e){switch(e.tag){case 31:case 13:case 19:var t=e.stateNode;return t===null&&(t=e.stateNode=new ud),t;case 22:return e=e.stateNode,t=e._retryCache,t===null&&(t=e._retryCache=new ud),t;default:throw Error(c(435,e.tag))}}function Gi(e,t){var a=iy(e);t.forEach(function(l){if(!a.has(l)){a.add(l);var i=my.bind(null,e,l);l.then(i,i)}})}function lt(e,t){var a=t.deletions;if(a!==null)for(var l=0;l<a.length;l++){var i=a[l],u=e,o=t,h=o;e:for(;h!==null;){switch(h.tag){case 27:if(Ra(h.type)){Oe=h.stateNode,at=!1;break e}break;case 5:Oe=h.stateNode,at=!1;break e;case 3:case 4:Oe=h.stateNode.containerInfo,at=!0;break e}h=h.return}if(Oe===null)throw Error(c(160));cd(u,o,i),Oe=null,at=!1,u=i.alternate,u!==null&&(u.return=null),i.return=null}if(t.subtreeFlags&13886)for(t=t.child;t!==null;)dd(t,e),t=t.sibling}var wt=null;function dd(e,t){var a=e.alternate,l=e.flags;switch(e.tag){case 0:case 11:case 14:case 15:lt(t,e),nt(e),l&4&&(Ea(3,e,e.return),An(3,e),Ea(5,e,e.return));break;case 1:lt(t,e),nt(e),l&512&&(He||a===null||Bt(a,a.return)),l&64&&ta&&(e=e.updateQueue,e!==null&&(l=e.callbacks,l!==null&&(a=e.shared.hiddenCallbacks,e.shared.hiddenCallbacks=a===null?l:a.concat(l))));break;case 26:var i=wt;if(lt(t,e),nt(e),l&512&&(He||a===null||Bt(a,a.return)),l&4){var u=a!==null?a.memoizedState:null;if(l=e.memoizedState,a===null)if(l===null)if(e.stateNode===null){e:{l=e.type,a=e.memoizedProps,i=i.ownerDocument||i;t:switch(l){case"title":u=i.getElementsByTagName("title")[0],(!u||u[tn]||u[Xe]||u.namespaceURI==="http://www.w3.org/2000/svg"||u.hasAttribute("itemprop"))&&(u=i.createElement(l),i.head.insertBefore(u,i.querySelector("head > title"))),Fe(u,l,a),u[Xe]=e,Ye(u),l=u;break e;case"link":var o=dh("link","href",i).get(l+(a.href||""));if(o){for(var h=0;h<o.length;h++)if(u=o[h],u.getAttribute("href")===(a.href==null||a.href===""?null:a.href)&&u.getAttribute("rel")===(a.rel==null?null:a.rel)&&u.getAttribute("title")===(a.title==null?null:a.title)&&u.getAttribute("crossorigin")===(a.crossOrigin==null?null:a.crossOrigin)){o.splice(h,1);break t}}u=i.createElement(l),Fe(u,l,a),i.head.appendChild(u);break;case"meta":if(o=dh("meta","content",i).get(l+(a.content||""))){for(h=0;h<o.length;h++)if(u=o[h],u.getAttribute("content")===(a.content==null?null:""+a.content)&&u.getAttribute("name")===(a.name==null?null:a.name)&&u.getAttribute("property")===(a.property==null?null:a.property)&&u.getAttribute("http-equiv")===(a.httpEquiv==null?null:a.httpEquiv)&&u.getAttribute("charset")===(a.charSet==null?null:a.charSet)){o.splice(h,1);break t}}u=i.createElement(l),Fe(u,l,a),i.head.appendChild(u);break;default:throw Error(c(468,l))}u[Xe]=e,Ye(u),l=u}e.stateNode=l}else hh(i,e.type,e.stateNode);else e.stateNode=fh(i,l,e.memoizedProps);else u!==l?(u===null?a.stateNode!==null&&(a=a.stateNode,a.parentNode.removeChild(a)):u.count--,l===null?hh(i,e.type,e.stateNode):fh(i,l,e.memoizedProps)):l===null&&e.stateNode!==null&&lr(e,e.memoizedProps,a.memoizedProps)}break;case 27:lt(t,e),nt(e),l&512&&(He||a===null||Bt(a,a.return)),a!==null&&l&4&&lr(e,e.memoizedProps,a.memoizedProps);break;case 5:if(lt(t,e),nt(e),l&512&&(He||a===null||Bt(a,a.return)),e.flags&32){i=e.stateNode;try{ml(i,"")}catch(Z){ve(e,e.return,Z)}}l&4&&e.stateNode!=null&&(i=e.memoizedProps,lr(e,i,a!==null?a.memoizedProps:i)),l&1024&&(ur=!0);break;case 6:if(lt(t,e),nt(e),l&4){if(e.stateNode===null)throw Error(c(162));l=e.memoizedProps,a=e.stateNode;try{a.nodeValue=l}catch(Z){ve(e,e.return,Z)}}break;case 3:if(iu=null,i=wt,wt=lu(t.containerInfo),lt(t,e),wt=i,nt(e),l&4&&a!==null&&a.memoizedState.isDehydrated)try{Xl(t.containerInfo)}catch(Z){ve(e,e.return,Z)}ur&&(ur=!1,hd(e));break;case 4:l=wt,wt=lu(e.stateNode.containerInfo),lt(t,e),nt(e),wt=l;break;case 12:lt(t,e),nt(e);break;case 31:lt(t,e),nt(e),l&4&&(l=e.updateQueue,l!==null&&(e.updateQueue=null,Gi(e,l)));break;case 13:lt(t,e),nt(e),e.child.flags&8192&&e.memoizedState!==null!=(a!==null&&a.memoizedState!==null)&&(Xi=rt()),l&4&&(l=e.updateQueue,l!==null&&(e.updateQueue=null,Gi(e,l)));break;case 22:i=e.memoizedState!==null;var b=a!==null&&a.memoizedState!==null,O=ta,D=He;if(ta=O||i,He=D||b,lt(t,e),He=D,ta=O,nt(e),l&8192)e:for(t=e.stateNode,t._visibility=i?t._visibility&-2:t._visibility|1,i&&(a===null||b||ta||He||tl(e)),a=null,t=e;;){if(t.tag===5||t.tag===26){if(a===null){b=a=t;try{if(u=b.stateNode,i)o=u.style,typeof o.setProperty=="function"?o.setProperty("display","none","important"):o.display="none";else{h=b.stateNode;var w=b.memoizedProps.style,A=w!=null&&w.hasOwnProperty("display")?w.display:null;h.style.display=A==null||typeof A=="boolean"?"":(""+A).trim()}}catch(Z){ve(b,b.return,Z)}}}else if(t.tag===6){if(a===null){b=t;try{b.stateNode.nodeValue=i?"":b.memoizedProps}catch(Z){ve(b,b.return,Z)}}}else if(t.tag===18){if(a===null){b=t;try{var _=b.stateNode;i?ah(_,!0):ah(b.stateNode,!1)}catch(Z){ve(b,b.return,Z)}}}else if((t.tag!==22&&t.tag!==23||t.memoizedState===null||t===e)&&t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break e;for(;t.sibling===null;){if(t.return===null||t.return===e)break e;a===t&&(a=null),t=t.return}a===t&&(a=null),t.sibling.return=t.return,t=t.sibling}l&4&&(l=e.updateQueue,l!==null&&(a=l.retryQueue,a!==null&&(l.retryQueue=null,Gi(e,a))));break;case 19:lt(t,e),nt(e),l&4&&(l=e.updateQueue,l!==null&&(e.updateQueue=null,Gi(e,l)));break;case 30:break;case 21:break;default:lt(t,e),nt(e)}}function nt(e){var t=e.flags;if(t&2){try{for(var a,l=e.return;l!==null;){if(nd(l)){a=l;break}l=l.return}if(a==null)throw Error(c(160));switch(a.tag){case 27:var i=a.stateNode,u=nr(e);Yi(e,u,i);break;case 5:var o=a.stateNode;a.flags&32&&(ml(o,""),a.flags&=-33);var h=nr(e);Yi(e,h,o);break;case 3:case 4:var b=a.stateNode.containerInfo,O=nr(e);ir(e,O,b);break;default:throw Error(c(161))}}catch(D){ve(e,e.return,D)}e.flags&=-3}t&4096&&(e.flags&=-4097)}function hd(e){if(e.subtreeFlags&1024)for(e=e.child;e!==null;){var t=e;hd(t),t.tag===5&&t.flags&1024&&t.stateNode.reset(),e=e.sibling}}function la(e,t){if(t.subtreeFlags&8772)for(t=t.child;t!==null;)sd(e,t.alternate,t),t=t.sibling}function tl(e){for(e=e.child;e!==null;){var t=e;switch(t.tag){case 0:case 11:case 14:case 15:Ea(4,t,t.return),tl(t);break;case 1:Bt(t,t.return);var a=t.stateNode;typeof a.componentWillUnmount=="function"&&ad(t,t.return,a),tl(t);break;case 27:Hn(t.stateNode);case 26:case 5:Bt(t,t.return),tl(t);break;case 22:t.memoizedState===null&&tl(t);break;case 30:tl(t);break;default:tl(t)}e=e.sibling}}function na(e,t,a){for(a=a&&(t.subtreeFlags&8772)!==0,t=t.child;t!==null;){var l=t.alternate,i=e,u=t,o=u.flags;switch(u.tag){case 0:case 11:case 15:na(i,u,a),An(4,u);break;case 1:if(na(i,u,a),l=u,i=l.stateNode,typeof i.componentDidMount=="function")try{i.componentDidMount()}catch(O){ve(l,l.return,O)}if(l=u,i=l.updateQueue,i!==null){var h=l.stateNode;try{var b=i.shared.hiddenCallbacks;if(b!==null)for(i.shared.hiddenCallbacks=null,i=0;i<b.length;i++)Go(b[i],h)}catch(O){ve(l,l.return,O)}}a&&o&64&&td(u),Rn(u,u.return);break;case 27:id(u);case 26:case 5:na(i,u,a),a&&l===null&&o&4&&ld(u),Rn(u,u.return);break;case 12:na(i,u,a);break;case 31:na(i,u,a),a&&o&4&&od(i,u);break;case 13:na(i,u,a),a&&o&4&&fd(i,u);break;case 22:u.memoizedState===null&&na(i,u,a),Rn(u,u.return);break;case 30:break;default:na(i,u,a)}t=t.sibling}}function sr(e,t){var a=null;e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),e=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(e=t.memoizedState.cachePool.pool),e!==a&&(e!=null&&e.refCount++,a!=null&&yn(a))}function rr(e,t){e=null,t.alternate!==null&&(e=t.alternate.memoizedState.cache),t=t.memoizedState.cache,t!==e&&(t.refCount++,e!=null&&yn(e))}function Ut(e,t,a,l){if(t.subtreeFlags&10256)for(t=t.child;t!==null;)md(e,t,a,l),t=t.sibling}function md(e,t,a,l){var i=t.flags;switch(t.tag){case 0:case 11:case 15:Ut(e,t,a,l),i&2048&&An(9,t);break;case 1:Ut(e,t,a,l);break;case 3:Ut(e,t,a,l),i&2048&&(e=null,t.alternate!==null&&(e=t.alternate.memoizedState.cache),t=t.memoizedState.cache,t!==e&&(t.refCount++,e!=null&&yn(e)));break;case 12:if(i&2048){Ut(e,t,a,l),e=t.stateNode;try{var u=t.memoizedProps,o=u.id,h=u.onPostCommit;typeof h=="function"&&h(o,t.alternate===null?"mount":"update",e.passiveEffectDuration,-0)}catch(b){ve(t,t.return,b)}}else Ut(e,t,a,l);break;case 31:Ut(e,t,a,l);break;case 13:Ut(e,t,a,l);break;case 23:break;case 22:u=t.stateNode,o=t.alternate,t.memoizedState!==null?u._visibility&2?Ut(e,t,a,l):_n(e,t):u._visibility&2?Ut(e,t,a,l):(u._visibility|=2,wl(e,t,a,l,(t.subtreeFlags&10256)!==0||!1)),i&2048&&sr(o,t);break;case 24:Ut(e,t,a,l),i&2048&&rr(t.alternate,t);break;default:Ut(e,t,a,l)}}function wl(e,t,a,l,i){for(i=i&&((t.subtreeFlags&10256)!==0||!1),t=t.child;t!==null;){var u=e,o=t,h=a,b=l,O=o.flags;switch(o.tag){case 0:case 11:case 15:wl(u,o,h,b,i),An(8,o);break;case 23:break;case 22:var D=o.stateNode;o.memoizedState!==null?D._visibility&2?wl(u,o,h,b,i):_n(u,o):(D._visibility|=2,wl(u,o,h,b,i)),i&&O&2048&&sr(o.alternate,o);break;case 24:wl(u,o,h,b,i),i&&O&2048&&rr(o.alternate,o);break;default:wl(u,o,h,b,i)}t=t.sibling}}function _n(e,t){if(t.subtreeFlags&10256)for(t=t.child;t!==null;){var a=e,l=t,i=l.flags;switch(l.tag){case 22:_n(a,l),i&2048&&sr(l.alternate,l);break;case 24:_n(a,l),i&2048&&rr(l.alternate,l);break;default:_n(a,l)}t=t.sibling}}var Dn=8192;function Ul(e,t,a){if(e.subtreeFlags&Dn)for(e=e.child;e!==null;)yd(e,t,a),e=e.sibling}function yd(e,t,a){switch(e.tag){case 26:Ul(e,t,a),e.flags&Dn&&e.memoizedState!==null&&Xy(a,wt,e.memoizedState,e.memoizedProps);break;case 5:Ul(e,t,a);break;case 3:case 4:var l=wt;wt=lu(e.stateNode.containerInfo),Ul(e,t,a),wt=l;break;case 22:e.memoizedState===null&&(l=e.alternate,l!==null&&l.memoizedState!==null?(l=Dn,Dn=16777216,Ul(e,t,a),Dn=l):Ul(e,t,a));break;default:Ul(e,t,a)}}function gd(e){var t=e.alternate;if(t!==null&&(e=t.child,e!==null)){t.child=null;do t=e.sibling,e.sibling=null,e=t;while(e!==null)}}function Mn(e){var t=e.deletions;if((e.flags&16)!==0){if(t!==null)for(var a=0;a<t.length;a++){var l=t[a];Ge=l,pd(l,e)}gd(e)}if(e.subtreeFlags&10256)for(e=e.child;e!==null;)vd(e),e=e.sibling}function vd(e){switch(e.tag){case 0:case 11:case 15:Mn(e),e.flags&2048&&Ea(9,e,e.return);break;case 3:Mn(e);break;case 12:Mn(e);break;case 22:var t=e.stateNode;e.memoizedState!==null&&t._visibility&2&&(e.return===null||e.return.tag!==13)?(t._visibility&=-3,Ki(e)):Mn(e);break;default:Mn(e)}}function Ki(e){var t=e.deletions;if((e.flags&16)!==0){if(t!==null)for(var a=0;a<t.length;a++){var l=t[a];Ge=l,pd(l,e)}gd(e)}for(e=e.child;e!==null;){switch(t=e,t.tag){case 0:case 11:case 15:Ea(8,t,t.return),Ki(t);break;case 22:a=t.stateNode,a._visibility&2&&(a._visibility&=-3,Ki(t));break;default:Ki(t)}e=e.sibling}}function pd(e,t){for(;Ge!==null;){var a=Ge;switch(a.tag){case 0:case 11:case 15:Ea(8,a,t);break;case 23:case 22:if(a.memoizedState!==null&&a.memoizedState.cachePool!==null){var l=a.memoizedState.cachePool.pool;l!=null&&l.refCount++}break;case 24:yn(a.memoizedState.cache)}if(l=a.child,l!==null)l.return=a,Ge=l;else e:for(a=e;Ge!==null;){l=Ge;var i=l.sibling,u=l.return;if(rd(l),l===a){Ge=null;break e}if(i!==null){i.return=u,Ge=i;break e}Ge=u}}}var uy={getCacheForType:function(e){var t=Ve(Ue),a=t.data.get(e);return a===void 0&&(a=e(),t.data.set(e,a)),a},cacheSignal:function(){return Ve(Ue).controller.signal}},sy=typeof WeakMap=="function"?WeakMap:Map,he=0,je=null,ie=null,se=0,ge=0,yt=null,ja=!1,kl=!1,cr=!1,ia=0,De=0,Ta=0,al=0,or=0,gt=0,ql=0,zn=null,it=null,fr=!1,Xi=0,bd=0,Zi=1/0,Vi=null,Na=null,Be=0,Ca=null,Hl=null,ua=0,dr=0,hr=null,xd=null,wn=0,mr=null;function vt(){return(he&2)!==0&&se!==0?se&-se:M.T!==null?xr():wc()}function Sd(){if(gt===0)if((se&536870912)===0||ce){var e=ti;ti<<=1,(ti&3932160)===0&&(ti=262144),gt=e}else gt=536870912;return e=ht.current,e!==null&&(e.flags|=32),gt}function ut(e,t,a){(e===je&&(ge===2||ge===9)||e.cancelPendingCommit!==null)&&(Ll(e,0),Oa(e,se,gt,!1)),en(e,a),((he&2)===0||e!==je)&&(e===je&&((he&2)===0&&(al|=a),De===4&&Oa(e,se,gt,!1)),Qt(e))}function Ed(e,t,a){if((he&6)!==0)throw Error(c(327));var l=!a&&(t&127)===0&&(t&e.expiredLanes)===0||Pl(e,t),i=l?oy(e,t):gr(e,t,!0),u=l;do{if(i===0){kl&&!l&&Oa(e,t,0,!1);break}else{if(a=e.current.alternate,u&&!ry(a)){i=gr(e,t,!1),u=!1;continue}if(i===2){if(u=t,e.errorRecoveryDisabledLanes&u)var o=0;else o=e.pendingLanes&-536870913,o=o!==0?o:o&536870912?536870912:0;if(o!==0){t=o;e:{var h=e;i=zn;var b=h.current.memoizedState.isDehydrated;if(b&&(Ll(h,o).flags|=256),o=gr(h,o,!1),o!==2){if(cr&&!b){h.errorRecoveryDisabledLanes|=u,al|=u,i=4;break e}u=it,it=i,u!==null&&(it===null?it=u:it.push.apply(it,u))}i=o}if(u=!1,i!==2)continue}}if(i===1){Ll(e,0),Oa(e,t,0,!0);break}e:{switch(l=e,u=i,u){case 0:case 1:throw Error(c(345));case 4:if((t&4194048)!==t)break;case 6:Oa(l,t,gt,!ja);break e;case 2:it=null;break;case 3:case 5:break;default:throw Error(c(329))}if((t&62914560)===t&&(i=Xi+300-rt(),10<i)){if(Oa(l,t,gt,!ja),li(l,0,!0)!==0)break e;ua=t,l.timeoutHandle=Pd(jd.bind(null,l,a,it,Vi,fr,t,gt,al,ql,ja,u,"Throttled",-0,0),i);break e}jd(l,a,it,Vi,fr,t,gt,al,ql,ja,u,null,-0,0)}}break}while(!0);Qt(e)}function jd(e,t,a,l,i,u,o,h,b,O,D,w,A,_){if(e.timeoutHandle=-1,w=t.subtreeFlags,w&8192||(w&16785408)===16785408){w={stylesheets:null,count:0,imgCount:0,imgBytes:0,suspenseyImages:[],waitingForImages:!0,waitingForViewTransition:!1,unsuspend:Xt},yd(t,u,w);var Z=(u&62914560)===u?Xi-rt():(u&4194048)===u?bd-rt():0;if(Z=Zy(w,Z),Z!==null){ua=u,e.cancelPendingCommit=Z(Dd.bind(null,e,t,u,a,l,i,o,h,b,D,w,null,A,_)),Oa(e,u,o,!O);return}}Dd(e,t,u,a,l,i,o,h,b)}function ry(e){for(var t=e;;){var a=t.tag;if((a===0||a===11||a===15)&&t.flags&16384&&(a=t.updateQueue,a!==null&&(a=a.stores,a!==null)))for(var l=0;l<a.length;l++){var i=a[l],u=i.getSnapshot;i=i.value;try{if(!ft(u(),i))return!1}catch{return!1}}if(a=t.child,t.subtreeFlags&16384&&a!==null)a.return=t,t=a;else{if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return!0;t=t.return}t.sibling.return=t.return,t=t.sibling}}return!0}function Oa(e,t,a,l){t&=~or,t&=~al,e.suspendedLanes|=t,e.pingedLanes&=~t,l&&(e.warmLanes|=t),l=e.expirationTimes;for(var i=t;0<i;){var u=31-ot(i),o=1<<u;l[u]=-1,i&=~o}a!==0&&Dc(e,a,t)}function Ji(){return(he&6)===0?(Un(0),!1):!0}function yr(){if(ie!==null){if(ge===0)var e=ie.return;else e=ie,Ft=Va=null,Ds(e),Rl=null,vn=0,e=ie;for(;e!==null;)ed(e.alternate,e),e=e.return;ie=null}}function Ll(e,t){var a=e.timeoutHandle;a!==-1&&(e.timeoutHandle=-1,Ay(a)),a=e.cancelPendingCommit,a!==null&&(e.cancelPendingCommit=null,a()),ua=0,yr(),je=e,ie=a=Vt(e.current,null),se=t,ge=0,yt=null,ja=!1,kl=Pl(e,t),cr=!1,ql=gt=or=al=Ta=De=0,it=zn=null,fr=!1,(t&8)!==0&&(t|=t&32);var l=e.entangledLanes;if(l!==0)for(e=e.entanglements,l&=t;0<l;){var i=31-ot(l),u=1<<i;t|=e[i],l&=~u}return ia=t,yi(),a}function Td(e,t){ae=null,M.H=Nn,t===Al||t===ji?(t=Lo(),ge=3):t===bs?(t=Lo(),ge=4):ge=t===Vs?8:t!==null&&typeof t=="object"&&typeof t.then=="function"?6:1,yt=t,ie===null&&(De=1,qi(e,Tt(t,e.current)))}function Nd(){var e=ht.current;return e===null?!0:(se&4194048)===se?At===null:(se&62914560)===se||(se&536870912)!==0?e===At:!1}function Cd(){var e=M.H;return M.H=Nn,e===null?Nn:e}function Od(){var e=M.A;return M.A=uy,e}function Fi(){De=4,ja||(se&4194048)!==se&&ht.current!==null||(kl=!0),(Ta&134217727)===0&&(al&134217727)===0||je===null||Oa(je,se,gt,!1)}function gr(e,t,a){var l=he;he|=2;var i=Cd(),u=Od();(je!==e||se!==t)&&(Vi=null,Ll(e,t)),t=!1;var o=De;e:do try{if(ge!==0&&ie!==null){var h=ie,b=yt;switch(ge){case 8:yr(),o=6;break e;case 3:case 2:case 9:case 6:ht.current===null&&(t=!0);var O=ge;if(ge=0,yt=null,Bl(e,h,b,O),a&&kl){o=0;break e}break;default:O=ge,ge=0,yt=null,Bl(e,h,b,O)}}cy(),o=De;break}catch(D){Td(e,D)}while(!0);return t&&e.shellSuspendCounter++,Ft=Va=null,he=l,M.H=i,M.A=u,ie===null&&(je=null,se=0,yi()),o}function cy(){for(;ie!==null;)Ad(ie)}function oy(e,t){var a=he;he|=2;var l=Cd(),i=Od();je!==e||se!==t?(Vi=null,Zi=rt()+500,Ll(e,t)):kl=Pl(e,t);e:do try{if(ge!==0&&ie!==null){t=ie;var u=yt;t:switch(ge){case 1:ge=0,yt=null,Bl(e,t,u,1);break;case 2:case 9:if(qo(u)){ge=0,yt=null,Rd(t);break}t=function(){ge!==2&&ge!==9||je!==e||(ge=7),Qt(e)},u.then(t,t);break e;case 3:ge=7;break e;case 4:ge=5;break e;case 7:qo(u)?(ge=0,yt=null,Rd(t)):(ge=0,yt=null,Bl(e,t,u,7));break;case 5:var o=null;switch(ie.tag){case 26:o=ie.memoizedState;case 5:case 27:var h=ie;if(o?mh(o):h.stateNode.complete){ge=0,yt=null;var b=h.sibling;if(b!==null)ie=b;else{var O=h.return;O!==null?(ie=O,$i(O)):ie=null}break t}}ge=0,yt=null,Bl(e,t,u,5);break;case 6:ge=0,yt=null,Bl(e,t,u,6);break;case 8:yr(),De=6;break e;default:throw Error(c(462))}}fy();break}catch(D){Td(e,D)}while(!0);return Ft=Va=null,M.H=l,M.A=i,he=a,ie!==null?0:(je=null,se=0,yi(),De)}function fy(){for(;ie!==null&&!w0();)Ad(ie)}function Ad(e){var t=If(e.alternate,e,ia);e.memoizedProps=e.pendingProps,t===null?$i(e):ie=t}function Rd(e){var t=e,a=t.alternate;switch(t.tag){case 15:case 0:t=Zf(a,t,t.pendingProps,t.type,void 0,se);break;case 11:t=Zf(a,t,t.pendingProps,t.type.render,t.ref,se);break;case 5:Ds(t);default:ed(a,t),t=ie=Co(t,ia),t=If(a,t,ia)}e.memoizedProps=e.pendingProps,t===null?$i(e):ie=t}function Bl(e,t,a,l){Ft=Va=null,Ds(t),Rl=null,vn=0;var i=t.return;try{if(Pm(e,i,t,a,se)){De=1,qi(e,Tt(a,e.current)),ie=null;return}}catch(u){if(i!==null)throw ie=i,u;De=1,qi(e,Tt(a,e.current)),ie=null;return}t.flags&32768?(ce||l===1?e=!0:kl||(se&536870912)!==0?e=!1:(ja=e=!0,(l===2||l===9||l===3||l===6)&&(l=ht.current,l!==null&&l.tag===13&&(l.flags|=16384))),_d(t,e)):$i(t)}function $i(e){var t=e;do{if((t.flags&32768)!==0){_d(t,ja);return}e=t.return;var a=ay(t.alternate,t,ia);if(a!==null){ie=a;return}if(t=t.sibling,t!==null){ie=t;return}ie=t=e}while(t!==null);De===0&&(De=5)}function _d(e,t){do{var a=ly(e.alternate,e);if(a!==null){a.flags&=32767,ie=a;return}if(a=e.return,a!==null&&(a.flags|=32768,a.subtreeFlags=0,a.deletions=null),!t&&(e=e.sibling,e!==null)){ie=e;return}ie=e=a}while(e!==null);De=6,ie=null}function Dd(e,t,a,l,i,u,o,h,b){e.cancelPendingCommit=null;do Wi();while(Be!==0);if((he&6)!==0)throw Error(c(327));if(t!==null){if(t===e.current)throw Error(c(177));if(u=t.lanes|t.childLanes,u|=ns,K0(e,a,u,o,h,b),e===je&&(ie=je=null,se=0),Hl=t,Ca=e,ua=a,dr=u,hr=i,xd=l,(t.subtreeFlags&10256)!==0||(t.flags&10256)!==0?(e.callbackNode=null,e.callbackPriority=0,yy(Pn,function(){return kd(),null})):(e.callbackNode=null,e.callbackPriority=0),l=(t.flags&13878)!==0,(t.subtreeFlags&13878)!==0||l){l=M.T,M.T=null,i=Y.p,Y.p=2,o=he,he|=4;try{ny(e,t,a)}finally{he=o,Y.p=i,M.T=l}}Be=1,Md(),zd(),wd()}}function Md(){if(Be===1){Be=0;var e=Ca,t=Hl,a=(t.flags&13878)!==0;if((t.subtreeFlags&13878)!==0||a){a=M.T,M.T=null;var l=Y.p;Y.p=2;var i=he;he|=4;try{dd(t,e);var u=Ar,o=vo(e.containerInfo),h=u.focusedElem,b=u.selectionRange;if(o!==h&&h&&h.ownerDocument&&go(h.ownerDocument.documentElement,h)){if(b!==null&&Pu(h)){var O=b.start,D=b.end;if(D===void 0&&(D=O),"selectionStart"in h)h.selectionStart=O,h.selectionEnd=Math.min(D,h.value.length);else{var w=h.ownerDocument||document,A=w&&w.defaultView||window;if(A.getSelection){var _=A.getSelection(),Z=h.textContent.length,W=Math.min(b.start,Z),Se=b.end===void 0?W:Math.min(b.end,Z);!_.extend&&W>Se&&(o=Se,Se=W,W=o);var j=yo(h,W),x=yo(h,Se);if(j&&x&&(_.rangeCount!==1||_.anchorNode!==j.node||_.anchorOffset!==j.offset||_.focusNode!==x.node||_.focusOffset!==x.offset)){var C=w.createRange();C.setStart(j.node,j.offset),_.removeAllRanges(),W>Se?(_.addRange(C),_.extend(x.node,x.offset)):(C.setEnd(x.node,x.offset),_.addRange(C))}}}}for(w=[],_=h;_=_.parentNode;)_.nodeType===1&&w.push({element:_,left:_.scrollLeft,top:_.scrollTop});for(typeof h.focus=="function"&&h.focus(),h=0;h<w.length;h++){var z=w[h];z.element.scrollLeft=z.left,z.element.scrollTop=z.top}}cu=!!Or,Ar=Or=null}finally{he=i,Y.p=l,M.T=a}}e.current=t,Be=2}}function zd(){if(Be===2){Be=0;var e=Ca,t=Hl,a=(t.flags&8772)!==0;if((t.subtreeFlags&8772)!==0||a){a=M.T,M.T=null;var l=Y.p;Y.p=2;var i=he;he|=4;try{sd(e,t.alternate,t)}finally{he=i,Y.p=l,M.T=a}}Be=3}}function wd(){if(Be===4||Be===3){Be=0,U0();var e=Ca,t=Hl,a=ua,l=xd;(t.subtreeFlags&10256)!==0||(t.flags&10256)!==0?Be=5:(Be=0,Hl=Ca=null,Ud(e,e.pendingLanes));var i=e.pendingLanes;if(i===0&&(Na=null),wu(a),t=t.stateNode,ct&&typeof ct.onCommitFiberRoot=="function")try{ct.onCommitFiberRoot(Il,t,void 0,(t.current.flags&128)===128)}catch{}if(l!==null){t=M.T,i=Y.p,Y.p=2,M.T=null;try{for(var u=e.onRecoverableError,o=0;o<l.length;o++){var h=l[o];u(h.value,{componentStack:h.stack})}}finally{M.T=t,Y.p=i}}(ua&3)!==0&&Wi(),Qt(e),i=e.pendingLanes,(a&261930)!==0&&(i&42)!==0?e===mr?wn++:(wn=0,mr=e):wn=0,Un(0)}}function Ud(e,t){(e.pooledCacheLanes&=t)===0&&(t=e.pooledCache,t!=null&&(e.pooledCache=null,yn(t)))}function Wi(){return Md(),zd(),wd(),kd()}function kd(){if(Be!==5)return!1;var e=Ca,t=dr;dr=0;var a=wu(ua),l=M.T,i=Y.p;try{Y.p=32>a?32:a,M.T=null,a=hr,hr=null;var u=Ca,o=ua;if(Be=0,Hl=Ca=null,ua=0,(he&6)!==0)throw Error(c(331));var h=he;if(he|=4,vd(u.current),md(u,u.current,o,a),he=h,Un(0,!1),ct&&typeof ct.onPostCommitFiberRoot=="function")try{ct.onPostCommitFiberRoot(Il,u)}catch{}return!0}finally{Y.p=i,M.T=l,Ud(e,t)}}function qd(e,t,a){t=Tt(a,t),t=Zs(e.stateNode,t,2),e=ba(e,t,2),e!==null&&(en(e,2),Qt(e))}function ve(e,t,a){if(e.tag===3)qd(e,e,a);else for(;t!==null;){if(t.tag===3){qd(t,e,a);break}else if(t.tag===1){var l=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof l.componentDidCatch=="function"&&(Na===null||!Na.has(l))){e=Tt(a,e),a=Hf(2),l=ba(t,a,2),l!==null&&(Lf(a,l,t,e),en(l,2),Qt(l));break}}t=t.return}}function vr(e,t,a){var l=e.pingCache;if(l===null){l=e.pingCache=new sy;var i=new Set;l.set(t,i)}else i=l.get(t),i===void 0&&(i=new Set,l.set(t,i));i.has(a)||(cr=!0,i.add(a),e=dy.bind(null,e,t,a),t.then(e,e))}function dy(e,t,a){var l=e.pingCache;l!==null&&l.delete(t),e.pingedLanes|=e.suspendedLanes&a,e.warmLanes&=~a,je===e&&(se&a)===a&&(De===4||De===3&&(se&62914560)===se&&300>rt()-Xi?(he&2)===0&&Ll(e,0):or|=a,ql===se&&(ql=0)),Qt(e)}function Hd(e,t){t===0&&(t=_c()),e=Ka(e,t),e!==null&&(en(e,t),Qt(e))}function hy(e){var t=e.memoizedState,a=0;t!==null&&(a=t.retryLane),Hd(e,a)}function my(e,t){var a=0;switch(e.tag){case 31:case 13:var l=e.stateNode,i=e.memoizedState;i!==null&&(a=i.retryLane);break;case 19:l=e.stateNode;break;case 22:l=e.stateNode._retryCache;break;default:throw Error(c(314))}l!==null&&l.delete(t),Hd(e,a)}function yy(e,t){return _u(e,t)}var Ii=null,Ql=null,pr=!1,Pi=!1,br=!1,Aa=0;function Qt(e){e!==Ql&&e.next===null&&(Ql===null?Ii=Ql=e:Ql=Ql.next=e),Pi=!0,pr||(pr=!0,vy())}function Un(e,t){if(!br&&Pi){br=!0;do for(var a=!1,l=Ii;l!==null;){if(e!==0){var i=l.pendingLanes;if(i===0)var u=0;else{var o=l.suspendedLanes,h=l.pingedLanes;u=(1<<31-ot(42|e)+1)-1,u&=i&~(o&~h),u=u&201326741?u&201326741|1:u?u|2:0}u!==0&&(a=!0,Yd(l,u))}else u=se,u=li(l,l===je?u:0,l.cancelPendingCommit!==null||l.timeoutHandle!==-1),(u&3)===0||Pl(l,u)||(a=!0,Yd(l,u));l=l.next}while(a);br=!1}}function gy(){Ld()}function Ld(){Pi=pr=!1;var e=0;Aa!==0&&Oy()&&(e=Aa);for(var t=rt(),a=null,l=Ii;l!==null;){var i=l.next,u=Bd(l,t);u===0?(l.next=null,a===null?Ii=i:a.next=i,i===null&&(Ql=a)):(a=l,(e!==0||(u&3)!==0)&&(Pi=!0)),l=i}Be!==0&&Be!==5||Un(e),Aa!==0&&(Aa=0)}function Bd(e,t){for(var a=e.suspendedLanes,l=e.pingedLanes,i=e.expirationTimes,u=e.pendingLanes&-62914561;0<u;){var o=31-ot(u),h=1<<o,b=i[o];b===-1?((h&a)===0||(h&l)!==0)&&(i[o]=G0(h,t)):b<=t&&(e.expiredLanes|=h),u&=~h}if(t=je,a=se,a=li(e,e===t?a:0,e.cancelPendingCommit!==null||e.timeoutHandle!==-1),l=e.callbackNode,a===0||e===t&&(ge===2||ge===9)||e.cancelPendingCommit!==null)return l!==null&&l!==null&&Du(l),e.callbackNode=null,e.callbackPriority=0;if((a&3)===0||Pl(e,a)){if(t=a&-a,t===e.callbackPriority)return t;switch(l!==null&&Du(l),wu(a)){case 2:case 8:a=Ac;break;case 32:a=Pn;break;case 268435456:a=Rc;break;default:a=Pn}return l=Qd.bind(null,e),a=_u(a,l),e.callbackPriority=t,e.callbackNode=a,t}return l!==null&&l!==null&&Du(l),e.callbackPriority=2,e.callbackNode=null,2}function Qd(e,t){if(Be!==0&&Be!==5)return e.callbackNode=null,e.callbackPriority=0,null;var a=e.callbackNode;if(Wi()&&e.callbackNode!==a)return null;var l=se;return l=li(e,e===je?l:0,e.cancelPendingCommit!==null||e.timeoutHandle!==-1),l===0?null:(Ed(e,l,t),Bd(e,rt()),e.callbackNode!=null&&e.callbackNode===a?Qd.bind(null,e):null)}function Yd(e,t){if(Wi())return null;Ed(e,t,!0)}function vy(){Ry(function(){(he&6)!==0?_u(Oc,gy):Ld()})}function xr(){if(Aa===0){var e=Cl;e===0&&(e=ei,ei<<=1,(ei&261888)===0&&(ei=256)),Aa=e}return Aa}function Gd(e){return e==null||typeof e=="symbol"||typeof e=="boolean"?null:typeof e=="function"?e:si(""+e)}function Kd(e,t){var a=t.ownerDocument.createElement("input");return a.name=t.name,a.value=t.value,e.id&&a.setAttribute("form",e.id),t.parentNode.insertBefore(a,t),e=new FormData(e),a.parentNode.removeChild(a),e}function py(e,t,a,l,i){if(t==="submit"&&a&&a.stateNode===i){var u=Gd((i[et]||null).action),o=l.submitter;o&&(t=(t=o[et]||null)?Gd(t.formAction):o.getAttribute("formAction"),t!==null&&(u=t,o=null));var h=new fi("action","action",null,l,i);e.push({event:h,listeners:[{instance:null,listener:function(){if(l.defaultPrevented){if(Aa!==0){var b=o?Kd(i,o):new FormData(i);Bs(a,{pending:!0,data:b,method:i.method,action:u},null,b)}}else typeof u=="function"&&(h.preventDefault(),b=o?Kd(i,o):new FormData(i),Bs(a,{pending:!0,data:b,method:i.method,action:u},u,b))},currentTarget:i}]})}}for(var Sr=0;Sr<ls.length;Sr++){var Er=ls[Sr],by=Er.toLowerCase(),xy=Er[0].toUpperCase()+Er.slice(1);zt(by,"on"+xy)}zt(xo,"onAnimationEnd"),zt(So,"onAnimationIteration"),zt(Eo,"onAnimationStart"),zt("dblclick","onDoubleClick"),zt("focusin","onFocus"),zt("focusout","onBlur"),zt(km,"onTransitionRun"),zt(qm,"onTransitionStart"),zt(Hm,"onTransitionCancel"),zt(jo,"onTransitionEnd"),dl("onMouseEnter",["mouseout","mouseover"]),dl("onMouseLeave",["mouseout","mouseover"]),dl("onPointerEnter",["pointerout","pointerover"]),dl("onPointerLeave",["pointerout","pointerover"]),Ba("onChange","change click focusin focusout input keydown keyup selectionchange".split(" ")),Ba("onSelect","focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" ")),Ba("onBeforeInput",["compositionend","keypress","textInput","paste"]),Ba("onCompositionEnd","compositionend focusout keydown keypress keyup mousedown".split(" ")),Ba("onCompositionStart","compositionstart focusout keydown keypress keyup mousedown".split(" ")),Ba("onCompositionUpdate","compositionupdate focusout keydown keypress keyup mousedown".split(" "));var kn="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange resize seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Sy=new Set("beforetoggle cancel close invalid load scroll scrollend toggle".split(" ").concat(kn));function Xd(e,t){t=(t&4)!==0;for(var a=0;a<e.length;a++){var l=e[a],i=l.event;l=l.listeners;e:{var u=void 0;if(t)for(var o=l.length-1;0<=o;o--){var h=l[o],b=h.instance,O=h.currentTarget;if(h=h.listener,b!==u&&i.isPropagationStopped())break e;u=h,i.currentTarget=O;try{u(i)}catch(D){mi(D)}i.currentTarget=null,u=b}else for(o=0;o<l.length;o++){if(h=l[o],b=h.instance,O=h.currentTarget,h=h.listener,b!==u&&i.isPropagationStopped())break e;u=h,i.currentTarget=O;try{u(i)}catch(D){mi(D)}i.currentTarget=null,u=b}}}}function ue(e,t){var a=t[Uu];a===void 0&&(a=t[Uu]=new Set);var l=e+"__bubble";a.has(l)||(Zd(t,e,2,!1),a.add(l))}function jr(e,t,a){var l=0;t&&(l|=4),Zd(a,e,l,t)}var eu="_reactListening"+Math.random().toString(36).slice(2);function Tr(e){if(!e[eu]){e[eu]=!0,qc.forEach(function(a){a!=="selectionchange"&&(Sy.has(a)||jr(a,!1,e),jr(a,!0,e))});var t=e.nodeType===9?e:e.ownerDocument;t===null||t[eu]||(t[eu]=!0,jr("selectionchange",!1,t))}}function Zd(e,t,a,l){switch(Sh(t)){case 2:var i=Fy;break;case 8:i=$y;break;default:i=Lr}a=i.bind(null,t,a,e),i=void 0,!Ku||t!=="touchstart"&&t!=="touchmove"&&t!=="wheel"||(i=!0),l?i!==void 0?e.addEventListener(t,a,{capture:!0,passive:i}):e.addEventListener(t,a,!0):i!==void 0?e.addEventListener(t,a,{passive:i}):e.addEventListener(t,a,!1)}function Nr(e,t,a,l,i){var u=l;if((t&1)===0&&(t&2)===0&&l!==null)e:for(;;){if(l===null)return;var o=l.tag;if(o===3||o===4){var h=l.stateNode.containerInfo;if(h===i)break;if(o===4)for(o=l.return;o!==null;){var b=o.tag;if((b===3||b===4)&&o.stateNode.containerInfo===i)return;o=o.return}for(;h!==null;){if(o=cl(h),o===null)return;if(b=o.tag,b===5||b===6||b===26||b===27){l=u=o;continue e}h=h.parentNode}}l=l.return}Fc(function(){var O=u,D=Yu(a),w=[];e:{var A=To.get(e);if(A!==void 0){var _=fi,Z=e;switch(e){case"keypress":if(ci(a)===0)break e;case"keydown":case"keyup":_=mm;break;case"focusin":Z="focus",_=Ju;break;case"focusout":Z="blur",_=Ju;break;case"beforeblur":case"afterblur":_=Ju;break;case"click":if(a.button===2)break e;case"auxclick":case"dblclick":case"mousedown":case"mousemove":case"mouseup":case"mouseout":case"mouseover":case"contextmenu":_=Ic;break;case"drag":case"dragend":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"dragstart":case"drop":_=am;break;case"touchcancel":case"touchend":case"touchmove":case"touchstart":_=vm;break;case xo:case So:case Eo:_=im;break;case jo:_=bm;break;case"scroll":case"scrollend":_=em;break;case"wheel":_=Sm;break;case"copy":case"cut":case"paste":_=sm;break;case"gotpointercapture":case"lostpointercapture":case"pointercancel":case"pointerdown":case"pointermove":case"pointerout":case"pointerover":case"pointerup":_=eo;break;case"toggle":case"beforetoggle":_=jm}var W=(t&4)!==0,Se=!W&&(e==="scroll"||e==="scrollend"),j=W?A!==null?A+"Capture":null:A;W=[];for(var x=O,C;x!==null;){var z=x;if(C=z.stateNode,z=z.tag,z!==5&&z!==26&&z!==27||C===null||j===null||(z=ln(x,j),z!=null&&W.push(qn(x,z,C))),Se)break;x=x.return}0<W.length&&(A=new _(A,Z,null,a,D),w.push({event:A,listeners:W}))}}if((t&7)===0){e:{if(A=e==="mouseover"||e==="pointerover",_=e==="mouseout"||e==="pointerout",A&&a!==Qu&&(Z=a.relatedTarget||a.fromElement)&&(cl(Z)||Z[rl]))break e;if((_||A)&&(A=D.window===D?D:(A=D.ownerDocument)?A.defaultView||A.parentWindow:window,_?(Z=a.relatedTarget||a.toElement,_=O,Z=Z?cl(Z):null,Z!==null&&(Se=m(Z),W=Z.tag,Z!==Se||W!==5&&W!==27&&W!==6)&&(Z=null)):(_=null,Z=O),_!==Z)){if(W=Ic,z="onMouseLeave",j="onMouseEnter",x="mouse",(e==="pointerout"||e==="pointerover")&&(W=eo,z="onPointerLeave",j="onPointerEnter",x="pointer"),Se=_==null?A:an(_),C=Z==null?A:an(Z),A=new W(z,x+"leave",_,a,D),A.target=Se,A.relatedTarget=C,z=null,cl(D)===O&&(W=new W(j,x+"enter",Z,a,D),W.target=C,W.relatedTarget=Se,z=W),Se=z,_&&Z)t:{for(W=Ey,j=_,x=Z,C=0,z=j;z;z=W(z))C++;z=0;for(var F=x;F;F=W(F))z++;for(;0<C-z;)j=W(j),C--;for(;0<z-C;)x=W(x),z--;for(;C--;){if(j===x||x!==null&&j===x.alternate){W=j;break t}j=W(j),x=W(x)}W=null}else W=null;_!==null&&Vd(w,A,_,W,!1),Z!==null&&Se!==null&&Vd(w,Se,Z,W,!0)}}e:{if(A=O?an(O):window,_=A.nodeName&&A.nodeName.toLowerCase(),_==="select"||_==="input"&&A.type==="file")var fe=ro;else if(uo(A))if(co)fe=zm;else{fe=Dm;var J=_m}else _=A.nodeName,!_||_.toLowerCase()!=="input"||A.type!=="checkbox"&&A.type!=="radio"?O&&Bu(O.elementType)&&(fe=ro):fe=Mm;if(fe&&(fe=fe(e,O))){so(w,fe,a,D);break e}J&&J(e,A,O),e==="focusout"&&O&&A.type==="number"&&O.memoizedProps.value!=null&&Lu(A,"number",A.value)}switch(J=O?an(O):window,e){case"focusin":(uo(J)||J.contentEditable==="true")&&(pl=J,es=O,dn=null);break;case"focusout":dn=es=pl=null;break;case"mousedown":ts=!0;break;case"contextmenu":case"mouseup":case"dragend":ts=!1,po(w,a,D);break;case"selectionchange":if(Um)break;case"keydown":case"keyup":po(w,a,D)}var le;if($u)e:{switch(e){case"compositionstart":var re="onCompositionStart";break e;case"compositionend":re="onCompositionEnd";break e;case"compositionupdate":re="onCompositionUpdate";break e}re=void 0}else vl?no(e,a)&&(re="onCompositionEnd"):e==="keydown"&&a.keyCode===229&&(re="onCompositionStart");re&&(to&&a.locale!=="ko"&&(vl||re!=="onCompositionStart"?re==="onCompositionEnd"&&vl&&(le=$c()):(da=D,Xu="value"in da?da.value:da.textContent,vl=!0)),J=tu(O,re),0<J.length&&(re=new Pc(re,e,null,a,D),w.push({event:re,listeners:J}),le?re.data=le:(le=io(a),le!==null&&(re.data=le)))),(le=Nm?Cm(e,a):Om(e,a))&&(re=tu(O,"onBeforeInput"),0<re.length&&(J=new Pc("onBeforeInput","beforeinput",null,a,D),w.push({event:J,listeners:re}),J.data=le)),py(w,e,O,a,D)}Xd(w,t)})}function qn(e,t,a){return{instance:e,listener:t,currentTarget:a}}function tu(e,t){for(var a=t+"Capture",l=[];e!==null;){var i=e,u=i.stateNode;if(i=i.tag,i!==5&&i!==26&&i!==27||u===null||(i=ln(e,a),i!=null&&l.unshift(qn(e,i,u)),i=ln(e,t),i!=null&&l.push(qn(e,i,u))),e.tag===3)return l;e=e.return}return[]}function Ey(e){if(e===null)return null;do e=e.return;while(e&&e.tag!==5&&e.tag!==27);return e||null}function Vd(e,t,a,l,i){for(var u=t._reactName,o=[];a!==null&&a!==l;){var h=a,b=h.alternate,O=h.stateNode;if(h=h.tag,b!==null&&b===l)break;h!==5&&h!==26&&h!==27||O===null||(b=O,i?(O=ln(a,u),O!=null&&o.unshift(qn(a,O,b))):i||(O=ln(a,u),O!=null&&o.push(qn(a,O,b)))),a=a.return}o.length!==0&&e.push({event:t,listeners:o})}var jy=/\r\n?/g,Ty=/\u0000|\uFFFD/g;function Jd(e){return(typeof e=="string"?e:""+e).replace(jy,` 9 + `).replace(Ty,"")}function Fd(e,t){return t=Jd(t),Jd(e)===t}function xe(e,t,a,l,i,u){switch(a){case"children":typeof l=="string"?t==="body"||t==="textarea"&&l===""||ml(e,l):(typeof l=="number"||typeof l=="bigint")&&t!=="body"&&ml(e,""+l);break;case"className":ii(e,"class",l);break;case"tabIndex":ii(e,"tabindex",l);break;case"dir":case"role":case"viewBox":case"width":case"height":ii(e,a,l);break;case"style":Vc(e,l,u);break;case"data":if(t!=="object"){ii(e,"data",l);break}case"src":case"href":if(l===""&&(t!=="a"||a!=="href")){e.removeAttribute(a);break}if(l==null||typeof l=="function"||typeof l=="symbol"||typeof l=="boolean"){e.removeAttribute(a);break}l=si(""+l),e.setAttribute(a,l);break;case"action":case"formAction":if(typeof l=="function"){e.setAttribute(a,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof u=="function"&&(a==="formAction"?(t!=="input"&&xe(e,t,"name",i.name,i,null),xe(e,t,"formEncType",i.formEncType,i,null),xe(e,t,"formMethod",i.formMethod,i,null),xe(e,t,"formTarget",i.formTarget,i,null)):(xe(e,t,"encType",i.encType,i,null),xe(e,t,"method",i.method,i,null),xe(e,t,"target",i.target,i,null)));if(l==null||typeof l=="symbol"||typeof l=="boolean"){e.removeAttribute(a);break}l=si(""+l),e.setAttribute(a,l);break;case"onClick":l!=null&&(e.onclick=Xt);break;case"onScroll":l!=null&&ue("scroll",e);break;case"onScrollEnd":l!=null&&ue("scrollend",e);break;case"dangerouslySetInnerHTML":if(l!=null){if(typeof l!="object"||!("__html"in l))throw Error(c(61));if(a=l.__html,a!=null){if(i.children!=null)throw Error(c(60));e.innerHTML=a}}break;case"multiple":e.multiple=l&&typeof l!="function"&&typeof l!="symbol";break;case"muted":e.muted=l&&typeof l!="function"&&typeof l!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(l==null||typeof l=="function"||typeof l=="boolean"||typeof l=="symbol"){e.removeAttribute("xlink:href");break}a=si(""+l),e.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",a);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":l!=null&&typeof l!="function"&&typeof l!="symbol"?e.setAttribute(a,""+l):e.removeAttribute(a);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":l&&typeof l!="function"&&typeof l!="symbol"?e.setAttribute(a,""):e.removeAttribute(a);break;case"capture":case"download":l===!0?e.setAttribute(a,""):l!==!1&&l!=null&&typeof l!="function"&&typeof l!="symbol"?e.setAttribute(a,l):e.removeAttribute(a);break;case"cols":case"rows":case"size":case"span":l!=null&&typeof l!="function"&&typeof l!="symbol"&&!isNaN(l)&&1<=l?e.setAttribute(a,l):e.removeAttribute(a);break;case"rowSpan":case"start":l==null||typeof l=="function"||typeof l=="symbol"||isNaN(l)?e.removeAttribute(a):e.setAttribute(a,l);break;case"popover":ue("beforetoggle",e),ue("toggle",e),ni(e,"popover",l);break;case"xlinkActuate":Kt(e,"http://www.w3.org/1999/xlink","xlink:actuate",l);break;case"xlinkArcrole":Kt(e,"http://www.w3.org/1999/xlink","xlink:arcrole",l);break;case"xlinkRole":Kt(e,"http://www.w3.org/1999/xlink","xlink:role",l);break;case"xlinkShow":Kt(e,"http://www.w3.org/1999/xlink","xlink:show",l);break;case"xlinkTitle":Kt(e,"http://www.w3.org/1999/xlink","xlink:title",l);break;case"xlinkType":Kt(e,"http://www.w3.org/1999/xlink","xlink:type",l);break;case"xmlBase":Kt(e,"http://www.w3.org/XML/1998/namespace","xml:base",l);break;case"xmlLang":Kt(e,"http://www.w3.org/XML/1998/namespace","xml:lang",l);break;case"xmlSpace":Kt(e,"http://www.w3.org/XML/1998/namespace","xml:space",l);break;case"is":ni(e,"is",l);break;case"innerText":case"textContent":break;default:(!(2<a.length)||a[0]!=="o"&&a[0]!=="O"||a[1]!=="n"&&a[1]!=="N")&&(a=I0.get(a)||a,ni(e,a,l))}}function Cr(e,t,a,l,i,u){switch(a){case"style":Vc(e,l,u);break;case"dangerouslySetInnerHTML":if(l!=null){if(typeof l!="object"||!("__html"in l))throw Error(c(61));if(a=l.__html,a!=null){if(i.children!=null)throw Error(c(60));e.innerHTML=a}}break;case"children":typeof l=="string"?ml(e,l):(typeof l=="number"||typeof l=="bigint")&&ml(e,""+l);break;case"onScroll":l!=null&&ue("scroll",e);break;case"onScrollEnd":l!=null&&ue("scrollend",e);break;case"onClick":l!=null&&(e.onclick=Xt);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"innerHTML":case"ref":break;case"innerText":case"textContent":break;default:if(!Hc.hasOwnProperty(a))e:{if(a[0]==="o"&&a[1]==="n"&&(i=a.endsWith("Capture"),t=a.slice(2,i?a.length-7:void 0),u=e[et]||null,u=u!=null?u[a]:null,typeof u=="function"&&e.removeEventListener(t,u,i),typeof l=="function")){typeof u!="function"&&u!==null&&(a in e?e[a]=null:e.hasAttribute(a)&&e.removeAttribute(a)),e.addEventListener(t,l,i);break e}a in e?e[a]=l:l===!0?e.setAttribute(a,""):ni(e,a,l)}}}function Fe(e,t,a){switch(t){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"img":ue("error",e),ue("load",e);var l=!1,i=!1,u;for(u in a)if(a.hasOwnProperty(u)){var o=a[u];if(o!=null)switch(u){case"src":l=!0;break;case"srcSet":i=!0;break;case"children":case"dangerouslySetInnerHTML":throw Error(c(137,t));default:xe(e,t,u,o,a,null)}}i&&xe(e,t,"srcSet",a.srcSet,a,null),l&&xe(e,t,"src",a.src,a,null);return;case"input":ue("invalid",e);var h=u=o=i=null,b=null,O=null;for(l in a)if(a.hasOwnProperty(l)){var D=a[l];if(D!=null)switch(l){case"name":i=D;break;case"type":o=D;break;case"checked":b=D;break;case"defaultChecked":O=D;break;case"value":u=D;break;case"defaultValue":h=D;break;case"children":case"dangerouslySetInnerHTML":if(D!=null)throw Error(c(137,t));break;default:xe(e,t,l,D,a,null)}}Gc(e,u,h,b,O,o,i,!1);return;case"select":ue("invalid",e),l=o=u=null;for(i in a)if(a.hasOwnProperty(i)&&(h=a[i],h!=null))switch(i){case"value":u=h;break;case"defaultValue":o=h;break;case"multiple":l=h;default:xe(e,t,i,h,a,null)}t=u,a=o,e.multiple=!!l,t!=null?hl(e,!!l,t,!1):a!=null&&hl(e,!!l,a,!0);return;case"textarea":ue("invalid",e),u=i=l=null;for(o in a)if(a.hasOwnProperty(o)&&(h=a[o],h!=null))switch(o){case"value":l=h;break;case"defaultValue":i=h;break;case"children":u=h;break;case"dangerouslySetInnerHTML":if(h!=null)throw Error(c(91));break;default:xe(e,t,o,h,a,null)}Xc(e,l,i,u);return;case"option":for(b in a)a.hasOwnProperty(b)&&(l=a[b],l!=null)&&(b==="selected"?e.selected=l&&typeof l!="function"&&typeof l!="symbol":xe(e,t,b,l,a,null));return;case"dialog":ue("beforetoggle",e),ue("toggle",e),ue("cancel",e),ue("close",e);break;case"iframe":case"object":ue("load",e);break;case"video":case"audio":for(l=0;l<kn.length;l++)ue(kn[l],e);break;case"image":ue("error",e),ue("load",e);break;case"details":ue("toggle",e);break;case"embed":case"source":case"link":ue("error",e),ue("load",e);case"area":case"base":case"br":case"col":case"hr":case"keygen":case"meta":case"param":case"track":case"wbr":case"menuitem":for(O in a)if(a.hasOwnProperty(O)&&(l=a[O],l!=null))switch(O){case"children":case"dangerouslySetInnerHTML":throw Error(c(137,t));default:xe(e,t,O,l,a,null)}return;default:if(Bu(t)){for(D in a)a.hasOwnProperty(D)&&(l=a[D],l!==void 0&&Cr(e,t,D,l,a,void 0));return}}for(h in a)a.hasOwnProperty(h)&&(l=a[h],l!=null&&xe(e,t,h,l,a,null))}function Ny(e,t,a,l){switch(t){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"input":var i=null,u=null,o=null,h=null,b=null,O=null,D=null;for(_ in a){var w=a[_];if(a.hasOwnProperty(_)&&w!=null)switch(_){case"checked":break;case"value":break;case"defaultValue":b=w;default:l.hasOwnProperty(_)||xe(e,t,_,null,l,w)}}for(var A in l){var _=l[A];if(w=a[A],l.hasOwnProperty(A)&&(_!=null||w!=null))switch(A){case"type":u=_;break;case"name":i=_;break;case"checked":O=_;break;case"defaultChecked":D=_;break;case"value":o=_;break;case"defaultValue":h=_;break;case"children":case"dangerouslySetInnerHTML":if(_!=null)throw Error(c(137,t));break;default:_!==w&&xe(e,t,A,_,l,w)}}Hu(e,o,h,b,O,D,u,i);return;case"select":_=o=h=A=null;for(u in a)if(b=a[u],a.hasOwnProperty(u)&&b!=null)switch(u){case"value":break;case"multiple":_=b;default:l.hasOwnProperty(u)||xe(e,t,u,null,l,b)}for(i in l)if(u=l[i],b=a[i],l.hasOwnProperty(i)&&(u!=null||b!=null))switch(i){case"value":A=u;break;case"defaultValue":h=u;break;case"multiple":o=u;default:u!==b&&xe(e,t,i,u,l,b)}t=h,a=o,l=_,A!=null?hl(e,!!a,A,!1):!!l!=!!a&&(t!=null?hl(e,!!a,t,!0):hl(e,!!a,a?[]:"",!1));return;case"textarea":_=A=null;for(h in a)if(i=a[h],a.hasOwnProperty(h)&&i!=null&&!l.hasOwnProperty(h))switch(h){case"value":break;case"children":break;default:xe(e,t,h,null,l,i)}for(o in l)if(i=l[o],u=a[o],l.hasOwnProperty(o)&&(i!=null||u!=null))switch(o){case"value":A=i;break;case"defaultValue":_=i;break;case"children":break;case"dangerouslySetInnerHTML":if(i!=null)throw Error(c(91));break;default:i!==u&&xe(e,t,o,i,l,u)}Kc(e,A,_);return;case"option":for(var Z in a)A=a[Z],a.hasOwnProperty(Z)&&A!=null&&!l.hasOwnProperty(Z)&&(Z==="selected"?e.selected=!1:xe(e,t,Z,null,l,A));for(b in l)A=l[b],_=a[b],l.hasOwnProperty(b)&&A!==_&&(A!=null||_!=null)&&(b==="selected"?e.selected=A&&typeof A!="function"&&typeof A!="symbol":xe(e,t,b,A,l,_));return;case"img":case"link":case"area":case"base":case"br":case"col":case"embed":case"hr":case"keygen":case"meta":case"param":case"source":case"track":case"wbr":case"menuitem":for(var W in a)A=a[W],a.hasOwnProperty(W)&&A!=null&&!l.hasOwnProperty(W)&&xe(e,t,W,null,l,A);for(O in l)if(A=l[O],_=a[O],l.hasOwnProperty(O)&&A!==_&&(A!=null||_!=null))switch(O){case"children":case"dangerouslySetInnerHTML":if(A!=null)throw Error(c(137,t));break;default:xe(e,t,O,A,l,_)}return;default:if(Bu(t)){for(var Se in a)A=a[Se],a.hasOwnProperty(Se)&&A!==void 0&&!l.hasOwnProperty(Se)&&Cr(e,t,Se,void 0,l,A);for(D in l)A=l[D],_=a[D],!l.hasOwnProperty(D)||A===_||A===void 0&&_===void 0||Cr(e,t,D,A,l,_);return}}for(var j in a)A=a[j],a.hasOwnProperty(j)&&A!=null&&!l.hasOwnProperty(j)&&xe(e,t,j,null,l,A);for(w in l)A=l[w],_=a[w],!l.hasOwnProperty(w)||A===_||A==null&&_==null||xe(e,t,w,A,l,_)}function $d(e){switch(e){case"css":case"script":case"font":case"img":case"image":case"input":case"link":return!0;default:return!1}}function Cy(){if(typeof performance.getEntriesByType=="function"){for(var e=0,t=0,a=performance.getEntriesByType("resource"),l=0;l<a.length;l++){var i=a[l],u=i.transferSize,o=i.initiatorType,h=i.duration;if(u&&h&&$d(o)){for(o=0,h=i.responseEnd,l+=1;l<a.length;l++){var b=a[l],O=b.startTime;if(O>h)break;var D=b.transferSize,w=b.initiatorType;D&&$d(w)&&(b=b.responseEnd,o+=D*(b<h?1:(h-O)/(b-O)))}if(--l,t+=8*(u+o)/(i.duration/1e3),e++,10<e)break}}if(0<e)return t/e/1e6}return navigator.connection&&(e=navigator.connection.downlink,typeof e=="number")?e:5}var Or=null,Ar=null;function au(e){return e.nodeType===9?e:e.ownerDocument}function Wd(e){switch(e){case"http://www.w3.org/2000/svg":return 1;case"http://www.w3.org/1998/Math/MathML":return 2;default:return 0}}function Id(e,t){if(e===0)switch(t){case"svg":return 1;case"math":return 2;default:return 0}return e===1&&t==="foreignObject"?0:e}function Rr(e,t){return e==="textarea"||e==="noscript"||typeof t.children=="string"||typeof t.children=="number"||typeof t.children=="bigint"||typeof t.dangerouslySetInnerHTML=="object"&&t.dangerouslySetInnerHTML!==null&&t.dangerouslySetInnerHTML.__html!=null}var _r=null;function Oy(){var e=window.event;return e&&e.type==="popstate"?e===_r?!1:(_r=e,!0):(_r=null,!1)}var Pd=typeof setTimeout=="function"?setTimeout:void 0,Ay=typeof clearTimeout=="function"?clearTimeout:void 0,eh=typeof Promise=="function"?Promise:void 0,Ry=typeof queueMicrotask=="function"?queueMicrotask:typeof eh<"u"?function(e){return eh.resolve(null).then(e).catch(_y)}:Pd;function _y(e){setTimeout(function(){throw e})}function Ra(e){return e==="head"}function th(e,t){var a=t,l=0;do{var i=a.nextSibling;if(e.removeChild(a),i&&i.nodeType===8)if(a=i.data,a==="/$"||a==="/&"){if(l===0){e.removeChild(i),Xl(t);return}l--}else if(a==="$"||a==="$?"||a==="$~"||a==="$!"||a==="&")l++;else if(a==="html")Hn(e.ownerDocument.documentElement);else if(a==="head"){a=e.ownerDocument.head,Hn(a);for(var u=a.firstChild;u;){var o=u.nextSibling,h=u.nodeName;u[tn]||h==="SCRIPT"||h==="STYLE"||h==="LINK"&&u.rel.toLowerCase()==="stylesheet"||a.removeChild(u),u=o}}else a==="body"&&Hn(e.ownerDocument.body);a=i}while(a);Xl(t)}function ah(e,t){var a=e;e=0;do{var l=a.nextSibling;if(a.nodeType===1?t?(a._stashedDisplay=a.style.display,a.style.display="none"):(a.style.display=a._stashedDisplay||"",a.getAttribute("style")===""&&a.removeAttribute("style")):a.nodeType===3&&(t?(a._stashedText=a.nodeValue,a.nodeValue=""):a.nodeValue=a._stashedText||""),l&&l.nodeType===8)if(a=l.data,a==="/$"){if(e===0)break;e--}else a!=="$"&&a!=="$?"&&a!=="$~"&&a!=="$!"||e++;a=l}while(a)}function Dr(e){var t=e.firstChild;for(t&&t.nodeType===10&&(t=t.nextSibling);t;){var a=t;switch(t=t.nextSibling,a.nodeName){case"HTML":case"HEAD":case"BODY":Dr(a),ku(a);continue;case"SCRIPT":case"STYLE":continue;case"LINK":if(a.rel.toLowerCase()==="stylesheet")continue}e.removeChild(a)}}function Dy(e,t,a,l){for(;e.nodeType===1;){var i=a;if(e.nodeName.toLowerCase()!==t.toLowerCase()){if(!l&&(e.nodeName!=="INPUT"||e.type!=="hidden"))break}else if(l){if(!e[tn])switch(t){case"meta":if(!e.hasAttribute("itemprop"))break;return e;case"link":if(u=e.getAttribute("rel"),u==="stylesheet"&&e.hasAttribute("data-precedence"))break;if(u!==i.rel||e.getAttribute("href")!==(i.href==null||i.href===""?null:i.href)||e.getAttribute("crossorigin")!==(i.crossOrigin==null?null:i.crossOrigin)||e.getAttribute("title")!==(i.title==null?null:i.title))break;return e;case"style":if(e.hasAttribute("data-precedence"))break;return e;case"script":if(u=e.getAttribute("src"),(u!==(i.src==null?null:i.src)||e.getAttribute("type")!==(i.type==null?null:i.type)||e.getAttribute("crossorigin")!==(i.crossOrigin==null?null:i.crossOrigin))&&u&&e.hasAttribute("async")&&!e.hasAttribute("itemprop"))break;return e;default:return e}}else if(t==="input"&&e.type==="hidden"){var u=i.name==null?null:""+i.name;if(i.type==="hidden"&&e.getAttribute("name")===u)return e}else return e;if(e=Rt(e.nextSibling),e===null)break}return null}function My(e,t,a){if(t==="")return null;for(;e.nodeType!==3;)if((e.nodeType!==1||e.nodeName!=="INPUT"||e.type!=="hidden")&&!a||(e=Rt(e.nextSibling),e===null))return null;return e}function lh(e,t){for(;e.nodeType!==8;)if((e.nodeType!==1||e.nodeName!=="INPUT"||e.type!=="hidden")&&!t||(e=Rt(e.nextSibling),e===null))return null;return e}function Mr(e){return e.data==="$?"||e.data==="$~"}function zr(e){return e.data==="$!"||e.data==="$?"&&e.ownerDocument.readyState!=="loading"}function zy(e,t){var a=e.ownerDocument;if(e.data==="$~")e._reactRetry=t;else if(e.data!=="$?"||a.readyState!=="loading")t();else{var l=function(){t(),a.removeEventListener("DOMContentLoaded",l)};a.addEventListener("DOMContentLoaded",l),e._reactRetry=l}}function Rt(e){for(;e!=null;e=e.nextSibling){var t=e.nodeType;if(t===1||t===3)break;if(t===8){if(t=e.data,t==="$"||t==="$!"||t==="$?"||t==="$~"||t==="&"||t==="F!"||t==="F")break;if(t==="/$"||t==="/&")return null}}return e}var wr=null;function nh(e){e=e.nextSibling;for(var t=0;e;){if(e.nodeType===8){var a=e.data;if(a==="/$"||a==="/&"){if(t===0)return Rt(e.nextSibling);t--}else a!=="$"&&a!=="$!"&&a!=="$?"&&a!=="$~"&&a!=="&"||t++}e=e.nextSibling}return null}function ih(e){e=e.previousSibling;for(var t=0;e;){if(e.nodeType===8){var a=e.data;if(a==="$"||a==="$!"||a==="$?"||a==="$~"||a==="&"){if(t===0)return e;t--}else a!=="/$"&&a!=="/&"||t++}e=e.previousSibling}return null}function uh(e,t,a){switch(t=au(a),e){case"html":if(e=t.documentElement,!e)throw Error(c(452));return e;case"head":if(e=t.head,!e)throw Error(c(453));return e;case"body":if(e=t.body,!e)throw Error(c(454));return e;default:throw Error(c(451))}}function Hn(e){for(var t=e.attributes;t.length;)e.removeAttributeNode(t[0]);ku(e)}var _t=new Map,sh=new Set;function lu(e){return typeof e.getRootNode=="function"?e.getRootNode():e.nodeType===9?e:e.ownerDocument}var sa=Y.d;Y.d={f:wy,r:Uy,D:ky,C:qy,L:Hy,m:Ly,X:Qy,S:By,M:Yy};function wy(){var e=sa.f(),t=Ji();return e||t}function Uy(e){var t=ol(e);t!==null&&t.tag===5&&t.type==="form"?Tf(t):sa.r(e)}var Yl=typeof document>"u"?null:document;function rh(e,t,a){var l=Yl;if(l&&typeof t=="string"&&t){var i=Et(t);i='link[rel="'+e+'"][href="'+i+'"]',typeof a=="string"&&(i+='[crossorigin="'+a+'"]'),sh.has(i)||(sh.add(i),e={rel:e,crossOrigin:a,href:t},l.querySelector(i)===null&&(t=l.createElement("link"),Fe(t,"link",e),Ye(t),l.head.appendChild(t)))}}function ky(e){sa.D(e),rh("dns-prefetch",e,null)}function qy(e,t){sa.C(e,t),rh("preconnect",e,t)}function Hy(e,t,a){sa.L(e,t,a);var l=Yl;if(l&&e&&t){var i='link[rel="preload"][as="'+Et(t)+'"]';t==="image"&&a&&a.imageSrcSet?(i+='[imagesrcset="'+Et(a.imageSrcSet)+'"]',typeof a.imageSizes=="string"&&(i+='[imagesizes="'+Et(a.imageSizes)+'"]')):i+='[href="'+Et(e)+'"]';var u=i;switch(t){case"style":u=Gl(e);break;case"script":u=Kl(e)}_t.has(u)||(e=T({rel:"preload",href:t==="image"&&a&&a.imageSrcSet?void 0:e,as:t},a),_t.set(u,e),l.querySelector(i)!==null||t==="style"&&l.querySelector(Ln(u))||t==="script"&&l.querySelector(Bn(u))||(t=l.createElement("link"),Fe(t,"link",e),Ye(t),l.head.appendChild(t)))}}function Ly(e,t){sa.m(e,t);var a=Yl;if(a&&e){var l=t&&typeof t.as=="string"?t.as:"script",i='link[rel="modulepreload"][as="'+Et(l)+'"][href="'+Et(e)+'"]',u=i;switch(l){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":u=Kl(e)}if(!_t.has(u)&&(e=T({rel:"modulepreload",href:e},t),_t.set(u,e),a.querySelector(i)===null)){switch(l){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(a.querySelector(Bn(u)))return}l=a.createElement("link"),Fe(l,"link",e),Ye(l),a.head.appendChild(l)}}}function By(e,t,a){sa.S(e,t,a);var l=Yl;if(l&&e){var i=fl(l).hoistableStyles,u=Gl(e);t=t||"default";var o=i.get(u);if(!o){var h={loading:0,preload:null};if(o=l.querySelector(Ln(u)))h.loading=5;else{e=T({rel:"stylesheet",href:e,"data-precedence":t},a),(a=_t.get(u))&&Ur(e,a);var b=o=l.createElement("link");Ye(b),Fe(b,"link",e),b._p=new Promise(function(O,D){b.onload=O,b.onerror=D}),b.addEventListener("load",function(){h.loading|=1}),b.addEventListener("error",function(){h.loading|=2}),h.loading|=4,nu(o,t,l)}o={type:"stylesheet",instance:o,count:1,state:h},i.set(u,o)}}}function Qy(e,t){sa.X(e,t);var a=Yl;if(a&&e){var l=fl(a).hoistableScripts,i=Kl(e),u=l.get(i);u||(u=a.querySelector(Bn(i)),u||(e=T({src:e,async:!0},t),(t=_t.get(i))&&kr(e,t),u=a.createElement("script"),Ye(u),Fe(u,"link",e),a.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},l.set(i,u))}}function Yy(e,t){sa.M(e,t);var a=Yl;if(a&&e){var l=fl(a).hoistableScripts,i=Kl(e),u=l.get(i);u||(u=a.querySelector(Bn(i)),u||(e=T({src:e,async:!0,type:"module"},t),(t=_t.get(i))&&kr(e,t),u=a.createElement("script"),Ye(u),Fe(u,"link",e),a.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},l.set(i,u))}}function ch(e,t,a,l){var i=(i=ne.current)?lu(i):null;if(!i)throw Error(c(446));switch(e){case"meta":case"title":return null;case"style":return typeof a.precedence=="string"&&typeof a.href=="string"?(t=Gl(a.href),a=fl(i).hoistableStyles,l=a.get(t),l||(l={type:"style",instance:null,count:0,state:null},a.set(t,l)),l):{type:"void",instance:null,count:0,state:null};case"link":if(a.rel==="stylesheet"&&typeof a.href=="string"&&typeof a.precedence=="string"){e=Gl(a.href);var u=fl(i).hoistableStyles,o=u.get(e);if(o||(i=i.ownerDocument||i,o={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},u.set(e,o),(u=i.querySelector(Ln(e)))&&!u._p&&(o.instance=u,o.state.loading=5),_t.has(e)||(a={rel:"preload",as:"style",href:a.href,crossOrigin:a.crossOrigin,integrity:a.integrity,media:a.media,hrefLang:a.hrefLang,referrerPolicy:a.referrerPolicy},_t.set(e,a),u||Gy(i,e,a,o.state))),t&&l===null)throw Error(c(528,""));return o}if(t&&l!==null)throw Error(c(529,""));return null;case"script":return t=a.async,a=a.src,typeof a=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=Kl(a),a=fl(i).hoistableScripts,l=a.get(t),l||(l={type:"script",instance:null,count:0,state:null},a.set(t,l)),l):{type:"void",instance:null,count:0,state:null};default:throw Error(c(444,e))}}function Gl(e){return'href="'+Et(e)+'"'}function Ln(e){return'link[rel="stylesheet"]['+e+"]"}function oh(e){return T({},e,{"data-precedence":e.precedence,precedence:null})}function Gy(e,t,a,l){e.querySelector('link[rel="preload"][as="style"]['+t+"]")?l.loading=1:(t=e.createElement("link"),l.preload=t,t.addEventListener("load",function(){return l.loading|=1}),t.addEventListener("error",function(){return l.loading|=2}),Fe(t,"link",a),Ye(t),e.head.appendChild(t))}function Kl(e){return'[src="'+Et(e)+'"]'}function Bn(e){return"script[async]"+e}function fh(e,t,a){if(t.count++,t.instance===null)switch(t.type){case"style":var l=e.querySelector('style[data-href~="'+Et(a.href)+'"]');if(l)return t.instance=l,Ye(l),l;var i=T({},a,{"data-href":a.href,"data-precedence":a.precedence,href:null,precedence:null});return l=(e.ownerDocument||e).createElement("style"),Ye(l),Fe(l,"style",i),nu(l,a.precedence,e),t.instance=l;case"stylesheet":i=Gl(a.href);var u=e.querySelector(Ln(i));if(u)return t.state.loading|=4,t.instance=u,Ye(u),u;l=oh(a),(i=_t.get(i))&&Ur(l,i),u=(e.ownerDocument||e).createElement("link"),Ye(u);var o=u;return o._p=new Promise(function(h,b){o.onload=h,o.onerror=b}),Fe(u,"link",l),t.state.loading|=4,nu(u,a.precedence,e),t.instance=u;case"script":return u=Kl(a.src),(i=e.querySelector(Bn(u)))?(t.instance=i,Ye(i),i):(l=a,(i=_t.get(u))&&(l=T({},a),kr(l,i)),e=e.ownerDocument||e,i=e.createElement("script"),Ye(i),Fe(i,"link",l),e.head.appendChild(i),t.instance=i);case"void":return null;default:throw Error(c(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(l=t.instance,t.state.loading|=4,nu(l,a.precedence,e));return t.instance}function nu(e,t,a){for(var l=a.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),i=l.length?l[l.length-1]:null,u=i,o=0;o<l.length;o++){var h=l[o];if(h.dataset.precedence===t)u=h;else if(u!==i)break}u?u.parentNode.insertBefore(e,u.nextSibling):(t=a.nodeType===9?a.head:a,t.insertBefore(e,t.firstChild))}function Ur(e,t){e.crossOrigin==null&&(e.crossOrigin=t.crossOrigin),e.referrerPolicy==null&&(e.referrerPolicy=t.referrerPolicy),e.title==null&&(e.title=t.title)}function kr(e,t){e.crossOrigin==null&&(e.crossOrigin=t.crossOrigin),e.referrerPolicy==null&&(e.referrerPolicy=t.referrerPolicy),e.integrity==null&&(e.integrity=t.integrity)}var iu=null;function dh(e,t,a){if(iu===null){var l=new Map,i=iu=new Map;i.set(a,l)}else i=iu,l=i.get(a),l||(l=new Map,i.set(a,l));if(l.has(e))return l;for(l.set(e,null),a=a.getElementsByTagName(e),i=0;i<a.length;i++){var u=a[i];if(!(u[tn]||u[Xe]||e==="link"&&u.getAttribute("rel")==="stylesheet")&&u.namespaceURI!=="http://www.w3.org/2000/svg"){var o=u.getAttribute(t)||"";o=e+o;var h=l.get(o);h?h.push(u):l.set(o,[u])}}return l}function hh(e,t,a){e=e.ownerDocument||e,e.head.insertBefore(a,t==="title"?e.querySelector("head > title"):null)}function Ky(e,t,a){if(a===1||t.itemProp!=null)return!1;switch(e){case"meta":case"title":return!0;case"style":if(typeof t.precedence!="string"||typeof t.href!="string"||t.href==="")break;return!0;case"link":if(typeof t.rel!="string"||typeof t.href!="string"||t.href===""||t.onLoad||t.onError)break;return t.rel==="stylesheet"?(e=t.disabled,typeof t.precedence=="string"&&e==null):!0;case"script":if(t.async&&typeof t.async!="function"&&typeof t.async!="symbol"&&!t.onLoad&&!t.onError&&t.src&&typeof t.src=="string")return!0}return!1}function mh(e){return!(e.type==="stylesheet"&&(e.state.loading&3)===0)}function Xy(e,t,a,l){if(a.type==="stylesheet"&&(typeof l.media!="string"||matchMedia(l.media).matches!==!1)&&(a.state.loading&4)===0){if(a.instance===null){var i=Gl(l.href),u=t.querySelector(Ln(i));if(u){t=u._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(e.count++,e=uu.bind(e),t.then(e,e)),a.state.loading|=4,a.instance=u,Ye(u);return}u=t.ownerDocument||t,l=oh(l),(i=_t.get(i))&&Ur(l,i),u=u.createElement("link"),Ye(u);var o=u;o._p=new Promise(function(h,b){o.onload=h,o.onerror=b}),Fe(u,"link",l),a.instance=u}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(a,t),(t=a.state.preload)&&(a.state.loading&3)===0&&(e.count++,a=uu.bind(e),t.addEventListener("load",a),t.addEventListener("error",a))}}var qr=0;function Zy(e,t){return e.stylesheets&&e.count===0&&ru(e,e.stylesheets),0<e.count||0<e.imgCount?function(a){var l=setTimeout(function(){if(e.stylesheets&&ru(e,e.stylesheets),e.unsuspend){var u=e.unsuspend;e.unsuspend=null,u()}},6e4+t);0<e.imgBytes&&qr===0&&(qr=62500*Cy());var i=setTimeout(function(){if(e.waitingForImages=!1,e.count===0&&(e.stylesheets&&ru(e,e.stylesheets),e.unsuspend)){var u=e.unsuspend;e.unsuspend=null,u()}},(e.imgBytes>qr?50:800)+t);return e.unsuspend=a,function(){e.unsuspend=null,clearTimeout(l),clearTimeout(i)}}:null}function uu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)ru(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var su=null;function ru(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,su=new Map,t.forEach(Vy,e),su=null,uu.call(e))}function Vy(e,t){if(!(t.state.loading&4)){var a=su.get(e);if(a)var l=a.get(null);else{a=new Map,su.set(e,a);for(var i=e.querySelectorAll("link[data-precedence],style[data-precedence]"),u=0;u<i.length;u++){var o=i[u];(o.nodeName==="LINK"||o.getAttribute("media")!=="not all")&&(a.set(o.dataset.precedence,o),l=o)}l&&a.set(null,l)}i=t.instance,o=i.getAttribute("data-precedence"),u=a.get(o)||l,u===l&&a.set(null,i),a.set(o,i),this.count++,l=uu.bind(this),i.addEventListener("load",l),i.addEventListener("error",l),u?u.parentNode.insertBefore(i,u.nextSibling):(e=e.nodeType===9?e.head:e,e.insertBefore(i,e.firstChild)),t.state.loading|=4}}var Qn={$$typeof:K,Provider:null,Consumer:null,_currentValue:I,_currentValue2:I,_threadCount:0};function Jy(e,t,a,l,i,u,o,h,b){this.tag=1,this.containerInfo=e,this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.next=this.pendingContext=this.context=this.cancelPendingCommit=null,this.callbackPriority=0,this.expirationTimes=Mu(-1),this.entangledLanes=this.shellSuspendCounter=this.errorRecoveryDisabledLanes=this.expiredLanes=this.warmLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Mu(0),this.hiddenUpdates=Mu(null),this.identifierPrefix=l,this.onUncaughtError=i,this.onCaughtError=u,this.onRecoverableError=o,this.pooledCache=null,this.pooledCacheLanes=0,this.formState=b,this.incompleteTransitions=new Map}function yh(e,t,a,l,i,u,o,h,b,O,D,w){return e=new Jy(e,t,a,o,b,O,D,w,h),t=1,u===!0&&(t|=24),u=dt(3,null,null,t),e.current=u,u.stateNode=e,t=gs(),t.refCount++,e.pooledCache=t,t.refCount++,u.memoizedState={element:l,isDehydrated:a,cache:t},xs(u),e}function gh(e){return e?(e=Sl,e):Sl}function vh(e,t,a,l,i,u){i=gh(i),l.context===null?l.context=i:l.pendingContext=i,l=pa(t),l.payload={element:a},u=u===void 0?null:u,u!==null&&(l.callback=u),a=ba(e,l,t),a!==null&&(ut(a,e,t),bn(a,e,t))}function ph(e,t){if(e=e.memoizedState,e!==null&&e.dehydrated!==null){var a=e.retryLane;e.retryLane=a!==0&&a<t?a:t}}function Hr(e,t){ph(e,t),(e=e.alternate)&&ph(e,t)}function bh(e){if(e.tag===13||e.tag===31){var t=Ka(e,67108864);t!==null&&ut(t,e,67108864),Hr(e,67108864)}}function xh(e){if(e.tag===13||e.tag===31){var t=vt();t=zu(t);var a=Ka(e,t);a!==null&&ut(a,e,t),Hr(e,t)}}var cu=!0;function Fy(e,t,a,l){var i=M.T;M.T=null;var u=Y.p;try{Y.p=2,Lr(e,t,a,l)}finally{Y.p=u,M.T=i}}function $y(e,t,a,l){var i=M.T;M.T=null;var u=Y.p;try{Y.p=8,Lr(e,t,a,l)}finally{Y.p=u,M.T=i}}function Lr(e,t,a,l){if(cu){var i=Br(l);if(i===null)Nr(e,t,l,ou,a),Eh(e,l);else if(Iy(i,e,t,a,l))l.stopPropagation();else if(Eh(e,l),t&4&&-1<Wy.indexOf(e)){for(;i!==null;){var u=ol(i);if(u!==null)switch(u.tag){case 3:if(u=u.stateNode,u.current.memoizedState.isDehydrated){var o=La(u.pendingLanes);if(o!==0){var h=u;for(h.pendingLanes|=2,h.entangledLanes|=2;o;){var b=1<<31-ot(o);h.entanglements[1]|=b,o&=~b}Qt(u),(he&6)===0&&(Zi=rt()+500,Un(0))}}break;case 31:case 13:h=Ka(u,2),h!==null&&ut(h,u,2),Ji(),Hr(u,2)}if(u=Br(l),u===null&&Nr(e,t,l,ou,a),u===i)break;i=u}i!==null&&l.stopPropagation()}else Nr(e,t,l,null,a)}}function Br(e){return e=Yu(e),Qr(e)}var ou=null;function Qr(e){if(ou=null,e=cl(e),e!==null){var t=m(e);if(t===null)e=null;else{var a=t.tag;if(a===13){if(e=y(t),e!==null)return e;e=null}else if(a===31){if(e=g(t),e!==null)return e;e=null}else if(a===3){if(t.stateNode.current.memoizedState.isDehydrated)return t.tag===3?t.stateNode.containerInfo:null;e=null}else t!==e&&(e=null)}}return ou=e,null}function Sh(e){switch(e){case"beforetoggle":case"cancel":case"click":case"close":case"contextmenu":case"copy":case"cut":case"auxclick":case"dblclick":case"dragend":case"dragstart":case"drop":case"focusin":case"focusout":case"input":case"invalid":case"keydown":case"keypress":case"keyup":case"mousedown":case"mouseup":case"paste":case"pause":case"play":case"pointercancel":case"pointerdown":case"pointerup":case"ratechange":case"reset":case"resize":case"seeked":case"submit":case"toggle":case"touchcancel":case"touchend":case"touchstart":case"volumechange":case"change":case"selectionchange":case"textInput":case"compositionstart":case"compositionend":case"compositionupdate":case"beforeblur":case"afterblur":case"beforeinput":case"blur":case"fullscreenchange":case"focus":case"hashchange":case"popstate":case"select":case"selectstart":return 2;case"drag":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"mousemove":case"mouseout":case"mouseover":case"pointermove":case"pointerout":case"pointerover":case"scroll":case"touchmove":case"wheel":case"mouseenter":case"mouseleave":case"pointerenter":case"pointerleave":return 8;case"message":switch(k0()){case Oc:return 2;case Ac:return 8;case Pn:case q0:return 32;case Rc:return 268435456;default:return 32}default:return 32}}var Yr=!1,_a=null,Da=null,Ma=null,Yn=new Map,Gn=new Map,za=[],Wy="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset".split(" ");function Eh(e,t){switch(e){case"focusin":case"focusout":_a=null;break;case"dragenter":case"dragleave":Da=null;break;case"mouseover":case"mouseout":Ma=null;break;case"pointerover":case"pointerout":Yn.delete(t.pointerId);break;case"gotpointercapture":case"lostpointercapture":Gn.delete(t.pointerId)}}function Kn(e,t,a,l,i,u){return e===null||e.nativeEvent!==u?(e={blockedOn:t,domEventName:a,eventSystemFlags:l,nativeEvent:u,targetContainers:[i]},t!==null&&(t=ol(t),t!==null&&bh(t)),e):(e.eventSystemFlags|=l,t=e.targetContainers,i!==null&&t.indexOf(i)===-1&&t.push(i),e)}function Iy(e,t,a,l,i){switch(t){case"focusin":return _a=Kn(_a,e,t,a,l,i),!0;case"dragenter":return Da=Kn(Da,e,t,a,l,i),!0;case"mouseover":return Ma=Kn(Ma,e,t,a,l,i),!0;case"pointerover":var u=i.pointerId;return Yn.set(u,Kn(Yn.get(u)||null,e,t,a,l,i)),!0;case"gotpointercapture":return u=i.pointerId,Gn.set(u,Kn(Gn.get(u)||null,e,t,a,l,i)),!0}return!1}function jh(e){var t=cl(e.target);if(t!==null){var a=m(t);if(a!==null){if(t=a.tag,t===13){if(t=y(a),t!==null){e.blockedOn=t,Uc(e.priority,function(){xh(a)});return}}else if(t===31){if(t=g(a),t!==null){e.blockedOn=t,Uc(e.priority,function(){xh(a)});return}}else if(t===3&&a.stateNode.current.memoizedState.isDehydrated){e.blockedOn=a.tag===3?a.stateNode.containerInfo:null;return}}}e.blockedOn=null}function fu(e){if(e.blockedOn!==null)return!1;for(var t=e.targetContainers;0<t.length;){var a=Br(e.nativeEvent);if(a===null){a=e.nativeEvent;var l=new a.constructor(a.type,a);Qu=l,a.target.dispatchEvent(l),Qu=null}else return t=ol(a),t!==null&&bh(t),e.blockedOn=a,!1;t.shift()}return!0}function Th(e,t,a){fu(e)&&a.delete(t)}function Py(){Yr=!1,_a!==null&&fu(_a)&&(_a=null),Da!==null&&fu(Da)&&(Da=null),Ma!==null&&fu(Ma)&&(Ma=null),Yn.forEach(Th),Gn.forEach(Th)}function du(e,t){e.blockedOn===t&&(e.blockedOn=null,Yr||(Yr=!0,n.unstable_scheduleCallback(n.unstable_NormalPriority,Py)))}var hu=null;function Nh(e){hu!==e&&(hu=e,n.unstable_scheduleCallback(n.unstable_NormalPriority,function(){hu===e&&(hu=null);for(var t=0;t<e.length;t+=3){var a=e[t],l=e[t+1],i=e[t+2];if(typeof l!="function"){if(Qr(l||a)===null)continue;break}var u=ol(a);u!==null&&(e.splice(t,3),t-=3,Bs(u,{pending:!0,data:i,method:a.method,action:l},l,i))}}))}function Xl(e){function t(b){return du(b,e)}_a!==null&&du(_a,e),Da!==null&&du(Da,e),Ma!==null&&du(Ma,e),Yn.forEach(t),Gn.forEach(t);for(var a=0;a<za.length;a++){var l=za[a];l.blockedOn===e&&(l.blockedOn=null)}for(;0<za.length&&(a=za[0],a.blockedOn===null);)jh(a),a.blockedOn===null&&za.shift();if(a=(e.ownerDocument||e).$$reactFormReplay,a!=null)for(l=0;l<a.length;l+=3){var i=a[l],u=a[l+1],o=i[et]||null;if(typeof u=="function")o||Nh(a);else if(o){var h=null;if(u&&u.hasAttribute("formAction")){if(i=u,o=u[et]||null)h=o.formAction;else if(Qr(i)!==null)continue}else h=o.action;typeof h=="function"?a[l+1]=h:(a.splice(l,3),l-=3),Nh(a)}}}function Ch(){function e(u){u.canIntercept&&u.info==="react-transition"&&u.intercept({handler:function(){return new Promise(function(o){return i=o})},focusReset:"manual",scroll:"manual"})}function t(){i!==null&&(i(),i=null),l||setTimeout(a,20)}function a(){if(!l&&!navigation.transition){var u=navigation.currentEntry;u&&u.url!=null&&navigation.navigate(u.url,{state:u.getState(),info:"react-transition",history:"replace"})}}if(typeof navigation=="object"){var l=!1,i=null;return navigation.addEventListener("navigate",e),navigation.addEventListener("navigatesuccess",t),navigation.addEventListener("navigateerror",t),setTimeout(a,100),function(){l=!0,navigation.removeEventListener("navigate",e),navigation.removeEventListener("navigatesuccess",t),navigation.removeEventListener("navigateerror",t),i!==null&&(i(),i=null)}}}function Gr(e){this._internalRoot=e}mu.prototype.render=Gr.prototype.render=function(e){var t=this._internalRoot;if(t===null)throw Error(c(409));var a=t.current,l=vt();vh(a,l,e,t,null,null)},mu.prototype.unmount=Gr.prototype.unmount=function(){var e=this._internalRoot;if(e!==null){this._internalRoot=null;var t=e.containerInfo;vh(e.current,2,null,e,null,null),Ji(),t[rl]=null}};function mu(e){this._internalRoot=e}mu.prototype.unstable_scheduleHydration=function(e){if(e){var t=wc();e={blockedOn:null,target:e,priority:t};for(var a=0;a<za.length&&t!==0&&t<za[a].priority;a++);za.splice(a,0,e),a===0&&jh(e)}};var Oh=s.version;if(Oh!=="19.2.4")throw Error(c(527,Oh,"19.2.4"));Y.findDOMNode=function(e){var t=e._reactInternals;if(t===void 0)throw typeof e.render=="function"?Error(c(188)):(e=Object.keys(e).join(","),Error(c(268,e)));return e=v(t),e=e!==null?E(e):null,e=e===null?null:e.stateNode,e};var eg={bundleType:0,version:"19.2.4",rendererPackageName:"react-dom",currentDispatcherRef:M,reconcilerVersion:"19.2.4"};if(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"){var yu=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!yu.isDisabled&&yu.supportsFiber)try{Il=yu.inject(eg),ct=yu}catch{}}return Zn.createRoot=function(e,t){if(!d(e))throw Error(c(299));var a=!1,l="",i=wf,u=Uf,o=kf;return t!=null&&(t.unstable_strictMode===!0&&(a=!0),t.identifierPrefix!==void 0&&(l=t.identifierPrefix),t.onUncaughtError!==void 0&&(i=t.onUncaughtError),t.onCaughtError!==void 0&&(u=t.onCaughtError),t.onRecoverableError!==void 0&&(o=t.onRecoverableError)),t=yh(e,1,!1,null,null,a,l,null,i,u,o,Ch),e[rl]=t.current,Tr(e),new Gr(t)},Zn.hydrateRoot=function(e,t,a){if(!d(e))throw Error(c(299));var l=!1,i="",u=wf,o=Uf,h=kf,b=null;return a!=null&&(a.unstable_strictMode===!0&&(l=!0),a.identifierPrefix!==void 0&&(i=a.identifierPrefix),a.onUncaughtError!==void 0&&(u=a.onUncaughtError),a.onCaughtError!==void 0&&(o=a.onCaughtError),a.onRecoverableError!==void 0&&(h=a.onRecoverableError),a.formState!==void 0&&(b=a.formState)),t=yh(e,1,!0,t,a??null,l,i,b,u,o,h,Ch),t.context=gh(null),a=t.current,l=vt(),l=zu(l),i=pa(l),i.callback=null,ba(a,i,l),a=l,t.current.lanes=a,en(t,a),Qt(t),e[rl]=t.current,Tr(e),new mu(t)},Zn.version="19.2.4",Zn}var qh;function fg(){if(qh)return Zr.exports;qh=1;function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(s){console.error(s)}}return n(),Zr.exports=og(),Zr.exports}var dg=fg();var Hh="popstate";function hg(n={}){function s(c,d){let{pathname:m,search:y,hash:g}=c.location;return tc("",{pathname:m,search:y,hash:g},d.state&&d.state.usr||null,d.state&&d.state.key||"default")}function r(c,d){return typeof d=="string"?d:Jn(d)}return yg(s,r,null,n)}function Ae(n,s){if(n===!1||n===null||typeof n>"u")throw new Error(s)}function kt(n,s){if(!n){typeof console<"u"&&console.warn(s);try{throw new Error(s)}catch{}}}function mg(){return Math.random().toString(36).substring(2,10)}function Lh(n,s){return{usr:n.state,key:n.key,idx:s}}function tc(n,s,r=null,c){return{pathname:typeof n=="string"?n:n.pathname,search:"",hash:"",...typeof s=="string"?Zl(s):s,state:r,key:s&&s.key||c||mg()}}function Jn({pathname:n="/",search:s="",hash:r=""}){return s&&s!=="?"&&(n+=s.charAt(0)==="?"?s:"?"+s),r&&r!=="#"&&(n+=r.charAt(0)==="#"?r:"#"+r),n}function Zl(n){let s={};if(n){let r=n.indexOf("#");r>=0&&(s.hash=n.substring(r),n=n.substring(0,r));let c=n.indexOf("?");c>=0&&(s.search=n.substring(c),n=n.substring(0,c)),n&&(s.pathname=n)}return s}function yg(n,s,r,c={}){let{window:d=document.defaultView,v5Compat:m=!1}=c,y=d.history,g="POP",p=null,v=E();v==null&&(v=0,y.replaceState({...y.state,idx:v},""));function E(){return(y.state||{idx:null}).idx}function T(){g="POP";let q=E(),Q=q==null?null:q-v;v=q,p&&p({action:g,location:B.location,delta:Q})}function R(q,Q){g="PUSH";let L=tc(B.location,q,Q);v=E()+1;let K=Lh(L,v),X=B.createHref(L);try{y.pushState(K,"",X)}catch(P){if(P instanceof DOMException&&P.name==="DataCloneError")throw P;d.location.assign(X)}m&&p&&p({action:g,location:B.location,delta:1})}function U(q,Q){g="REPLACE";let L=tc(B.location,q,Q);v=E();let K=Lh(L,v),X=B.createHref(L);y.replaceState(K,"",X),m&&p&&p({action:g,location:B.location,delta:0})}function k(q){return gg(q)}let B={get action(){return g},get location(){return n(d,y)},listen(q){if(p)throw new Error("A history only accepts one active listener");return d.addEventListener(Hh,T),p=q,()=>{d.removeEventListener(Hh,T),p=null}},createHref(q){return s(d,q)},createURL:k,encodeLocation(q){let Q=k(q);return{pathname:Q.pathname,search:Q.search,hash:Q.hash}},push:R,replace:U,go(q){return y.go(q)}};return B}function gg(n,s=!1){let r="http://localhost";typeof window<"u"&&(r=window.location.origin!=="null"?window.location.origin:window.location.href),Ae(r,"No window.location.(origin|href) available to create URL");let c=typeof n=="string"?n:Jn(n);return c=c.replace(/ $/,"%20"),!s&&c.startsWith("//")&&(c=r+c),new URL(c,r)}function l0(n,s,r="/"){return vg(n,s,r,!1)}function vg(n,s,r,c){let d=typeof s=="string"?Zl(s):s,m=ca(d.pathname||"/",r);if(m==null)return null;let y=n0(n);pg(y);let g=null;for(let p=0;g==null&&p<y.length;++p){let v=Rg(m);g=Og(y[p],v,c)}return g}function n0(n,s=[],r=[],c="",d=!1){let m=(y,g,p=d,v)=>{let E={relativePath:v===void 0?y.path||"":v,caseSensitive:y.caseSensitive===!0,childrenIndex:g,route:y};if(E.relativePath.startsWith("/")){if(!E.relativePath.startsWith(c)&&p)return;Ae(E.relativePath.startsWith(c),`Absolute route path "${E.relativePath}" nested under path "${c}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),E.relativePath=E.relativePath.slice(c.length)}let T=ra([c,E.relativePath]),R=r.concat(E);y.children&&y.children.length>0&&(Ae(y.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${T}".`),n0(y.children,s,R,T,p)),!(y.path==null&&!y.index)&&s.push({path:T,score:Ng(T,y.index),routesMeta:R})};return n.forEach((y,g)=>{if(y.path===""||!y.path?.includes("?"))m(y,g);else for(let p of i0(y.path))m(y,g,!0,p)}),s}function i0(n){let s=n.split("/");if(s.length===0)return[];let[r,...c]=s,d=r.endsWith("?"),m=r.replace(/\?$/,"");if(c.length===0)return d?[m,""]:[m];let y=i0(c.join("/")),g=[];return g.push(...y.map(p=>p===""?m:[m,p].join("/"))),d&&g.push(...y),g.map(p=>n.startsWith("/")&&p===""?"/":p)}function pg(n){n.sort((s,r)=>s.score!==r.score?r.score-s.score:Cg(s.routesMeta.map(c=>c.childrenIndex),r.routesMeta.map(c=>c.childrenIndex)))}var bg=/^:[\w-]+$/,xg=3,Sg=2,Eg=1,jg=10,Tg=-2,Bh=n=>n==="*";function Ng(n,s){let r=n.split("/"),c=r.length;return r.some(Bh)&&(c+=Tg),s&&(c+=Sg),r.filter(d=>!Bh(d)).reduce((d,m)=>d+(bg.test(m)?xg:m===""?Eg:jg),c)}function Cg(n,s){return n.length===s.length&&n.slice(0,-1).every((c,d)=>c===s[d])?n[n.length-1]-s[s.length-1]:0}function Og(n,s,r=!1){let{routesMeta:c}=n,d={},m="/",y=[];for(let g=0;g<c.length;++g){let p=c[g],v=g===c.length-1,E=m==="/"?s:s.slice(m.length)||"/",T=Su({path:p.relativePath,caseSensitive:p.caseSensitive,end:v},E),R=p.route;if(!T&&v&&r&&!c[c.length-1].route.index&&(T=Su({path:p.relativePath,caseSensitive:p.caseSensitive,end:!1},E)),!T)return null;Object.assign(d,T.params),y.push({params:d,pathname:ra([m,T.pathname]),pathnameBase:zg(ra([m,T.pathnameBase])),route:R}),T.pathnameBase!=="/"&&(m=ra([m,T.pathnameBase]))}return y}function Su(n,s){typeof n=="string"&&(n={path:n,caseSensitive:!1,end:!0});let[r,c]=Ag(n.path,n.caseSensitive,n.end),d=s.match(r);if(!d)return null;let m=d[0],y=m.replace(/(.)\/+$/,"$1"),g=d.slice(1);return{params:c.reduce((v,{paramName:E,isOptional:T},R)=>{if(E==="*"){let k=g[R]||"";y=m.slice(0,m.length-k.length).replace(/(.)\/+$/,"$1")}const U=g[R];return T&&!U?v[E]=void 0:v[E]=(U||"").replace(/%2F/g,"/"),v},{}),pathname:m,pathnameBase:y,pattern:n}}function Ag(n,s=!1,r=!0){kt(n==="*"||!n.endsWith("*")||n.endsWith("/*"),`Route path "${n}" will be treated as if it were "${n.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${n.replace(/\*$/,"/*")}".`);let c=[],d="^"+n.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(y,g,p)=>(c.push({paramName:g,isOptional:p!=null}),p?"/?([^\\/]+)?":"/([^\\/]+)")).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return n.endsWith("*")?(c.push({paramName:"*"}),d+=n==="*"||n==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):r?d+="\\/*$":n!==""&&n!=="/"&&(d+="(?:(?=\\/|$))"),[new RegExp(d,s?void 0:"i"),c]}function Rg(n){try{return n.split("/").map(s=>decodeURIComponent(s).replace(/\//g,"%2F")).join("/")}catch(s){return kt(!1,`The URL path "${n}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${s}).`),n}}function ca(n,s){if(s==="/")return n;if(!n.toLowerCase().startsWith(s.toLowerCase()))return null;let r=s.endsWith("/")?s.length-1:s.length,c=n.charAt(r);return c&&c!=="/"?null:n.slice(r)||"/"}var _g=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;function Dg(n,s="/"){let{pathname:r,search:c="",hash:d=""}=typeof n=="string"?Zl(n):n,m;return r?(r=r.replace(/\/\/+/g,"/"),r.startsWith("/")?m=Qh(r.substring(1),"/"):m=Qh(r,s)):m=s,{pathname:m,search:wg(c),hash:Ug(d)}}function Qh(n,s){let r=s.replace(/\/+$/,"").split("/");return n.split("/").forEach(d=>{d===".."?r.length>1&&r.pop():d!=="."&&r.push(d)}),r.length>1?r.join("/"):"/"}function $r(n,s,r,c){return`Cannot include a '${n}' character in a manually specified \`to.${s}\` field [${JSON.stringify(c)}]. Please separate it out to the \`to.${r}\` field. Alternatively you may provide the full path as a string in <Link to="..."> and the router will parse it for you.`}function Mg(n){return n.filter((s,r)=>r===0||s.route.path&&s.route.path.length>0)}function fc(n){let s=Mg(n);return s.map((r,c)=>c===s.length-1?r.pathname:r.pathnameBase)}function dc(n,s,r,c=!1){let d;typeof n=="string"?d=Zl(n):(d={...n},Ae(!d.pathname||!d.pathname.includes("?"),$r("?","pathname","search",d)),Ae(!d.pathname||!d.pathname.includes("#"),$r("#","pathname","hash",d)),Ae(!d.search||!d.search.includes("#"),$r("#","search","hash",d)));let m=n===""||d.pathname==="",y=m?"/":d.pathname,g;if(y==null)g=r;else{let T=s.length-1;if(!c&&y.startsWith("..")){let R=y.split("/");for(;R[0]==="..";)R.shift(),T-=1;d.pathname=R.join("/")}g=T>=0?s[T]:"/"}let p=Dg(d,g),v=y&&y!=="/"&&y.endsWith("/"),E=(m||y===".")&&r.endsWith("/");return!p.pathname.endsWith("/")&&(v||E)&&(p.pathname+="/"),p}var ra=n=>n.join("/").replace(/\/\/+/g,"/"),zg=n=>n.replace(/\/+$/,"").replace(/^\/*/,"/"),wg=n=>!n||n==="?"?"":n.startsWith("?")?n:"?"+n,Ug=n=>!n||n==="#"?"":n.startsWith("#")?n:"#"+n,kg=class{constructor(n,s,r,c=!1){this.status=n,this.statusText=s||"",this.internal=c,r instanceof Error?(this.data=r.toString(),this.error=r):this.data=r}};function qg(n){return n!=null&&typeof n.status=="number"&&typeof n.statusText=="string"&&typeof n.internal=="boolean"&&"data"in n}function Hg(n){return n.map(s=>s.route.path).filter(Boolean).join("/").replace(/\/\/*/g,"/")||"/"}var u0=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function s0(n,s){let r=n;if(typeof r!="string"||!_g.test(r))return{absoluteURL:void 0,isExternal:!1,to:r};let c=r,d=!1;if(u0)try{let m=new URL(window.location.href),y=r.startsWith("//")?new URL(m.protocol+r):new URL(r),g=ca(y.pathname,s);y.origin===m.origin&&g!=null?r=g+y.search+y.hash:d=!0}catch{kt(!1,`<Link to="${r}"> contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:c,isExternal:d,to:r}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var r0=["POST","PUT","PATCH","DELETE"];new Set(r0);var Lg=["GET",...r0];new Set(Lg);var Vl=N.createContext(null);Vl.displayName="DataRouter";var Tu=N.createContext(null);Tu.displayName="DataRouterState";var Bg=N.createContext(!1),c0=N.createContext({isTransitioning:!1});c0.displayName="ViewTransition";var Qg=N.createContext(new Map);Qg.displayName="Fetchers";var Yg=N.createContext(null);Yg.displayName="Await";var bt=N.createContext(null);bt.displayName="Navigation";var $n=N.createContext(null);$n.displayName="Location";var qt=N.createContext({outlet:null,matches:[],isDataRoute:!1});qt.displayName="Route";var hc=N.createContext(null);hc.displayName="RouteError";var o0="REACT_ROUTER_ERROR",Gg="REDIRECT",Kg="ROUTE_ERROR_RESPONSE";function Xg(n){if(n.startsWith(`${o0}:${Gg}:{`))try{let s=JSON.parse(n.slice(28));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string"&&typeof s.location=="string"&&typeof s.reloadDocument=="boolean"&&typeof s.replace=="boolean")return s}catch{}}function Zg(n){if(n.startsWith(`${o0}:${Kg}:{`))try{let s=JSON.parse(n.slice(40));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string")return new kg(s.status,s.statusText,s.data)}catch{}}function Vg(n,{relative:s}={}){Ae(Jl(),"useHref() may be used only in the context of a <Router> component.");let{basename:r,navigator:c}=N.useContext(bt),{hash:d,pathname:m,search:y}=Wn(n,{relative:s}),g=m;return r!=="/"&&(g=m==="/"?r:ra([r,m])),c.createHref({pathname:g,search:y,hash:d})}function Jl(){return N.useContext($n)!=null}function ka(){return Ae(Jl(),"useLocation() may be used only in the context of a <Router> component."),N.useContext($n).location}var f0="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function d0(n){N.useContext(bt).static||N.useLayoutEffect(n)}function mc(){let{isDataRoute:n}=N.useContext(qt);return n?rv():Jg()}function Jg(){Ae(Jl(),"useNavigate() may be used only in the context of a <Router> component.");let n=N.useContext(Vl),{basename:s,navigator:r}=N.useContext(bt),{matches:c}=N.useContext(qt),{pathname:d}=ka(),m=JSON.stringify(fc(c)),y=N.useRef(!1);return d0(()=>{y.current=!0}),N.useCallback((p,v={})=>{if(kt(y.current,f0),!y.current)return;if(typeof p=="number"){r.go(p);return}let E=dc(p,JSON.parse(m),d,v.relative==="path");n==null&&s!=="/"&&(E.pathname=E.pathname==="/"?s:ra([s,E.pathname])),(v.replace?r.replace:r.push)(E,v.state,v)},[s,r,m,d,n])}var Fg=N.createContext(null);function $g(n){let s=N.useContext(qt).outlet;return N.useMemo(()=>s&&N.createElement(Fg.Provider,{value:n},s),[s,n])}function Wn(n,{relative:s}={}){let{matches:r}=N.useContext(qt),{pathname:c}=ka(),d=JSON.stringify(fc(r));return N.useMemo(()=>dc(n,JSON.parse(d),c,s==="path"),[n,d,c,s])}function Wg(n,s){return h0(n,s)}function h0(n,s,r,c,d){Ae(Jl(),"useRoutes() may be used only in the context of a <Router> component.");let{navigator:m}=N.useContext(bt),{matches:y}=N.useContext(qt),g=y[y.length-1],p=g?g.params:{},v=g?g.pathname:"/",E=g?g.pathnameBase:"/",T=g&&g.route;{let L=T&&T.path||"";y0(v,!T||L.endsWith("*")||L.endsWith("*?"),`You rendered descendant <Routes> (or called \`useRoutes()\`) at "${v}" (under <Route path="${L}">) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. 10 + 11 + Please change the parent <Route path="${L}"> to <Route path="${L==="/"?"*":`${L}/*`}">.`)}let R=ka(),U;if(s){let L=typeof s=="string"?Zl(s):s;Ae(E==="/"||L.pathname?.startsWith(E),`When overriding the location using \`<Routes location>\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${E}" but pathname "${L.pathname}" was given in the \`location\` prop.`),U=L}else U=R;let k=U.pathname||"/",B=k;if(E!=="/"){let L=E.replace(/^\//,"").split("/");B="/"+k.replace(/^\//,"").split("/").slice(L.length).join("/")}let q=l0(n,{pathname:B});kt(T||q!=null,`No routes matched location "${U.pathname}${U.search}${U.hash}" `),kt(q==null||q[q.length-1].route.element!==void 0||q[q.length-1].route.Component!==void 0||q[q.length-1].route.lazy!==void 0,`Matched leaf route at location "${U.pathname}${U.search}${U.hash}" does not have an element or Component. This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.`);let Q=av(q&&q.map(L=>Object.assign({},L,{params:Object.assign({},p,L.params),pathname:ra([E,m.encodeLocation?m.encodeLocation(L.pathname.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:L.pathname]),pathnameBase:L.pathnameBase==="/"?E:ra([E,m.encodeLocation?m.encodeLocation(L.pathnameBase.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:L.pathnameBase])})),y,r,c,d);return s&&Q?N.createElement($n.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",...U},navigationType:"POP"}},Q):Q}function Ig(){let n=sv(),s=qg(n)?`${n.status} ${n.statusText}`:n instanceof Error?n.message:JSON.stringify(n),r=n instanceof Error?n.stack:null,c="rgba(200,200,200, 0.5)",d={padding:"0.5rem",backgroundColor:c},m={padding:"2px 4px",backgroundColor:c},y=null;return console.error("Error handled by React Router default ErrorBoundary:",n),y=N.createElement(N.Fragment,null,N.createElement("p",null,"💿 Hey developer 👋"),N.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",N.createElement("code",{style:m},"ErrorBoundary")," or"," ",N.createElement("code",{style:m},"errorElement")," prop on your route.")),N.createElement(N.Fragment,null,N.createElement("h2",null,"Unexpected Application Error!"),N.createElement("h3",{style:{fontStyle:"italic"}},s),r?N.createElement("pre",{style:d},r):null,y)}var Pg=N.createElement(Ig,null),m0=class extends N.Component{constructor(n){super(n),this.state={location:n.location,revalidation:n.revalidation,error:n.error}}static getDerivedStateFromError(n){return{error:n}}static getDerivedStateFromProps(n,s){return s.location!==n.location||s.revalidation!=="idle"&&n.revalidation==="idle"?{error:n.error,location:n.location,revalidation:n.revalidation}:{error:n.error!==void 0?n.error:s.error,location:s.location,revalidation:n.revalidation||s.revalidation}}componentDidCatch(n,s){this.props.onError?this.props.onError(n,s):console.error("React Router caught the following error during render",n)}render(){let n=this.state.error;if(this.context&&typeof n=="object"&&n&&"digest"in n&&typeof n.digest=="string"){const r=Zg(n.digest);r&&(n=r)}let s=n!==void 0?N.createElement(qt.Provider,{value:this.props.routeContext},N.createElement(hc.Provider,{value:n,children:this.props.component})):this.props.children;return this.context?N.createElement(ev,{error:n},s):s}};m0.contextType=Bg;var Wr=new WeakMap;function ev({children:n,error:s}){let{basename:r}=N.useContext(bt);if(typeof s=="object"&&s&&"digest"in s&&typeof s.digest=="string"){let c=Xg(s.digest);if(c){let d=Wr.get(s);if(d)throw d;let m=s0(c.location,r);if(u0&&!Wr.get(s))if(m.isExternal||c.reloadDocument)window.location.href=m.absoluteURL||m.to;else{const y=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(m.to,{replace:c.replace}));throw Wr.set(s,y),y}return N.createElement("meta",{httpEquiv:"refresh",content:`0;url=${m.absoluteURL||m.to}`})}}return n}function tv({routeContext:n,match:s,children:r}){let c=N.useContext(Vl);return c&&c.static&&c.staticContext&&(s.route.errorElement||s.route.ErrorBoundary)&&(c.staticContext._deepestRenderedBoundaryId=s.route.id),N.createElement(qt.Provider,{value:n},r)}function av(n,s=[],r=null,c=null,d=null){if(n==null){if(!r)return null;if(r.errors)n=r.matches;else if(s.length===0&&!r.initialized&&r.matches.length>0)n=r.matches;else return null}let m=n,y=r?.errors;if(y!=null){let E=m.findIndex(T=>T.route.id&&y?.[T.route.id]!==void 0);Ae(E>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(y).join(",")}`),m=m.slice(0,Math.min(m.length,E+1))}let g=!1,p=-1;if(r)for(let E=0;E<m.length;E++){let T=m[E];if((T.route.HydrateFallback||T.route.hydrateFallbackElement)&&(p=E),T.route.id){let{loaderData:R,errors:U}=r,k=T.route.loader&&!R.hasOwnProperty(T.route.id)&&(!U||U[T.route.id]===void 0);if(T.route.lazy||k){g=!0,p>=0?m=m.slice(0,p+1):m=[m[0]];break}}}let v=r&&c?(E,T)=>{c(E,{location:r.location,params:r.matches?.[0]?.params??{},unstable_pattern:Hg(r.matches),errorInfo:T})}:void 0;return m.reduceRight((E,T,R)=>{let U,k=!1,B=null,q=null;r&&(U=y&&T.route.id?y[T.route.id]:void 0,B=T.route.errorElement||Pg,g&&(p<0&&R===0?(y0("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),k=!0,q=null):p===R&&(k=!0,q=T.route.hydrateFallbackElement||null)));let Q=s.concat(m.slice(0,R+1)),L=()=>{let K;return U?K=B:k?K=q:T.route.Component?K=N.createElement(T.route.Component,null):T.route.element?K=T.route.element:K=E,N.createElement(tv,{match:T,routeContext:{outlet:E,matches:Q,isDataRoute:r!=null},children:K})};return r&&(T.route.ErrorBoundary||T.route.errorElement||R===0)?N.createElement(m0,{location:r.location,revalidation:r.revalidation,component:B,error:U,children:L(),routeContext:{outlet:null,matches:Q,isDataRoute:!0},onError:v}):L()},null)}function yc(n){return`${n} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function lv(n){let s=N.useContext(Vl);return Ae(s,yc(n)),s}function nv(n){let s=N.useContext(Tu);return Ae(s,yc(n)),s}function iv(n){let s=N.useContext(qt);return Ae(s,yc(n)),s}function gc(n){let s=iv(n),r=s.matches[s.matches.length-1];return Ae(r.route.id,`${n} can only be used on routes that contain a unique "id"`),r.route.id}function uv(){return gc("useRouteId")}function sv(){let n=N.useContext(hc),s=nv("useRouteError"),r=gc("useRouteError");return n!==void 0?n:s.errors?.[r]}function rv(){let{router:n}=lv("useNavigate"),s=gc("useNavigate"),r=N.useRef(!1);return d0(()=>{r.current=!0}),N.useCallback(async(d,m={})=>{kt(r.current,f0),r.current&&(typeof d=="number"?await n.navigate(d):await n.navigate(d,{fromRouteId:s,...m}))},[n,s])}var Yh={};function y0(n,s,r){!s&&!Yh[n]&&(Yh[n]=!0,kt(!1,r))}N.memo(cv);function cv({routes:n,future:s,state:r,onError:c}){return h0(n,void 0,r,c,s)}function g0({to:n,replace:s,state:r,relative:c}){Ae(Jl(),"<Navigate> may be used only in the context of a <Router> component.");let{static:d}=N.useContext(bt);kt(!d,"<Navigate> must not be used on the initial render in a <StaticRouter>. This is a no-op, but you should modify your code so the <Navigate> is only ever rendered in response to some user interaction or state change.");let{matches:m}=N.useContext(qt),{pathname:y}=ka(),g=mc(),p=dc(n,fc(m),y,c==="path"),v=JSON.stringify(p);return N.useEffect(()=>{g(JSON.parse(v),{replace:s,state:r,relative:c})},[g,v,c,s,r]),null}function ov(n){return $g(n.context)}function Yt(n){Ae(!1,"A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.")}function fv({basename:n="/",children:s=null,location:r,navigationType:c="POP",navigator:d,static:m=!1,unstable_useTransitions:y}){Ae(!Jl(),"You cannot render a <Router> inside another <Router>. You should never have more than one in your app.");let g=n.replace(/^\/*/,"/"),p=N.useMemo(()=>({basename:g,navigator:d,static:m,unstable_useTransitions:y,future:{}}),[g,d,m,y]);typeof r=="string"&&(r=Zl(r));let{pathname:v="/",search:E="",hash:T="",state:R=null,key:U="default"}=r,k=N.useMemo(()=>{let B=ca(v,g);return B==null?null:{location:{pathname:B,search:E,hash:T,state:R,key:U},navigationType:c}},[g,v,E,T,R,U,c]);return kt(k!=null,`<Router basename="${g}"> is not able to match the URL "${v}${E}${T}" because it does not start with the basename, so the <Router> won't render anything.`),k==null?null:N.createElement(bt.Provider,{value:p},N.createElement($n.Provider,{children:s,value:k}))}function dv({children:n,location:s}){return Wg(ac(n),s)}function ac(n,s=[]){let r=[];return N.Children.forEach(n,(c,d)=>{if(!N.isValidElement(c))return;let m=[...s,d];if(c.type===N.Fragment){r.push.apply(r,ac(c.props.children,m));return}Ae(c.type===Yt,`[${typeof c.type=="string"?c.type:c.type.name}] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>`),Ae(!c.props.index||!c.props.children,"An index route cannot have child routes.");let y={id:c.props.id||m.join("-"),caseSensitive:c.props.caseSensitive,element:c.props.element,Component:c.props.Component,index:c.props.index,path:c.props.path,middleware:c.props.middleware,loader:c.props.loader,action:c.props.action,hydrateFallbackElement:c.props.hydrateFallbackElement,HydrateFallback:c.props.HydrateFallback,errorElement:c.props.errorElement,ErrorBoundary:c.props.ErrorBoundary,hasErrorBoundary:c.props.hasErrorBoundary===!0||c.props.ErrorBoundary!=null||c.props.errorElement!=null,shouldRevalidate:c.props.shouldRevalidate,handle:c.props.handle,lazy:c.props.lazy};c.props.children&&(y.children=ac(c.props.children,m)),r.push(y)}),r}var bu="get",xu="application/x-www-form-urlencoded";function Nu(n){return typeof HTMLElement<"u"&&n instanceof HTMLElement}function hv(n){return Nu(n)&&n.tagName.toLowerCase()==="button"}function mv(n){return Nu(n)&&n.tagName.toLowerCase()==="form"}function yv(n){return Nu(n)&&n.tagName.toLowerCase()==="input"}function gv(n){return!!(n.metaKey||n.altKey||n.ctrlKey||n.shiftKey)}function vv(n,s){return n.button===0&&(!s||s==="_self")&&!gv(n)}var vu=null;function pv(){if(vu===null)try{new FormData(document.createElement("form"),0),vu=!1}catch{vu=!0}return vu}var bv=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function Ir(n){return n!=null&&!bv.has(n)?(kt(!1,`"${n}" is not a valid \`encType\` for \`<Form>\`/\`<fetcher.Form>\` and will default to "${xu}"`),null):n}function xv(n,s){let r,c,d,m,y;if(mv(n)){let g=n.getAttribute("action");c=g?ca(g,s):null,r=n.getAttribute("method")||bu,d=Ir(n.getAttribute("enctype"))||xu,m=new FormData(n)}else if(hv(n)||yv(n)&&(n.type==="submit"||n.type==="image")){let g=n.form;if(g==null)throw new Error('Cannot submit a <button> or <input type="submit"> without a <form>');let p=n.getAttribute("formaction")||g.getAttribute("action");if(c=p?ca(p,s):null,r=n.getAttribute("formmethod")||g.getAttribute("method")||bu,d=Ir(n.getAttribute("formenctype"))||Ir(g.getAttribute("enctype"))||xu,m=new FormData(g,n),!pv()){let{name:v,type:E,value:T}=n;if(E==="image"){let R=v?`${v}.`:"";m.append(`${R}x`,"0"),m.append(`${R}y`,"0")}else v&&m.append(v,T)}}else{if(Nu(n))throw new Error('Cannot submit element that is not <form>, <button>, or <input type="submit|image">');r=bu,c=null,d=xu,y=n}return m&&d==="text/plain"&&(y=m,m=void 0),{action:c,method:r.toLowerCase(),encType:d,formData:m,body:y}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");function vc(n,s){if(n===!1||n===null||typeof n>"u")throw new Error(s)}function Sv(n,s,r,c){let d=typeof n=="string"?new URL(n,typeof window>"u"?"server://singlefetch/":window.location.origin):n;return r?d.pathname.endsWith("/")?d.pathname=`${d.pathname}_.${c}`:d.pathname=`${d.pathname}.${c}`:d.pathname==="/"?d.pathname=`_root.${c}`:s&&ca(d.pathname,s)==="/"?d.pathname=`${s.replace(/\/$/,"")}/_root.${c}`:d.pathname=`${d.pathname.replace(/\/$/,"")}.${c}`,d}async function Ev(n,s){if(n.id in s)return s[n.id];try{let r=await import(n.module);return s[n.id]=r,r}catch(r){return console.error(`Error loading route module \`${n.module}\`, reloading page...`),console.error(r),window.__reactRouterContext&&window.__reactRouterContext.isSpaMode,window.location.reload(),new Promise(()=>{})}}function jv(n){return n==null?!1:n.href==null?n.rel==="preload"&&typeof n.imageSrcSet=="string"&&typeof n.imageSizes=="string":typeof n.rel=="string"&&typeof n.href=="string"}async function Tv(n,s,r){let c=await Promise.all(n.map(async d=>{let m=s.routes[d.route.id];if(m){let y=await Ev(m,r);return y.links?y.links():[]}return[]}));return Av(c.flat(1).filter(jv).filter(d=>d.rel==="stylesheet"||d.rel==="preload").map(d=>d.rel==="stylesheet"?{...d,rel:"prefetch",as:"style"}:{...d,rel:"prefetch"}))}function Gh(n,s,r,c,d,m){let y=(p,v)=>r[v]?p.route.id!==r[v].route.id:!0,g=(p,v)=>r[v].pathname!==p.pathname||r[v].route.path?.endsWith("*")&&r[v].params["*"]!==p.params["*"];return m==="assets"?s.filter((p,v)=>y(p,v)||g(p,v)):m==="data"?s.filter((p,v)=>{let E=c.routes[p.route.id];if(!E||!E.hasLoader)return!1;if(y(p,v)||g(p,v))return!0;if(p.route.shouldRevalidate){let T=p.route.shouldRevalidate({currentUrl:new URL(d.pathname+d.search+d.hash,window.origin),currentParams:r[0]?.params||{},nextUrl:new URL(n,window.origin),nextParams:p.params,defaultShouldRevalidate:!0});if(typeof T=="boolean")return T}return!0}):[]}function Nv(n,s,{includeHydrateFallback:r}={}){return Cv(n.map(c=>{let d=s.routes[c.route.id];if(!d)return[];let m=[d.module];return d.clientActionModule&&(m=m.concat(d.clientActionModule)),d.clientLoaderModule&&(m=m.concat(d.clientLoaderModule)),r&&d.hydrateFallbackModule&&(m=m.concat(d.hydrateFallbackModule)),d.imports&&(m=m.concat(d.imports)),m}).flat(1))}function Cv(n){return[...new Set(n)]}function Ov(n){let s={},r=Object.keys(n).sort();for(let c of r)s[c]=n[c];return s}function Av(n,s){let r=new Set;return new Set(s),n.reduce((c,d)=>{let m=JSON.stringify(Ov(d));return r.has(m)||(r.add(m),c.push({key:m,link:d})),c},[])}function v0(){let n=N.useContext(Vl);return vc(n,"You must render this element inside a <DataRouterContext.Provider> element"),n}function Rv(){let n=N.useContext(Tu);return vc(n,"You must render this element inside a <DataRouterStateContext.Provider> element"),n}var pc=N.createContext(void 0);pc.displayName="FrameworkContext";function p0(){let n=N.useContext(pc);return vc(n,"You must render this element inside a <HydratedRouter> element"),n}function _v(n,s){let r=N.useContext(pc),[c,d]=N.useState(!1),[m,y]=N.useState(!1),{onFocus:g,onBlur:p,onMouseEnter:v,onMouseLeave:E,onTouchStart:T}=s,R=N.useRef(null);N.useEffect(()=>{if(n==="render"&&y(!0),n==="viewport"){let B=Q=>{Q.forEach(L=>{y(L.isIntersecting)})},q=new IntersectionObserver(B,{threshold:.5});return R.current&&q.observe(R.current),()=>{q.disconnect()}}},[n]),N.useEffect(()=>{if(c){let B=setTimeout(()=>{y(!0)},100);return()=>{clearTimeout(B)}}},[c]);let U=()=>{d(!0)},k=()=>{d(!1),y(!1)};return r?n!=="intent"?[m,R,{}]:[m,R,{onFocus:Vn(g,U),onBlur:Vn(p,k),onMouseEnter:Vn(v,U),onMouseLeave:Vn(E,k),onTouchStart:Vn(T,U)}]:[!1,R,{}]}function Vn(n,s){return r=>{n&&n(r),r.defaultPrevented||s(r)}}function Dv({page:n,...s}){let{router:r}=v0(),c=N.useMemo(()=>l0(r.routes,n,r.basename),[r.routes,n,r.basename]);return c?N.createElement(zv,{page:n,matches:c,...s}):null}function Mv(n){let{manifest:s,routeModules:r}=p0(),[c,d]=N.useState([]);return N.useEffect(()=>{let m=!1;return Tv(n,s,r).then(y=>{m||d(y)}),()=>{m=!0}},[n,s,r]),c}function zv({page:n,matches:s,...r}){let c=ka(),{future:d,manifest:m,routeModules:y}=p0(),{basename:g}=v0(),{loaderData:p,matches:v}=Rv(),E=N.useMemo(()=>Gh(n,s,v,m,c,"data"),[n,s,v,m,c]),T=N.useMemo(()=>Gh(n,s,v,m,c,"assets"),[n,s,v,m,c]),R=N.useMemo(()=>{if(n===c.pathname+c.search+c.hash)return[];let B=new Set,q=!1;if(s.forEach(L=>{let K=m.routes[L.route.id];!K||!K.hasLoader||(!E.some(X=>X.route.id===L.route.id)&&L.route.id in p&&y[L.route.id]?.shouldRevalidate||K.hasClientLoader?q=!0:B.add(L.route.id))}),B.size===0)return[];let Q=Sv(n,g,d.unstable_trailingSlashAwareDataRequests,"data");return q&&B.size>0&&Q.searchParams.set("_routes",s.filter(L=>B.has(L.route.id)).map(L=>L.route.id).join(",")),[Q.pathname+Q.search]},[g,d.unstable_trailingSlashAwareDataRequests,p,c,m,E,s,n,y]),U=N.useMemo(()=>Nv(T,m),[T,m]),k=Mv(T);return N.createElement(N.Fragment,null,R.map(B=>N.createElement("link",{key:B,rel:"prefetch",as:"fetch",href:B,...r})),U.map(B=>N.createElement("link",{key:B,rel:"modulepreload",href:B,...r})),k.map(({key:B,link:q})=>N.createElement("link",{key:B,nonce:r.nonce,...q,crossOrigin:q.crossOrigin??r.crossOrigin})))}function wv(...n){return s=>{n.forEach(r=>{typeof r=="function"?r(s):r!=null&&(r.current=s)})}}var Uv=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";try{Uv&&(window.__reactRouterVersion="7.13.0")}catch{}function kv({basename:n,children:s,unstable_useTransitions:r,window:c}){let d=N.useRef();d.current==null&&(d.current=hg({window:c,v5Compat:!0}));let m=d.current,[y,g]=N.useState({action:m.action,location:m.location}),p=N.useCallback(v=>{r===!1?g(v):N.startTransition(()=>g(v))},[r]);return N.useLayoutEffect(()=>m.listen(p),[m,p]),N.createElement(fv,{basename:n,children:s,location:y.location,navigationType:y.action,navigator:m,unstable_useTransitions:r})}var b0=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,x0=N.forwardRef(function({onClick:s,discover:r="render",prefetch:c="none",relative:d,reloadDocument:m,replace:y,state:g,target:p,to:v,preventScrollReset:E,viewTransition:T,unstable_defaultShouldRevalidate:R,...U},k){let{basename:B,unstable_useTransitions:q}=N.useContext(bt),Q=typeof v=="string"&&b0.test(v),L=s0(v,B);v=L.to;let K=Vg(v,{relative:d}),[X,P,me]=_v(c,U),$=Lv(v,{replace:y,state:g,target:p,preventScrollReset:E,relative:d,viewTransition:T,unstable_defaultShouldRevalidate:R,unstable_useTransitions:q});function oe(Ke){s&&s(Ke),Ke.defaultPrevented||$(Ke)}let Re=N.createElement("a",{...U,...me,href:L.absoluteURL||K,onClick:L.isExternal||m?s:oe,ref:wv(k,P),target:p,"data-discover":!Q&&r==="render"?"true":void 0});return X&&!Q?N.createElement(N.Fragment,null,Re,N.createElement(Dv,{page:K})):Re});x0.displayName="Link";var S0=N.forwardRef(function({"aria-current":s="page",caseSensitive:r=!1,className:c="",end:d=!1,style:m,to:y,viewTransition:g,children:p,...v},E){let T=Wn(y,{relative:v.relative}),R=ka(),U=N.useContext(Tu),{navigator:k,basename:B}=N.useContext(bt),q=U!=null&&Kv(T)&&g===!0,Q=k.encodeLocation?k.encodeLocation(T).pathname:T.pathname,L=R.pathname,K=U&&U.navigation&&U.navigation.location?U.navigation.location.pathname:null;r||(L=L.toLowerCase(),K=K?K.toLowerCase():null,Q=Q.toLowerCase()),K&&B&&(K=ca(K,B)||K);const X=Q!=="/"&&Q.endsWith("/")?Q.length-1:Q.length;let P=L===Q||!d&&L.startsWith(Q)&&L.charAt(X)==="/",me=K!=null&&(K===Q||!d&&K.startsWith(Q)&&K.charAt(Q.length)==="/"),$={isActive:P,isPending:me,isTransitioning:q},oe=P?s:void 0,Re;typeof c=="function"?Re=c($):Re=[c,P?"active":null,me?"pending":null,q?"transitioning":null].filter(Boolean).join(" ");let Ke=typeof m=="function"?m($):m;return N.createElement(x0,{...v,"aria-current":oe,className:Re,ref:E,style:Ke,to:y,viewTransition:g},typeof p=="function"?p($):p)});S0.displayName="NavLink";var qv=N.forwardRef(({discover:n="render",fetcherKey:s,navigate:r,reloadDocument:c,replace:d,state:m,method:y=bu,action:g,onSubmit:p,relative:v,preventScrollReset:E,viewTransition:T,unstable_defaultShouldRevalidate:R,...U},k)=>{let{unstable_useTransitions:B}=N.useContext(bt),q=Yv(),Q=Gv(g,{relative:v}),L=y.toLowerCase()==="get"?"get":"post",K=typeof g=="string"&&b0.test(g),X=P=>{if(p&&p(P),P.defaultPrevented)return;P.preventDefault();let me=P.nativeEvent.submitter,$=me?.getAttribute("formmethod")||y,oe=()=>q(me||P.currentTarget,{fetcherKey:s,method:$,navigate:r,replace:d,state:m,relative:v,preventScrollReset:E,viewTransition:T,unstable_defaultShouldRevalidate:R});B&&r!==!1?N.startTransition(()=>oe()):oe()};return N.createElement("form",{ref:k,method:L,action:Q,onSubmit:c?p:X,...U,"data-discover":!K&&n==="render"?"true":void 0})});qv.displayName="Form";function Hv(n){return`${n} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function E0(n){let s=N.useContext(Vl);return Ae(s,Hv(n)),s}function Lv(n,{target:s,replace:r,state:c,preventScrollReset:d,relative:m,viewTransition:y,unstable_defaultShouldRevalidate:g,unstable_useTransitions:p}={}){let v=mc(),E=ka(),T=Wn(n,{relative:m});return N.useCallback(R=>{if(vv(R,s)){R.preventDefault();let U=r!==void 0?r:Jn(E)===Jn(T),k=()=>v(n,{replace:U,state:c,preventScrollReset:d,relative:m,viewTransition:y,unstable_defaultShouldRevalidate:g});p?N.startTransition(()=>k()):k()}},[E,v,T,r,c,s,n,d,m,y,g,p])}var Bv=0,Qv=()=>`__${String(++Bv)}__`;function Yv(){let{router:n}=E0("useSubmit"),{basename:s}=N.useContext(bt),r=uv(),c=n.fetch,d=n.navigate;return N.useCallback(async(m,y={})=>{let{action:g,method:p,encType:v,formData:E,body:T}=xv(m,s);if(y.navigate===!1){let R=y.fetcherKey||Qv();await c(R,r,y.action||g,{unstable_defaultShouldRevalidate:y.unstable_defaultShouldRevalidate,preventScrollReset:y.preventScrollReset,formData:E,body:T,formMethod:y.method||p,formEncType:y.encType||v,flushSync:y.flushSync})}else await d(y.action||g,{unstable_defaultShouldRevalidate:y.unstable_defaultShouldRevalidate,preventScrollReset:y.preventScrollReset,formData:E,body:T,formMethod:y.method||p,formEncType:y.encType||v,replace:y.replace,state:y.state,fromRouteId:r,flushSync:y.flushSync,viewTransition:y.viewTransition})},[c,d,s,r])}function Gv(n,{relative:s}={}){let{basename:r}=N.useContext(bt),c=N.useContext(qt);Ae(c,"useFormAction must be used inside a RouteContext");let[d]=c.matches.slice(-1),m={...Wn(n||".",{relative:s})},y=ka();if(n==null){m.search=y.search;let g=new URLSearchParams(m.search),p=g.getAll("index");if(p.some(E=>E==="")){g.delete("index"),p.filter(T=>T).forEach(T=>g.append("index",T));let E=g.toString();m.search=E?`?${E}`:""}}return(!n||n===".")&&d.route.index&&(m.search=m.search?m.search.replace(/^\?/,"?index&"):"?index"),r!=="/"&&(m.pathname=m.pathname==="/"?r:ra([r,m.pathname])),Jn(m)}function Kv(n,{relative:s}={}){let r=N.useContext(c0);Ae(r!=null,"`useViewTransitionState` must be used within `react-router-dom`'s `RouterProvider`. Did you accidentally import `RouterProvider` from `react-router`?");let{basename:c}=E0("useViewTransitionState"),d=Wn(n,{relative:s});if(!r.isTransitioning)return!1;let m=ca(r.currentLocation.pathname,c)||r.currentLocation.pathname,y=ca(r.nextLocation.pathname,c)||r.nextLocation.pathname;return Su(d.pathname,y)!=null||Su(d.pathname,m)!=null}var Fl=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(n){return this.listeners.add(n),this.onSubscribe(),()=>{this.listeners.delete(n),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}},Xv={setTimeout:(n,s)=>setTimeout(n,s),clearTimeout:n=>clearTimeout(n),setInterval:(n,s)=>setInterval(n,s),clearInterval:n=>clearInterval(n)},Zv=class{#e=Xv;#t=!1;setTimeoutProvider(n){this.#e=n}setTimeout(n,s){return this.#e.setTimeout(n,s)}clearTimeout(n){this.#e.clearTimeout(n)}setInterval(n,s){return this.#e.setInterval(n,s)}clearInterval(n){this.#e.clearInterval(n)}},nl=new Zv;function Vv(n){setTimeout(n,0)}var il=typeof window>"u"||"Deno"in globalThis;function Pe(){}function Jv(n,s){return typeof n=="function"?n(s):n}function lc(n){return typeof n=="number"&&n>=0&&n!==1/0}function j0(n,s){return Math.max(n+(s||0)-Date.now(),0)}function Ua(n,s){return typeof n=="function"?n(s):n}function Dt(n,s){return typeof n=="function"?n(s):n}function Kh(n,s){const{type:r="all",exact:c,fetchStatus:d,predicate:m,queryKey:y,stale:g}=n;if(y){if(c){if(s.queryHash!==bc(y,s.options))return!1}else if(!Fn(s.queryKey,y))return!1}if(r!=="all"){const p=s.isActive();if(r==="active"&&!p||r==="inactive"&&p)return!1}return!(typeof g=="boolean"&&s.isStale()!==g||d&&d!==s.state.fetchStatus||m&&!m(s))}function Xh(n,s){const{exact:r,status:c,predicate:d,mutationKey:m}=n;if(m){if(!s.options.mutationKey)return!1;if(r){if(ul(s.options.mutationKey)!==ul(m))return!1}else if(!Fn(s.options.mutationKey,m))return!1}return!(c&&s.state.status!==c||d&&!d(s))}function bc(n,s){return(s?.queryKeyHashFn||ul)(n)}function ul(n){return JSON.stringify(n,(s,r)=>nc(r)?Object.keys(r).sort().reduce((c,d)=>(c[d]=r[d],c),{}):r)}function Fn(n,s){return n===s?!0:typeof n!=typeof s?!1:n&&s&&typeof n=="object"&&typeof s=="object"?Object.keys(s).every(r=>Fn(n[r],s[r])):!1}var Fv=Object.prototype.hasOwnProperty;function T0(n,s,r=0){if(n===s)return n;if(r>500)return s;const c=Zh(n)&&Zh(s);if(!c&&!(nc(n)&&nc(s)))return s;const m=(c?n:Object.keys(n)).length,y=c?s:Object.keys(s),g=y.length,p=c?new Array(g):{};let v=0;for(let E=0;E<g;E++){const T=c?E:y[E],R=n[T],U=s[T];if(R===U){p[T]=R,(c?E<m:Fv.call(n,T))&&v++;continue}if(R===null||U===null||typeof R!="object"||typeof U!="object"){p[T]=U;continue}const k=T0(R,U,r+1);p[T]=k,k===R&&v++}return m===g&&v===m?n:p}function Eu(n,s){if(!s||Object.keys(n).length!==Object.keys(s).length)return!1;for(const r in n)if(n[r]!==s[r])return!1;return!0}function Zh(n){return Array.isArray(n)&&n.length===Object.keys(n).length}function nc(n){if(!Vh(n))return!1;const s=n.constructor;if(s===void 0)return!0;const r=s.prototype;return!(!Vh(r)||!r.hasOwnProperty("isPrototypeOf")||Object.getPrototypeOf(n)!==Object.prototype)}function Vh(n){return Object.prototype.toString.call(n)==="[object Object]"}function $v(n){return new Promise(s=>{nl.setTimeout(s,n)})}function ic(n,s,r){return typeof r.structuralSharing=="function"?r.structuralSharing(n,s):r.structuralSharing!==!1?T0(n,s):s}function Wv(n,s,r=0){const c=[...n,s];return r&&c.length>r?c.slice(1):c}function Iv(n,s,r=0){const c=[s,...n];return r&&c.length>r?c.slice(0,-1):c}var xc=Symbol();function N0(n,s){return!n.queryFn&&s?.initialPromise?()=>s.initialPromise:!n.queryFn||n.queryFn===xc?()=>Promise.reject(new Error(`Missing queryFn: '${n.queryHash}'`)):n.queryFn}function Sc(n,s){return typeof n=="function"?n(...s):!!n}function Pv(n,s,r){let c=!1,d;return Object.defineProperty(n,"signal",{enumerable:!0,get:()=>(d??=s(),c||(c=!0,d.aborted?r():d.addEventListener("abort",r,{once:!0})),d)}),n}var ep=class extends Fl{#e;#t;#a;constructor(){super(),this.#a=n=>{if(!il&&window.addEventListener){const s=()=>n();return window.addEventListener("visibilitychange",s,!1),()=>{window.removeEventListener("visibilitychange",s)}}}}onSubscribe(){this.#t||this.setEventListener(this.#a)}onUnsubscribe(){this.hasListeners()||(this.#t?.(),this.#t=void 0)}setEventListener(n){this.#a=n,this.#t?.(),this.#t=n(s=>{typeof s=="boolean"?this.setFocused(s):this.onFocus()})}setFocused(n){this.#e!==n&&(this.#e=n,this.onFocus())}onFocus(){const n=this.isFocused();this.listeners.forEach(s=>{s(n)})}isFocused(){return typeof this.#e=="boolean"?this.#e:globalThis.document?.visibilityState!=="hidden"}},Ec=new ep;function uc(){let n,s;const r=new Promise((d,m)=>{n=d,s=m});r.status="pending",r.catch(()=>{});function c(d){Object.assign(r,d),delete r.resolve,delete r.reject}return r.resolve=d=>{c({status:"fulfilled",value:d}),n(d)},r.reject=d=>{c({status:"rejected",reason:d}),s(d)},r}var tp=Vv;function ap(){let n=[],s=0,r=g=>{g()},c=g=>{g()},d=tp;const m=g=>{s?n.push(g):d(()=>{r(g)})},y=()=>{const g=n;n=[],g.length&&d(()=>{c(()=>{g.forEach(p=>{r(p)})})})};return{batch:g=>{let p;s++;try{p=g()}finally{s--,s||y()}return p},batchCalls:g=>(...p)=>{m(()=>{g(...p)})},schedule:m,setNotifyFunction:g=>{r=g},setBatchNotifyFunction:g=>{c=g},setScheduler:g=>{d=g}}}var Qe=ap(),lp=class extends Fl{#e=!0;#t;#a;constructor(){super(),this.#a=n=>{if(!il&&window.addEventListener){const s=()=>n(!0),r=()=>n(!1);return window.addEventListener("online",s,!1),window.addEventListener("offline",r,!1),()=>{window.removeEventListener("online",s),window.removeEventListener("offline",r)}}}}onSubscribe(){this.#t||this.setEventListener(this.#a)}onUnsubscribe(){this.hasListeners()||(this.#t?.(),this.#t=void 0)}setEventListener(n){this.#a=n,this.#t?.(),this.#t=n(this.setOnline.bind(this))}setOnline(n){this.#e!==n&&(this.#e=n,this.listeners.forEach(r=>{r(n)}))}isOnline(){return this.#e}},ju=new lp;function np(n){return Math.min(1e3*2**n,3e4)}function C0(n){return(n??"online")==="online"?ju.isOnline():!0}var sc=class extends Error{constructor(n){super("CancelledError"),this.revert=n?.revert,this.silent=n?.silent}};function O0(n){let s=!1,r=0,c;const d=uc(),m=()=>d.status!=="pending",y=B=>{if(!m()){const q=new sc(B);R(q),n.onCancel?.(q)}},g=()=>{s=!0},p=()=>{s=!1},v=()=>Ec.isFocused()&&(n.networkMode==="always"||ju.isOnline())&&n.canRun(),E=()=>C0(n.networkMode)&&n.canRun(),T=B=>{m()||(c?.(),d.resolve(B))},R=B=>{m()||(c?.(),d.reject(B))},U=()=>new Promise(B=>{c=q=>{(m()||v())&&B(q)},n.onPause?.()}).then(()=>{c=void 0,m()||n.onContinue?.()}),k=()=>{if(m())return;let B;const q=r===0?n.initialPromise:void 0;try{B=q??n.fn()}catch(Q){B=Promise.reject(Q)}Promise.resolve(B).then(T).catch(Q=>{if(m())return;const L=n.retry??(il?0:3),K=n.retryDelay??np,X=typeof K=="function"?K(r,Q):K,P=L===!0||typeof L=="number"&&r<L||typeof L=="function"&&L(r,Q);if(s||!P){R(Q);return}r++,n.onFail?.(r,Q),$v(X).then(()=>v()?void 0:U()).then(()=>{s?R(Q):k()})})};return{promise:d,status:()=>d.status,cancel:y,continue:()=>(c?.(),d),cancelRetry:g,continueRetry:p,canStart:E,start:()=>(E()?k():U().then(k),d)}}var A0=class{#e;destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),lc(this.gcTime)&&(this.#e=nl.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(n){this.gcTime=Math.max(this.gcTime||0,n??(il?1/0:300*1e3))}clearGcTimeout(){this.#e&&(nl.clearTimeout(this.#e),this.#e=void 0)}},ip=class extends A0{#e;#t;#a;#n;#l;#u;#s;constructor(n){super(),this.#s=!1,this.#u=n.defaultOptions,this.setOptions(n.options),this.observers=[],this.#n=n.client,this.#a=this.#n.getQueryCache(),this.queryKey=n.queryKey,this.queryHash=n.queryHash,this.#e=Fh(this.options),this.state=n.state??this.#e,this.scheduleGc()}get meta(){return this.options.meta}get promise(){return this.#l?.promise}setOptions(n){if(this.options={...this.#u,...n},this.updateGcTime(this.options.gcTime),this.state&&this.state.data===void 0){const s=Fh(this.options);s.data!==void 0&&(this.setState(Jh(s.data,s.dataUpdatedAt)),this.#e=s)}}optionalRemove(){!this.observers.length&&this.state.fetchStatus==="idle"&&this.#a.remove(this)}setData(n,s){const r=ic(this.state.data,n,this.options);return this.#i({data:r,type:"success",dataUpdatedAt:s?.updatedAt,manual:s?.manual}),r}setState(n,s){this.#i({type:"setState",state:n,setStateOptions:s})}cancel(n){const s=this.#l?.promise;return this.#l?.cancel(n),s?s.then(Pe).catch(Pe):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}reset(){this.destroy(),this.setState(this.#e)}isActive(){return this.observers.some(n=>Dt(n.options.enabled,this)!==!1)}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===xc||this.state.dataUpdateCount+this.state.errorUpdateCount===0}isStatic(){return this.getObserversCount()>0?this.observers.some(n=>Ua(n.options.staleTime,this)==="static"):!1}isStale(){return this.getObserversCount()>0?this.observers.some(n=>n.getCurrentResult().isStale):this.state.data===void 0||this.state.isInvalidated}isStaleByTime(n=0){return this.state.data===void 0?!0:n==="static"?!1:this.state.isInvalidated?!0:!j0(this.state.dataUpdatedAt,n)}onFocus(){this.observers.find(s=>s.shouldFetchOnWindowFocus())?.refetch({cancelRefetch:!1}),this.#l?.continue()}onOnline(){this.observers.find(s=>s.shouldFetchOnReconnect())?.refetch({cancelRefetch:!1}),this.#l?.continue()}addObserver(n){this.observers.includes(n)||(this.observers.push(n),this.clearGcTimeout(),this.#a.notify({type:"observerAdded",query:this,observer:n}))}removeObserver(n){this.observers.includes(n)&&(this.observers=this.observers.filter(s=>s!==n),this.observers.length||(this.#l&&(this.#s?this.#l.cancel({revert:!0}):this.#l.cancelRetry()),this.scheduleGc()),this.#a.notify({type:"observerRemoved",query:this,observer:n}))}getObserversCount(){return this.observers.length}invalidate(){this.state.isInvalidated||this.#i({type:"invalidate"})}async fetch(n,s){if(this.state.fetchStatus!=="idle"&&this.#l?.status()!=="rejected"){if(this.state.data!==void 0&&s?.cancelRefetch)this.cancel({silent:!0});else if(this.#l)return this.#l.continueRetry(),this.#l.promise}if(n&&this.setOptions(n),!this.options.queryFn){const g=this.observers.find(p=>p.options.queryFn);g&&this.setOptions(g.options)}const r=new AbortController,c=g=>{Object.defineProperty(g,"signal",{enumerable:!0,get:()=>(this.#s=!0,r.signal)})},d=()=>{const g=N0(this.options,s),v=(()=>{const E={client:this.#n,queryKey:this.queryKey,meta:this.meta};return c(E),E})();return this.#s=!1,this.options.persister?this.options.persister(g,v,this):g(v)},y=(()=>{const g={fetchOptions:s,options:this.options,queryKey:this.queryKey,client:this.#n,state:this.state,fetchFn:d};return c(g),g})();this.options.behavior?.onFetch(y,this),this.#t=this.state,(this.state.fetchStatus==="idle"||this.state.fetchMeta!==y.fetchOptions?.meta)&&this.#i({type:"fetch",meta:y.fetchOptions?.meta}),this.#l=O0({initialPromise:s?.initialPromise,fn:y.fetchFn,onCancel:g=>{g instanceof sc&&g.revert&&this.setState({...this.#t,fetchStatus:"idle"}),r.abort()},onFail:(g,p)=>{this.#i({type:"failed",failureCount:g,error:p})},onPause:()=>{this.#i({type:"pause"})},onContinue:()=>{this.#i({type:"continue"})},retry:y.options.retry,retryDelay:y.options.retryDelay,networkMode:y.options.networkMode,canRun:()=>!0});try{const g=await this.#l.start();if(g===void 0)throw new Error(`${this.queryHash} data is undefined`);return this.setData(g),this.#a.config.onSuccess?.(g,this),this.#a.config.onSettled?.(g,this.state.error,this),g}catch(g){if(g instanceof sc){if(g.silent)return this.#l.promise;if(g.revert){if(this.state.data===void 0)throw g;return this.state.data}}throw this.#i({type:"error",error:g}),this.#a.config.onError?.(g,this),this.#a.config.onSettled?.(this.state.data,g,this),g}finally{this.scheduleGc()}}#i(n){const s=r=>{switch(n.type){case"failed":return{...r,fetchFailureCount:n.failureCount,fetchFailureReason:n.error};case"pause":return{...r,fetchStatus:"paused"};case"continue":return{...r,fetchStatus:"fetching"};case"fetch":return{...r,...R0(r.data,this.options),fetchMeta:n.meta??null};case"success":const c={...r,...Jh(n.data,n.dataUpdatedAt),dataUpdateCount:r.dataUpdateCount+1,...!n.manual&&{fetchStatus:"idle",fetchFailureCount:0,fetchFailureReason:null}};return this.#t=n.manual?c:void 0,c;case"error":const d=n.error;return{...r,error:d,errorUpdateCount:r.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:r.fetchFailureCount+1,fetchFailureReason:d,fetchStatus:"idle",status:"error",isInvalidated:!0};case"invalidate":return{...r,isInvalidated:!0};case"setState":return{...r,...n.state}}};this.state=s(this.state),Qe.batch(()=>{this.observers.forEach(r=>{r.onQueryUpdate()}),this.#a.notify({query:this,type:"updated",action:n})})}};function R0(n,s){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:C0(s.networkMode)?"fetching":"paused",...n===void 0&&{error:null,status:"pending"}}}function Jh(n,s){return{data:n,dataUpdatedAt:s??Date.now(),error:null,isInvalidated:!1,status:"success"}}function Fh(n){const s=typeof n.initialData=="function"?n.initialData():n.initialData,r=s!==void 0,c=r?typeof n.initialDataUpdatedAt=="function"?n.initialDataUpdatedAt():n.initialDataUpdatedAt:0;return{data:s,dataUpdateCount:0,dataUpdatedAt:r?c??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:r?"success":"pending",fetchStatus:"idle"}}var up=class extends Fl{constructor(n,s){super(),this.options=s,this.#e=n,this.#i=null,this.#s=uc(),this.bindMethods(),this.setOptions(s)}#e;#t=void 0;#a=void 0;#n=void 0;#l;#u;#s;#i;#y;#d;#h;#c;#o;#r;#m=new Set;bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){this.listeners.size===1&&(this.#t.addObserver(this),$h(this.#t,this.options)?this.#f():this.updateResult(),this.#b())}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return rc(this.#t,this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return rc(this.#t,this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,this.#x(),this.#S(),this.#t.removeObserver(this)}setOptions(n){const s=this.options,r=this.#t;if(this.options=this.#e.defaultQueryOptions(n),this.options.enabled!==void 0&&typeof this.options.enabled!="boolean"&&typeof this.options.enabled!="function"&&typeof Dt(this.options.enabled,this.#t)!="boolean")throw new Error("Expected enabled to be a boolean or a callback that returns a boolean");this.#E(),this.#t.setOptions(this.options),s._defaulted&&!Eu(this.options,s)&&this.#e.getQueryCache().notify({type:"observerOptionsUpdated",query:this.#t,observer:this});const c=this.hasListeners();c&&Wh(this.#t,r,this.options,s)&&this.#f(),this.updateResult(),c&&(this.#t!==r||Dt(this.options.enabled,this.#t)!==Dt(s.enabled,this.#t)||Ua(this.options.staleTime,this.#t)!==Ua(s.staleTime,this.#t))&&this.#g();const d=this.#v();c&&(this.#t!==r||Dt(this.options.enabled,this.#t)!==Dt(s.enabled,this.#t)||d!==this.#r)&&this.#p(d)}getOptimisticResult(n){const s=this.#e.getQueryCache().build(this.#e,n),r=this.createResult(s,n);return rp(this,r)&&(this.#n=r,this.#u=this.options,this.#l=this.#t.state),r}getCurrentResult(){return this.#n}trackResult(n,s){return new Proxy(n,{get:(r,c)=>(this.trackProp(c),s?.(c),c==="promise"&&(this.trackProp("data"),!this.options.experimental_prefetchInRender&&this.#s.status==="pending"&&this.#s.reject(new Error("experimental_prefetchInRender feature flag is not enabled"))),Reflect.get(r,c))})}trackProp(n){this.#m.add(n)}getCurrentQuery(){return this.#t}refetch({...n}={}){return this.fetch({...n})}fetchOptimistic(n){const s=this.#e.defaultQueryOptions(n),r=this.#e.getQueryCache().build(this.#e,s);return r.fetch().then(()=>this.createResult(r,s))}fetch(n){return this.#f({...n,cancelRefetch:n.cancelRefetch??!0}).then(()=>(this.updateResult(),this.#n))}#f(n){this.#E();let s=this.#t.fetch(this.options,n);return n?.throwOnError||(s=s.catch(Pe)),s}#g(){this.#x();const n=Ua(this.options.staleTime,this.#t);if(il||this.#n.isStale||!lc(n))return;const r=j0(this.#n.dataUpdatedAt,n)+1;this.#c=nl.setTimeout(()=>{this.#n.isStale||this.updateResult()},r)}#v(){return(typeof this.options.refetchInterval=="function"?this.options.refetchInterval(this.#t):this.options.refetchInterval)??!1}#p(n){this.#S(),this.#r=n,!(il||Dt(this.options.enabled,this.#t)===!1||!lc(this.#r)||this.#r===0)&&(this.#o=nl.setInterval(()=>{(this.options.refetchIntervalInBackground||Ec.isFocused())&&this.#f()},this.#r))}#b(){this.#g(),this.#p(this.#v())}#x(){this.#c&&(nl.clearTimeout(this.#c),this.#c=void 0)}#S(){this.#o&&(nl.clearInterval(this.#o),this.#o=void 0)}createResult(n,s){const r=this.#t,c=this.options,d=this.#n,m=this.#l,y=this.#u,p=n!==r?n.state:this.#a,{state:v}=n;let E={...v},T=!1,R;if(s._optimisticResults){const oe=this.hasListeners(),Re=!oe&&$h(n,s),Ke=oe&&Wh(n,r,s,c);(Re||Ke)&&(E={...E,...R0(v.data,n.options)}),s._optimisticResults==="isRestoring"&&(E.fetchStatus="idle")}let{error:U,errorUpdatedAt:k,status:B}=E;R=E.data;let q=!1;if(s.placeholderData!==void 0&&R===void 0&&B==="pending"){let oe;d?.isPlaceholderData&&s.placeholderData===y?.placeholderData?(oe=d.data,q=!0):oe=typeof s.placeholderData=="function"?s.placeholderData(this.#h?.state.data,this.#h):s.placeholderData,oe!==void 0&&(B="success",R=ic(d?.data,oe,s),T=!0)}if(s.select&&R!==void 0&&!q)if(d&&R===m?.data&&s.select===this.#y)R=this.#d;else try{this.#y=s.select,R=s.select(R),R=ic(d?.data,R,s),this.#d=R,this.#i=null}catch(oe){this.#i=oe}this.#i&&(U=this.#i,R=this.#d,k=Date.now(),B="error");const Q=E.fetchStatus==="fetching",L=B==="pending",K=B==="error",X=L&&Q,P=R!==void 0,$={status:B,fetchStatus:E.fetchStatus,isPending:L,isSuccess:B==="success",isError:K,isInitialLoading:X,isLoading:X,data:R,dataUpdatedAt:E.dataUpdatedAt,error:U,errorUpdatedAt:k,failureCount:E.fetchFailureCount,failureReason:E.fetchFailureReason,errorUpdateCount:E.errorUpdateCount,isFetched:E.dataUpdateCount>0||E.errorUpdateCount>0,isFetchedAfterMount:E.dataUpdateCount>p.dataUpdateCount||E.errorUpdateCount>p.errorUpdateCount,isFetching:Q,isRefetching:Q&&!L,isLoadingError:K&&!P,isPaused:E.fetchStatus==="paused",isPlaceholderData:T,isRefetchError:K&&P,isStale:jc(n,s),refetch:this.refetch,promise:this.#s,isEnabled:Dt(s.enabled,n)!==!1};if(this.options.experimental_prefetchInRender){const oe=$.data!==void 0,Re=$.status==="error"&&!oe,Ke=xt=>{Re?xt.reject($.error):oe&&xt.resolve($.data)},st=()=>{const xt=this.#s=$.promise=uc();Ke(xt)},Le=this.#s;switch(Le.status){case"pending":n.queryHash===r.queryHash&&Ke(Le);break;case"fulfilled":(Re||$.data!==Le.value)&&st();break;case"rejected":(!Re||$.error!==Le.reason)&&st();break}}return $}updateResult(){const n=this.#n,s=this.createResult(this.#t,this.options);if(this.#l=this.#t.state,this.#u=this.options,this.#l.data!==void 0&&(this.#h=this.#t),Eu(s,n))return;this.#n=s;const r=()=>{if(!n)return!0;const{notifyOnChangeProps:c}=this.options,d=typeof c=="function"?c():c;if(d==="all"||!d&&!this.#m.size)return!0;const m=new Set(d??this.#m);return this.options.throwOnError&&m.add("error"),Object.keys(this.#n).some(y=>{const g=y;return this.#n[g]!==n[g]&&m.has(g)})};this.#j({listeners:r()})}#E(){const n=this.#e.getQueryCache().build(this.#e,this.options);if(n===this.#t)return;const s=this.#t;this.#t=n,this.#a=n.state,this.hasListeners()&&(s?.removeObserver(this),n.addObserver(this))}onQueryUpdate(){this.updateResult(),this.hasListeners()&&this.#b()}#j(n){Qe.batch(()=>{n.listeners&&this.listeners.forEach(s=>{s(this.#n)}),this.#e.getQueryCache().notify({query:this.#t,type:"observerResultsUpdated"})})}};function sp(n,s){return Dt(s.enabled,n)!==!1&&n.state.data===void 0&&!(n.state.status==="error"&&s.retryOnMount===!1)}function $h(n,s){return sp(n,s)||n.state.data!==void 0&&rc(n,s,s.refetchOnMount)}function rc(n,s,r){if(Dt(s.enabled,n)!==!1&&Ua(s.staleTime,n)!=="static"){const c=typeof r=="function"?r(n):r;return c==="always"||c!==!1&&jc(n,s)}return!1}function Wh(n,s,r,c){return(n!==s||Dt(c.enabled,n)===!1)&&(!r.suspense||n.state.status!=="error")&&jc(n,r)}function jc(n,s){return Dt(s.enabled,n)!==!1&&n.isStaleByTime(Ua(s.staleTime,n))}function rp(n,s){return!Eu(n.getCurrentResult(),s)}function Ih(n){return{onFetch:(s,r)=>{const c=s.options,d=s.fetchOptions?.meta?.fetchMore?.direction,m=s.state.data?.pages||[],y=s.state.data?.pageParams||[];let g={pages:[],pageParams:[]},p=0;const v=async()=>{let E=!1;const T=k=>{Pv(k,()=>s.signal,()=>E=!0)},R=N0(s.options,s.fetchOptions),U=async(k,B,q)=>{if(E)return Promise.reject();if(B==null&&k.pages.length)return Promise.resolve(k);const L=(()=>{const me={client:s.client,queryKey:s.queryKey,pageParam:B,direction:q?"backward":"forward",meta:s.options.meta};return T(me),me})(),K=await R(L),{maxPages:X}=s.options,P=q?Iv:Wv;return{pages:P(k.pages,K,X),pageParams:P(k.pageParams,B,X)}};if(d&&m.length){const k=d==="backward",B=k?cp:Ph,q={pages:m,pageParams:y},Q=B(c,q);g=await U(q,Q,k)}else{const k=n??m.length;do{const B=p===0?y[0]??c.initialPageParam:Ph(c,g);if(p>0&&B==null)break;g=await U(g,B),p++}while(p<k)}return g};s.options.persister?s.fetchFn=()=>s.options.persister?.(v,{client:s.client,queryKey:s.queryKey,meta:s.options.meta,signal:s.signal},r):s.fetchFn=v}}}function Ph(n,{pages:s,pageParams:r}){const c=s.length-1;return s.length>0?n.getNextPageParam(s[c],s,r[c],r):void 0}function cp(n,{pages:s,pageParams:r}){return s.length>0?n.getPreviousPageParam?.(s[0],s,r[0],r):void 0}var op=class extends A0{#e;#t;#a;#n;constructor(n){super(),this.#e=n.client,this.mutationId=n.mutationId,this.#a=n.mutationCache,this.#t=[],this.state=n.state||_0(),this.setOptions(n.options),this.scheduleGc()}setOptions(n){this.options=n,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(n){this.#t.includes(n)||(this.#t.push(n),this.clearGcTimeout(),this.#a.notify({type:"observerAdded",mutation:this,observer:n}))}removeObserver(n){this.#t=this.#t.filter(s=>s!==n),this.scheduleGc(),this.#a.notify({type:"observerRemoved",mutation:this,observer:n})}optionalRemove(){this.#t.length||(this.state.status==="pending"?this.scheduleGc():this.#a.remove(this))}continue(){return this.#n?.continue()??this.execute(this.state.variables)}async execute(n){const s=()=>{this.#l({type:"continue"})},r={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};this.#n=O0({fn:()=>this.options.mutationFn?this.options.mutationFn(n,r):Promise.reject(new Error("No mutationFn found")),onFail:(m,y)=>{this.#l({type:"failed",failureCount:m,error:y})},onPause:()=>{this.#l({type:"pause"})},onContinue:s,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>this.#a.canRun(this)});const c=this.state.status==="pending",d=!this.#n.canStart();try{if(c)s();else{this.#l({type:"pending",variables:n,isPaused:d}),this.#a.config.onMutate&&await this.#a.config.onMutate(n,this,r);const y=await this.options.onMutate?.(n,r);y!==this.state.context&&this.#l({type:"pending",context:y,variables:n,isPaused:d})}const m=await this.#n.start();return await this.#a.config.onSuccess?.(m,n,this.state.context,this,r),await this.options.onSuccess?.(m,n,this.state.context,r),await this.#a.config.onSettled?.(m,null,this.state.variables,this.state.context,this,r),await this.options.onSettled?.(m,null,n,this.state.context,r),this.#l({type:"success",data:m}),m}catch(m){try{await this.#a.config.onError?.(m,n,this.state.context,this,r)}catch(y){Promise.reject(y)}try{await this.options.onError?.(m,n,this.state.context,r)}catch(y){Promise.reject(y)}try{await this.#a.config.onSettled?.(void 0,m,this.state.variables,this.state.context,this,r)}catch(y){Promise.reject(y)}try{await this.options.onSettled?.(void 0,m,n,this.state.context,r)}catch(y){Promise.reject(y)}throw this.#l({type:"error",error:m}),m}finally{this.#a.runNext(this)}}#l(n){const s=r=>{switch(n.type){case"failed":return{...r,failureCount:n.failureCount,failureReason:n.error};case"pause":return{...r,isPaused:!0};case"continue":return{...r,isPaused:!1};case"pending":return{...r,context:n.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:n.isPaused,status:"pending",variables:n.variables,submittedAt:Date.now()};case"success":return{...r,data:n.data,failureCount:0,failureReason:null,error:null,status:"success",isPaused:!1};case"error":return{...r,data:void 0,error:n.error,failureCount:r.failureCount+1,failureReason:n.error,isPaused:!1,status:"error"}}};this.state=s(this.state),Qe.batch(()=>{this.#t.forEach(r=>{r.onMutationUpdate(n)}),this.#a.notify({mutation:this,type:"updated",action:n})})}};function _0(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:"idle",variables:void 0,submittedAt:0}}var fp=class extends Fl{constructor(n={}){super(),this.config=n,this.#e=new Set,this.#t=new Map,this.#a=0}#e;#t;#a;build(n,s,r){const c=new op({client:n,mutationCache:this,mutationId:++this.#a,options:n.defaultMutationOptions(s),state:r});return this.add(c),c}add(n){this.#e.add(n);const s=pu(n);if(typeof s=="string"){const r=this.#t.get(s);r?r.push(n):this.#t.set(s,[n])}this.notify({type:"added",mutation:n})}remove(n){if(this.#e.delete(n)){const s=pu(n);if(typeof s=="string"){const r=this.#t.get(s);if(r)if(r.length>1){const c=r.indexOf(n);c!==-1&&r.splice(c,1)}else r[0]===n&&this.#t.delete(s)}}this.notify({type:"removed",mutation:n})}canRun(n){const s=pu(n);if(typeof s=="string"){const c=this.#t.get(s)?.find(d=>d.state.status==="pending");return!c||c===n}else return!0}runNext(n){const s=pu(n);return typeof s=="string"?this.#t.get(s)?.find(c=>c!==n&&c.state.isPaused)?.continue()??Promise.resolve():Promise.resolve()}clear(){Qe.batch(()=>{this.#e.forEach(n=>{this.notify({type:"removed",mutation:n})}),this.#e.clear(),this.#t.clear()})}getAll(){return Array.from(this.#e)}find(n){const s={exact:!0,...n};return this.getAll().find(r=>Xh(s,r))}findAll(n={}){return this.getAll().filter(s=>Xh(n,s))}notify(n){Qe.batch(()=>{this.listeners.forEach(s=>{s(n)})})}resumePausedMutations(){const n=this.getAll().filter(s=>s.state.isPaused);return Qe.batch(()=>Promise.all(n.map(s=>s.continue().catch(Pe))))}};function pu(n){return n.options.scope?.id}var dp=class extends Fl{#e;#t=void 0;#a;#n;constructor(s,r){super(),this.#e=s,this.setOptions(r),this.bindMethods(),this.#l()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(s){const r=this.options;this.options=this.#e.defaultMutationOptions(s),Eu(this.options,r)||this.#e.getMutationCache().notify({type:"observerOptionsUpdated",mutation:this.#a,observer:this}),r?.mutationKey&&this.options.mutationKey&&ul(r.mutationKey)!==ul(this.options.mutationKey)?this.reset():this.#a?.state.status==="pending"&&this.#a.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#a?.removeObserver(this)}onMutationUpdate(s){this.#l(),this.#u(s)}getCurrentResult(){return this.#t}reset(){this.#a?.removeObserver(this),this.#a=void 0,this.#l(),this.#u()}mutate(s,r){return this.#n=r,this.#a?.removeObserver(this),this.#a=this.#e.getMutationCache().build(this.#e,this.options),this.#a.addObserver(this),this.#a.execute(s)}#l(){const s=this.#a?.state??_0();this.#t={...s,isPending:s.status==="pending",isSuccess:s.status==="success",isError:s.status==="error",isIdle:s.status==="idle",mutate:this.mutate,reset:this.reset}}#u(s){Qe.batch(()=>{if(this.#n&&this.hasListeners()){const r=this.#t.variables,c=this.#t.context,d={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};if(s?.type==="success"){try{this.#n.onSuccess?.(s.data,r,c,d)}catch(m){Promise.reject(m)}try{this.#n.onSettled?.(s.data,null,r,c,d)}catch(m){Promise.reject(m)}}else if(s?.type==="error"){try{this.#n.onError?.(s.error,r,c,d)}catch(m){Promise.reject(m)}try{this.#n.onSettled?.(void 0,s.error,r,c,d)}catch(m){Promise.reject(m)}}}this.listeners.forEach(r=>{r(this.#t)})})}},hp=class extends Fl{constructor(n={}){super(),this.config=n,this.#e=new Map}#e;build(n,s,r){const c=s.queryKey,d=s.queryHash??bc(c,s);let m=this.get(d);return m||(m=new ip({client:n,queryKey:c,queryHash:d,options:n.defaultQueryOptions(s),state:r,defaultOptions:n.getQueryDefaults(c)}),this.add(m)),m}add(n){this.#e.has(n.queryHash)||(this.#e.set(n.queryHash,n),this.notify({type:"added",query:n}))}remove(n){const s=this.#e.get(n.queryHash);s&&(n.destroy(),s===n&&this.#e.delete(n.queryHash),this.notify({type:"removed",query:n}))}clear(){Qe.batch(()=>{this.getAll().forEach(n=>{this.remove(n)})})}get(n){return this.#e.get(n)}getAll(){return[...this.#e.values()]}find(n){const s={exact:!0,...n};return this.getAll().find(r=>Kh(s,r))}findAll(n={}){const s=this.getAll();return Object.keys(n).length>0?s.filter(r=>Kh(n,r)):s}notify(n){Qe.batch(()=>{this.listeners.forEach(s=>{s(n)})})}onFocus(){Qe.batch(()=>{this.getAll().forEach(n=>{n.onFocus()})})}onOnline(){Qe.batch(()=>{this.getAll().forEach(n=>{n.onOnline()})})}},mp=class{#e;#t;#a;#n;#l;#u;#s;#i;constructor(n={}){this.#e=n.queryCache||new hp,this.#t=n.mutationCache||new fp,this.#a=n.defaultOptions||{},this.#n=new Map,this.#l=new Map,this.#u=0}mount(){this.#u++,this.#u===1&&(this.#s=Ec.subscribe(async n=>{n&&(await this.resumePausedMutations(),this.#e.onFocus())}),this.#i=ju.subscribe(async n=>{n&&(await this.resumePausedMutations(),this.#e.onOnline())}))}unmount(){this.#u--,this.#u===0&&(this.#s?.(),this.#s=void 0,this.#i?.(),this.#i=void 0)}isFetching(n){return this.#e.findAll({...n,fetchStatus:"fetching"}).length}isMutating(n){return this.#t.findAll({...n,status:"pending"}).length}getQueryData(n){const s=this.defaultQueryOptions({queryKey:n});return this.#e.get(s.queryHash)?.state.data}ensureQueryData(n){const s=this.defaultQueryOptions(n),r=this.#e.build(this,s),c=r.state.data;return c===void 0?this.fetchQuery(n):(n.revalidateIfStale&&r.isStaleByTime(Ua(s.staleTime,r))&&this.prefetchQuery(s),Promise.resolve(c))}getQueriesData(n){return this.#e.findAll(n).map(({queryKey:s,state:r})=>{const c=r.data;return[s,c]})}setQueryData(n,s,r){const c=this.defaultQueryOptions({queryKey:n}),m=this.#e.get(c.queryHash)?.state.data,y=Jv(s,m);if(y!==void 0)return this.#e.build(this,c).setData(y,{...r,manual:!0})}setQueriesData(n,s,r){return Qe.batch(()=>this.#e.findAll(n).map(({queryKey:c})=>[c,this.setQueryData(c,s,r)]))}getQueryState(n){const s=this.defaultQueryOptions({queryKey:n});return this.#e.get(s.queryHash)?.state}removeQueries(n){const s=this.#e;Qe.batch(()=>{s.findAll(n).forEach(r=>{s.remove(r)})})}resetQueries(n,s){const r=this.#e;return Qe.batch(()=>(r.findAll(n).forEach(c=>{c.reset()}),this.refetchQueries({type:"active",...n},s)))}cancelQueries(n,s={}){const r={revert:!0,...s},c=Qe.batch(()=>this.#e.findAll(n).map(d=>d.cancel(r)));return Promise.all(c).then(Pe).catch(Pe)}invalidateQueries(n,s={}){return Qe.batch(()=>(this.#e.findAll(n).forEach(r=>{r.invalidate()}),n?.refetchType==="none"?Promise.resolve():this.refetchQueries({...n,type:n?.refetchType??n?.type??"active"},s)))}refetchQueries(n,s={}){const r={...s,cancelRefetch:s.cancelRefetch??!0},c=Qe.batch(()=>this.#e.findAll(n).filter(d=>!d.isDisabled()&&!d.isStatic()).map(d=>{let m=d.fetch(void 0,r);return r.throwOnError||(m=m.catch(Pe)),d.state.fetchStatus==="paused"?Promise.resolve():m}));return Promise.all(c).then(Pe)}fetchQuery(n){const s=this.defaultQueryOptions(n);s.retry===void 0&&(s.retry=!1);const r=this.#e.build(this,s);return r.isStaleByTime(Ua(s.staleTime,r))?r.fetch(s):Promise.resolve(r.state.data)}prefetchQuery(n){return this.fetchQuery(n).then(Pe).catch(Pe)}fetchInfiniteQuery(n){return n.behavior=Ih(n.pages),this.fetchQuery(n)}prefetchInfiniteQuery(n){return this.fetchInfiniteQuery(n).then(Pe).catch(Pe)}ensureInfiniteQueryData(n){return n.behavior=Ih(n.pages),this.ensureQueryData(n)}resumePausedMutations(){return ju.isOnline()?this.#t.resumePausedMutations():Promise.resolve()}getQueryCache(){return this.#e}getMutationCache(){return this.#t}getDefaultOptions(){return this.#a}setDefaultOptions(n){this.#a=n}setQueryDefaults(n,s){this.#n.set(ul(n),{queryKey:n,defaultOptions:s})}getQueryDefaults(n){const s=[...this.#n.values()],r={};return s.forEach(c=>{Fn(n,c.queryKey)&&Object.assign(r,c.defaultOptions)}),r}setMutationDefaults(n,s){this.#l.set(ul(n),{mutationKey:n,defaultOptions:s})}getMutationDefaults(n){const s=[...this.#l.values()],r={};return s.forEach(c=>{Fn(n,c.mutationKey)&&Object.assign(r,c.defaultOptions)}),r}defaultQueryOptions(n){if(n._defaulted)return n;const s={...this.#a.queries,...this.getQueryDefaults(n.queryKey),...n,_defaulted:!0};return s.queryHash||(s.queryHash=bc(s.queryKey,s)),s.refetchOnReconnect===void 0&&(s.refetchOnReconnect=s.networkMode!=="always"),s.throwOnError===void 0&&(s.throwOnError=!!s.suspense),!s.networkMode&&s.persister&&(s.networkMode="offlineFirst"),s.queryFn===xc&&(s.enabled=!1),s}defaultMutationOptions(n){return n?._defaulted?n:{...this.#a.mutations,...n?.mutationKey&&this.getMutationDefaults(n.mutationKey),...n,_defaulted:!0}}clear(){this.#e.clear(),this.#t.clear()}},D0=N.createContext(void 0),qa=n=>{const s=N.useContext(D0);if(!s)throw new Error("No QueryClient set, use QueryClientProvider to set one");return s},yp=({client:n,children:s})=>(N.useEffect(()=>(n.mount(),()=>{n.unmount()}),[n]),f.jsx(D0.Provider,{value:n,children:s})),M0=N.createContext(!1),gp=()=>N.useContext(M0);M0.Provider;function vp(){let n=!1;return{clearReset:()=>{n=!1},reset:()=>{n=!0},isReset:()=>n}}var pp=N.createContext(vp()),bp=()=>N.useContext(pp),xp=(n,s,r)=>{const c=r?.state.error&&typeof n.throwOnError=="function"?Sc(n.throwOnError,[r.state.error,r]):n.throwOnError;(n.suspense||n.experimental_prefetchInRender||c)&&(s.isReset()||(n.retryOnMount=!1))},Sp=n=>{N.useEffect(()=>{n.clearReset()},[n])},Ep=({result:n,errorResetBoundary:s,throwOnError:r,query:c,suspense:d})=>n.isError&&!s.isReset()&&!n.isFetching&&c&&(d&&n.data===void 0||Sc(r,[n.error,c])),jp=n=>{if(n.suspense){const r=d=>d==="static"?d:Math.max(d??1e3,1e3),c=n.staleTime;n.staleTime=typeof c=="function"?(...d)=>r(c(...d)):r(c),typeof n.gcTime=="number"&&(n.gcTime=Math.max(n.gcTime,1e3))}},Tp=(n,s)=>n.isLoading&&n.isFetching&&!s,Np=(n,s)=>n?.suspense&&s.isPending,e0=(n,s,r)=>s.fetchOptimistic(n).catch(()=>{r.clearReset()});function Cp(n,s,r){const c=gp(),d=bp(),m=qa(),y=m.defaultQueryOptions(n);m.getDefaultOptions().queries?._experimental_beforeQuery?.(y);const g=m.getQueryCache().get(y.queryHash);y._optimisticResults=c?"isRestoring":"optimistic",jp(y),xp(y,d,g),Sp(d);const p=!m.getQueryCache().get(y.queryHash),[v]=N.useState(()=>new s(m,y)),E=v.getOptimisticResult(y),T=!c&&n.subscribed!==!1;if(N.useSyncExternalStore(N.useCallback(R=>{const U=T?v.subscribe(Qe.batchCalls(R)):Pe;return v.updateResult(),U},[v,T]),()=>v.getCurrentResult(),()=>v.getCurrentResult()),N.useEffect(()=>{v.setOptions(y)},[y,v]),Np(y,E))throw e0(y,v,d);if(Ep({result:E,errorResetBoundary:d,throwOnError:y.throwOnError,query:g,suspense:y.suspense}))throw E.error;return m.getDefaultOptions().queries?._experimental_afterQuery?.(y,E),y.experimental_prefetchInRender&&!il&&Tp(E,c)&&(p?e0(y,v,d):g?.promise)?.catch(Pe).finally(()=>{v.updateResult()}),y.notifyOnChangeProps?E:v.trackResult(E)}function sl(n,s){return Cp(n,up)}function pt(n,s){const r=qa(),[c]=N.useState(()=>new dp(r,n));N.useEffect(()=>{c.setOptions(n)},[c,n]);const d=N.useSyncExternalStore(N.useCallback(y=>c.subscribe(Qe.batchCalls(y)),[c]),()=>c.getCurrentResult(),()=>c.getCurrentResult()),m=N.useCallback((y,g)=>{c.mutate(y,g).catch(Pe)},[c]);if(d.error&&Sc(c.options.throwOnError,[d.error]))throw d.error;return{...d,mutate:m,mutateAsync:d.mutate}}const t0=n=>{let s;const r=new Set,c=(v,E)=>{const T=typeof v=="function"?v(s):v;if(!Object.is(T,s)){const R=s;s=E??(typeof T!="object"||T===null)?T:Object.assign({},s,T),r.forEach(U=>U(s,R))}},d=()=>s,g={setState:c,getState:d,getInitialState:()=>p,subscribe:v=>(r.add(v),()=>r.delete(v))},p=s=n(c,d,g);return g},Op=(n=>n?t0(n):t0),Ap=n=>n;function Rp(n,s=Ap){const r=gu.useSyncExternalStore(n.subscribe,gu.useCallback(()=>s(n.getState()),[n,s]),gu.useCallback(()=>s(n.getInitialState()),[n,s]));return gu.useDebugValue(r),r}const _p=n=>{const s=Op(n),r=c=>Rp(s,c);return Object.assign(r,s),r},Dp=(n=>_p);function Mp(n,s){let r;try{r=n()}catch{return}return{getItem:d=>{var m;const y=p=>p===null?null:JSON.parse(p,void 0),g=(m=r.getItem(d))!=null?m:null;return g instanceof Promise?g.then(y):y(g)},setItem:(d,m)=>r.setItem(d,JSON.stringify(m,void 0)),removeItem:d=>r.removeItem(d)}}const cc=n=>s=>{try{const r=n(s);return r instanceof Promise?r:{then(c){return cc(c)(r)},catch(c){return this}}}catch(r){return{then(c){return this},catch(c){return cc(c)(r)}}}},zp=(n,s)=>(r,c,d)=>{let m={storage:Mp(()=>window.localStorage),partialize:q=>q,version:0,merge:(q,Q)=>({...Q,...q}),...s},y=!1,g=0;const p=new Set,v=new Set;let E=m.storage;if(!E)return n((...q)=>{console.warn(`[zustand persist middleware] Unable to update item '${m.name}', the given storage is currently unavailable.`),r(...q)},c,d);const T=()=>{const q=m.partialize({...c()});return E.setItem(m.name,{state:q,version:m.version})},R=d.setState;d.setState=(q,Q)=>(R(q,Q),T());const U=n((...q)=>(r(...q),T()),c,d);d.getInitialState=()=>U;let k;const B=()=>{var q,Q;if(!E)return;const L=++g;y=!1,p.forEach(X=>{var P;return X((P=c())!=null?P:U)});const K=((Q=m.onRehydrateStorage)==null?void 0:Q.call(m,(q=c())!=null?q:U))||void 0;return cc(E.getItem.bind(E))(m.name).then(X=>{if(X)if(typeof X.version=="number"&&X.version!==m.version){if(m.migrate){const P=m.migrate(X.state,X.version);return P instanceof Promise?P.then(me=>[!0,me]):[!0,P]}console.error("State loaded from storage couldn't be migrated since no migrate function was provided")}else return[!1,X.state];return[!1,void 0]}).then(X=>{var P;if(L!==g)return;const[me,$]=X;if(k=m.merge($,(P=c())!=null?P:U),r(k,!0),me)return T()}).then(()=>{L===g&&(K?.(k,void 0),k=c(),y=!0,v.forEach(X=>X(k)))}).catch(X=>{L===g&&K?.(void 0,X)})};return d.persist={setOptions:q=>{m={...m,...q},q.storage&&(E=q.storage)},clearStorage:()=>{E?.removeItem(m.name)},getOptions:()=>m,rehydrate:()=>B(),hasHydrated:()=>y,onHydrate:q=>(p.add(q),()=>{p.delete(q)}),onFinishHydration:q=>(v.add(q),()=>{v.delete(q)})},m.skipHydration||B(),k||U},wp=zp,Up="/admin";class kp{token=null;setToken(s){this.token=s}getToken(){return this.token}async request(s,r={}){const c={"Content-Type":"application/json",...r.headers};this.token&&(c.Authorization=`Bearer ${this.token}`);const d=await fetch(`${Up}${s}`,{...r,headers:c});if(!d.ok){const m=await d.json().catch(()=>({error:"unknown_error",message:d.statusText}));throw new Error(m.message||m.error)}return d.json()}async getStats(){return this.request("/stats")}async getApps(){return this.request("/apps")}async createApp(s){return this.request("/apps",{method:"POST",body:JSON.stringify(s)})}async deleteApp(s){return this.request(`/apps/${s}`,{method:"DELETE"})}async rotateAppSecret(s){return this.request(`/apps/${s}/rotate`,{method:"POST"})}async getOIDCClients(){return this.request("/oidc/clients")}async createOIDCClient(s){return this.request("/oidc/clients",{method:"POST",body:JSON.stringify(s)})}async updateOIDCClient(s,r){return this.request(`/oidc/clients/${s}`,{method:"PUT",body:JSON.stringify(r)})}async deleteOIDCClient(s){return this.request(`/oidc/clients/${s}`,{method:"DELETE"})}async rotateOIDCClientSecret(s){return this.request(`/oidc/clients/${s}/rotate-secret`,{method:"POST"})}async getSessions(s){const r=new URLSearchParams;s?.app_id&&r.set("app_id",s.app_id),s?.did&&r.set("did",s.did),s?.limit&&r.set("limit",s.limit.toString());const c=r.toString();return this.request(`/sessions${c?`?${c}`:""}`)}async revokeSession(s){return this.request(`/sessions/${s}`,{method:"DELETE"})}async revokeAllSessions(s,r){return this.request("/sessions/revoke-all",{method:"POST",body:JSON.stringify({did:s,app_id:r})})}async getKeys(){return this.request("/keys")}async rotateKeys(){return this.request("/keys/rotate",{method:"POST"})}async deleteKey(s){return this.request(`/keys/${s}`,{method:"DELETE"})}async getUsers(s){const r=new URLSearchParams;s?.limit&&r.set("limit",s.limit.toString()),s?.offset&&r.set("offset",s.offset.toString());const c=r.toString();return this.request(`/users${c?`?${c}`:""}`)}async getUser(s){return this.request(`/users/${encodeURIComponent(s)}`)}async revokeUserMFA(s){return this.request(`/users/${encodeURIComponent(s)}/mfa`,{method:"DELETE"})}async deleteUserPasskey(s,r){return this.request(`/users/${encodeURIComponent(s)}/passkeys/${encodeURIComponent(r)}`,{method:"DELETE"})}}const Te=new kp,Tc=Dp()(wp(n=>({token:null,isAuthenticated:!1,setToken:s=>{Te.setToken(s),n({token:s,isAuthenticated:!0})},logout:()=>{Te.setToken(null),n({token:null,isAuthenticated:!1})}}),{name:"atauth-admin-auth",onRehydrateStorage:()=>n=>{n?.token&&Te.setToken(n.token)}})),qp=[{to:"/dashboard",label:"Dashboard",icon:Lp},{to:"/apps",label:"Legacy Apps",icon:Bp},{to:"/oidc-clients",label:"OIDC Clients",icon:Qp},{to:"/sessions",label:"Sessions",icon:Yp},{to:"/keys",label:"Signing Keys",icon:Gp},{to:"/users",label:"Users",icon:Kp}];function Hp(){return f.jsxs("aside",{className:"w-64 bg-gray-900 text-white flex flex-col",children:[f.jsxs("div",{className:"p-4 border-b border-gray-700",children:[f.jsx("h1",{className:"text-xl font-bold",children:"ATAuth Admin"}),f.jsx("p",{className:"text-sm text-gray-400",children:"Identity Management"})]}),f.jsx("nav",{className:"flex-1 py-4",children:qp.map(n=>f.jsxs(S0,{to:n.to,className:({isActive:s})=>`flex items-center gap-3 px-4 py-3 text-sm transition-colors ${s?"bg-blue-600 text-white":"text-gray-300 hover:bg-gray-800 hover:text-white"}`,children:[f.jsx(n.icon,{className:"w-5 h-5"}),n.label]},n.to))}),f.jsx("div",{className:"p-4 border-t border-gray-700 text-xs text-gray-500",children:"ATAuth Gateway v1.3.0"})]})}function Lp({className:n}){return f.jsx("svg",{className:n,fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:f.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"})})}function Bp({className:n}){return f.jsx("svg",{className:n,fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:f.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"})})}function Qp({className:n}){return f.jsx("svg",{className:n,fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:f.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"})})}function Yp({className:n}){return f.jsx("svg",{className:n,fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:f.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"})})}function Gp({className:n}){return f.jsx("svg",{className:n,fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:f.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"})})}function Kp({className:n}){return f.jsx("svg",{className:n,fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:f.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"})})}function Xp(){return f.jsxs("div",{className:"flex h-screen overflow-hidden",children:[f.jsx(Hp,{}),f.jsx("main",{className:"flex-1 overflow-auto bg-gray-100 dark:bg-gray-900",children:f.jsx(ov,{})})]})}function $l({title:n}){const s=Tc(r=>r.logout);return f.jsxs("header",{className:"h-16 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between px-6",children:[f.jsx("h2",{className:"text-xl font-semibold text-gray-900 dark:text-white",children:n}),f.jsx("button",{onClick:s,className:"text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors",children:"Sign Out"})]})}function Zp(){const[n,s]=N.useState(""),[r,c]=N.useState(""),[d,m]=N.useState(!1),y=mc(),g=Tc(v=>v.setToken),p=async v=>{v.preventDefault(),c(""),m(!0);try{Te.setToken(n),await Te.getStats(),g(n),y("/dashboard")}catch(E){c(E instanceof Error?E.message:"Invalid admin token"),Te.setToken(null)}finally{m(!1)}};return f.jsx("div",{className:"min-h-screen flex items-center justify-center bg-gray-100 dark:bg-gray-900 p-4",children:f.jsx("div",{className:"w-full max-w-md",children:f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8",children:[f.jsxs("div",{className:"text-center mb-8",children:[f.jsx("h1",{className:"text-2xl font-bold text-gray-900 dark:text-white",children:"ATAuth Admin"}),f.jsx("p",{className:"text-gray-600 dark:text-gray-400 mt-2",children:"Enter your admin token to continue"})]}),f.jsxs("form",{onSubmit:p,className:"space-y-6",children:[f.jsxs("div",{children:[f.jsx("label",{htmlFor:"token",className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"Admin Token"}),f.jsx("input",{type:"password",id:"token",value:n,onChange:v=>s(v.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",placeholder:"Enter your ADMIN_TOKEN",required:!0})]}),r&&f.jsx("div",{className:"p-3 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-md",children:f.jsx("p",{className:"text-sm text-red-600 dark:text-red-400",children:r})}),f.jsx("button",{type:"submit",disabled:d||!n,className:"w-full py-2 px-4 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",children:d?"Verifying...":"Sign In"})]}),f.jsx("p",{className:"mt-6 text-center text-xs text-gray-500 dark:text-gray-400",children:"The admin token is set via the ADMIN_TOKEN environment variable on the gateway server."})]})})})}function Vp(){const{data:n,isLoading:s,error:r}=sl({queryKey:["stats"],queryFn:()=>Te.getStats(),refetchInterval:3e4});return f.jsxs("div",{children:[f.jsx($l,{title:"Dashboard"}),f.jsxs("div",{className:"p-6",children:[s?f.jsx("div",{className:"text-gray-500 dark:text-gray-400",children:"Loading statistics..."}):r?f.jsx("div",{className:"p-4 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md",children:"Failed to load statistics"}):n?f.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6",children:[f.jsx(ll,{title:"Total Apps",value:n.apps_count,subtitle:"Legacy + OIDC"}),f.jsx(ll,{title:"OIDC Clients",value:n.oidc_clients_count,subtitle:"OAuth 2.0 / OIDC"}),f.jsx(ll,{title:"Active Sessions",value:n.active_sessions_count,subtitle:"Currently logged in"}),f.jsx(ll,{title:"Unique Users",value:n.users_count,subtitle:"Registered DIDs"}),f.jsx(ll,{title:"Passkeys",value:n.passkeys_count,subtitle:"WebAuthn credentials"}),f.jsx(ll,{title:"MFA Enabled",value:n.mfa_enabled_count,subtitle:"TOTP configured"}),f.jsx(ll,{title:"Verified Emails",value:n.verified_emails_count,subtitle:"Confirmed addresses"})]}):null,f.jsxs("div",{className:"mt-8",children:[f.jsx("h3",{className:"text-lg font-semibold text-gray-900 dark:text-white mb-4",children:"Quick Actions"}),f.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-4",children:[f.jsx(Pr,{title:"Register New App",description:"Add a legacy HMAC-signed app",href:"/apps"}),f.jsx(Pr,{title:"Create OIDC Client",description:"Register an OpenID Connect client",href:"/oidc-clients"}),f.jsx(Pr,{title:"Rotate Signing Keys",description:"Generate new JWT signing keys",href:"/keys"})]})]})]})]})}function ll({title:n,value:s,subtitle:r}){return f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow p-6",children:[f.jsx("h4",{className:"text-sm font-medium text-gray-500 dark:text-gray-400",children:n}),f.jsx("p",{className:"mt-2 text-3xl font-bold text-gray-900 dark:text-white",children:s.toLocaleString()}),f.jsx("p",{className:"mt-1 text-sm text-gray-500 dark:text-gray-400",children:r})]})}function Pr({title:n,description:s,href:r}){return f.jsxs("a",{href:r,className:"block p-4 bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-md transition-shadow",children:[f.jsx("h4",{className:"font-medium text-gray-900 dark:text-white",children:n}),f.jsx("p",{className:"mt-1 text-sm text-gray-500 dark:text-gray-400",children:s})]})}function Jp(){const n=qa(),[s,r]=N.useState(!1),[c,d]=N.useState(null),{data:m,isLoading:y,error:g}=sl({queryKey:["apps"],queryFn:()=>Te.getApps()}),p=pt({mutationFn:R=>Te.deleteApp(R),onSuccess:()=>{n.invalidateQueries({queryKey:["apps"]}),n.invalidateQueries({queryKey:["stats"]})}}),v=pt({mutationFn:R=>Te.rotateAppSecret(R),onSuccess:(R,U)=>{d({appId:U,secret:R.hmac_secret})}}),E=(R,U)=>{confirm(`Are you sure you want to delete "${U}"? This cannot be undone.`)&&p.mutate(R)},T=(R,U)=>{confirm(`Rotate secret for "${U}"? The old secret will stop working immediately.`)&&v.mutate(R)};return f.jsxs("div",{children:[f.jsx($l,{title:"Legacy Apps"}),f.jsxs("div",{className:"p-6",children:[f.jsxs("div",{className:"flex justify-between items-center mb-6",children:[f.jsx("p",{className:"text-gray-600 dark:text-gray-400",children:"Manage legacy apps using HMAC-signed tokens"}),f.jsx("button",{onClick:()=>r(!0),className:"px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors",children:"Create App"})]}),s&&f.jsx(Fp,{onClose:()=>r(!1),onCreated:R=>{r(!1),d(R),n.invalidateQueries({queryKey:["apps"]}),n.invalidateQueries({queryKey:["stats"]})}}),c&&f.jsx($p,{appId:c.appId,secret:c.secret,onClose:()=>d(null)}),y?f.jsx("div",{className:"text-gray-500",children:"Loading apps..."}):g?f.jsx("div",{className:"p-4 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md",children:"Failed to load apps"}):m?.apps.length===0?f.jsx("div",{className:"text-center py-12 bg-white dark:bg-gray-800 rounded-lg",children:f.jsx("p",{className:"text-gray-500 dark:text-gray-400",children:"No apps registered yet"})}):f.jsx("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden",children:f.jsxs("table",{className:"min-w-full divide-y divide-gray-200 dark:divide-gray-700",children:[f.jsx("thead",{className:"bg-gray-50 dark:bg-gray-900",children:f.jsxs("tr",{children:[f.jsx("th",{className:"px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider",children:"Name"}),f.jsx("th",{className:"px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider",children:"App ID"}),f.jsx("th",{className:"px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider",children:"Token TTL"}),f.jsx("th",{className:"px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider",children:"Created"}),f.jsx("th",{className:"px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider",children:"Actions"})]})}),f.jsx("tbody",{className:"divide-y divide-gray-200 dark:divide-gray-700",children:m?.apps.map(R=>f.jsxs("tr",{children:[f.jsxs("td",{className:"px-6 py-4 whitespace-nowrap",children:[f.jsx("div",{className:"text-sm font-medium text-gray-900 dark:text-white",children:R.name}),f.jsx("div",{className:"text-sm text-gray-500 dark:text-gray-400",children:R.callback_url})]}),f.jsxs("td",{className:"px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 font-mono",children:[R.id.substring(0,8),"..."]}),f.jsx("td",{className:"px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400",children:Wp(R.token_ttl_seconds)}),f.jsx("td",{className:"px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400",children:new Date(R.created_at).toLocaleDateString()}),f.jsxs("td",{className:"px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2",children:[f.jsx("button",{onClick:()=>T(R.id,R.name),className:"text-yellow-600 hover:text-yellow-900 dark:hover:text-yellow-400",disabled:v.isPending,children:"Rotate Secret"}),f.jsx("button",{onClick:()=>E(R.id,R.name),className:"text-red-600 hover:text-red-900 dark:hover:text-red-400",disabled:p.isPending,children:"Delete"})]})]},R.id))})]})})]})]})}function Fp({onClose:n,onCreated:s}){const[r,c]=N.useState(""),[d,m]=N.useState(""),[y,g]=N.useState("3600"),p=pt({mutationFn:()=>Te.createApp({name:r,callback_url:d,token_ttl_seconds:parseInt(y,10)}),onSuccess:E=>{s({appId:E.id,secret:E.hmac_secret})}}),v=E=>{E.preventDefault(),p.mutate()};return f.jsx("div",{className:"fixed inset-0 bg-black/50 flex items-center justify-center z-50",children:f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md p-6",children:[f.jsx("h3",{className:"text-lg font-semibold text-gray-900 dark:text-white mb-4",children:"Create Legacy App"}),f.jsxs("form",{onSubmit:v,className:"space-y-4",children:[f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"App Name"}),f.jsx("input",{type:"text",value:r,onChange:E=>c(E.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",required:!0})]}),f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"Callback URL"}),f.jsx("input",{type:"url",value:d,onChange:E=>m(E.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",placeholder:"https://myapp.com/auth/callback",required:!0})]}),f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"Token TTL (seconds)"}),f.jsx("input",{type:"number",value:y,onChange:E=>g(E.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",min:"60",required:!0})]}),p.error&&f.jsx("div",{className:"p-3 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md text-sm",children:p.error instanceof Error?p.error.message:"Failed to create app"}),f.jsxs("div",{className:"flex justify-end gap-3 pt-4",children:[f.jsx("button",{type:"button",onClick:n,className:"px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md",children:"Cancel"}),f.jsx("button",{type:"submit",disabled:p.isPending,className:"px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-md",children:p.isPending?"Creating...":"Create"})]})]})]})})}function $p({appId:n,secret:s,onClose:r}){const[c,d]=N.useState(!1),m=async()=>{await navigator.clipboard.writeText(s),d(!0),setTimeout(()=>d(!1),2e3)};return f.jsx("div",{className:"fixed inset-0 bg-black/50 flex items-center justify-center z-50",children:f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md p-6",children:[f.jsx("h3",{className:"text-lg font-semibold text-gray-900 dark:text-white mb-2",children:"App Secret"}),f.jsx("p",{className:"text-sm text-yellow-600 dark:text-yellow-400 mb-4",children:"Save this secret now! It won't be shown again."}),f.jsxs("div",{className:"mb-4",children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1",children:"App ID"}),f.jsx("code",{className:"block p-2 bg-gray-100 dark:bg-gray-900 rounded text-sm break-all",children:n})]}),f.jsxs("div",{className:"mb-6",children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1",children:"HMAC Secret"}),f.jsxs("div",{className:"relative",children:[f.jsx("code",{className:"block p-2 bg-gray-100 dark:bg-gray-900 rounded text-sm break-all pr-20",children:s}),f.jsx("button",{onClick:m,className:"absolute right-2 top-1/2 -translate-y-1/2 px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600",children:c?"Copied!":"Copy"})]})]}),f.jsx("button",{onClick:r,className:"w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md",children:"Done"})]})})}function Wp(n){return n<60?`${n}s`:n<3600?`${Math.floor(n/60)}m`:n<86400?`${Math.floor(n/3600)}h`:`${Math.floor(n/86400)}d`}function Ip(){const n=qa(),[s,r]=N.useState(!1),[c,d]=N.useState(null),[m,y]=N.useState(null),{data:g,isLoading:p,error:v}=sl({queryKey:["oidc-clients"],queryFn:()=>Te.getOIDCClients()}),E=pt({mutationFn:k=>Te.deleteOIDCClient(k),onSuccess:()=>{n.invalidateQueries({queryKey:["oidc-clients"]}),n.invalidateQueries({queryKey:["stats"]})}}),T=pt({mutationFn:k=>Te.rotateOIDCClientSecret(k),onSuccess:(k,B)=>{y({clientId:B,secret:k.client_secret})}}),R=(k,B)=>{confirm(`Delete OIDC client "${B}"? All associated tokens will be invalidated.`)&&E.mutate(k)},U=(k,B)=>{confirm(`Rotate secret for "${B}"? The old secret will stop working.`)&&T.mutate(k)};return f.jsxs("div",{children:[f.jsx($l,{title:"OIDC Clients"}),f.jsxs("div",{className:"p-6",children:[f.jsxs("div",{className:"flex justify-between items-center mb-6",children:[f.jsx("p",{className:"text-gray-600 dark:text-gray-400",children:"Manage OpenID Connect clients for OAuth 2.0 authentication"}),f.jsx("button",{onClick:()=>r(!0),className:"px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors",children:"Create OIDC Client"})]}),s&&f.jsx(Pp,{onClose:()=>r(!1),onCreated:k=>{r(!1),y(k),n.invalidateQueries({queryKey:["oidc-clients"]}),n.invalidateQueries({queryKey:["stats"]})}}),c&&f.jsx(eb,{clientId:c,client:g?.clients.find(k=>k.id===c),onClose:()=>d(null),onSaved:()=>{d(null),n.invalidateQueries({queryKey:["oidc-clients"]})}}),m&&f.jsx(tb,{clientId:m.clientId,secret:m.secret,onClose:()=>y(null)}),p?f.jsx("div",{className:"text-gray-500",children:"Loading OIDC clients..."}):v?f.jsx("div",{className:"p-4 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md",children:"Failed to load OIDC clients"}):g?.clients.length===0?f.jsxs("div",{className:"text-center py-12 bg-white dark:bg-gray-800 rounded-lg",children:[f.jsx("p",{className:"text-gray-500 dark:text-gray-400",children:"No OIDC clients registered"}),f.jsx("p",{className:"text-sm text-gray-400 mt-2",children:"Create an OIDC client to enable OAuth 2.0 / OpenID Connect authentication"})]}):f.jsx("div",{className:"space-y-4",children:g?.clients.map(k=>f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow p-6",children:[f.jsxs("div",{className:"flex justify-between items-start",children:[f.jsxs("div",{children:[f.jsx("h3",{className:"text-lg font-medium text-gray-900 dark:text-white",children:k.name}),f.jsxs("p",{className:"text-sm text-gray-500 dark:text-gray-400 font-mono mt-1",children:["Client ID: ",k.id]})]}),f.jsxs("div",{className:"flex gap-2",children:[f.jsx("button",{onClick:()=>d(k.id),className:"px-3 py-1 text-sm text-blue-600 hover:text-blue-800 dark:hover:text-blue-400",children:"Edit"}),f.jsx("button",{onClick:()=>U(k.id,k.name),className:"px-3 py-1 text-sm text-yellow-600 hover:text-yellow-800 dark:hover:text-yellow-400",children:"Rotate Secret"}),f.jsx("button",{onClick:()=>R(k.id,k.name),className:"px-3 py-1 text-sm text-red-600 hover:text-red-800 dark:hover:text-red-400",children:"Delete"})]})]}),f.jsxs("div",{className:"mt-4 grid grid-cols-2 gap-4 text-sm",children:[f.jsxs("div",{children:[f.jsx("span",{className:"text-gray-500 dark:text-gray-400",children:"Redirect URIs:"}),f.jsx("ul",{className:"mt-1 space-y-1",children:k.redirect_uris.map((B,q)=>f.jsx("li",{className:"text-gray-900 dark:text-white font-mono text-xs",children:B},q))})]}),f.jsxs("div",{children:[f.jsx("span",{className:"text-gray-500 dark:text-gray-400",children:"Scopes:"}),f.jsx("div",{className:"mt-1 flex flex-wrap gap-1",children:k.allowed_scopes.map(B=>f.jsx("span",{className:"px-2 py-0.5 bg-gray-100 dark:bg-gray-700 rounded text-xs",children:B},B))})]})]}),f.jsxs("div",{className:"mt-4 flex gap-6 text-xs text-gray-500 dark:text-gray-400",children:[f.jsxs("span",{children:["PKCE: ",k.require_pkce?"Required":"Optional"]}),f.jsxs("span",{children:["Access Token: ",ec(k.access_token_ttl_seconds)]}),f.jsxs("span",{children:["ID Token: ",ec(k.id_token_ttl_seconds)]}),f.jsxs("span",{children:["Refresh Token: ",ec(k.refresh_token_ttl_seconds)]})]})]},k.id))})]})]})}function Pp({onClose:n,onCreated:s}){const[r,c]=N.useState(""),[d,m]=N.useState(""),[y,g]=N.useState("openid profile email"),[p,v]=N.useState(!0),[E,T]=N.useState("3600"),[R,U]=N.useState("3600"),[k,B]=N.useState("604800"),q=pt({mutationFn:()=>Te.createOIDCClient({name:r,redirect_uris:d.split(` 12 + `).map(L=>L.trim()).filter(Boolean),allowed_scopes:y.split(" ").filter(Boolean),require_pkce:p,access_token_ttl_seconds:parseInt(E,10),id_token_ttl_seconds:parseInt(R,10),refresh_token_ttl_seconds:parseInt(k,10)}),onSuccess:L=>{s({clientId:L.id,secret:L.client_secret})}}),Q=L=>{L.preventDefault(),q.mutate()};return f.jsx("div",{className:"fixed inset-0 bg-black/50 flex items-center justify-center z-50 overflow-auto py-8",children:f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-lg p-6 m-4",children:[f.jsx("h3",{className:"text-lg font-semibold text-gray-900 dark:text-white mb-4",children:"Create OIDC Client"}),f.jsxs("form",{onSubmit:Q,className:"space-y-4",children:[f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"Client Name"}),f.jsx("input",{type:"text",value:r,onChange:L=>c(L.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",required:!0})]}),f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"Redirect URIs (one per line)"}),f.jsx("textarea",{value:d,onChange:L=>m(L.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm",rows:3,placeholder:`https://myapp.com/callback 13 + https://myapp.com/auth/callback`,required:!0})]}),f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"Allowed Scopes (space-separated)"}),f.jsx("input",{type:"text",value:y,onChange:L=>g(L.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm"})]}),f.jsxs("div",{className:"flex items-center gap-2",children:[f.jsx("input",{type:"checkbox",id:"requirePkce",checked:p,onChange:L=>v(L.target.checked),className:"h-4 w-4 text-blue-600 rounded"}),f.jsx("label",{htmlFor:"requirePkce",className:"text-sm text-gray-700 dark:text-gray-300",children:"Require PKCE (recommended)"})]}),f.jsxs("div",{className:"grid grid-cols-3 gap-4",children:[f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"Access Token TTL"}),f.jsx("input",{type:"number",value:E,onChange:L=>T(L.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",min:"60"})]}),f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"ID Token TTL"}),f.jsx("input",{type:"number",value:R,onChange:L=>U(L.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",min:"60"})]}),f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"Refresh Token TTL"}),f.jsx("input",{type:"number",value:k,onChange:L=>B(L.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",min:"60"})]})]}),q.error&&f.jsx("div",{className:"p-3 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md text-sm",children:q.error instanceof Error?q.error.message:"Failed to create client"}),f.jsxs("div",{className:"flex justify-end gap-3 pt-4",children:[f.jsx("button",{type:"button",onClick:n,className:"px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md",children:"Cancel"}),f.jsx("button",{type:"submit",disabled:q.isPending,className:"px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-md",children:q.isPending?"Creating...":"Create"})]})]})]})})}function eb({clientId:n,client:s,onClose:r,onSaved:c}){const[d,m]=N.useState(s?.name||""),[y,g]=N.useState(s?.redirect_uris.join(` 14 + `)||""),[p,v]=N.useState(s?.allowed_scopes.join(" ")||""),[E,T]=N.useState(s?.require_pkce??!0),[R,U]=N.useState(String(s?.access_token_ttl_seconds||3600)),[k,B]=N.useState(String(s?.id_token_ttl_seconds||3600)),[q,Q]=N.useState(String(s?.refresh_token_ttl_seconds||604800)),L=pt({mutationFn:()=>Te.updateOIDCClient(n,{name:d,redirect_uris:y.split(` 15 + `).map(X=>X.trim()).filter(Boolean),allowed_scopes:p.split(" ").filter(Boolean),require_pkce:E,access_token_ttl_seconds:parseInt(R,10),id_token_ttl_seconds:parseInt(k,10),refresh_token_ttl_seconds:parseInt(q,10)}),onSuccess:c}),K=X=>{X.preventDefault(),L.mutate()};return s?f.jsx("div",{className:"fixed inset-0 bg-black/50 flex items-center justify-center z-50 overflow-auto py-8",children:f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-lg p-6 m-4",children:[f.jsx("h3",{className:"text-lg font-semibold text-gray-900 dark:text-white mb-4",children:"Edit OIDC Client"}),f.jsxs("form",{onSubmit:K,className:"space-y-4",children:[f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"Client Name"}),f.jsx("input",{type:"text",value:d,onChange:X=>m(X.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",required:!0})]}),f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"Redirect URIs (one per line)"}),f.jsx("textarea",{value:y,onChange:X=>g(X.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm",rows:3,required:!0})]}),f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"Allowed Scopes (space-separated)"}),f.jsx("input",{type:"text",value:p,onChange:X=>v(X.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm"})]}),f.jsxs("div",{className:"flex items-center gap-2",children:[f.jsx("input",{type:"checkbox",id:"editRequirePkce",checked:E,onChange:X=>T(X.target.checked),className:"h-4 w-4 text-blue-600 rounded"}),f.jsx("label",{htmlFor:"editRequirePkce",className:"text-sm text-gray-700 dark:text-gray-300",children:"Require PKCE"})]}),f.jsxs("div",{className:"grid grid-cols-3 gap-4",children:[f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"Access Token TTL"}),f.jsx("input",{type:"number",value:R,onChange:X=>U(X.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",min:"60"})]}),f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"ID Token TTL"}),f.jsx("input",{type:"number",value:k,onChange:X=>B(X.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",min:"60"})]}),f.jsxs("div",{children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:"Refresh Token TTL"}),f.jsx("input",{type:"number",value:q,onChange:X=>Q(X.target.value),className:"mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",min:"60"})]})]}),L.error&&f.jsx("div",{className:"p-3 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md text-sm",children:L.error instanceof Error?L.error.message:"Failed to update client"}),f.jsxs("div",{className:"flex justify-end gap-3 pt-4",children:[f.jsx("button",{type:"button",onClick:r,className:"px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md",children:"Cancel"}),f.jsx("button",{type:"submit",disabled:L.isPending,className:"px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-md",children:L.isPending?"Saving...":"Save Changes"})]})]})]})}):null}function tb({clientId:n,secret:s,onClose:r}){const[c,d]=N.useState(!1),m=async()=>{await navigator.clipboard.writeText(s),d(!0),setTimeout(()=>d(!1),2e3)};return f.jsx("div",{className:"fixed inset-0 bg-black/50 flex items-center justify-center z-50",children:f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md p-6",children:[f.jsx("h3",{className:"text-lg font-semibold text-gray-900 dark:text-white mb-2",children:"Client Secret"}),f.jsx("p",{className:"text-sm text-yellow-600 dark:text-yellow-400 mb-4",children:"Save this secret now! It won't be shown again."}),f.jsxs("div",{className:"mb-4",children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1",children:"Client ID"}),f.jsx("code",{className:"block p-2 bg-gray-100 dark:bg-gray-900 rounded text-sm break-all",children:n})]}),f.jsxs("div",{className:"mb-6",children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1",children:"Client Secret"}),f.jsxs("div",{className:"relative",children:[f.jsx("code",{className:"block p-2 bg-gray-100 dark:bg-gray-900 rounded text-sm break-all pr-20",children:s}),f.jsx("button",{onClick:m,className:"absolute right-2 top-1/2 -translate-y-1/2 px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600",children:c?"Copied!":"Copy"})]})]}),f.jsx("button",{onClick:r,className:"w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md",children:"Done"})]})})}function ec(n){return n<60?`${n}s`:n<3600?`${Math.floor(n/60)}m`:n<86400?`${Math.floor(n/3600)}h`:`${Math.floor(n/86400)}d`}function ab(){const n=qa(),[s,r]=N.useState(""),[c,d]=N.useState(""),{data:m,isLoading:y,error:g,refetch:p}=sl({queryKey:["sessions",s,c],queryFn:()=>Te.getSessions({did:s||void 0,app_id:c||void 0,limit:100}),refetchInterval:3e4}),v=pt({mutationFn:U=>Te.revokeSession(U),onSuccess:()=>{n.invalidateQueries({queryKey:["sessions"]}),n.invalidateQueries({queryKey:["stats"]})}}),E=pt({mutationFn:({did:U,appId:k})=>Te.revokeAllSessions(U,k),onSuccess:()=>{n.invalidateQueries({queryKey:["sessions"]}),n.invalidateQueries({queryKey:["stats"]})}}),T=U=>{confirm("Revoke this session?")&&v.mutate(U)},R=()=>{if(!s){alert("Enter a DID to revoke all sessions for that user");return}confirm(`Revoke all sessions for ${s}?`)&&E.mutate({did:s,appId:c||void 0})};return f.jsxs("div",{children:[f.jsx($l,{title:"Sessions"}),f.jsxs("div",{className:"p-6",children:[f.jsxs("div",{className:"mb-6",children:[f.jsx("p",{className:"text-gray-600 dark:text-gray-400 mb-4",children:"View and manage active user sessions"}),f.jsxs("div",{className:"flex gap-4 items-end",children:[f.jsxs("div",{className:"flex-1",children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1",children:"Filter by DID"}),f.jsx("input",{type:"text",value:s,onChange:U=>r(U.target.value),className:"block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",placeholder:"did:plc:..."})]}),f.jsxs("div",{className:"flex-1",children:[f.jsx("label",{className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1",children:"Filter by App ID"}),f.jsx("input",{type:"text",value:c,onChange:U=>d(U.target.value),className:"block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",placeholder:"app-id"})]}),f.jsx("button",{onClick:()=>p(),className:"px-4 py-2 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-md",children:"Refresh"}),s&&f.jsx("button",{onClick:R,disabled:E.isPending,className:"px-4 py-2 bg-red-600 hover:bg-red-700 disabled:bg-red-400 text-white rounded-md",children:"Revoke All"})]})]}),y?f.jsx("div",{className:"text-gray-500",children:"Loading sessions..."}):g?f.jsx("div",{className:"p-4 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md",children:"Failed to load sessions"}):m?.sessions.length===0?f.jsx("div",{className:"text-center py-12 bg-white dark:bg-gray-800 rounded-lg",children:f.jsx("p",{className:"text-gray-500 dark:text-gray-400",children:"No active sessions found"})}):f.jsx("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden",children:f.jsxs("table",{className:"min-w-full divide-y divide-gray-200 dark:divide-gray-700",children:[f.jsx("thead",{className:"bg-gray-50 dark:bg-gray-900",children:f.jsxs("tr",{children:[f.jsx("th",{className:"px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider",children:"User"}),f.jsx("th",{className:"px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider",children:"App"}),f.jsx("th",{className:"px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider",children:"Status"}),f.jsx("th",{className:"px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider",children:"Created"}),f.jsx("th",{className:"px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider",children:"Expires"}),f.jsx("th",{className:"px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider",children:"Actions"})]})}),f.jsx("tbody",{className:"divide-y divide-gray-200 dark:divide-gray-700",children:m?.sessions.map(U=>f.jsxs("tr",{children:[f.jsxs("td",{className:"px-6 py-4 whitespace-nowrap",children:[f.jsxs("div",{className:"text-sm font-medium text-gray-900 dark:text-white",children:["@",U.handle]}),f.jsxs("div",{className:"text-xs text-gray-500 dark:text-gray-400 font-mono",children:[U.did.substring(0,20),"..."]})]}),f.jsxs("td",{className:"px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 font-mono",children:[U.app_id.substring(0,8),"..."]}),f.jsx("td",{className:"px-6 py-4 whitespace-nowrap",children:f.jsx(lb,{status:U.connection_state})}),f.jsx("td",{className:"px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400",children:a0(U.created_at)}),f.jsx("td",{className:"px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400",children:a0(U.expires_at)}),f.jsx("td",{className:"px-6 py-4 whitespace-nowrap text-right text-sm font-medium",children:f.jsx("button",{onClick:()=>T(U.id),className:"text-red-600 hover:text-red-900 dark:hover:text-red-400",disabled:v.isPending,children:"Revoke"})})]},U.id))})]})})]})]})}function lb({status:n}){const s={active:"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400",pending:"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400",connected:"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400",disconnected:"bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400"};return f.jsx("span",{className:`inline-flex px-2 py-0.5 text-xs font-medium rounded-full ${s[n]||s.pending}`,children:n})}function a0(n){const s=new Date(n);return s.toLocaleDateString()+" "+s.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function nb(){const n=qa(),{data:s,isLoading:r,error:c}=sl({queryKey:["keys"],queryFn:()=>Te.getKeys()}),d=pt({mutationFn:()=>Te.rotateKeys(),onSuccess:()=>{n.invalidateQueries({queryKey:["keys"]})}}),m=pt({mutationFn:p=>Te.deleteKey(p),onSuccess:()=>{n.invalidateQueries({queryKey:["keys"]})}}),y=()=>{confirm("Generate a new signing key? The current key will remain valid but not be used for new tokens.")&&d.mutate()},g=(p,v)=>{if(v){alert("Cannot delete the active signing key. Rotate first to create a new key.");return}confirm(`Delete key ${p}? Any tokens signed with this key will become invalid.`)&&m.mutate(p)};return f.jsxs("div",{children:[f.jsx($l,{title:"Signing Keys"}),f.jsxs("div",{className:"p-6",children:[f.jsxs("div",{className:"flex justify-between items-center mb-6",children:[f.jsxs("div",{children:[f.jsx("p",{className:"text-gray-600 dark:text-gray-400",children:"Manage JWT signing keys for OIDC tokens"}),f.jsx("p",{className:"text-sm text-gray-500 dark:text-gray-500 mt-1",children:"Keys are used to sign ID tokens, access tokens, and other JWTs"})]}),f.jsx("button",{onClick:y,disabled:d.isPending,className:"px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-md transition-colors",children:d.isPending?"Generating...":"Rotate Keys"})]}),d.isSuccess&&f.jsxs("div",{className:"mb-6 p-4 bg-green-50 dark:bg-green-900/30 text-green-700 dark:text-green-400 rounded-md",children:["New signing key generated: ",f.jsx("code",{className:"font-mono",children:d.data.kid})]}),r?f.jsx("div",{className:"text-gray-500",children:"Loading keys..."}):c?f.jsx("div",{className:"p-4 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md",children:"Failed to load keys"}):s?.keys.length===0?f.jsxs("div",{className:"text-center py-12 bg-white dark:bg-gray-800 rounded-lg",children:[f.jsx("p",{className:"text-gray-500 dark:text-gray-400",children:"No signing keys configured"}),f.jsx("p",{className:"text-sm text-gray-400 mt-2",children:'Click "Rotate Keys" to generate an initial signing key'})]}):f.jsx("div",{className:"space-y-4",children:s?.keys.map(p=>f.jsx("div",{className:`bg-white dark:bg-gray-800 rounded-lg shadow p-6 ${p.use_for_signing?"ring-2 ring-blue-500":""}`,children:f.jsxs("div",{className:"flex justify-between items-start",children:[f.jsxs("div",{children:[f.jsxs("div",{className:"flex items-center gap-3",children:[f.jsx("h3",{className:"text-lg font-mono text-gray-900 dark:text-white",children:p.kid}),p.use_for_signing&&f.jsx("span",{className:"px-2 py-0.5 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400 text-xs font-medium rounded",children:"Active Signing Key"}),p.is_active&&!p.use_for_signing&&f.jsx("span",{className:"px-2 py-0.5 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 text-xs font-medium rounded",children:"Valid for Verification"}),!p.is_active&&f.jsx("span",{className:"px-2 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 text-xs font-medium rounded",children:"Inactive"})]}),f.jsxs("p",{className:"text-sm text-gray-500 dark:text-gray-400 mt-1",children:["Algorithm: ",f.jsx("span",{className:"font-mono",children:p.algorithm})]}),f.jsxs("p",{className:"text-sm text-gray-500 dark:text-gray-400",children:["Created: ",new Date(p.created_at).toLocaleString()]})]}),f.jsx("button",{onClick:()=>g(p.kid,p.use_for_signing),disabled:m.isPending||p.use_for_signing,className:`px-3 py-1 text-sm rounded ${p.use_for_signing?"text-gray-400 cursor-not-allowed":"text-red-600 hover:text-red-800 dark:hover:text-red-400"}`,children:"Delete"})]})},p.kid))}),f.jsxs("div",{className:"mt-8 p-4 bg-gray-50 dark:bg-gray-800/50 rounded-lg",children:[f.jsx("h4",{className:"text-sm font-medium text-gray-900 dark:text-white mb-2",children:"Key Rotation Best Practices"}),f.jsxs("ul",{className:"text-sm text-gray-600 dark:text-gray-400 space-y-1",children:[f.jsx("li",{children:"- Rotate keys periodically (e.g., every 90 days)"}),f.jsx("li",{children:"- Keep old keys active for verification until all existing tokens expire"}),f.jsx("li",{children:"- Only delete old keys after their tokens have expired"}),f.jsx("li",{children:"- The JWKS endpoint automatically publishes all active keys"})]})]})]})]})}function ib(){const n=qa(),[s,r]=N.useState(null),{data:c,isLoading:d,error:m}=sl({queryKey:["users"],queryFn:()=>Te.getUsers({limit:100})}),{data:y,isLoading:g}=sl({queryKey:["user",s],queryFn:()=>s?Te.getUser(s):null,enabled:!!s});return f.jsxs("div",{children:[f.jsx($l,{title:"Users"}),f.jsxs("div",{className:"p-6",children:[f.jsx("p",{className:"text-gray-600 dark:text-gray-400 mb-6",children:"View and manage user accounts, passkeys, MFA, and sessions"}),d?f.jsx("div",{className:"text-gray-500",children:"Loading users..."}):m?f.jsx("div",{className:"p-4 bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 rounded-md",children:"Failed to load users"}):c?.users.length===0?f.jsx("div",{className:"text-center py-12 bg-white dark:bg-gray-800 rounded-lg",children:f.jsx("p",{className:"text-gray-500 dark:text-gray-400",children:"No users found"})}):f.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-6",children:[f.jsx("div",{className:"lg:col-span-1",children:f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden",children:[f.jsx("div",{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700",children:f.jsxs("h3",{className:"text-sm font-medium text-gray-900 dark:text-white",children:["Users (",c?.users.length,")"]})}),f.jsx("div",{className:"divide-y divide-gray-200 dark:divide-gray-700 max-h-[600px] overflow-auto",children:c?.users.map(p=>f.jsxs("button",{onClick:()=>r(p.did),className:`w-full px-4 py-3 text-left hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors ${s===p.did?"bg-blue-50 dark:bg-blue-900/30":""}`,children:[f.jsxs("div",{className:"font-medium text-gray-900 dark:text-white",children:["@",p.handle]}),f.jsxs("div",{className:"text-xs text-gray-500 dark:text-gray-400 font-mono mt-0.5",children:[p.did.substring(0,30),"..."]}),f.jsxs("div",{className:"flex gap-3 mt-2 text-xs text-gray-500 dark:text-gray-400",children:[p.passkeys_count>0&&f.jsxs("span",{title:"Passkeys",children:[p.passkeys_count," passkeys"]}),p.mfa_enabled&&f.jsx("span",{className:"text-green-600 dark:text-green-400",children:"MFA"}),p.sessions_count>0&&f.jsxs("span",{children:[p.sessions_count," sessions"]})]})]},p.did))})]})}),f.jsx("div",{className:"lg:col-span-2",children:s?g?f.jsx("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow p-6",children:f.jsx("div",{className:"text-gray-500",children:"Loading user details..."})}):y?.user?f.jsx(ub,{user:y.user,onRefresh:()=>n.invalidateQueries({queryKey:["user",s]})}):null:f.jsx("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow p-6 text-center",children:f.jsx("p",{className:"text-gray-500 dark:text-gray-400",children:"Select a user to view details"})})})]})]})]})}function ub({user:n,onRefresh:s}){const r=qa(),c=pt({mutationFn:()=>Te.revokeUserMFA(n.did),onSuccess:()=>{s(),r.invalidateQueries({queryKey:["stats"]})}}),d=pt({mutationFn:g=>Te.deleteUserPasskey(n.did,g),onSuccess:()=>{s(),r.invalidateQueries({queryKey:["stats"]})}}),m=()=>{confirm("Disable MFA for this user? They will need to set it up again.")&&c.mutate()},y=(g,p)=>{confirm(`Delete passkey "${p||g.substring(0,8)}"?`)&&d.mutate(g)};return f.jsxs("div",{className:"space-y-6",children:[f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow p-6",children:[f.jsx("h3",{className:"text-lg font-medium text-gray-900 dark:text-white mb-4",children:"User Information"}),f.jsxs("dl",{className:"grid grid-cols-2 gap-4",children:[f.jsxs("div",{children:[f.jsx("dt",{className:"text-sm text-gray-500 dark:text-gray-400",children:"Handle"}),f.jsxs("dd",{className:"text-gray-900 dark:text-white",children:["@",n.handle]})]}),f.jsxs("div",{children:[f.jsx("dt",{className:"text-sm text-gray-500 dark:text-gray-400",children:"DID"}),f.jsx("dd",{className:"text-gray-900 dark:text-white font-mono text-sm break-all",children:n.did})]})]})]}),f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow p-6",children:[f.jsx("div",{className:"flex justify-between items-center mb-4",children:f.jsxs("h3",{className:"text-lg font-medium text-gray-900 dark:text-white",children:["Passkeys (",n.passkeys.length,")"]})}),n.passkeys.length===0?f.jsx("p",{className:"text-gray-500 dark:text-gray-400",children:"No passkeys registered"}):f.jsx("div",{className:"space-y-3",children:n.passkeys.map(g=>f.jsxs("div",{className:"flex justify-between items-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg",children:[f.jsxs("div",{children:[f.jsx("div",{className:"font-medium text-gray-900 dark:text-white",children:g.name||`Passkey ${g.id.substring(0,8)}...`}),f.jsxs("div",{className:"text-sm text-gray-500 dark:text-gray-400",children:[g.device_type," • ",g.backed_up?"Backed up":"Not backed up",g.last_used_at&&f.jsxs(f.Fragment,{children:[" • Last used: ",new Date(g.last_used_at).toLocaleDateString()]})]})]}),f.jsx("button",{onClick:()=>y(g.id,g.name),className:"text-red-600 hover:text-red-800 dark:hover:text-red-400 text-sm",disabled:d.isPending,children:"Delete"})]},g.id))})]}),f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow p-6",children:[f.jsxs("div",{className:"flex justify-between items-center mb-4",children:[f.jsx("h3",{className:"text-lg font-medium text-gray-900 dark:text-white",children:"Two-Factor Authentication"}),n.mfa_enabled&&f.jsx("button",{onClick:m,className:"text-red-600 hover:text-red-800 dark:hover:text-red-400 text-sm",disabled:c.isPending,children:"Disable MFA"})]}),f.jsx("p",{className:"text-gray-500 dark:text-gray-400",children:n.mfa_enabled?f.jsxs("span",{className:"flex items-center gap-2",children:[f.jsx("span",{className:"w-2 h-2 bg-green-500 rounded-full"}),"TOTP Enabled"]}):"Not configured"})]}),f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow p-6",children:[f.jsxs("h3",{className:"text-lg font-medium text-gray-900 dark:text-white mb-4",children:["Email Addresses (",n.emails.length,")"]}),n.emails.length===0?f.jsx("p",{className:"text-gray-500 dark:text-gray-400",children:"No emails registered"}):f.jsx("div",{className:"space-y-2",children:n.emails.map(g=>f.jsxs("div",{className:"flex items-center gap-3 p-2 bg-gray-50 dark:bg-gray-700 rounded",children:[f.jsx("span",{className:"text-gray-900 dark:text-white",children:g.email}),g.is_primary&&f.jsx("span",{className:"px-2 py-0.5 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400 text-xs rounded",children:"Primary"}),g.verified?f.jsx("span",{className:"px-2 py-0.5 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 text-xs rounded",children:"Verified"}):f.jsx("span",{className:"px-2 py-0.5 bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-400 text-xs rounded",children:"Pending"})]},g.email))})]}),f.jsxs("div",{className:"bg-white dark:bg-gray-800 rounded-lg shadow p-6",children:[f.jsxs("h3",{className:"text-lg font-medium text-gray-900 dark:text-white mb-4",children:["Active Sessions (",n.sessions.length,")"]}),n.sessions.length===0?f.jsx("p",{className:"text-gray-500 dark:text-gray-400",children:"No active sessions"}):f.jsx("div",{className:"space-y-2",children:n.sessions.map(g=>f.jsx("div",{className:"flex justify-between items-center p-2 bg-gray-50 dark:bg-gray-700 rounded",children:f.jsxs("div",{className:"text-sm",children:[f.jsxs("span",{className:"text-gray-900 dark:text-white font-mono",children:[g.app_id.substring(0,12),"..."]}),f.jsxs("span",{className:"text-gray-500 dark:text-gray-400 ml-2",children:["Expires: ",new Date(g.expires_at).toLocaleDateString()]})]})},g.id))})]})]})}const sb=new mp({defaultOptions:{queries:{staleTime:3e4,retry:1}}});function rb({children:n}){return Tc(r=>r.isAuthenticated)?f.jsx(f.Fragment,{children:n}):f.jsx(g0,{to:"/login",replace:!0})}function cb(){return f.jsx(yp,{client:sb,children:f.jsx(kv,{basename:"/admin",children:f.jsxs(dv,{children:[f.jsx(Yt,{path:"/login",element:f.jsx(Zp,{})}),f.jsxs(Yt,{element:f.jsx(rb,{children:f.jsx(Xp,{})}),children:[f.jsx(Yt,{path:"/dashboard",element:f.jsx(Vp,{})}),f.jsx(Yt,{path:"/apps",element:f.jsx(Jp,{})}),f.jsx(Yt,{path:"/oidc-clients",element:f.jsx(Ip,{})}),f.jsx(Yt,{path:"/sessions",element:f.jsx(ab,{})}),f.jsx(Yt,{path:"/keys",element:f.jsx(nb,{})}),f.jsx(Yt,{path:"/users",element:f.jsx(ib,{})}),f.jsx(Yt,{path:"/",element:f.jsx(g0,{to:"/dashboard",replace:!0})})]})]})})})}dg.createRoot(document.getElementById("root")).render(f.jsx(N.StrictMode,{children:f.jsx(cb,{})}));
+14
gateway/public/admin/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <link rel="icon" type="image/svg+xml" href="/admin/vite.svg" /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 + <title>admin-ui</title> 8 + <script type="module" crossorigin src="/admin/assets/index-D_DjgfgI.js"></script> 9 + <link rel="stylesheet" crossorigin href="/admin/assets/index-BTKH7v1n.css"> 10 + </head> 11 + <body> 12 + <div id="root"></div> 13 + </body> 14 + </html>
+1
gateway/public/admin/vite.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
+169 -5
gateway/src/index.ts
··· 3 3 * 4 4 * AT Protocol OAuth gateway for application authentication. 5 5 * Issues HMAC-signed tokens for backend servers to verify user identity. 6 + * Now with OpenID Connect (OIDC) provider support. 6 7 */ 7 8 8 9 import express from 'express'; ··· 13 14 14 15 import { DatabaseService } from './services/database.js'; 15 16 import { OAuthService } from './services/oauth.js'; 17 + import { OIDCService } from './services/oidc/index.js'; 18 + import { PasskeyService } from './services/passkey.js'; 19 + import { MFAService } from './services/mfa.js'; 20 + import { EmailService } from './services/email.js'; 16 21 import { createAuthRoutes } from './routes/auth.js'; 17 22 import { createTokenRoutes } from './routes/token.js'; 18 23 import { createAdminRoutes } from './routes/admin.js'; 19 24 import { createSessionRoutes } from './routes/session.js'; 25 + import { createOIDCRouter } from './routes/oidc/index.js'; 26 + import { createPasskeyRouter } from './routes/passkey.js'; 27 + import { createMFARouter } from './routes/mfa.js'; 28 + import { createEmailRouter } from './routes/email.js'; 20 29 import { authRateLimit, apiRateLimit, adminRateLimit } from './middleware/rateLimit.js'; 21 30 import { HttpError } from './utils/errors.js'; 22 31 ··· 25 34 port: parseInt(process.env.PORT || '3100', 10), 26 35 host: process.env.HOST || '0.0.0.0', 27 36 28 - // OAuth client configuration 37 + // OAuth client configuration (for AT Protocol) 29 38 clientId: process.env.OAUTH_CLIENT_ID || 'https://auth.example.com/client-metadata.json', 30 39 redirectUri: process.env.OAUTH_REDIRECT_URI || 'https://auth.example.com/auth/callback', 31 40 ··· 37 46 38 47 // CORS origins 39 48 corsOrigins: process.env.CORS_ORIGINS?.split(',') || ['http://localhost:3000'], 49 + 50 + // OIDC configuration 51 + oidc: { 52 + enabled: process.env.OIDC_ENABLED === 'true', 53 + issuer: process.env.OIDC_ISSUER || 'https://auth.example.com', 54 + keySecret: process.env.OIDC_KEY_SECRET || 'change-me-in-production-32-bytes!', 55 + keyAlgorithm: (process.env.OIDC_KEY_ALGORITHM || 'ES256') as 'ES256' | 'RS256', 56 + }, 57 + 58 + // Passkey/WebAuthn configuration 59 + passkey: { 60 + enabled: process.env.PASSKEY_ENABLED !== 'false', // Enabled by default 61 + rpName: process.env.WEBAUTHN_RP_NAME || 'ATAuth', 62 + rpID: process.env.WEBAUTHN_RP_ID || 'localhost', 63 + origin: process.env.WEBAUTHN_ORIGIN || 'http://localhost:3100', 64 + }, 65 + 66 + // MFA/TOTP configuration 67 + mfa: { 68 + enabled: process.env.MFA_ENABLED !== 'false', // Enabled by default 69 + issuer: process.env.MFA_TOTP_ISSUER || 'ATAuth', 70 + // 32-byte hex encryption key for TOTP secrets (64 hex characters) 71 + encryptionKey: process.env.MFA_ENCRYPTION_KEY || '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 72 + backupCodesCount: parseInt(process.env.MFA_BACKUP_CODES_COUNT || '10', 10), 73 + }, 74 + 75 + // Email configuration 76 + email: { 77 + enabled: process.env.EMAIL_ENABLED === 'true', // Disabled by default 78 + provider: (process.env.EMAIL_PROVIDER || 'mock') as 'smtp' | 'resend' | 'sendgrid' | 'mailgun' | 'mock', 79 + from: process.env.EMAIL_FROM || 'ATAuth <noreply@example.com>', 80 + smtp: { 81 + host: process.env.SMTP_HOST || 'localhost', 82 + port: parseInt(process.env.SMTP_PORT || '587', 10), 83 + secure: process.env.SMTP_SECURE === 'true', 84 + user: process.env.SMTP_USER, 85 + pass: process.env.SMTP_PASS, 86 + }, 87 + apiKey: process.env.EMAIL_API_KEY, 88 + codeExpiry: parseInt(process.env.EMAIL_CODE_EXPIRY || '900', 10), // 15 minutes 89 + }, 40 90 }; 41 91 42 92 async function main(): Promise<void> { ··· 59 109 console.error('Failed to initialize OAuth client:', error); 60 110 } 61 111 112 + // Initialize OIDC service if enabled 113 + let oidcService: OIDCService | null = null; 114 + if (config.oidc.enabled) { 115 + oidcService = new OIDCService(db, { 116 + issuer: config.oidc.issuer, 117 + keySecret: config.oidc.keySecret, 118 + keyAlgorithm: config.oidc.keyAlgorithm, 119 + }); 120 + try { 121 + await oidcService.initialize(config.oidc.keyAlgorithm); 122 + console.log('OIDC service initialized'); 123 + } catch (error) { 124 + console.error('Failed to initialize OIDC service:', error); 125 + } 126 + } 127 + 128 + // Initialize Passkey service 129 + let passkeyService: PasskeyService | null = null; 130 + if (config.passkey.enabled) { 131 + passkeyService = new PasskeyService(db, { 132 + rpName: config.passkey.rpName, 133 + rpID: config.passkey.rpID, 134 + origin: config.passkey.origin, 135 + }); 136 + console.log('Passkey service initialized'); 137 + } 138 + 139 + // Initialize MFA service 140 + let mfaService: MFAService | null = null; 141 + if (config.mfa.enabled) { 142 + mfaService = new MFAService(db, { 143 + issuer: config.mfa.issuer, 144 + encryptionKey: config.mfa.encryptionKey, 145 + backupCodesCount: config.mfa.backupCodesCount, 146 + }); 147 + console.log('MFA service initialized'); 148 + } 149 + 150 + // Initialize Email service 151 + let emailService: EmailService | null = null; 152 + if (config.email.enabled) { 153 + emailService = new EmailService(db, { 154 + provider: config.email.provider, 155 + smtp: config.email.smtp, 156 + apiKey: config.email.apiKey, 157 + from: config.email.from, 158 + codeExpiry: config.email.codeExpiry, 159 + }); 160 + console.log('Email service initialized'); 161 + } 162 + 62 163 // Create Express app 63 164 const app = express(); 64 165 ··· 70 171 origin: config.corsOrigins, 71 172 credentials: true, 72 173 methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], 73 - allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'], 174 + allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'X-Session-Id'], 74 175 })); 75 176 app.use(express.json()); 76 177 ··· 92 193 // Routes with rate limiting 93 194 app.use('/auth', authRateLimit, createAuthRoutes(db, oauth)); 94 195 app.use('/token', apiRateLimit, createTokenRoutes(db)); 95 - app.use('/admin', adminRateLimit, createAdminRoutes(db, config.adminToken)); 196 + app.use('/admin', adminRateLimit, createAdminRoutes(db, config.adminToken, oidcService, passkeyService, mfaService)); 96 197 app.use('/session', apiRateLimit, createSessionRoutes(db)); 97 198 199 + // OIDC routes (if enabled) 200 + if (oidcService) { 201 + const { wellKnownRouter, oauthRouter } = createOIDCRouter(db, oidcService, oauth); 202 + app.use('/.well-known', wellKnownRouter); 203 + app.use('/oauth', authRateLimit, oauthRouter); 204 + console.log('OIDC routes enabled'); 205 + } 206 + 207 + // Passkey routes (if enabled) 208 + if (passkeyService) { 209 + app.use('/auth/passkey', authRateLimit, createPasskeyRouter(db, passkeyService, oidcService)); 210 + console.log('Passkey routes enabled'); 211 + } 212 + 213 + // MFA routes (if enabled) 214 + if (mfaService) { 215 + app.use('/auth/mfa', authRateLimit, createMFARouter(db, mfaService, passkeyService, oidcService)); 216 + console.log('MFA routes enabled'); 217 + } 218 + 219 + // Email routes (if enabled) 220 + if (emailService) { 221 + app.use('/auth/email', authRateLimit, createEmailRouter(db, emailService, oidcService)); 222 + console.log('Email routes enabled'); 223 + } 224 + 225 + // Admin UI static files (if available) 226 + const adminUiPath = path.join(process.cwd(), 'public', 'admin'); 227 + if (fs.existsSync(adminUiPath)) { 228 + app.use('/admin', express.static(adminUiPath)); 229 + // SPA fallback for admin routes 230 + app.get('/admin/*', (_req, res) => { 231 + res.sendFile(path.join(adminUiPath, 'index.html')); 232 + }); 233 + console.log('Admin UI enabled at /admin'); 234 + } 235 + 98 236 // OAuth client metadata (for AT Protocol discovery) 99 237 app.get('/client-metadata.json', (_req, res) => { 100 238 res.json({ ··· 143 281 setInterval(() => { 144 282 const statesDeleted = db.cleanupOldOAuthStates(); 145 283 const sessionsDeleted = db.cleanupExpiredSessions(); 146 - if (statesDeleted > 0 || sessionsDeleted > 0) { 147 - console.log(`Cleanup: ${statesDeleted} OAuth states, ${sessionsDeleted} sessions`); 284 + const authCodesDeleted = db.cleanupExpiredAuthorizationCodes(); 285 + const refreshTokensDeleted = db.cleanupExpiredRefreshTokens(); 286 + const emailCodesDeleted = db.cleanupExpiredEmailVerificationCodes(); 287 + 288 + const total = statesDeleted + sessionsDeleted + authCodesDeleted + refreshTokensDeleted + emailCodesDeleted; 289 + if (total > 0) { 290 + console.log(`Cleanup: ${statesDeleted} OAuth states, ${sessionsDeleted} sessions, ${authCodesDeleted} auth codes, ${refreshTokensDeleted} refresh tokens, ${emailCodesDeleted} email codes`); 148 291 } 149 292 }, 60 * 60 * 1000); 150 293 ··· 157 300 console.log('Admin endpoints enabled'); 158 301 } else { 159 302 console.log('Admin endpoints disabled (set ADMIN_TOKEN to enable)'); 303 + } 304 + if (config.oidc.enabled) { 305 + console.log(`OIDC enabled - Issuer: ${config.oidc.issuer}`); 306 + console.log(`OIDC Discovery: ${config.oidc.issuer}/.well-known/openid-configuration`); 307 + } else { 308 + console.log('OIDC disabled (set OIDC_ENABLED=true to enable)'); 309 + } 310 + if (config.passkey.enabled) { 311 + console.log(`Passkey enabled - RP ID: ${config.passkey.rpID}`); 312 + } else { 313 + console.log('Passkey disabled (set PASSKEY_ENABLED=true to enable)'); 314 + } 315 + if (config.mfa.enabled) { 316 + console.log(`MFA enabled - Issuer: ${config.mfa.issuer}`); 317 + } else { 318 + console.log('MFA disabled (set MFA_ENABLED=true to enable)'); 319 + } 320 + if (config.email.enabled) { 321 + console.log(`Email enabled - Provider: ${config.email.provider}`); 322 + } else { 323 + console.log('Email disabled (set EMAIL_ENABLED=true to enable)'); 160 324 } 161 325 }); 162 326 }
+395 -1
gateway/src/routes/admin.ts
··· 2 2 * Admin Routes 3 3 * 4 4 * Application registration and management endpoints. 5 + * Now includes OIDC client, session, and key management. 5 6 * Express 5 automatically forwards async errors to the error handler. 6 7 */ 7 8 ··· 10 11 import { DatabaseService } from '../services/database.js'; 11 12 import { generateHmacSecret } from '../utils/hmac.js'; 12 13 import { httpError } from '../utils/errors.js'; 14 + import type { OIDCService } from '../services/oidc/index.js'; 15 + import type { PasskeyService } from '../services/passkey.js'; 16 + import type { MFAService } from '../services/mfa.js'; 13 17 14 18 /** 15 19 * Constant-time string comparison to prevent timing attacks. ··· 25 29 return crypto.timingSafeEqual(hashA, hashB); 26 30 } 27 31 28 - export function createAdminRoutes(db: DatabaseService, adminToken?: string): Router { 32 + export function createAdminRoutes( 33 + db: DatabaseService, 34 + adminToken?: string, 35 + oidcService?: OIDCService | null, 36 + passkeyService?: PasskeyService | null, 37 + mfaService?: MFAService | null 38 + ): Router { 29 39 const router = Router(); 30 40 31 41 const requireAdmin = (req: Request, _res: Response, next: NextFunction) => { ··· 150 160 router.post('/cleanup', requireAdmin, async (_req: Request, res: Response) => { 151 161 const statesDeleted = db.cleanupOldOAuthStates(); 152 162 const sessionsDeleted = db.cleanupExpiredSessions(); 163 + const authCodesDeleted = db.cleanupExpiredAuthorizationCodes(); 164 + const refreshTokensDeleted = db.cleanupExpiredRefreshTokens(); 165 + const emailCodesDeleted = db.cleanupExpiredEmailVerificationCodes(); 153 166 154 167 res.json({ 155 168 oauth_states_deleted: statesDeleted, 156 169 sessions_deleted: sessionsDeleted, 170 + authorization_codes_deleted: authCodesDeleted, 171 + refresh_tokens_deleted: refreshTokensDeleted, 172 + email_codes_deleted: emailCodesDeleted, 157 173 }); 174 + }); 175 + 176 + // ===== OIDC Client Management ===== 177 + 178 + /** 179 + * GET /admin/oidc/clients 180 + * List all OIDC clients 181 + */ 182 + router.get('/oidc/clients', requireAdmin, async (_req: Request, res: Response) => { 183 + const clients = db.getAllOIDCClients(); 184 + res.json({ 185 + clients: clients.map(c => ({ 186 + id: c.id, 187 + name: c.name, 188 + client_type: c.client_type, 189 + redirect_uris: c.redirect_uris, 190 + grant_types: c.grant_types, 191 + allowed_scopes: c.allowed_scopes, 192 + require_pkce: c.require_pkce, 193 + token_endpoint_auth_method: c.token_endpoint_auth_method, 194 + created_at: c.created_at.toISOString(), 195 + })), 196 + }); 197 + }); 198 + 199 + /** 200 + * POST /admin/oidc/clients 201 + * Create a new OIDC client 202 + */ 203 + router.post('/oidc/clients', requireAdmin, async (req: Request, res: Response) => { 204 + const { 205 + id, 206 + name, 207 + redirect_uris, 208 + grant_types, 209 + allowed_scopes, 210 + require_pkce, 211 + token_endpoint_auth_method, 212 + id_token_ttl_seconds, 213 + access_token_ttl_seconds, 214 + refresh_token_ttl_seconds, 215 + } = req.body; 216 + 217 + if (!id || !name) { 218 + throw httpError.badRequest('missing_params', 'id and name are required'); 219 + } 220 + 221 + const existing = db.getOIDCClient(id); 222 + if (existing) { 223 + throw httpError.conflict('client_exists', `OIDC client '${id}' already exists`); 224 + } 225 + 226 + // Generate client secret 227 + const clientSecret = crypto.randomBytes(32).toString('hex'); 228 + const clientSecretHash = crypto.createHash('sha256').update(clientSecret).digest('hex'); 229 + 230 + // Create app entry with OIDC configuration 231 + db.upsertApp({ 232 + id, 233 + name, 234 + hmac_secret: generateHmacSecret(), 235 + token_ttl_seconds: access_token_ttl_seconds || 3600, 236 + callback_url: redirect_uris?.[0], 237 + }); 238 + 239 + // Update with OIDC-specific fields 240 + db.updateOIDCClient(id, { 241 + client_type: 'oidc', 242 + client_secret: clientSecretHash, 243 + redirect_uris: redirect_uris || [], 244 + grant_types: grant_types || ['authorization_code'], 245 + allowed_scopes: allowed_scopes || ['openid'], 246 + require_pkce: require_pkce ?? true, 247 + token_endpoint_auth_method: token_endpoint_auth_method || 'client_secret_basic', 248 + id_token_ttl_seconds: id_token_ttl_seconds || 3600, 249 + access_token_ttl_seconds: access_token_ttl_seconds || 3600, 250 + refresh_token_ttl_seconds: refresh_token_ttl_seconds || 604800, 251 + }); 252 + 253 + res.status(201).json({ 254 + id, 255 + name, 256 + client_secret: clientSecret, // Only shown once! 257 + redirect_uris: redirect_uris || [], 258 + grant_types: grant_types || ['authorization_code'], 259 + allowed_scopes: allowed_scopes || ['openid'], 260 + require_pkce: require_pkce ?? true, 261 + message: 'OIDC client created. Store the client_secret securely!', 262 + }); 263 + }); 264 + 265 + /** 266 + * GET /admin/oidc/clients/:id 267 + * Get OIDC client details 268 + */ 269 + router.get('/oidc/clients/:id', requireAdmin, async (req: Request, res: Response) => { 270 + const client = db.getOIDCClient(req.params.id); 271 + if (!client) { 272 + throw httpError.notFound('client_not_found', `OIDC client '${req.params.id}' not found`); 273 + } 274 + 275 + res.json({ 276 + id: client.id, 277 + name: client.name, 278 + client_type: client.client_type, 279 + redirect_uris: client.redirect_uris, 280 + grant_types: client.grant_types, 281 + allowed_scopes: client.allowed_scopes, 282 + require_pkce: client.require_pkce, 283 + token_endpoint_auth_method: client.token_endpoint_auth_method, 284 + id_token_ttl_seconds: client.id_token_ttl_seconds, 285 + access_token_ttl_seconds: client.access_token_ttl_seconds, 286 + refresh_token_ttl_seconds: client.refresh_token_ttl_seconds, 287 + created_at: client.created_at.toISOString(), 288 + }); 289 + }); 290 + 291 + /** 292 + * PUT /admin/oidc/clients/:id 293 + * Update OIDC client 294 + */ 295 + router.put('/oidc/clients/:id', requireAdmin, async (req: Request, res: Response) => { 296 + const existing = db.getOIDCClient(req.params.id); 297 + if (!existing) { 298 + throw httpError.notFound('client_not_found', `OIDC client '${req.params.id}' not found`); 299 + } 300 + 301 + const { 302 + name, 303 + redirect_uris, 304 + grant_types, 305 + allowed_scopes, 306 + require_pkce, 307 + token_endpoint_auth_method, 308 + id_token_ttl_seconds, 309 + access_token_ttl_seconds, 310 + refresh_token_ttl_seconds, 311 + } = req.body; 312 + 313 + db.updateOIDCClient(req.params.id, { 314 + redirect_uris: redirect_uris ?? existing.redirect_uris, 315 + grant_types: grant_types ?? existing.grant_types, 316 + allowed_scopes: allowed_scopes ?? existing.allowed_scopes, 317 + require_pkce: require_pkce ?? existing.require_pkce, 318 + token_endpoint_auth_method: token_endpoint_auth_method ?? existing.token_endpoint_auth_method, 319 + id_token_ttl_seconds: id_token_ttl_seconds ?? existing.id_token_ttl_seconds, 320 + access_token_ttl_seconds: access_token_ttl_seconds ?? existing.access_token_ttl_seconds, 321 + refresh_token_ttl_seconds: refresh_token_ttl_seconds ?? existing.refresh_token_ttl_seconds, 322 + }); 323 + 324 + // Update app name if provided 325 + if (name) { 326 + const app = db.getApp(req.params.id); 327 + if (app) { 328 + db.upsertApp({ ...app, name }); 329 + } 330 + } 331 + 332 + res.json({ 333 + id: req.params.id, 334 + message: 'OIDC client updated', 335 + }); 336 + }); 337 + 338 + /** 339 + * DELETE /admin/oidc/clients/:id 340 + * Delete OIDC client 341 + */ 342 + router.delete('/oidc/clients/:id', requireAdmin, async (req: Request, res: Response) => { 343 + const existing = db.getOIDCClient(req.params.id); 344 + if (!existing) { 345 + throw httpError.notFound('client_not_found', `OIDC client '${req.params.id}' not found`); 346 + } 347 + 348 + db.deleteApp(req.params.id); 349 + 350 + res.json({ 351 + message: 'OIDC client deleted', 352 + }); 353 + }); 354 + 355 + /** 356 + * POST /admin/oidc/clients/:id/rotate-secret 357 + * Rotate OIDC client secret 358 + */ 359 + router.post('/oidc/clients/:id/rotate-secret', requireAdmin, async (req: Request, res: Response) => { 360 + const existing = db.getOIDCClient(req.params.id); 361 + if (!existing) { 362 + throw httpError.notFound('client_not_found', `OIDC client '${req.params.id}' not found`); 363 + } 364 + 365 + const clientSecret = crypto.randomBytes(32).toString('hex'); 366 + const clientSecretHash = crypto.createHash('sha256').update(clientSecret).digest('hex'); 367 + 368 + db.updateOIDCClientSecret(req.params.id, clientSecretHash); 369 + 370 + res.json({ 371 + id: req.params.id, 372 + client_secret: clientSecret, 373 + message: 'Client secret rotated. Update your application configuration!', 374 + }); 375 + }); 376 + 377 + // ===== Session Management ===== 378 + 379 + /** 380 + * GET /admin/sessions 381 + * List active sessions with optional filters 382 + */ 383 + router.get('/sessions', requireAdmin, async (req: Request, res: Response) => { 384 + const { app_id, did, limit } = req.query; 385 + const sessions = db.getAllActiveSessions( 386 + app_id as string | undefined, 387 + did as string | undefined, 388 + limit ? parseInt(limit as string, 10) : 100 389 + ); 390 + 391 + res.json({ 392 + sessions: sessions.map(s => ({ 393 + id: s.id, 394 + did: s.did, 395 + handle: s.handle, 396 + app_id: s.app_id, 397 + created_at: s.created_at.toISOString(), 398 + expires_at: s.expires_at.toISOString(), 399 + connection_state: s.connection_state, 400 + last_activity: s.last_activity?.toISOString(), 401 + })), 402 + }); 403 + }); 404 + 405 + /** 406 + * DELETE /admin/sessions/:id 407 + * Revoke a specific session 408 + */ 409 + router.delete('/sessions/:id', requireAdmin, async (req: Request, res: Response) => { 410 + db.deleteSession(req.params.id); 411 + res.json({ message: 'Session revoked' }); 412 + }); 413 + 414 + /** 415 + * POST /admin/sessions/revoke-all 416 + * Revoke all sessions for a user/app combination 417 + */ 418 + router.post('/sessions/revoke-all', requireAdmin, async (req: Request, res: Response) => { 419 + const { did, app_id } = req.body; 420 + if (!did) { 421 + throw httpError.badRequest('missing_params', 'did is required'); 422 + } 423 + 424 + const count = db.revokeAllSessionsForUser(did, app_id); 425 + res.json({ 426 + sessions_revoked: count, 427 + }); 428 + }); 429 + 430 + // ===== Key Management ===== 431 + 432 + /** 433 + * GET /admin/keys 434 + * List signing keys (if OIDC service is available) 435 + */ 436 + router.get('/keys', requireAdmin, async (_req: Request, res: Response) => { 437 + if (!oidcService) { 438 + throw httpError.badRequest('oidc_disabled', 'OIDC is not enabled'); 439 + } 440 + 441 + const keys = db.getActiveOIDCKeys(); 442 + res.json({ 443 + keys: keys.map(k => ({ 444 + kid: k.kid, 445 + algorithm: k.algorithm, 446 + is_active: k.is_active, 447 + use_for_signing: k.use_for_signing, 448 + created_at: k.created_at.toISOString(), 449 + expires_at: k.expires_at?.toISOString(), 450 + })), 451 + }); 452 + }); 453 + 454 + /** 455 + * POST /admin/keys/rotate 456 + * Rotate signing keys 457 + */ 458 + router.post('/keys/rotate', requireAdmin, async (req: Request, res: Response) => { 459 + if (!oidcService) { 460 + throw httpError.badRequest('oidc_disabled', 'OIDC is not enabled'); 461 + } 462 + 463 + const { algorithm } = req.body; 464 + await oidcService.keyManager.rotateKeys(algorithm || 'ES256'); 465 + 466 + res.json({ 467 + message: 'Signing key rotated', 468 + }); 469 + }); 470 + 471 + // ===== User Management ===== 472 + 473 + /** 474 + * GET /admin/users/:did 475 + * Get user details including MFA status and passkeys 476 + */ 477 + router.get('/users/:did', requireAdmin, async (req: Request, res: Response) => { 478 + const { did } = req.params; 479 + 480 + const passkeys = passkeyService?.listPasskeys(did) || []; 481 + const mfaStatus = mfaService?.getMFAStatus(did, passkeys.length) || { 482 + totp_enabled: false, 483 + passkey_count: passkeys.length, 484 + backup_codes_remaining: 0, 485 + }; 486 + const emails = db.getUserEmails(did); 487 + 488 + res.json({ 489 + did, 490 + mfa: mfaStatus, 491 + passkeys: passkeys.map(p => ({ 492 + id: p.id, 493 + name: p.name, 494 + device_type: p.device_type, 495 + backed_up: p.backed_up, 496 + created_at: p.created_at, 497 + last_used_at: p.last_used_at, 498 + })), 499 + emails: emails.map(e => ({ 500 + email: e.email, 501 + verified: e.verified, 502 + is_primary: e.is_primary, 503 + created_at: e.created_at.toISOString(), 504 + })), 505 + }); 506 + }); 507 + 508 + /** 509 + * DELETE /admin/users/:did/mfa 510 + * Reset MFA for a user (admin override) 511 + */ 512 + router.delete('/users/:did/mfa', requireAdmin, async (req: Request, res: Response) => { 513 + const { did } = req.params; 514 + 515 + if (mfaService) { 516 + mfaService.disableTOTP(did); 517 + } 518 + 519 + res.json({ 520 + message: 'MFA disabled for user', 521 + }); 522 + }); 523 + 524 + /** 525 + * DELETE /admin/users/:did/passkeys/:passkeyId 526 + * Delete a passkey for a user (admin override) 527 + */ 528 + router.delete('/users/:did/passkeys/:passkeyId', requireAdmin, async (req: Request, res: Response) => { 529 + const { did, passkeyId } = req.params; 530 + 531 + if (passkeyService) { 532 + const success = passkeyService.deletePasskey(did, passkeyId); 533 + if (!success) { 534 + throw httpError.notFound('passkey_not_found', 'Passkey not found'); 535 + } 536 + } 537 + 538 + res.json({ 539 + message: 'Passkey deleted', 540 + }); 541 + }); 542 + 543 + // ===== Stats ===== 544 + 545 + /** 546 + * GET /admin/stats 547 + * Get system statistics 548 + */ 549 + router.get('/stats', requireAdmin, async (_req: Request, res: Response) => { 550 + const stats = db.getStats(); 551 + res.json(stats); 158 552 }); 159 553 160 554 return router;
+354
gateway/src/routes/email.ts
··· 1 + /** 2 + * Email Routes 3 + * 4 + * Handles email verification and account recovery endpoints 5 + */ 6 + 7 + import { Router, Request, Response } from 'express'; 8 + import type { DatabaseService } from '../services/database.js'; 9 + import type { EmailService } from '../services/email.js'; 10 + import type { OIDCService } from '../services/oidc/index.js'; 11 + import { HttpError } from '../utils/errors.js'; 12 + 13 + export function createEmailRouter( 14 + db: DatabaseService, 15 + emailService: EmailService, 16 + oidcService: OIDCService | null 17 + ): Router { 18 + const router = Router(); 19 + 20 + /** 21 + * POST /auth/email/add 22 + * Add an email to the authenticated user's account 23 + */ 24 + router.post('/add', async (req: Request, res: Response) => { 25 + try { 26 + const { did } = await authenticateRequest(req, db, oidcService); 27 + 28 + if (!did) { 29 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 30 + } 31 + 32 + const { email } = req.body as { email: string }; 33 + 34 + if (!email || !isValidEmail(email)) { 35 + throw new HttpError(400, 'invalid_request', 'Invalid email address'); 36 + } 37 + 38 + const result = await emailService.addEmail(did, email); 39 + 40 + if (!result.success) { 41 + throw new HttpError(400, 'add_failed', result.error || 'Failed to add email'); 42 + } 43 + 44 + res.json({ 45 + success: true, 46 + message: 'Verification code sent to email', 47 + }); 48 + } catch (error) { 49 + handleError(res, error); 50 + } 51 + }); 52 + 53 + /** 54 + * POST /auth/email/verify 55 + * Verify an email address with a code 56 + */ 57 + router.post('/verify', async (req: Request, res: Response) => { 58 + try { 59 + const { did } = await authenticateRequest(req, db, oidcService); 60 + 61 + if (!did) { 62 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 63 + } 64 + 65 + const { email, code } = req.body as { email: string; code: string }; 66 + 67 + if (!email || !code) { 68 + throw new HttpError(400, 'invalid_request', 'Missing email or code'); 69 + } 70 + 71 + const result = emailService.verifyEmail(did, email, code); 72 + 73 + if (!result.success) { 74 + throw new HttpError(400, 'verification_failed', result.error || 'Verification failed'); 75 + } 76 + 77 + res.json({ 78 + success: true, 79 + message: 'Email verified successfully', 80 + }); 81 + } catch (error) { 82 + handleError(res, error); 83 + } 84 + }); 85 + 86 + /** 87 + * POST /auth/email/resend 88 + * Resend verification code to an email 89 + */ 90 + router.post('/resend', async (req: Request, res: Response) => { 91 + try { 92 + const { did } = await authenticateRequest(req, db, oidcService); 93 + 94 + if (!did) { 95 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 96 + } 97 + 98 + const { email } = req.body as { email: string }; 99 + 100 + if (!email) { 101 + throw new HttpError(400, 'invalid_request', 'Missing email'); 102 + } 103 + 104 + const result = await emailService.resendVerificationCode(email); 105 + 106 + if (!result.success) { 107 + throw new HttpError(400, 'resend_failed', result.error || 'Failed to resend code'); 108 + } 109 + 110 + res.json({ 111 + success: true, 112 + message: 'Verification code resent', 113 + }); 114 + } catch (error) { 115 + handleError(res, error); 116 + } 117 + }); 118 + 119 + /** 120 + * DELETE /auth/email/:email 121 + * Remove an email from the authenticated user's account 122 + */ 123 + router.delete('/:email', async (req: Request, res: Response) => { 124 + try { 125 + const { did } = await authenticateRequest(req, db, oidcService); 126 + 127 + if (!did) { 128 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 129 + } 130 + 131 + const { email } = req.params; 132 + 133 + const result = emailService.removeEmail(did, email); 134 + 135 + if (!result.success) { 136 + throw new HttpError(400, 'remove_failed', result.error || 'Failed to remove email'); 137 + } 138 + 139 + res.json({ 140 + success: true, 141 + message: 'Email removed successfully', 142 + }); 143 + } catch (error) { 144 + handleError(res, error); 145 + } 146 + }); 147 + 148 + /** 149 + * POST /auth/email/set-primary 150 + * Set an email as primary 151 + */ 152 + router.post('/set-primary', async (req: Request, res: Response) => { 153 + try { 154 + const { did } = await authenticateRequest(req, db, oidcService); 155 + 156 + if (!did) { 157 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 158 + } 159 + 160 + const { email } = req.body as { email: string }; 161 + 162 + if (!email) { 163 + throw new HttpError(400, 'invalid_request', 'Missing email'); 164 + } 165 + 166 + const result = emailService.setPrimaryEmail(did, email); 167 + 168 + if (!result.success) { 169 + throw new HttpError(400, 'set_primary_failed', result.error || 'Failed to set primary email'); 170 + } 171 + 172 + res.json({ 173 + success: true, 174 + message: 'Primary email updated', 175 + }); 176 + } catch (error) { 177 + handleError(res, error); 178 + } 179 + }); 180 + 181 + /** 182 + * GET /auth/email/list 183 + * List user's emails 184 + */ 185 + router.get('/list', async (req: Request, res: Response) => { 186 + try { 187 + const { did } = await authenticateRequest(req, db, oidcService); 188 + 189 + if (!did) { 190 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 191 + } 192 + 193 + const emails = emailService.getUserEmails(did); 194 + 195 + res.json({ 196 + success: true, 197 + emails: emails.map(e => ({ 198 + email: e.email, 199 + verified: e.verified, 200 + is_primary: e.is_primary, 201 + verified_at: e.verified_at?.toISOString(), 202 + created_at: e.created_at.toISOString(), 203 + })), 204 + }); 205 + } catch (error) { 206 + handleError(res, error); 207 + } 208 + }); 209 + 210 + /** 211 + * POST /auth/recovery/request 212 + * Request account recovery via email 213 + */ 214 + router.post('/recovery/request', async (req: Request, res: Response) => { 215 + try { 216 + const { email } = req.body as { email: string }; 217 + 218 + if (!email || !isValidEmail(email)) { 219 + throw new HttpError(400, 'invalid_request', 'Invalid email address'); 220 + } 221 + 222 + const result = await emailService.requestRecovery(email); 223 + 224 + // Always return success to avoid email enumeration 225 + res.json({ 226 + success: true, 227 + message: 'If an account exists with this email, a recovery code has been sent', 228 + }); 229 + } catch (error) { 230 + handleError(res, error); 231 + } 232 + }); 233 + 234 + /** 235 + * POST /auth/recovery/verify 236 + * Verify recovery code 237 + */ 238 + router.post('/recovery/verify', async (req: Request, res: Response) => { 239 + try { 240 + const { email, code, client_id, scope } = req.body as { 241 + email: string; 242 + code: string; 243 + client_id?: string; 244 + scope?: string; 245 + }; 246 + 247 + if (!email || !code) { 248 + throw new HttpError(400, 'invalid_request', 'Missing email or code'); 249 + } 250 + 251 + const result = emailService.verifyRecovery(email, code); 252 + 253 + if (!result.success || !result.did) { 254 + throw new HttpError(401, 'verification_failed', result.error || 'Verification failed'); 255 + } 256 + 257 + // If OIDC service is available and client_id is provided, issue tokens 258 + if (oidcService && client_id) { 259 + const client = db.getOIDCClient(client_id); 260 + const mapping = db.getUserMapping(result.did, client_id); 261 + if (client) { 262 + const tokenResponse = oidcService.tokenService.createTokenResponse({ 263 + sub: result.did, 264 + clientId: client_id, 265 + scope: scope || 'openid', 266 + did: result.did, 267 + handle: mapping?.handle || '', 268 + accessTokenTtl: client.access_token_ttl_seconds, 269 + idTokenTtl: client.id_token_ttl_seconds, 270 + }); 271 + 272 + return res.json({ 273 + success: true, 274 + did: result.did, 275 + tokens: tokenResponse, 276 + }); 277 + } 278 + } 279 + 280 + res.json({ 281 + success: true, 282 + did: result.did, 283 + }); 284 + } catch (error) { 285 + handleError(res, error); 286 + } 287 + }); 288 + 289 + return router; 290 + } 291 + 292 + /** 293 + * Authenticate request via session cookie or access token 294 + */ 295 + async function authenticateRequest( 296 + req: Request, 297 + db: DatabaseService, 298 + oidcService: OIDCService | null 299 + ): Promise<{ did?: string; handle?: string }> { 300 + // Try to authenticate via access token 301 + const authHeader = req.headers.authorization; 302 + if (authHeader?.startsWith('Bearer ') && oidcService) { 303 + const token = authHeader.slice(7); 304 + const claims = oidcService.tokenService.verifyAccessToken(token); 305 + if (claims) { 306 + const mapping = db.getUserMapping(claims.sub, claims.client_id); 307 + return { 308 + did: claims.sub, 309 + handle: mapping?.handle, 310 + }; 311 + } 312 + } 313 + 314 + // Try to authenticate via session header 315 + const sessionId = req.headers['x-session-id'] as string; 316 + if (sessionId) { 317 + const session = db.getSession(sessionId); 318 + if (session && new Date(session.expires_at) > new Date()) { 319 + return { 320 + did: session.did, 321 + handle: session.handle, 322 + }; 323 + } 324 + } 325 + 326 + return {}; 327 + } 328 + 329 + /** 330 + * Validate email format 331 + */ 332 + function isValidEmail(email: string): boolean { 333 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 334 + return emailRegex.test(email); 335 + } 336 + 337 + /** 338 + * Handle errors consistently 339 + */ 340 + function handleError(res: Response, error: unknown): void { 341 + if (error instanceof HttpError) { 342 + res.status(error.statusCode).json({ 343 + error: error.code, 344 + message: error.message, 345 + }); 346 + return; 347 + } 348 + 349 + console.error('[Email] Error:', error); 350 + res.status(500).json({ 351 + error: 'server_error', 352 + message: 'Internal server error', 353 + }); 354 + }
+353
gateway/src/routes/mfa.ts
··· 1 + /** 2 + * MFA Routes 3 + * 4 + * Handles TOTP setup, verification, and backup code management 5 + */ 6 + 7 + import { Router, Request, Response } from 'express'; 8 + import type { DatabaseService } from '../services/database.js'; 9 + import type { MFAService } from '../services/mfa.js'; 10 + import type { PasskeyService } from '../services/passkey.js'; 11 + import type { OIDCService } from '../services/oidc/index.js'; 12 + import { HttpError } from '../utils/errors.js'; 13 + 14 + export function createMFARouter( 15 + db: DatabaseService, 16 + mfaService: MFAService, 17 + passkeyService: PasskeyService | null, 18 + oidcService: OIDCService | null 19 + ): Router { 20 + const router = Router(); 21 + 22 + /** 23 + * POST /auth/mfa/totp/setup 24 + * Start TOTP setup - returns secret and QR code 25 + */ 26 + router.post('/totp/setup', async (req: Request, res: Response) => { 27 + try { 28 + const { did, handle } = await authenticateRequest(req, db, oidcService); 29 + 30 + if (!did) { 31 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 32 + } 33 + 34 + // Check if TOTP is already enabled 35 + if (mfaService.isTOTPEnabled(did)) { 36 + throw new HttpError(400, 'already_enabled', 'TOTP is already enabled'); 37 + } 38 + 39 + const setup = await mfaService.setupTOTP(did, handle || ''); 40 + 41 + res.json({ 42 + success: true, 43 + ...setup, 44 + }); 45 + } catch (error) { 46 + handleError(res, error); 47 + } 48 + }); 49 + 50 + /** 51 + * POST /auth/mfa/totp/verify-setup 52 + * Verify TOTP code during setup to enable TOTP 53 + */ 54 + router.post('/totp/verify-setup', async (req: Request, res: Response) => { 55 + try { 56 + const { did } = await authenticateRequest(req, db, oidcService); 57 + 58 + if (!did) { 59 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 60 + } 61 + 62 + const { code } = req.body as { code: string }; 63 + 64 + if (!code) { 65 + throw new HttpError(400, 'invalid_request', 'Missing code'); 66 + } 67 + 68 + const success = mfaService.verifyAndEnableTOTP(did, code); 69 + 70 + if (!success) { 71 + throw new HttpError(400, 'invalid_code', 'Invalid verification code'); 72 + } 73 + 74 + // Generate backup codes 75 + const backupCodes = mfaService.generateBackupCodes(did); 76 + 77 + res.json({ 78 + success: true, 79 + message: 'TOTP enabled successfully', 80 + backup_codes: backupCodes, 81 + }); 82 + } catch (error) { 83 + handleError(res, error); 84 + } 85 + }); 86 + 87 + /** 88 + * POST /auth/mfa/totp/verify 89 + * Verify TOTP code during login 90 + */ 91 + router.post('/totp/verify', async (req: Request, res: Response) => { 92 + try { 93 + const { did, code, client_id, scope } = req.body as { 94 + did: string; 95 + code: string; 96 + client_id?: string; 97 + scope?: string; 98 + }; 99 + 100 + if (!did || !code) { 101 + throw new HttpError(400, 'invalid_request', 'Missing did or code'); 102 + } 103 + 104 + const success = mfaService.verifyTOTP(did, code); 105 + 106 + if (!success) { 107 + throw new HttpError(401, 'invalid_code', 'Invalid TOTP code'); 108 + } 109 + 110 + // If OIDC service is available and client_id is provided, issue tokens 111 + if (oidcService && client_id) { 112 + const client = db.getOIDCClient(client_id); 113 + const mapping = db.getUserMapping(did, client_id); 114 + if (client) { 115 + const tokenResponse = oidcService.tokenService.createTokenResponse({ 116 + sub: did, 117 + clientId: client_id, 118 + scope: scope || 'openid', 119 + did, 120 + handle: mapping?.handle || '', 121 + accessTokenTtl: client.access_token_ttl_seconds, 122 + idTokenTtl: client.id_token_ttl_seconds, 123 + }); 124 + 125 + return res.json({ 126 + success: true, 127 + did, 128 + tokens: tokenResponse, 129 + }); 130 + } 131 + } 132 + 133 + res.json({ 134 + success: true, 135 + did, 136 + }); 137 + } catch (error) { 138 + handleError(res, error); 139 + } 140 + }); 141 + 142 + /** 143 + * POST /auth/mfa/totp/disable 144 + * Disable TOTP for the authenticated user 145 + */ 146 + router.post('/totp/disable', async (req: Request, res: Response) => { 147 + try { 148 + const { did } = await authenticateRequest(req, db, oidcService); 149 + 150 + if (!did) { 151 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 152 + } 153 + 154 + const { code } = req.body as { code: string }; 155 + 156 + if (!code) { 157 + throw new HttpError(400, 'invalid_request', 'Missing code'); 158 + } 159 + 160 + // Verify code before disabling 161 + const verified = mfaService.verifyTOTP(did, code); 162 + if (!verified) { 163 + throw new HttpError(401, 'invalid_code', 'Invalid TOTP code'); 164 + } 165 + 166 + mfaService.disableTOTP(did); 167 + 168 + res.json({ 169 + success: true, 170 + message: 'TOTP disabled successfully', 171 + }); 172 + } catch (error) { 173 + handleError(res, error); 174 + } 175 + }); 176 + 177 + /** 178 + * POST /auth/mfa/backup-codes 179 + * Generate new backup codes (regenerates all codes) 180 + */ 181 + router.post('/backup-codes', async (req: Request, res: Response) => { 182 + try { 183 + const { did } = await authenticateRequest(req, db, oidcService); 184 + 185 + if (!did) { 186 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 187 + } 188 + 189 + const { code } = req.body as { code: string }; 190 + 191 + // Require TOTP verification to regenerate backup codes 192 + if (!mfaService.isTOTPEnabled(did)) { 193 + throw new HttpError(400, 'totp_not_enabled', 'TOTP must be enabled to generate backup codes'); 194 + } 195 + 196 + if (!code) { 197 + throw new HttpError(400, 'invalid_request', 'Missing TOTP code'); 198 + } 199 + 200 + const verified = mfaService.verifyTOTP(did, code); 201 + if (!verified) { 202 + throw new HttpError(401, 'invalid_code', 'Invalid TOTP code'); 203 + } 204 + 205 + const backupCodes = mfaService.generateBackupCodes(did); 206 + 207 + res.json({ 208 + success: true, 209 + codes: backupCodes, 210 + generated_at: new Date().toISOString(), 211 + }); 212 + } catch (error) { 213 + handleError(res, error); 214 + } 215 + }); 216 + 217 + /** 218 + * POST /auth/mfa/backup-codes/verify 219 + * Verify a backup code during login 220 + */ 221 + router.post('/backup-codes/verify', async (req: Request, res: Response) => { 222 + try { 223 + const { did, code, client_id, scope } = req.body as { 224 + did: string; 225 + code: string; 226 + client_id?: string; 227 + scope?: string; 228 + }; 229 + 230 + if (!did || !code) { 231 + throw new HttpError(400, 'invalid_request', 'Missing did or code'); 232 + } 233 + 234 + const success = mfaService.verifyBackupCode(did, code); 235 + 236 + if (!success) { 237 + throw new HttpError(401, 'invalid_code', 'Invalid backup code'); 238 + } 239 + 240 + // If OIDC service is available and client_id is provided, issue tokens 241 + if (oidcService && client_id) { 242 + const client = db.getOIDCClient(client_id); 243 + const mapping = db.getUserMapping(did, client_id); 244 + if (client) { 245 + const tokenResponse = oidcService.tokenService.createTokenResponse({ 246 + sub: did, 247 + clientId: client_id, 248 + scope: scope || 'openid', 249 + did, 250 + handle: mapping?.handle || '', 251 + accessTokenTtl: client.access_token_ttl_seconds, 252 + idTokenTtl: client.id_token_ttl_seconds, 253 + }); 254 + 255 + return res.json({ 256 + success: true, 257 + did, 258 + tokens: tokenResponse, 259 + }); 260 + } 261 + } 262 + 263 + res.json({ 264 + success: true, 265 + did, 266 + }); 267 + } catch (error) { 268 + handleError(res, error); 269 + } 270 + }); 271 + 272 + /** 273 + * GET /auth/mfa/status 274 + * Get MFA status for the authenticated user 275 + */ 276 + router.get('/status', async (req: Request, res: Response) => { 277 + try { 278 + const { did } = await authenticateRequest(req, db, oidcService); 279 + 280 + if (!did) { 281 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 282 + } 283 + 284 + const passkeyCount = passkeyService?.getPasskeyCount(did) ?? 0; 285 + const status = mfaService.getMFAStatus(did, passkeyCount); 286 + 287 + res.json({ 288 + success: true, 289 + ...status, 290 + }); 291 + } catch (error) { 292 + handleError(res, error); 293 + } 294 + }); 295 + 296 + return router; 297 + } 298 + 299 + /** 300 + * Authenticate request via session cookie or access token 301 + */ 302 + async function authenticateRequest( 303 + req: Request, 304 + db: DatabaseService, 305 + oidcService: OIDCService | null 306 + ): Promise<{ did?: string; handle?: string }> { 307 + // Try to authenticate via access token 308 + const authHeader = req.headers.authorization; 309 + if (authHeader?.startsWith('Bearer ') && oidcService) { 310 + const token = authHeader.slice(7); 311 + const claims = oidcService.tokenService.verifyAccessToken(token); 312 + if (claims) { 313 + const mapping = db.getUserMapping(claims.sub, claims.client_id); 314 + return { 315 + did: claims.sub, 316 + handle: mapping?.handle, 317 + }; 318 + } 319 + } 320 + 321 + // Try to authenticate via session header 322 + const sessionId = req.headers['x-session-id'] as string; 323 + if (sessionId) { 324 + const session = db.getSession(sessionId); 325 + if (session && new Date(session.expires_at) > new Date()) { 326 + return { 327 + did: session.did, 328 + handle: session.handle, 329 + }; 330 + } 331 + } 332 + 333 + return {}; 334 + } 335 + 336 + /** 337 + * Handle errors consistently 338 + */ 339 + function handleError(res: Response, error: unknown): void { 340 + if (error instanceof HttpError) { 341 + res.status(error.statusCode).json({ 342 + error: error.code, 343 + message: error.message, 344 + }); 345 + return; 346 + } 347 + 348 + console.error('[MFA] Error:', error); 349 + res.status(500).json({ 350 + error: 'server_error', 351 + message: 'Internal server error', 352 + }); 353 + }
+203
gateway/src/routes/oidc/authorize.ts
··· 1 + /** 2 + * OIDC Authorization Endpoint 3 + * 4 + * Handles /oauth/authorize - the entry point for OIDC authentication 5 + */ 6 + 7 + import { Router, Request, Response } from 'express'; 8 + import crypto from 'crypto'; 9 + import type { DatabaseService } from '../../services/database.js'; 10 + import type { OIDCService } from '../../services/oidc/index.js'; 11 + import type { OAuthService } from '../../services/oauth.js'; 12 + import { parseScopes, hasOpenIdScope, validateScopes } from '../../services/oidc/claims.js'; 13 + import { isValidCodeChallengeMethod } from '../../services/oidc/pkce.js'; 14 + 15 + export function createAuthorizeRouter( 16 + db: DatabaseService, 17 + oidcService: OIDCService, 18 + oauthService: OAuthService 19 + ): Router { 20 + const router = Router(); 21 + 22 + /** 23 + * GET/POST /oauth/authorize 24 + * Start the authorization flow 25 + */ 26 + router.all('/authorize', async (req: Request, res: Response) => { 27 + try { 28 + // Extract parameters from query or body 29 + const params = req.method === 'POST' ? req.body : req.query; 30 + 31 + const { 32 + response_type, 33 + client_id, 34 + redirect_uri, 35 + scope, 36 + state, 37 + nonce, 38 + code_challenge, 39 + code_challenge_method, 40 + } = params as { 41 + response_type?: string; 42 + client_id?: string; 43 + redirect_uri?: string; 44 + scope?: string; 45 + state?: string; 46 + nonce?: string; 47 + code_challenge?: string; 48 + code_challenge_method?: string; 49 + }; 50 + 51 + // Validate required parameters 52 + if (!response_type || !client_id || !redirect_uri || !scope || !state) { 53 + return res.status(400).json({ 54 + error: 'invalid_request', 55 + error_description: 'Missing required parameters: response_type, client_id, redirect_uri, scope, state', 56 + }); 57 + } 58 + 59 + // Only support authorization code flow 60 + if (response_type !== 'code') { 61 + return res.status(400).json({ 62 + error: 'unsupported_response_type', 63 + error_description: 'Only response_type=code is supported', 64 + }); 65 + } 66 + 67 + // Get client configuration 68 + const client = db.getOIDCClient(client_id); 69 + if (!client) { 70 + return res.status(400).json({ 71 + error: 'invalid_client', 72 + error_description: 'Unknown client_id', 73 + }); 74 + } 75 + 76 + // Verify client is OIDC type 77 + if (client.client_type !== 'oidc') { 78 + return res.status(400).json({ 79 + error: 'invalid_client', 80 + error_description: 'Client is not configured for OIDC', 81 + }); 82 + } 83 + 84 + // Validate redirect_uri 85 + if (!client.redirect_uris.includes(redirect_uri)) { 86 + return res.status(400).json({ 87 + error: 'invalid_request', 88 + error_description: 'Invalid redirect_uri', 89 + }); 90 + } 91 + 92 + // Validate scopes 93 + const requestedScopes = parseScopes(scope); 94 + if (!hasOpenIdScope(requestedScopes)) { 95 + return redirectWithError(res, redirect_uri, state, 'invalid_scope', 'openid scope is required'); 96 + } 97 + 98 + const scopeValidation = validateScopes(requestedScopes, client.allowed_scopes); 99 + if (!scopeValidation.valid) { 100 + return redirectWithError(res, redirect_uri, state, 'invalid_scope', scopeValidation.error || 'Invalid scope'); 101 + } 102 + 103 + // Check PKCE requirements 104 + if (client.require_pkce && !code_challenge) { 105 + return redirectWithError(res, redirect_uri, state, 'invalid_request', 'PKCE code_challenge is required'); 106 + } 107 + 108 + // Validate code_challenge_method 109 + if (code_challenge && code_challenge_method && !isValidCodeChallengeMethod(code_challenge_method)) { 110 + return redirectWithError(res, redirect_uri, state, 'invalid_request', 'Invalid code_challenge_method'); 111 + } 112 + 113 + // Generate authorization code 114 + const authCode = crypto.randomBytes(32).toString('base64url'); 115 + 116 + // Store the authorization request 117 + // We'll complete this after AT Protocol OAuth returns 118 + const authState = { 119 + code: authCode, 120 + client_id, 121 + redirect_uri, 122 + scope: scopeValidation.scopes.join(' '), 123 + nonce, 124 + code_challenge, 125 + code_challenge_method: code_challenge_method as 'S256' | 'plain' | undefined, 126 + state, 127 + created_at: Math.floor(Date.now() / 1000), 128 + expires_at: Math.floor(Date.now() / 1000) + 600, // 10 minutes 129 + }; 130 + 131 + // Store state for callback 132 + // We use the AT Protocol OAuth flow as the identity provider 133 + // Generate AT Protocol OAuth URL 134 + const atprotoAuth = await oauthService.generateAuthUrl( 135 + client_id, 136 + '', // No handle yet - will be filled by user 137 + `${oidcService.issuer}/oauth/callback` 138 + ); 139 + 140 + if (!atprotoAuth) { 141 + return redirectWithError(res, redirect_uri, state, 'server_error', 'Failed to generate auth URL'); 142 + } 143 + 144 + // Store our OIDC state in the database, keyed by AT Protocol state 145 + const atprotoState = atprotoAuth.state; 146 + if (atprotoState) { 147 + db.saveAuthorizationCode({ 148 + code: authCode, 149 + client_id, 150 + redirect_uri, 151 + scope: scopeValidation.scopes.join(' '), 152 + nonce, 153 + code_challenge, 154 + code_challenge_method: code_challenge_method as 'S256' | 'plain' | undefined, 155 + did: '', // Will be filled on callback 156 + handle: '', // Will be filled on callback 157 + created_at: authState.created_at, 158 + expires_at: authState.expires_at, 159 + used: false, 160 + }); 161 + 162 + // Store mapping from AT Protocol state to our auth code 163 + db.saveOAuthState({ 164 + state: atprotoState, 165 + code_verifier: authCode, // Reuse this field to store our auth code 166 + app_id: client_id, 167 + redirect_uri, 168 + created_at: authState.created_at, 169 + }); 170 + } 171 + 172 + // Redirect user to AT Protocol OAuth 173 + res.redirect(atprotoAuth.url); 174 + } catch (error) { 175 + console.error('[OIDC Authorize] Error:', error); 176 + res.status(500).json({ 177 + error: 'server_error', 178 + error_description: 'Internal server error', 179 + }); 180 + } 181 + }); 182 + 183 + return router; 184 + } 185 + 186 + /** 187 + * Redirect with error parameters 188 + */ 189 + function redirectWithError( 190 + res: Response, 191 + redirectUri: string, 192 + state: string | undefined, 193 + error: string, 194 + errorDescription: string 195 + ): void { 196 + const url = new URL(redirectUri); 197 + url.searchParams.set('error', error); 198 + url.searchParams.set('error_description', errorDescription); 199 + if (state) { 200 + url.searchParams.set('state', state); 201 + } 202 + res.redirect(url.toString()); 203 + }
+32
gateway/src/routes/oidc/discovery.ts
··· 1 + /** 2 + * OIDC Discovery Routes 3 + * 4 + * Handles /.well-known/openid-configuration and /.well-known/jwks.json 5 + */ 6 + 7 + import { Router } from 'express'; 8 + import type { OIDCService } from '../../services/oidc/index.js'; 9 + 10 + export function createDiscoveryRouter(oidcService: OIDCService): Router { 11 + const router = Router(); 12 + 13 + /** 14 + * GET /.well-known/openid-configuration 15 + * OpenID Connect Discovery Document 16 + */ 17 + router.get('/openid-configuration', (_req, res) => { 18 + const discoveryDoc = oidcService.getDiscoveryDocument(); 19 + res.json(discoveryDoc); 20 + }); 21 + 22 + /** 23 + * GET /.well-known/jwks.json 24 + * JSON Web Key Set 25 + */ 26 + router.get('/jwks.json', (_req, res) => { 27 + const jwks = oidcService.getJWKS(); 28 + res.json(jwks); 29 + }); 30 + 31 + return router; 32 + }
+51
gateway/src/routes/oidc/index.ts
··· 1 + /** 2 + * OIDC Routes 3 + * 4 + * Aggregates all OIDC-related routes 5 + */ 6 + 7 + import { Router } from 'express'; 8 + import type { DatabaseService } from '../../services/database.js'; 9 + import type { OIDCService } from '../../services/oidc/index.js'; 10 + import type { OAuthService } from '../../services/oauth.js'; 11 + 12 + import { createDiscoveryRouter } from './discovery.js'; 13 + import { createAuthorizeRouter } from './authorize.js'; 14 + import { createTokenRouter } from './token.js'; 15 + import { createUserInfoRouter } from './userinfo.js'; 16 + import { createRevokeRouter } from './revoke.js'; 17 + import { createLogoutRouter } from './logout.js'; 18 + 19 + export function createOIDCRouter( 20 + db: DatabaseService, 21 + oidcService: OIDCService, 22 + oauthService: OAuthService 23 + ): { wellKnownRouter: Router; oauthRouter: Router } { 24 + // Discovery endpoints go under /.well-known 25 + const wellKnownRouter = createDiscoveryRouter(oidcService); 26 + 27 + // OAuth/OIDC endpoints go under /oauth 28 + const oauthRouter = Router(); 29 + 30 + // Mount sub-routers 31 + const authorizeRouter = createAuthorizeRouter(db, oidcService, oauthService); 32 + const tokenRouter = createTokenRouter(db, oidcService); 33 + const userInfoRouter = createUserInfoRouter(db, oidcService); 34 + const revokeRouter = createRevokeRouter(db); 35 + const logoutRouter = createLogoutRouter(db, oidcService); 36 + 37 + oauthRouter.use('/', authorizeRouter); 38 + oauthRouter.use('/', tokenRouter); 39 + oauthRouter.use('/', userInfoRouter); 40 + oauthRouter.use('/', revokeRouter); 41 + oauthRouter.use('/', logoutRouter); 42 + 43 + return { wellKnownRouter, oauthRouter }; 44 + } 45 + 46 + export { createDiscoveryRouter } from './discovery.js'; 47 + export { createAuthorizeRouter } from './authorize.js'; 48 + export { createTokenRouter } from './token.js'; 49 + export { createUserInfoRouter } from './userinfo.js'; 50 + export { createRevokeRouter } from './revoke.js'; 51 + export { createLogoutRouter } from './logout.js';
+127
gateway/src/routes/oidc/logout.ts
··· 1 + /** 2 + * OIDC End Session Endpoint 3 + * 4 + * Handles /oauth/end_session - RP-initiated logout 5 + */ 6 + 7 + import { Router, Request, Response } from 'express'; 8 + import crypto from 'crypto'; 9 + import type { DatabaseService } from '../../services/database.js'; 10 + import type { OIDCService } from '../../services/oidc/index.js'; 11 + 12 + export function createLogoutRouter(db: DatabaseService, oidcService: OIDCService): Router { 13 + const router = Router(); 14 + 15 + /** 16 + * GET /oauth/end_session 17 + * RP-initiated logout 18 + */ 19 + router.get('/end_session', async (req: Request, res: Response) => { 20 + try { 21 + const { 22 + id_token_hint, 23 + client_id, 24 + post_logout_redirect_uri, 25 + state, 26 + } = req.query as { 27 + id_token_hint?: string; 28 + client_id?: string; 29 + post_logout_redirect_uri?: string; 30 + state?: string; 31 + }; 32 + 33 + let sub: string | undefined; 34 + let tokenClientId: string | undefined; 35 + 36 + // Verify id_token_hint if provided 37 + if (id_token_hint) { 38 + const claims = oidcService.tokenService.verifyIdToken(id_token_hint); 39 + if (claims) { 40 + sub = claims.sub; 41 + tokenClientId = claims.aud; 42 + } 43 + } 44 + 45 + // Use client_id from token or parameter 46 + const effectiveClientId = client_id || tokenClientId; 47 + 48 + // Validate post_logout_redirect_uri if provided 49 + if (post_logout_redirect_uri && effectiveClientId) { 50 + const client = db.getOIDCClient(effectiveClientId); 51 + if (!client) { 52 + return res.status(400).json({ 53 + error: 'invalid_request', 54 + error_description: 'Unknown client', 55 + }); 56 + } 57 + 58 + // Check if redirect URI is registered 59 + // For logout, we could have a separate list, but for simplicity use redirect_uris 60 + if (!client.redirect_uris.some((uri) => post_logout_redirect_uri.startsWith(uri.split('?')[0]))) { 61 + return res.status(400).json({ 62 + error: 'invalid_request', 63 + error_description: 'Invalid post_logout_redirect_uri', 64 + }); 65 + } 66 + } 67 + 68 + // Revoke all refresh tokens for this user and client 69 + if (sub && effectiveClientId) { 70 + db.revokeAllRefreshTokensForUser(sub, effectiveClientId); 71 + } 72 + 73 + // If we have a post_logout_redirect_uri, redirect there 74 + if (post_logout_redirect_uri) { 75 + const redirectUrl = new URL(post_logout_redirect_uri); 76 + if (state) { 77 + redirectUrl.searchParams.set('state', state); 78 + } 79 + return res.redirect(redirectUrl.toString()); 80 + } 81 + 82 + // Otherwise, show a logged out page 83 + res.send(` 84 + <!DOCTYPE html> 85 + <html> 86 + <head> 87 + <title>Logged Out - ATAuth</title> 88 + <style> 89 + body { 90 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 91 + display: flex; 92 + justify-content: center; 93 + align-items: center; 94 + height: 100vh; 95 + margin: 0; 96 + background-color: #f5f5f5; 97 + } 98 + .container { 99 + text-align: center; 100 + padding: 2rem; 101 + background: white; 102 + border-radius: 8px; 103 + box-shadow: 0 2px 4px rgba(0,0,0,0.1); 104 + } 105 + h1 { color: #333; } 106 + p { color: #666; } 107 + </style> 108 + </head> 109 + <body> 110 + <div class="container"> 111 + <h1>Logged Out</h1> 112 + <p>You have been successfully logged out.</p> 113 + </div> 114 + </body> 115 + </html> 116 + `); 117 + } catch (error) { 118 + console.error('[OIDC Logout] Error:', error); 119 + res.status(500).json({ 120 + error: 'server_error', 121 + error_description: 'Internal server error', 122 + }); 123 + } 124 + }); 125 + 126 + return router; 127 + }
+123
gateway/src/routes/oidc/revoke.ts
··· 1 + /** 2 + * OIDC Token Revocation Endpoint 3 + * 4 + * Handles /oauth/revoke - revokes access or refresh tokens 5 + */ 6 + 7 + import { Router, Request, Response } from 'express'; 8 + import crypto from 'crypto'; 9 + import type { DatabaseService } from '../../services/database.js'; 10 + 11 + export function createRevokeRouter(db: DatabaseService): Router { 12 + const router = Router(); 13 + 14 + /** 15 + * POST /oauth/revoke 16 + * Revoke an access token or refresh token 17 + */ 18 + router.post('/revoke', async (req: Request, res: Response) => { 19 + try { 20 + // Parse client credentials from Authorization header or body 21 + let clientId: string | undefined; 22 + let clientSecret: string | undefined; 23 + 24 + const authHeader = req.headers.authorization; 25 + if (authHeader && authHeader.startsWith('Basic ')) { 26 + const base64 = authHeader.slice(6); 27 + const decoded = Buffer.from(base64, 'base64').toString('utf8'); 28 + const [id, secret] = decoded.split(':'); 29 + clientId = decodeURIComponent(id); 30 + clientSecret = secret ? decodeURIComponent(secret) : undefined; 31 + } 32 + 33 + const { 34 + token, 35 + token_type_hint, 36 + client_id: bodyClientId, 37 + client_secret: bodyClientSecret, 38 + } = req.body as { 39 + token?: string; 40 + token_type_hint?: string; 41 + client_id?: string; 42 + client_secret?: string; 43 + }; 44 + 45 + clientId = bodyClientId || clientId; 46 + clientSecret = bodyClientSecret || clientSecret; 47 + 48 + if (!token) { 49 + // Per RFC 7009, invalid tokens should return 200 OK 50 + res.status(200).send(); 51 + return; 52 + } 53 + 54 + // Get client configuration 55 + if (clientId) { 56 + const client = db.getOIDCClient(clientId); 57 + if (!client) { 58 + res.status(401).json({ 59 + error: 'invalid_client', 60 + error_description: 'Unknown client', 61 + }); 62 + return; 63 + } 64 + 65 + // Validate client authentication 66 + if (client.token_endpoint_auth_method !== 'none') { 67 + if (!clientSecret) { 68 + res.status(401).json({ 69 + error: 'invalid_client', 70 + error_description: 'Client authentication required', 71 + }); 72 + return; 73 + } 74 + 75 + const expectedSecret = client.client_secret; 76 + if (!expectedSecret || !crypto.timingSafeEqual(Buffer.from(clientSecret), Buffer.from(expectedSecret))) { 77 + res.status(401).json({ 78 + error: 'invalid_client', 79 + error_description: 'Invalid client credentials', 80 + }); 81 + return; 82 + } 83 + } 84 + } 85 + 86 + // Try to revoke as refresh token first 87 + if (!token_type_hint || token_type_hint === 'refresh_token') { 88 + const tokenHash = crypto.createHash('sha256').update(token).digest('hex'); 89 + const refreshToken = db.getRefreshToken(tokenHash); 90 + 91 + if (refreshToken) { 92 + // Verify client owns this token 93 + if (clientId && refreshToken.client_id !== clientId) { 94 + // Token doesn't belong to this client, return success anyway (per RFC) 95 + res.status(200).send(); 96 + return; 97 + } 98 + 99 + // Revoke the token and its entire family 100 + db.revokeRefreshToken(tokenHash); 101 + if (refreshToken.family_id) { 102 + db.revokeRefreshTokenFamily(refreshToken.family_id); 103 + } 104 + 105 + res.status(200).send(); 106 + return; 107 + } 108 + } 109 + 110 + // Access tokens are JWTs and can't be truly revoked without a blacklist 111 + // For now, we just return 200 OK as per RFC 7009 112 + // A proper implementation would add the token to a revocation list 113 + 114 + res.status(200).send(); 115 + } catch (error) { 116 + console.error('[OIDC Revoke] Error:', error); 117 + // Per RFC 7009, errors should still return 200 if the token is invalid 118 + res.status(200).send(); 119 + } 120 + }); 121 + 122 + return router; 123 + }
+394
gateway/src/routes/oidc/token.ts
··· 1 + /** 2 + * OIDC Token Endpoint 3 + * 4 + * Handles /oauth/token - exchanges authorization codes for tokens 5 + */ 6 + 7 + import { Router, Request, Response } from 'express'; 8 + import crypto from 'crypto'; 9 + import type { DatabaseService } from '../../services/database.js'; 10 + import type { OIDCService } from '../../services/oidc/index.js'; 11 + import { verifyCodeChallenge, isValidCodeVerifier } from '../../services/oidc/pkce.js'; 12 + import { hasOfflineAccessScope } from '../../services/oidc/claims.js'; 13 + 14 + export function createTokenRouter(db: DatabaseService, oidcService: OIDCService): Router { 15 + const router = Router(); 16 + 17 + /** 18 + * POST /oauth/token 19 + * Exchange authorization code for tokens 20 + */ 21 + router.post('/token', async (req: Request, res: Response) => { 22 + try { 23 + // Parse client credentials from Authorization header or body 24 + let clientId: string | undefined; 25 + let clientSecret: string | undefined; 26 + 27 + const authHeader = req.headers.authorization; 28 + if (authHeader && authHeader.startsWith('Basic ')) { 29 + const base64 = authHeader.slice(6); 30 + const decoded = Buffer.from(base64, 'base64').toString('utf8'); 31 + const [id, secret] = decoded.split(':'); 32 + clientId = decodeURIComponent(id); 33 + clientSecret = secret ? decodeURIComponent(secret) : undefined; 34 + } 35 + 36 + // Body parameters can override 37 + const { 38 + grant_type, 39 + code, 40 + redirect_uri, 41 + client_id: bodyClientId, 42 + client_secret: bodyClientSecret, 43 + code_verifier, 44 + refresh_token, 45 + scope, 46 + } = req.body as { 47 + grant_type?: string; 48 + code?: string; 49 + redirect_uri?: string; 50 + client_id?: string; 51 + client_secret?: string; 52 + code_verifier?: string; 53 + refresh_token?: string; 54 + scope?: string; 55 + }; 56 + 57 + clientId = bodyClientId || clientId; 58 + clientSecret = bodyClientSecret || clientSecret; 59 + 60 + // Validate grant_type 61 + if (!grant_type) { 62 + return res.status(400).json({ 63 + error: 'invalid_request', 64 + error_description: 'Missing grant_type', 65 + }); 66 + } 67 + 68 + if (grant_type === 'authorization_code') { 69 + return handleAuthorizationCodeGrant( 70 + db, 71 + oidcService, 72 + res, 73 + clientId, 74 + clientSecret, 75 + code, 76 + redirect_uri, 77 + code_verifier 78 + ); 79 + } else if (grant_type === 'refresh_token') { 80 + return handleRefreshTokenGrant(db, oidcService, res, clientId, clientSecret, refresh_token, scope); 81 + } else { 82 + return res.status(400).json({ 83 + error: 'unsupported_grant_type', 84 + error_description: 'Only authorization_code and refresh_token grants are supported', 85 + }); 86 + } 87 + } catch (error) { 88 + console.error('[OIDC Token] Error:', error); 89 + res.status(500).json({ 90 + error: 'server_error', 91 + error_description: 'Internal server error', 92 + }); 93 + } 94 + }); 95 + 96 + return router; 97 + } 98 + 99 + async function handleAuthorizationCodeGrant( 100 + db: DatabaseService, 101 + oidcService: OIDCService, 102 + res: Response, 103 + clientId: string | undefined, 104 + clientSecret: string | undefined, 105 + code: string | undefined, 106 + redirectUri: string | undefined, 107 + codeVerifier: string | undefined 108 + ): Promise<void> { 109 + // Validate required parameters 110 + if (!code || !redirectUri) { 111 + res.status(400).json({ 112 + error: 'invalid_request', 113 + error_description: 'Missing code or redirect_uri', 114 + }); 115 + return; 116 + } 117 + 118 + // Get the authorization code 119 + const authCode = db.getAuthorizationCode(code); 120 + if (!authCode) { 121 + res.status(400).json({ 122 + error: 'invalid_grant', 123 + error_description: 'Invalid or expired authorization code', 124 + }); 125 + return; 126 + } 127 + 128 + // Check if code is already used 129 + if (authCode.used) { 130 + res.status(400).json({ 131 + error: 'invalid_grant', 132 + error_description: 'Authorization code has already been used', 133 + }); 134 + return; 135 + } 136 + 137 + // Check if code is expired 138 + if (authCode.expires_at < Math.floor(Date.now() / 1000)) { 139 + res.status(400).json({ 140 + error: 'invalid_grant', 141 + error_description: 'Authorization code has expired', 142 + }); 143 + return; 144 + } 145 + 146 + // Validate client_id matches 147 + if (clientId && authCode.client_id !== clientId) { 148 + res.status(400).json({ 149 + error: 'invalid_grant', 150 + error_description: 'client_id does not match', 151 + }); 152 + return; 153 + } 154 + 155 + clientId = authCode.client_id; 156 + 157 + // Validate redirect_uri matches 158 + if (authCode.redirect_uri !== redirectUri) { 159 + res.status(400).json({ 160 + error: 'invalid_grant', 161 + error_description: 'redirect_uri does not match', 162 + }); 163 + return; 164 + } 165 + 166 + // Get client configuration 167 + const client = db.getOIDCClient(clientId); 168 + if (!client) { 169 + res.status(400).json({ 170 + error: 'invalid_client', 171 + error_description: 'Unknown client', 172 + }); 173 + return; 174 + } 175 + 176 + // Validate client authentication 177 + if (client.token_endpoint_auth_method !== 'none') { 178 + if (!clientSecret) { 179 + res.status(401).json({ 180 + error: 'invalid_client', 181 + error_description: 'Client authentication required', 182 + }); 183 + return; 184 + } 185 + 186 + // Verify client secret (constant-time comparison) 187 + const expectedSecret = client.client_secret; 188 + if (!expectedSecret || !crypto.timingSafeEqual(Buffer.from(clientSecret), Buffer.from(expectedSecret))) { 189 + res.status(401).json({ 190 + error: 'invalid_client', 191 + error_description: 'Invalid client credentials', 192 + }); 193 + return; 194 + } 195 + } 196 + 197 + // Validate PKCE 198 + if (authCode.code_challenge) { 199 + if (!codeVerifier) { 200 + res.status(400).json({ 201 + error: 'invalid_request', 202 + error_description: 'Missing code_verifier', 203 + }); 204 + return; 205 + } 206 + 207 + if (!isValidCodeVerifier(codeVerifier)) { 208 + res.status(400).json({ 209 + error: 'invalid_request', 210 + error_description: 'Invalid code_verifier format', 211 + }); 212 + return; 213 + } 214 + 215 + const method = authCode.code_challenge_method || 'S256'; 216 + if (!verifyCodeChallenge(codeVerifier, authCode.code_challenge, method)) { 217 + res.status(400).json({ 218 + error: 'invalid_grant', 219 + error_description: 'Invalid code_verifier', 220 + }); 221 + return; 222 + } 223 + } 224 + 225 + // Mark code as used 226 + db.markAuthorizationCodeUsed(code); 227 + 228 + // Generate tokens 229 + const scopes = authCode.scope.split(' '); 230 + const includeRefreshToken = hasOfflineAccessScope(scopes); 231 + 232 + let refreshTokenValue: string | undefined; 233 + if (includeRefreshToken) { 234 + // Generate refresh token 235 + refreshTokenValue = crypto.randomBytes(32).toString('base64url'); 236 + const refreshTokenHash = crypto.createHash('sha256').update(refreshTokenValue).digest('hex'); 237 + const familyId = crypto.randomUUID(); 238 + 239 + db.saveRefreshToken({ 240 + token_hash: refreshTokenHash, 241 + client_id: clientId, 242 + did: authCode.did, 243 + handle: authCode.handle, 244 + user_id: authCode.user_id, 245 + scope: authCode.scope, 246 + expires_at: new Date(Date.now() + client.refresh_token_ttl_seconds * 1000), 247 + revoked: false, 248 + family_id: familyId, 249 + }); 250 + } 251 + 252 + const tokenResponse = oidcService.tokenService.createTokenResponse({ 253 + sub: authCode.did, 254 + clientId, 255 + scope: authCode.scope, 256 + did: authCode.did, 257 + handle: authCode.handle, 258 + nonce: authCode.nonce, 259 + accessTokenTtl: client.access_token_ttl_seconds, 260 + idTokenTtl: client.id_token_ttl_seconds, 261 + includeRefreshToken, 262 + refreshToken: refreshTokenValue, 263 + }); 264 + 265 + res.json(tokenResponse); 266 + } 267 + 268 + async function handleRefreshTokenGrant( 269 + db: DatabaseService, 270 + oidcService: OIDCService, 271 + res: Response, 272 + clientId: string | undefined, 273 + clientSecret: string | undefined, 274 + refreshToken: string | undefined, 275 + scope: string | undefined 276 + ): Promise<void> { 277 + if (!refreshToken) { 278 + res.status(400).json({ 279 + error: 'invalid_request', 280 + error_description: 'Missing refresh_token', 281 + }); 282 + return; 283 + } 284 + 285 + // Hash the refresh token to look it up 286 + const tokenHash = crypto.createHash('sha256').update(refreshToken).digest('hex'); 287 + const storedToken = db.getRefreshToken(tokenHash); 288 + 289 + if (!storedToken) { 290 + res.status(400).json({ 291 + error: 'invalid_grant', 292 + error_description: 'Invalid refresh token', 293 + }); 294 + return; 295 + } 296 + 297 + // Check if revoked 298 + if (storedToken.revoked) { 299 + // Revoke entire family (token reuse attack detection) 300 + if (storedToken.family_id) { 301 + db.revokeRefreshTokenFamily(storedToken.family_id); 302 + } 303 + res.status(400).json({ 304 + error: 'invalid_grant', 305 + error_description: 'Refresh token has been revoked', 306 + }); 307 + return; 308 + } 309 + 310 + // Check if expired 311 + if (storedToken.expires_at < new Date()) { 312 + res.status(400).json({ 313 + error: 'invalid_grant', 314 + error_description: 'Refresh token has expired', 315 + }); 316 + return; 317 + } 318 + 319 + // Validate client 320 + if (clientId && storedToken.client_id !== clientId) { 321 + res.status(400).json({ 322 + error: 'invalid_grant', 323 + error_description: 'client_id does not match', 324 + }); 325 + return; 326 + } 327 + 328 + clientId = storedToken.client_id; 329 + 330 + const client = db.getOIDCClient(clientId); 331 + if (!client) { 332 + res.status(400).json({ 333 + error: 'invalid_client', 334 + error_description: 'Unknown client', 335 + }); 336 + return; 337 + } 338 + 339 + // Validate client authentication 340 + if (client.token_endpoint_auth_method !== 'none') { 341 + if (!clientSecret) { 342 + res.status(401).json({ 343 + error: 'invalid_client', 344 + error_description: 'Client authentication required', 345 + }); 346 + return; 347 + } 348 + 349 + const expectedSecret = client.client_secret; 350 + if (!expectedSecret || !crypto.timingSafeEqual(Buffer.from(clientSecret), Buffer.from(expectedSecret))) { 351 + res.status(401).json({ 352 + error: 'invalid_client', 353 + error_description: 'Invalid client credentials', 354 + }); 355 + return; 356 + } 357 + } 358 + 359 + // Revoke old refresh token (rotation) 360 + db.revokeRefreshToken(tokenHash); 361 + 362 + // Generate new tokens 363 + const tokenScope = scope || storedToken.scope; 364 + 365 + // Generate new refresh token 366 + const newRefreshToken = crypto.randomBytes(32).toString('base64url'); 367 + const newRefreshTokenHash = crypto.createHash('sha256').update(newRefreshToken).digest('hex'); 368 + 369 + db.saveRefreshToken({ 370 + token_hash: newRefreshTokenHash, 371 + client_id: clientId, 372 + did: storedToken.did, 373 + handle: storedToken.handle, 374 + user_id: storedToken.user_id, 375 + scope: tokenScope, 376 + expires_at: new Date(Date.now() + client.refresh_token_ttl_seconds * 1000), 377 + revoked: false, 378 + family_id: storedToken.family_id, 379 + }); 380 + 381 + const tokenResponse = oidcService.tokenService.createTokenResponse({ 382 + sub: storedToken.did, 383 + clientId, 384 + scope: tokenScope, 385 + did: storedToken.did, 386 + handle: storedToken.handle, 387 + accessTokenTtl: client.access_token_ttl_seconds, 388 + idTokenTtl: client.id_token_ttl_seconds, 389 + includeRefreshToken: true, 390 + refreshToken: newRefreshToken, 391 + }); 392 + 393 + res.json(tokenResponse); 394 + }
+91
gateway/src/routes/oidc/userinfo.ts
··· 1 + /** 2 + * OIDC UserInfo Endpoint 3 + * 4 + * Handles /oauth/userinfo - returns claims about the authenticated user 5 + */ 6 + 7 + import { Router, Request, Response } from 'express'; 8 + import type { DatabaseService } from '../../services/database.js'; 9 + import type { OIDCService } from '../../services/oidc/index.js'; 10 + import { buildUserInfo, parseScopes } from '../../services/oidc/claims.js'; 11 + 12 + export function createUserInfoRouter(db: DatabaseService, oidcService: OIDCService): Router { 13 + const router = Router(); 14 + 15 + /** 16 + * GET/POST /oauth/userinfo 17 + * Returns claims about the authenticated user 18 + */ 19 + router.all('/userinfo', async (req: Request, res: Response) => { 20 + try { 21 + // Extract access token from Authorization header 22 + const authHeader = req.headers.authorization; 23 + if (!authHeader || !authHeader.startsWith('Bearer ')) { 24 + res.status(401).json({ 25 + error: 'invalid_token', 26 + error_description: 'Missing or invalid Authorization header', 27 + }); 28 + return; 29 + } 30 + 31 + const accessToken = authHeader.slice(7); 32 + 33 + // Verify the access token 34 + const claims = oidcService.tokenService.verifyAccessToken(accessToken); 35 + if (!claims) { 36 + res.status(401).json({ 37 + error: 'invalid_token', 38 + error_description: 'Invalid or expired access token', 39 + }); 40 + return; 41 + } 42 + 43 + // Get scopes from the token 44 + const scopes = parseScopes(claims.scope); 45 + 46 + // Build user info based on scopes 47 + const userInfo = buildUserInfo( 48 + { 49 + did: claims.sub, 50 + handle: '', // We need to look this up 51 + }, 52 + scopes 53 + ); 54 + 55 + // Try to get additional user info from sessions or mappings 56 + const mapping = db.getUserMapping(claims.sub, claims.client_id); 57 + if (mapping && mapping.handle) { 58 + const enhancedUserInfo = buildUserInfo( 59 + { 60 + did: claims.sub, 61 + handle: mapping.handle, 62 + }, 63 + scopes 64 + ); 65 + res.json(enhancedUserInfo); 66 + return; 67 + } 68 + 69 + // If we have email scope, try to get verified email 70 + if (scopes.includes('email')) { 71 + const emails = db.getUserEmails(claims.sub); 72 + const primaryEmail = emails.find((e) => e.is_primary && e.verified); 73 + if (primaryEmail) { 74 + const extendedInfo = userInfo as unknown as Record<string, unknown>; 75 + extendedInfo.email = primaryEmail.email; 76 + extendedInfo.email_verified = true; 77 + } 78 + } 79 + 80 + res.json(userInfo); 81 + } catch (error) { 82 + console.error('[OIDC UserInfo] Error:', error); 83 + res.status(500).json({ 84 + error: 'server_error', 85 + error_description: 'Internal server error', 86 + }); 87 + } 88 + }); 89 + 90 + return router; 91 + }
+308
gateway/src/routes/passkey.ts
··· 1 + /** 2 + * Passkey Routes 3 + * 4 + * Handles WebAuthn/FIDO2 passkey registration and authentication endpoints 5 + */ 6 + 7 + import { Router, Request, Response } from 'express'; 8 + import type { RegistrationResponseJSON, AuthenticationResponseJSON } from '@simplewebauthn/types'; 9 + import type { DatabaseService } from '../services/database.js'; 10 + import type { PasskeyService } from '../services/passkey.js'; 11 + import type { OIDCService } from '../services/oidc/index.js'; 12 + import { HttpError } from '../utils/errors.js'; 13 + 14 + export function createPasskeyRouter( 15 + db: DatabaseService, 16 + passkeyService: PasskeyService, 17 + oidcService: OIDCService | null 18 + ): Router { 19 + const router = Router(); 20 + 21 + /** 22 + * POST /auth/passkey/register/options 23 + * Get WebAuthn registration options 24 + * Requires authenticated user (via session or access token) 25 + */ 26 + router.post('/register/options', async (req: Request, res: Response) => { 27 + try { 28 + const { did, handle } = await authenticateRequest(req, db, oidcService); 29 + 30 + if (!did) { 31 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 32 + } 33 + 34 + const options = await passkeyService.generateRegistrationOptions(did, handle || ''); 35 + 36 + res.json({ 37 + success: true, 38 + options, 39 + }); 40 + } catch (error) { 41 + handleError(res, error); 42 + } 43 + }); 44 + 45 + /** 46 + * POST /auth/passkey/register/verify 47 + * Verify WebAuthn registration response 48 + */ 49 + router.post('/register/verify', async (req: Request, res: Response) => { 50 + try { 51 + const { did, handle } = await authenticateRequest(req, db, oidcService); 52 + 53 + if (!did) { 54 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 55 + } 56 + 57 + const { credential, name } = req.body as { 58 + credential: RegistrationResponseJSON; 59 + name?: string; 60 + }; 61 + 62 + if (!credential) { 63 + throw new HttpError(400, 'invalid_request', 'Missing credential'); 64 + } 65 + 66 + const result = await passkeyService.verifyRegistration( 67 + did, 68 + handle || '', 69 + credential, 70 + name 71 + ); 72 + 73 + if (!result.success) { 74 + throw new HttpError(400, 'verification_failed', result.error || 'Verification failed'); 75 + } 76 + 77 + res.json({ 78 + success: true, 79 + passkey_id: result.credentialId, 80 + }); 81 + } catch (error) { 82 + handleError(res, error); 83 + } 84 + }); 85 + 86 + /** 87 + * POST /auth/passkey/authenticate/options 88 + * Get WebAuthn authentication options 89 + */ 90 + router.post('/authenticate/options', async (req: Request, res: Response) => { 91 + try { 92 + const { did } = req.body as { did?: string }; 93 + 94 + const options = await passkeyService.generateAuthenticationOptions(did); 95 + 96 + res.json({ 97 + success: true, 98 + options, 99 + }); 100 + } catch (error) { 101 + handleError(res, error); 102 + } 103 + }); 104 + 105 + /** 106 + * POST /auth/passkey/authenticate/verify 107 + * Verify WebAuthn authentication response 108 + */ 109 + router.post('/authenticate/verify', async (req: Request, res: Response) => { 110 + try { 111 + const { credential, challenge, client_id, scope } = req.body as { 112 + credential: AuthenticationResponseJSON; 113 + challenge: string; 114 + client_id?: string; 115 + scope?: string; 116 + }; 117 + 118 + if (!credential || !challenge) { 119 + throw new HttpError(400, 'invalid_request', 'Missing credential or challenge'); 120 + } 121 + 122 + const result = await passkeyService.verifyAuthentication(credential, challenge); 123 + 124 + if (!result.success) { 125 + throw new HttpError(401, 'authentication_failed', result.error || 'Authentication failed'); 126 + } 127 + 128 + // If OIDC service is available and client_id is provided, issue tokens 129 + if (oidcService && client_id && result.did) { 130 + const client = db.getOIDCClient(client_id); 131 + if (client) { 132 + const tokenResponse = oidcService.tokenService.createTokenResponse({ 133 + sub: result.did, 134 + clientId: client_id, 135 + scope: scope || 'openid', 136 + did: result.did, 137 + handle: result.handle || '', 138 + accessTokenTtl: client.access_token_ttl_seconds, 139 + idTokenTtl: client.id_token_ttl_seconds, 140 + }); 141 + 142 + return res.json({ 143 + success: true, 144 + did: result.did, 145 + handle: result.handle, 146 + tokens: tokenResponse, 147 + }); 148 + } 149 + } 150 + 151 + // Return basic success response 152 + res.json({ 153 + success: true, 154 + did: result.did, 155 + handle: result.handle, 156 + }); 157 + } catch (error) { 158 + handleError(res, error); 159 + } 160 + }); 161 + 162 + /** 163 + * GET /auth/passkey/list 164 + * List user's registered passkeys 165 + */ 166 + router.get('/list', async (req: Request, res: Response) => { 167 + try { 168 + const { did } = await authenticateRequest(req, db, oidcService); 169 + 170 + if (!did) { 171 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 172 + } 173 + 174 + const passkeys = passkeyService.listPasskeys(did); 175 + 176 + res.json({ 177 + success: true, 178 + passkeys, 179 + }); 180 + } catch (error) { 181 + handleError(res, error); 182 + } 183 + }); 184 + 185 + /** 186 + * PUT /auth/passkey/:id 187 + * Rename a passkey 188 + */ 189 + router.put('/:id', async (req: Request, res: Response) => { 190 + try { 191 + const { did } = await authenticateRequest(req, db, oidcService); 192 + 193 + if (!did) { 194 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 195 + } 196 + 197 + const { id } = req.params; 198 + const { name } = req.body as { name: string }; 199 + 200 + if (!name) { 201 + throw new HttpError(400, 'invalid_request', 'Missing name'); 202 + } 203 + 204 + const success = passkeyService.renamePasskey(did, id, name); 205 + 206 + if (!success) { 207 + throw new HttpError(404, 'not_found', 'Passkey not found'); 208 + } 209 + 210 + res.json({ success: true }); 211 + } catch (error) { 212 + handleError(res, error); 213 + } 214 + }); 215 + 216 + /** 217 + * DELETE /auth/passkey/:id 218 + * Delete a passkey 219 + */ 220 + router.delete('/:id', async (req: Request, res: Response) => { 221 + try { 222 + const { did } = await authenticateRequest(req, db, oidcService); 223 + 224 + if (!did) { 225 + throw new HttpError(401, 'unauthorized', 'Authentication required'); 226 + } 227 + 228 + const { id } = req.params; 229 + 230 + // Prevent deleting last passkey if MFA is required 231 + // (This is a safety check - can be configured based on policy) 232 + const passkeyCount = passkeyService.getPasskeyCount(did); 233 + if (passkeyCount <= 1) { 234 + // Allow deletion but warn 235 + console.warn(`[Passkey] User ${did} is deleting their last passkey`); 236 + } 237 + 238 + const success = passkeyService.deletePasskey(did, id); 239 + 240 + if (!success) { 241 + throw new HttpError(404, 'not_found', 'Passkey not found'); 242 + } 243 + 244 + res.json({ success: true }); 245 + } catch (error) { 246 + handleError(res, error); 247 + } 248 + }); 249 + 250 + return router; 251 + } 252 + 253 + /** 254 + * Authenticate request via session cookie or access token 255 + */ 256 + async function authenticateRequest( 257 + req: Request, 258 + db: DatabaseService, 259 + oidcService: OIDCService | null 260 + ): Promise<{ did?: string; handle?: string }> { 261 + // Try to authenticate via access token 262 + const authHeader = req.headers.authorization; 263 + if (authHeader?.startsWith('Bearer ') && oidcService) { 264 + const token = authHeader.slice(7); 265 + const claims = oidcService.tokenService.verifyAccessToken(token); 266 + if (claims) { 267 + // Try to get handle from user mapping 268 + const mapping = db.getUserMapping(claims.sub, claims.client_id); 269 + return { 270 + did: claims.sub, 271 + handle: mapping?.handle, 272 + }; 273 + } 274 + } 275 + 276 + // Try to authenticate via session cookie 277 + const sessionId = req.headers['x-session-id'] as string; 278 + if (sessionId) { 279 + const session = db.getSession(sessionId); 280 + if (session && new Date(session.expires_at) > new Date()) { 281 + return { 282 + did: session.did, 283 + handle: session.handle, 284 + }; 285 + } 286 + } 287 + 288 + return {}; 289 + } 290 + 291 + /** 292 + * Handle errors consistently 293 + */ 294 + function handleError(res: Response, error: unknown): void { 295 + if (error instanceof HttpError) { 296 + res.status(error.statusCode).json({ 297 + error: error.code, 298 + message: error.message, 299 + }); 300 + return; 301 + } 302 + 303 + console.error('[Passkey] Error:', error); 304 + res.status(500).json({ 305 + error: 'server_error', 306 + message: 'Internal server error', 307 + }); 308 + }
+990 -18
gateway/src/services/database.ts
··· 1 1 /** 2 2 * Database Service 3 3 * 4 - * SQLite database for OAuth state, app sessions, and user mappings 4 + * SQLite database for OAuth state, app sessions, user mappings, 5 + * OIDC clients, passkeys, MFA, and email verification 5 6 */ 6 7 7 8 import Database from 'better-sqlite3'; ··· 13 14 UserMapping, 14 15 SessionConnectionState, 15 16 ActiveSession, 17 + OIDCClientConfig, 18 + OIDCKey, 19 + AuthorizationCode, 20 + RefreshToken, 21 + PasskeyCredential, 22 + MFATOTPConfig, 23 + MFABackupCode, 24 + UserEmail, 25 + EmailVerificationCode, 16 26 } from '../types/index.js'; 17 27 18 28 export class DatabaseService { ··· 82 92 CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at); 83 93 CREATE INDEX IF NOT EXISTS idx_sessions_did_app ON sessions(did, app_id); 84 94 `); 95 + 96 + // Run migrations for new columns and tables 97 + this.runMigrations(); 98 + } 99 + 100 + private runMigrations(): void { 101 + // Check if we need to add OIDC columns to apps table 102 + const appColumns = this.db.pragma('table_info(apps)') as Array<{ name: string }>; 103 + const columnNames = appColumns.map((c) => c.name); 104 + 105 + if (!columnNames.includes('client_type')) { 106 + this.db.exec(` 107 + -- Add OIDC columns to apps table 108 + ALTER TABLE apps ADD COLUMN client_type TEXT DEFAULT 'legacy'; 109 + ALTER TABLE apps ADD COLUMN client_secret TEXT; 110 + ALTER TABLE apps ADD COLUMN redirect_uris TEXT DEFAULT '[]'; 111 + ALTER TABLE apps ADD COLUMN grant_types TEXT DEFAULT '["authorization_code"]'; 112 + ALTER TABLE apps ADD COLUMN allowed_scopes TEXT DEFAULT '["openid"]'; 113 + ALTER TABLE apps ADD COLUMN id_token_ttl_seconds INTEGER DEFAULT 3600; 114 + ALTER TABLE apps ADD COLUMN access_token_ttl_seconds INTEGER DEFAULT 3600; 115 + ALTER TABLE apps ADD COLUMN refresh_token_ttl_seconds INTEGER DEFAULT 604800; 116 + ALTER TABLE apps ADD COLUMN require_pkce BOOLEAN DEFAULT 1; 117 + ALTER TABLE apps ADD COLUMN token_endpoint_auth_method TEXT DEFAULT 'client_secret_basic'; 118 + `); 119 + } 120 + 121 + // Create OIDC signing keys table 122 + this.db.exec(` 123 + CREATE TABLE IF NOT EXISTS oidc_keys ( 124 + kid TEXT PRIMARY KEY, 125 + algorithm TEXT NOT NULL, 126 + private_key_encrypted TEXT NOT NULL, 127 + public_key_jwk TEXT NOT NULL, 128 + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 129 + expires_at DATETIME, 130 + is_active BOOLEAN DEFAULT 1, 131 + use_for_signing BOOLEAN DEFAULT 1 132 + ); 133 + `); 134 + 135 + // Create authorization codes table 136 + this.db.exec(` 137 + CREATE TABLE IF NOT EXISTS authorization_codes ( 138 + code TEXT PRIMARY KEY, 139 + client_id TEXT NOT NULL, 140 + redirect_uri TEXT NOT NULL, 141 + scope TEXT NOT NULL, 142 + nonce TEXT, 143 + code_challenge TEXT, 144 + code_challenge_method TEXT, 145 + did TEXT NOT NULL, 146 + handle TEXT NOT NULL, 147 + user_id INTEGER, 148 + created_at INTEGER NOT NULL, 149 + expires_at INTEGER NOT NULL, 150 + used BOOLEAN DEFAULT 0, 151 + FOREIGN KEY (client_id) REFERENCES apps(id) 152 + ); 153 + 154 + CREATE INDEX IF NOT EXISTS idx_auth_codes_expires ON authorization_codes(expires_at); 155 + `); 156 + 157 + // Create refresh tokens table 158 + this.db.exec(` 159 + CREATE TABLE IF NOT EXISTS refresh_tokens ( 160 + token_hash TEXT PRIMARY KEY, 161 + client_id TEXT NOT NULL, 162 + did TEXT NOT NULL, 163 + handle TEXT NOT NULL, 164 + user_id INTEGER, 165 + scope TEXT NOT NULL, 166 + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 167 + expires_at DATETIME NOT NULL, 168 + last_used_at DATETIME, 169 + revoked BOOLEAN DEFAULT 0, 170 + family_id TEXT, 171 + FOREIGN KEY (client_id) REFERENCES apps(id) 172 + ); 173 + 174 + CREATE INDEX IF NOT EXISTS idx_refresh_tokens_client ON refresh_tokens(client_id, did); 175 + CREATE INDEX IF NOT EXISTS idx_refresh_tokens_family ON refresh_tokens(family_id); 176 + `); 177 + 178 + // Create passkey credentials table 179 + this.db.exec(` 180 + CREATE TABLE IF NOT EXISTS passkey_credentials ( 181 + id TEXT PRIMARY KEY, 182 + did TEXT NOT NULL, 183 + handle TEXT NOT NULL, 184 + public_key TEXT NOT NULL, 185 + counter INTEGER DEFAULT 0, 186 + device_type TEXT, 187 + backed_up BOOLEAN DEFAULT 0, 188 + transports TEXT, 189 + name TEXT, 190 + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 191 + last_used_at DATETIME 192 + ); 193 + 194 + CREATE INDEX IF NOT EXISTS idx_passkey_did ON passkey_credentials(did); 195 + `); 196 + 197 + // Create MFA TOTP table 198 + this.db.exec(` 199 + CREATE TABLE IF NOT EXISTS mfa_totp ( 200 + did TEXT PRIMARY KEY, 201 + secret_encrypted TEXT NOT NULL, 202 + enabled BOOLEAN DEFAULT 0, 203 + verified_at DATETIME, 204 + created_at DATETIME DEFAULT CURRENT_TIMESTAMP 205 + ); 206 + `); 207 + 208 + // Create MFA backup codes table 209 + this.db.exec(` 210 + CREATE TABLE IF NOT EXISTS mfa_backup_codes ( 211 + id INTEGER PRIMARY KEY AUTOINCREMENT, 212 + did TEXT NOT NULL, 213 + code_hash TEXT NOT NULL, 214 + used BOOLEAN DEFAULT 0, 215 + used_at DATETIME, 216 + created_at DATETIME DEFAULT CURRENT_TIMESTAMP 217 + ); 218 + 219 + CREATE INDEX IF NOT EXISTS idx_backup_did ON mfa_backup_codes(did); 220 + `); 221 + 222 + // Create user emails table 223 + this.db.exec(` 224 + CREATE TABLE IF NOT EXISTS user_emails ( 225 + id INTEGER PRIMARY KEY AUTOINCREMENT, 226 + did TEXT NOT NULL, 227 + email TEXT NOT NULL, 228 + verified BOOLEAN DEFAULT 0, 229 + verified_at DATETIME, 230 + is_primary BOOLEAN DEFAULT 0, 231 + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 232 + UNIQUE(did, email) 233 + ); 234 + 235 + CREATE INDEX IF NOT EXISTS idx_email_did ON user_emails(did); 236 + CREATE INDEX IF NOT EXISTS idx_email_address ON user_emails(email); 237 + `); 238 + 239 + // Create email verification codes table 240 + this.db.exec(` 241 + CREATE TABLE IF NOT EXISTS email_verification_codes ( 242 + id INTEGER PRIMARY KEY AUTOINCREMENT, 243 + email TEXT NOT NULL, 244 + code_hash TEXT NOT NULL, 245 + purpose TEXT NOT NULL, 246 + expires_at DATETIME NOT NULL, 247 + used BOOLEAN DEFAULT 0, 248 + created_at DATETIME DEFAULT CURRENT_TIMESTAMP 249 + ); 250 + 251 + CREATE INDEX IF NOT EXISTS idx_verify_email ON email_verification_codes(email); 252 + CREATE INDEX IF NOT EXISTS idx_verify_expires ON email_verification_codes(expires_at); 253 + `); 85 254 } 86 255 87 256 // App configuration methods ··· 263 432 } 264 433 265 434 deleteApp(appId: string): void { 266 - // Delete in order respecting foreign keys 267 - this.db.prepare('DELETE FROM oauth_states WHERE app_id = ?').run(appId); 435 + // Delete related data first (order respects foreign keys) 268 436 this.db.prepare('DELETE FROM sessions WHERE app_id = ?').run(appId); 437 + this.db.prepare('DELETE FROM oauth_states WHERE app_id = ?').run(appId); 269 438 this.db.prepare('DELETE FROM user_mappings WHERE app_id = ?').run(appId); 439 + this.db.prepare('DELETE FROM authorization_codes WHERE client_id = ?').run(appId); 440 + this.db.prepare('DELETE FROM refresh_tokens WHERE client_id = ?').run(appId); 441 + // Delete the app 270 442 this.db.prepare('DELETE FROM apps WHERE id = ?').run(appId); 271 443 } 272 444 273 - getStats(): { appCount: number; activeSessions: number; pendingOAuthStates: number } { 274 - const appCount = ( 275 - this.db.prepare('SELECT COUNT(*) as count FROM apps').get() as { count: number } 276 - ).count; 445 + getStats(): { 446 + apps_count: number; 447 + oidc_clients_count: number; 448 + active_sessions_count: number; 449 + users_count: number; 450 + passkeys_count: number; 451 + mfa_enabled_count: number; 452 + verified_emails_count: number; 453 + } { 454 + const appsCount = (this.db.prepare('SELECT COUNT(*) as count FROM apps').get() as { count: number }).count; 455 + const oidcClientsCount = (this.db.prepare("SELECT COUNT(*) as count FROM apps WHERE client_type = 'oidc'").get() as { count: number }).count; 456 + const activeSessionsCount = (this.db.prepare("SELECT COUNT(*) as count FROM sessions WHERE expires_at > datetime('now')").get() as { count: number }).count; 457 + const usersCount = (this.db.prepare('SELECT COUNT(DISTINCT did) as count FROM user_mappings').get() as { count: number }).count; 458 + const passkeysCount = (this.db.prepare('SELECT COUNT(*) as count FROM passkey_credentials').get() as { count: number }).count; 459 + const mfaEnabledCount = (this.db.prepare('SELECT COUNT(*) as count FROM mfa_totp WHERE enabled = 1').get() as { count: number }).count; 460 + const verifiedEmailsCount = (this.db.prepare('SELECT COUNT(*) as count FROM user_emails WHERE verified = 1').get() as { count: number }).count; 277 461 278 - const activeSessions = ( 279 - this.db 280 - .prepare("SELECT COUNT(*) as count FROM sessions WHERE expires_at > datetime('now')") 281 - .get() as { count: number } 282 - ).count; 462 + return { 463 + apps_count: appsCount, 464 + oidc_clients_count: oidcClientsCount, 465 + active_sessions_count: activeSessionsCount, 466 + users_count: usersCount, 467 + passkeys_count: passkeysCount, 468 + mfa_enabled_count: mfaEnabledCount, 469 + verified_emails_count: verifiedEmailsCount, 470 + }; 471 + } 472 + 473 + close(): void { 474 + this.db.close(); 475 + } 283 476 284 - const pendingOAuthStates = ( 285 - this.db.prepare('SELECT COUNT(*) as count FROM oauth_states').get() as { count: number } 286 - ).count; 477 + // ===== OIDC Key Management Methods ===== 287 478 288 - return { appCount, activeSessions, pendingOAuthStates }; 479 + saveOIDCKey(key: Omit<OIDCKey, 'created_at'>): void { 480 + const stmt = this.db.prepare(` 481 + INSERT INTO oidc_keys (kid, algorithm, private_key_encrypted, public_key_jwk, is_active, use_for_signing) 482 + VALUES (?, ?, ?, ?, ?, ?) 483 + `); 484 + stmt.run(key.kid, key.algorithm, key.private_key_encrypted, key.public_key_jwk, key.is_active ? 1 : 0, key.use_for_signing ? 1 : 0); 289 485 } 290 486 291 - close(): void { 292 - this.db.close(); 487 + getOIDCKey(kid: string): OIDCKey | null { 488 + const stmt = this.db.prepare('SELECT * FROM oidc_keys WHERE kid = ?'); 489 + const row = stmt.get(kid) as { 490 + kid: string; 491 + algorithm: 'ES256' | 'RS256'; 492 + private_key_encrypted: string; 493 + public_key_jwk: string; 494 + created_at: string; 495 + expires_at: string | null; 496 + is_active: number; 497 + use_for_signing: number; 498 + } | undefined; 499 + if (!row) return null; 500 + return { 501 + kid: row.kid, 502 + algorithm: row.algorithm, 503 + private_key_encrypted: row.private_key_encrypted, 504 + public_key_jwk: row.public_key_jwk, 505 + created_at: new Date(row.created_at), 506 + expires_at: row.expires_at ? new Date(row.expires_at) : undefined, 507 + is_active: Boolean(row.is_active), 508 + use_for_signing: Boolean(row.use_for_signing), 509 + }; 510 + } 511 + 512 + getActiveOIDCKeys(): OIDCKey[] { 513 + const stmt = this.db.prepare('SELECT * FROM oidc_keys WHERE is_active = 1 ORDER BY created_at DESC'); 514 + const rows = stmt.all() as Array<{ 515 + kid: string; 516 + algorithm: 'ES256' | 'RS256'; 517 + private_key_encrypted: string; 518 + public_key_jwk: string; 519 + created_at: string; 520 + expires_at: string | null; 521 + is_active: number; 522 + use_for_signing: number; 523 + }>; 524 + return rows.map(row => ({ 525 + kid: row.kid, 526 + algorithm: row.algorithm, 527 + private_key_encrypted: row.private_key_encrypted, 528 + public_key_jwk: row.public_key_jwk, 529 + created_at: new Date(row.created_at), 530 + expires_at: row.expires_at ? new Date(row.expires_at) : undefined, 531 + is_active: Boolean(row.is_active), 532 + use_for_signing: Boolean(row.use_for_signing), 533 + })); 534 + } 535 + 536 + getCurrentSigningKey(): OIDCKey | null { 537 + const stmt = this.db.prepare('SELECT * FROM oidc_keys WHERE is_active = 1 AND use_for_signing = 1 ORDER BY created_at DESC LIMIT 1'); 538 + const row = stmt.get() as { 539 + kid: string; 540 + algorithm: 'ES256' | 'RS256'; 541 + private_key_encrypted: string; 542 + public_key_jwk: string; 543 + created_at: string; 544 + expires_at: string | null; 545 + is_active: number; 546 + use_for_signing: number; 547 + } | undefined; 548 + if (!row) return null; 549 + return { 550 + kid: row.kid, 551 + algorithm: row.algorithm, 552 + private_key_encrypted: row.private_key_encrypted, 553 + public_key_jwk: row.public_key_jwk, 554 + created_at: new Date(row.created_at), 555 + expires_at: row.expires_at ? new Date(row.expires_at) : undefined, 556 + is_active: Boolean(row.is_active), 557 + use_for_signing: Boolean(row.use_for_signing), 558 + }; 559 + } 560 + 561 + markKeyAsNotSigning(kid: string): void { 562 + const stmt = this.db.prepare('UPDATE oidc_keys SET use_for_signing = 0 WHERE kid = ?'); 563 + stmt.run(kid); 564 + } 565 + 566 + deactivateKey(kid: string): void { 567 + const stmt = this.db.prepare('UPDATE oidc_keys SET is_active = 0, use_for_signing = 0 WHERE kid = ?'); 568 + stmt.run(kid); 569 + } 570 + 571 + deleteOIDCKey(kid: string): void { 572 + const stmt = this.db.prepare('DELETE FROM oidc_keys WHERE kid = ?'); 573 + stmt.run(kid); 574 + } 575 + 576 + // ===== Authorization Code Methods ===== 577 + 578 + saveAuthorizationCode(code: AuthorizationCode): void { 579 + const stmt = this.db.prepare(` 580 + INSERT INTO authorization_codes (code, client_id, redirect_uri, scope, nonce, code_challenge, code_challenge_method, did, handle, user_id, created_at, expires_at) 581 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 582 + `); 583 + stmt.run( 584 + code.code, 585 + code.client_id, 586 + code.redirect_uri, 587 + code.scope, 588 + code.nonce, 589 + code.code_challenge, 590 + code.code_challenge_method, 591 + code.did, 592 + code.handle, 593 + code.user_id, 594 + code.created_at, 595 + code.expires_at 596 + ); 597 + } 598 + 599 + getAuthorizationCode(code: string): AuthorizationCode | null { 600 + const stmt = this.db.prepare('SELECT * FROM authorization_codes WHERE code = ?'); 601 + const row = stmt.get(code) as { 602 + code: string; 603 + client_id: string; 604 + redirect_uri: string; 605 + scope: string; 606 + nonce: string | null; 607 + code_challenge: string | null; 608 + code_challenge_method: 'S256' | 'plain' | null; 609 + did: string; 610 + handle: string; 611 + user_id: number | null; 612 + created_at: number; 613 + expires_at: number; 614 + used: number; 615 + } | undefined; 616 + if (!row) return null; 617 + return { 618 + code: row.code, 619 + client_id: row.client_id, 620 + redirect_uri: row.redirect_uri, 621 + scope: row.scope, 622 + nonce: row.nonce ?? undefined, 623 + code_challenge: row.code_challenge ?? undefined, 624 + code_challenge_method: row.code_challenge_method ?? undefined, 625 + did: row.did, 626 + handle: row.handle, 627 + user_id: row.user_id ?? undefined, 628 + created_at: row.created_at, 629 + expires_at: row.expires_at, 630 + used: Boolean(row.used), 631 + }; 632 + } 633 + 634 + markAuthorizationCodeUsed(code: string): void { 635 + const stmt = this.db.prepare('UPDATE authorization_codes SET used = 1 WHERE code = ?'); 636 + stmt.run(code); 637 + } 638 + 639 + cleanupExpiredAuthorizationCodes(): number { 640 + const now = Math.floor(Date.now() / 1000); 641 + const stmt = this.db.prepare('DELETE FROM authorization_codes WHERE expires_at < ?'); 642 + const result = stmt.run(now); 643 + return result.changes; 644 + } 645 + 646 + // ===== Refresh Token Methods ===== 647 + 648 + saveRefreshToken(token: Omit<RefreshToken, 'created_at' | 'last_used_at'>): void { 649 + const stmt = this.db.prepare(` 650 + INSERT INTO refresh_tokens (token_hash, client_id, did, handle, user_id, scope, expires_at, family_id) 651 + VALUES (?, ?, ?, ?, ?, ?, ?, ?) 652 + `); 653 + stmt.run( 654 + token.token_hash, 655 + token.client_id, 656 + token.did, 657 + token.handle, 658 + token.user_id, 659 + token.scope, 660 + token.expires_at.toISOString(), 661 + token.family_id 662 + ); 663 + } 664 + 665 + getRefreshToken(tokenHash: string): RefreshToken | null { 666 + const stmt = this.db.prepare('SELECT * FROM refresh_tokens WHERE token_hash = ?'); 667 + const row = stmt.get(tokenHash) as { 668 + token_hash: string; 669 + client_id: string; 670 + did: string; 671 + handle: string; 672 + user_id: number | null; 673 + scope: string; 674 + created_at: string; 675 + expires_at: string; 676 + last_used_at: string | null; 677 + revoked: number; 678 + family_id: string | null; 679 + } | undefined; 680 + if (!row) return null; 681 + return { 682 + token_hash: row.token_hash, 683 + client_id: row.client_id, 684 + did: row.did, 685 + handle: row.handle, 686 + user_id: row.user_id ?? undefined, 687 + scope: row.scope, 688 + created_at: new Date(row.created_at), 689 + expires_at: new Date(row.expires_at), 690 + last_used_at: row.last_used_at ? new Date(row.last_used_at) : undefined, 691 + revoked: Boolean(row.revoked), 692 + family_id: row.family_id ?? undefined, 693 + }; 694 + } 695 + 696 + updateRefreshTokenLastUsed(tokenHash: string): void { 697 + const stmt = this.db.prepare("UPDATE refresh_tokens SET last_used_at = datetime('now') WHERE token_hash = ?"); 698 + stmt.run(tokenHash); 699 + } 700 + 701 + revokeRefreshToken(tokenHash: string): void { 702 + const stmt = this.db.prepare('UPDATE refresh_tokens SET revoked = 1 WHERE token_hash = ?'); 703 + stmt.run(tokenHash); 704 + } 705 + 706 + revokeRefreshTokenFamily(familyId: string): void { 707 + const stmt = this.db.prepare('UPDATE refresh_tokens SET revoked = 1 WHERE family_id = ?'); 708 + stmt.run(familyId); 709 + } 710 + 711 + revokeAllRefreshTokensForUser(did: string, clientId: string): number { 712 + const stmt = this.db.prepare('UPDATE refresh_tokens SET revoked = 1 WHERE did = ? AND client_id = ?'); 713 + const result = stmt.run(did, clientId); 714 + return result.changes; 715 + } 716 + 717 + cleanupExpiredRefreshTokens(): number { 718 + const stmt = this.db.prepare("DELETE FROM refresh_tokens WHERE expires_at < datetime('now')"); 719 + const result = stmt.run(); 720 + return result.changes; 721 + } 722 + 723 + // ===== Passkey Credential Methods ===== 724 + 725 + savePasskeyCredential(credential: Omit<PasskeyCredential, 'created_at' | 'last_used_at'>): void { 726 + const stmt = this.db.prepare(` 727 + INSERT INTO passkey_credentials (id, did, handle, public_key, counter, device_type, backed_up, transports, name) 728 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) 729 + `); 730 + stmt.run( 731 + credential.id, 732 + credential.did, 733 + credential.handle, 734 + credential.public_key, 735 + credential.counter, 736 + credential.device_type, 737 + credential.backed_up ? 1 : 0, 738 + credential.transports ? JSON.stringify(credential.transports) : null, 739 + credential.name 740 + ); 741 + } 742 + 743 + getPasskeyCredential(credentialId: string): PasskeyCredential | null { 744 + const stmt = this.db.prepare('SELECT * FROM passkey_credentials WHERE id = ?'); 745 + const row = stmt.get(credentialId) as { 746 + id: string; 747 + did: string; 748 + handle: string; 749 + public_key: string; 750 + counter: number; 751 + device_type: 'platform' | 'cross-platform' | null; 752 + backed_up: number; 753 + transports: string | null; 754 + name: string | null; 755 + created_at: string; 756 + last_used_at: string | null; 757 + } | undefined; 758 + if (!row) return null; 759 + return { 760 + id: row.id, 761 + did: row.did, 762 + handle: row.handle, 763 + public_key: row.public_key, 764 + counter: row.counter, 765 + device_type: row.device_type, 766 + backed_up: Boolean(row.backed_up), 767 + transports: row.transports ? JSON.parse(row.transports) : null, 768 + name: row.name, 769 + created_at: new Date(row.created_at), 770 + last_used_at: row.last_used_at ? new Date(row.last_used_at) : null, 771 + }; 772 + } 773 + 774 + getPasskeyCredentialsByDid(did: string): PasskeyCredential[] { 775 + const stmt = this.db.prepare('SELECT * FROM passkey_credentials WHERE did = ? ORDER BY created_at DESC'); 776 + const rows = stmt.all(did) as Array<{ 777 + id: string; 778 + did: string; 779 + handle: string; 780 + public_key: string; 781 + counter: number; 782 + device_type: 'platform' | 'cross-platform' | null; 783 + backed_up: number; 784 + transports: string | null; 785 + name: string | null; 786 + created_at: string; 787 + last_used_at: string | null; 788 + }>; 789 + return rows.map(row => ({ 790 + id: row.id, 791 + did: row.did, 792 + handle: row.handle, 793 + public_key: row.public_key, 794 + counter: row.counter, 795 + device_type: row.device_type, 796 + backed_up: Boolean(row.backed_up), 797 + transports: row.transports ? JSON.parse(row.transports) : null, 798 + name: row.name, 799 + created_at: new Date(row.created_at), 800 + last_used_at: row.last_used_at ? new Date(row.last_used_at) : null, 801 + })); 802 + } 803 + 804 + updatePasskeyCounter(credentialId: string, newCounter: number): void { 805 + const stmt = this.db.prepare("UPDATE passkey_credentials SET counter = ?, last_used_at = datetime('now') WHERE id = ?"); 806 + stmt.run(newCounter, credentialId); 807 + } 808 + 809 + renamePasskey(credentialId: string, name: string): void { 810 + const stmt = this.db.prepare('UPDATE passkey_credentials SET name = ? WHERE id = ?'); 811 + stmt.run(name, credentialId); 812 + } 813 + 814 + deletePasskeyCredential(credentialId: string): void { 815 + const stmt = this.db.prepare('DELETE FROM passkey_credentials WHERE id = ?'); 816 + stmt.run(credentialId); 817 + } 818 + 819 + countPasskeysByDid(did: string): number { 820 + const stmt = this.db.prepare('SELECT COUNT(*) as count FROM passkey_credentials WHERE did = ?'); 821 + const row = stmt.get(did) as { count: number }; 822 + return row.count; 823 + } 824 + 825 + // ===== MFA TOTP Methods ===== 826 + 827 + saveMFATOTP(config: Omit<MFATOTPConfig, 'created_at' | 'verified_at'>): void { 828 + const stmt = this.db.prepare(` 829 + INSERT OR REPLACE INTO mfa_totp (did, secret_encrypted, enabled) 830 + VALUES (?, ?, ?) 831 + `); 832 + stmt.run(config.did, config.secret_encrypted, config.enabled ? 1 : 0); 833 + } 834 + 835 + getMFATOTP(did: string): MFATOTPConfig | null { 836 + const stmt = this.db.prepare('SELECT * FROM mfa_totp WHERE did = ?'); 837 + const row = stmt.get(did) as { 838 + did: string; 839 + secret_encrypted: string; 840 + enabled: number; 841 + verified_at: string | null; 842 + created_at: string; 843 + } | undefined; 844 + if (!row) return null; 845 + return { 846 + did: row.did, 847 + secret_encrypted: row.secret_encrypted, 848 + enabled: Boolean(row.enabled), 849 + verified_at: row.verified_at ? new Date(row.verified_at) : null, 850 + created_at: new Date(row.created_at), 851 + }; 852 + } 853 + 854 + enableMFATOTP(did: string): void { 855 + const stmt = this.db.prepare("UPDATE mfa_totp SET enabled = 1, verified_at = datetime('now') WHERE did = ?"); 856 + stmt.run(did); 857 + } 858 + 859 + disableMFATOTP(did: string): void { 860 + const stmt = this.db.prepare('DELETE FROM mfa_totp WHERE did = ?'); 861 + stmt.run(did); 862 + } 863 + 864 + // ===== MFA Backup Codes Methods ===== 865 + 866 + saveBackupCodes(did: string, codeHashes: string[]): void { 867 + // Delete existing codes first 868 + this.db.prepare('DELETE FROM mfa_backup_codes WHERE did = ?').run(did); 869 + 870 + const stmt = this.db.prepare(` 871 + INSERT INTO mfa_backup_codes (did, code_hash) VALUES (?, ?) 872 + `); 873 + 874 + const insertMany = this.db.transaction((codes: string[]) => { 875 + for (const codeHash of codes) { 876 + stmt.run(did, codeHash); 877 + } 878 + }); 879 + 880 + insertMany(codeHashes); 881 + } 882 + 883 + getUnusedBackupCode(did: string, codeHash: string): MFABackupCode | null { 884 + const stmt = this.db.prepare('SELECT * FROM mfa_backup_codes WHERE did = ? AND code_hash = ? AND used = 0'); 885 + const row = stmt.get(did, codeHash) as { 886 + id: number; 887 + did: string; 888 + code_hash: string; 889 + used: number; 890 + used_at: string | null; 891 + created_at: string; 892 + } | undefined; 893 + if (!row) return null; 894 + return { 895 + id: row.id, 896 + did: row.did, 897 + code_hash: row.code_hash, 898 + used: Boolean(row.used), 899 + used_at: row.used_at ? new Date(row.used_at) : null, 900 + created_at: new Date(row.created_at), 901 + }; 902 + } 903 + 904 + markBackupCodeUsed(id: number): void { 905 + const stmt = this.db.prepare("UPDATE mfa_backup_codes SET used = 1, used_at = datetime('now') WHERE id = ?"); 906 + stmt.run(id); 907 + } 908 + 909 + countUnusedBackupCodes(did: string): number { 910 + const stmt = this.db.prepare('SELECT COUNT(*) as count FROM mfa_backup_codes WHERE did = ? AND used = 0'); 911 + const row = stmt.get(did) as { count: number }; 912 + return row.count; 913 + } 914 + 915 + // ===== User Email Methods ===== 916 + 917 + saveUserEmail(email: Omit<UserEmail, 'id' | 'created_at' | 'verified_at'>): void { 918 + const stmt = this.db.prepare(` 919 + INSERT INTO user_emails (did, email, verified, is_primary) 920 + VALUES (?, ?, ?, ?) 921 + ON CONFLICT(did, email) DO UPDATE SET is_primary = excluded.is_primary 922 + `); 923 + stmt.run(email.did, email.email, email.verified ? 1 : 0, email.is_primary ? 1 : 0); 924 + } 925 + 926 + getUserEmails(did: string): UserEmail[] { 927 + const stmt = this.db.prepare('SELECT * FROM user_emails WHERE did = ? ORDER BY is_primary DESC, created_at ASC'); 928 + const rows = stmt.all(did) as Array<{ 929 + id: number; 930 + did: string; 931 + email: string; 932 + verified: number; 933 + verified_at: string | null; 934 + is_primary: number; 935 + created_at: string; 936 + }>; 937 + return rows.map(row => ({ 938 + id: row.id, 939 + did: row.did, 940 + email: row.email, 941 + verified: Boolean(row.verified), 942 + verified_at: row.verified_at ? new Date(row.verified_at) : null, 943 + is_primary: Boolean(row.is_primary), 944 + created_at: new Date(row.created_at), 945 + })); 946 + } 947 + 948 + getUserByEmail(email: string): { did: string; handle?: string } | null { 949 + const stmt = this.db.prepare(` 950 + SELECT ue.did, um.handle FROM user_emails ue 951 + LEFT JOIN user_mappings um ON ue.did = um.did 952 + WHERE ue.email = ? AND ue.verified = 1 953 + LIMIT 1 954 + `); 955 + const row = stmt.get(email) as { did: string; handle?: string } | undefined; 956 + return row || null; 957 + } 958 + 959 + verifyUserEmail(did: string, email: string): void { 960 + const stmt = this.db.prepare("UPDATE user_emails SET verified = 1, verified_at = datetime('now') WHERE did = ? AND email = ?"); 961 + stmt.run(did, email); 962 + } 963 + 964 + deleteUserEmail(did: string, email: string): void { 965 + const stmt = this.db.prepare('DELETE FROM user_emails WHERE did = ? AND email = ?'); 966 + stmt.run(did, email); 967 + } 968 + 969 + setPrimaryEmail(did: string, email: string): void { 970 + // Unset all as non-primary first 971 + this.db.prepare('UPDATE user_emails SET is_primary = 0 WHERE did = ?').run(did); 972 + // Set the specified email as primary 973 + this.db.prepare('UPDATE user_emails SET is_primary = 1 WHERE did = ? AND email = ?').run(did, email); 974 + } 975 + 976 + // ===== Email Verification Code Methods ===== 977 + 978 + saveEmailVerificationCode(code: Omit<EmailVerificationCode, 'id' | 'created_at'>): void { 979 + const stmt = this.db.prepare(` 980 + INSERT INTO email_verification_codes (email, code_hash, purpose, expires_at) 981 + VALUES (?, ?, ?, ?) 982 + `); 983 + stmt.run(code.email, code.code_hash, code.purpose, code.expires_at.toISOString()); 984 + } 985 + 986 + getValidEmailVerificationCode(email: string, codeHash: string, purpose: string): EmailVerificationCode | null { 987 + const stmt = this.db.prepare(` 988 + SELECT * FROM email_verification_codes 989 + WHERE email = ? AND code_hash = ? AND purpose = ? AND used = 0 AND expires_at > datetime('now') 990 + `); 991 + const row = stmt.get(email, codeHash, purpose) as { 992 + id: number; 993 + email: string; 994 + code_hash: string; 995 + purpose: 'verify' | 'recovery'; 996 + expires_at: string; 997 + used: number; 998 + created_at: string; 999 + } | undefined; 1000 + if (!row) return null; 1001 + return { 1002 + id: row.id, 1003 + email: row.email, 1004 + code_hash: row.code_hash, 1005 + purpose: row.purpose, 1006 + expires_at: new Date(row.expires_at), 1007 + used: Boolean(row.used), 1008 + created_at: new Date(row.created_at), 1009 + }; 1010 + } 1011 + 1012 + markEmailVerificationCodeUsed(id: number): void { 1013 + const stmt = this.db.prepare('UPDATE email_verification_codes SET used = 1 WHERE id = ?'); 1014 + stmt.run(id); 1015 + } 1016 + 1017 + cleanupExpiredEmailVerificationCodes(): number { 1018 + const stmt = this.db.prepare("DELETE FROM email_verification_codes WHERE expires_at < datetime('now')"); 1019 + const result = stmt.run(); 1020 + return result.changes; 1021 + } 1022 + 1023 + // ===== OIDC Client Methods ===== 1024 + 1025 + getOIDCClient(clientId: string): OIDCClientConfig | null { 1026 + const stmt = this.db.prepare('SELECT * FROM apps WHERE id = ?'); 1027 + const row = stmt.get(clientId) as { 1028 + id: string; 1029 + name: string; 1030 + client_type: 'legacy' | 'oidc'; 1031 + hmac_secret?: string; 1032 + client_secret?: string; 1033 + redirect_uris: string; 1034 + grant_types: string; 1035 + allowed_scopes: string; 1036 + token_ttl_seconds: number; 1037 + id_token_ttl_seconds: number; 1038 + access_token_ttl_seconds: number; 1039 + refresh_token_ttl_seconds: number; 1040 + require_pkce: number; 1041 + token_endpoint_auth_method: string; 1042 + created_at: string; 1043 + } | undefined; 1044 + if (!row) return null; 1045 + return { 1046 + id: row.id, 1047 + name: row.name, 1048 + client_type: row.client_type, 1049 + hmac_secret: row.hmac_secret, 1050 + client_secret: row.client_secret, 1051 + redirect_uris: JSON.parse(row.redirect_uris || '[]'), 1052 + grant_types: JSON.parse(row.grant_types || '["authorization_code"]'), 1053 + allowed_scopes: JSON.parse(row.allowed_scopes || '["openid"]'), 1054 + token_ttl_seconds: row.token_ttl_seconds, 1055 + id_token_ttl_seconds: row.id_token_ttl_seconds, 1056 + access_token_ttl_seconds: row.access_token_ttl_seconds, 1057 + refresh_token_ttl_seconds: row.refresh_token_ttl_seconds, 1058 + require_pkce: Boolean(row.require_pkce), 1059 + token_endpoint_auth_method: row.token_endpoint_auth_method as 'client_secret_basic' | 'client_secret_post' | 'none', 1060 + created_at: new Date(row.created_at), 1061 + }; 1062 + } 1063 + 1064 + upsertOIDCClient(client: Omit<OIDCClientConfig, 'created_at'>): void { 1065 + const stmt = this.db.prepare(` 1066 + INSERT INTO apps (id, name, client_type, hmac_secret, client_secret, redirect_uris, grant_types, allowed_scopes, token_ttl_seconds, id_token_ttl_seconds, access_token_ttl_seconds, refresh_token_ttl_seconds, require_pkce, token_endpoint_auth_method, callback_url) 1067 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 1068 + ON CONFLICT(id) DO UPDATE SET 1069 + name = excluded.name, 1070 + client_type = excluded.client_type, 1071 + hmac_secret = COALESCE(excluded.hmac_secret, hmac_secret), 1072 + client_secret = COALESCE(excluded.client_secret, client_secret), 1073 + redirect_uris = excluded.redirect_uris, 1074 + grant_types = excluded.grant_types, 1075 + allowed_scopes = excluded.allowed_scopes, 1076 + token_ttl_seconds = excluded.token_ttl_seconds, 1077 + id_token_ttl_seconds = excluded.id_token_ttl_seconds, 1078 + access_token_ttl_seconds = excluded.access_token_ttl_seconds, 1079 + refresh_token_ttl_seconds = excluded.refresh_token_ttl_seconds, 1080 + require_pkce = excluded.require_pkce, 1081 + token_endpoint_auth_method = excluded.token_endpoint_auth_method, 1082 + callback_url = excluded.callback_url 1083 + `); 1084 + stmt.run( 1085 + client.id, 1086 + client.name, 1087 + client.client_type, 1088 + client.hmac_secret, 1089 + client.client_secret, 1090 + JSON.stringify(client.redirect_uris), 1091 + JSON.stringify(client.grant_types), 1092 + JSON.stringify(client.allowed_scopes), 1093 + client.token_ttl_seconds, 1094 + client.id_token_ttl_seconds, 1095 + client.access_token_ttl_seconds, 1096 + client.refresh_token_ttl_seconds, 1097 + client.require_pkce ? 1 : 0, 1098 + client.token_endpoint_auth_method, 1099 + client.redirect_uris[0] || null 1100 + ); 1101 + } 1102 + 1103 + getAllOIDCClients(): OIDCClientConfig[] { 1104 + const stmt = this.db.prepare("SELECT * FROM apps WHERE client_type = 'oidc' ORDER BY created_at DESC"); 1105 + const rows = stmt.all() as Array<{ 1106 + id: string; 1107 + name: string; 1108 + client_type: 'legacy' | 'oidc'; 1109 + hmac_secret?: string; 1110 + client_secret?: string; 1111 + redirect_uris: string; 1112 + grant_types: string; 1113 + allowed_scopes: string; 1114 + token_ttl_seconds: number; 1115 + id_token_ttl_seconds: number; 1116 + access_token_ttl_seconds: number; 1117 + refresh_token_ttl_seconds: number; 1118 + require_pkce: number; 1119 + token_endpoint_auth_method: string; 1120 + created_at: string; 1121 + }>; 1122 + return rows.map(row => ({ 1123 + id: row.id, 1124 + name: row.name, 1125 + client_type: row.client_type, 1126 + hmac_secret: row.hmac_secret, 1127 + client_secret: row.client_secret, 1128 + redirect_uris: JSON.parse(row.redirect_uris || '[]'), 1129 + grant_types: JSON.parse(row.grant_types || '["authorization_code"]'), 1130 + allowed_scopes: JSON.parse(row.allowed_scopes || '["openid"]'), 1131 + token_ttl_seconds: row.token_ttl_seconds, 1132 + id_token_ttl_seconds: row.id_token_ttl_seconds, 1133 + access_token_ttl_seconds: row.access_token_ttl_seconds, 1134 + refresh_token_ttl_seconds: row.refresh_token_ttl_seconds, 1135 + require_pkce: Boolean(row.require_pkce), 1136 + token_endpoint_auth_method: row.token_endpoint_auth_method as 'client_secret_basic' | 'client_secret_post' | 'none', 1137 + created_at: new Date(row.created_at), 1138 + })); 1139 + } 1140 + 1141 + updateOIDCClient(clientId: string, updates: { 1142 + client_type?: 'oidc'; 1143 + client_secret?: string; 1144 + redirect_uris?: string[]; 1145 + grant_types?: string[]; 1146 + allowed_scopes?: string[]; 1147 + require_pkce?: boolean; 1148 + token_endpoint_auth_method?: string; 1149 + id_token_ttl_seconds?: number; 1150 + access_token_ttl_seconds?: number; 1151 + refresh_token_ttl_seconds?: number; 1152 + }): void { 1153 + const sets: string[] = []; 1154 + const values: unknown[] = []; 1155 + 1156 + if (updates.client_type !== undefined) { 1157 + sets.push('client_type = ?'); 1158 + values.push(updates.client_type); 1159 + } 1160 + if (updates.client_secret !== undefined) { 1161 + sets.push('client_secret = ?'); 1162 + values.push(updates.client_secret); 1163 + } 1164 + if (updates.redirect_uris !== undefined) { 1165 + sets.push('redirect_uris = ?'); 1166 + values.push(JSON.stringify(updates.redirect_uris)); 1167 + } 1168 + if (updates.grant_types !== undefined) { 1169 + sets.push('grant_types = ?'); 1170 + values.push(JSON.stringify(updates.grant_types)); 1171 + } 1172 + if (updates.allowed_scopes !== undefined) { 1173 + sets.push('allowed_scopes = ?'); 1174 + values.push(JSON.stringify(updates.allowed_scopes)); 1175 + } 1176 + if (updates.require_pkce !== undefined) { 1177 + sets.push('require_pkce = ?'); 1178 + values.push(updates.require_pkce ? 1 : 0); 1179 + } 1180 + if (updates.token_endpoint_auth_method !== undefined) { 1181 + sets.push('token_endpoint_auth_method = ?'); 1182 + values.push(updates.token_endpoint_auth_method); 1183 + } 1184 + if (updates.id_token_ttl_seconds !== undefined) { 1185 + sets.push('id_token_ttl_seconds = ?'); 1186 + values.push(updates.id_token_ttl_seconds); 1187 + } 1188 + if (updates.access_token_ttl_seconds !== undefined) { 1189 + sets.push('access_token_ttl_seconds = ?'); 1190 + values.push(updates.access_token_ttl_seconds); 1191 + sets.push('token_ttl_seconds = ?'); 1192 + values.push(updates.access_token_ttl_seconds); 1193 + } 1194 + if (updates.refresh_token_ttl_seconds !== undefined) { 1195 + sets.push('refresh_token_ttl_seconds = ?'); 1196 + values.push(updates.refresh_token_ttl_seconds); 1197 + } 1198 + 1199 + if (sets.length > 0) { 1200 + values.push(clientId); 1201 + const stmt = this.db.prepare(`UPDATE apps SET ${sets.join(', ')} WHERE id = ?`); 1202 + stmt.run(...values); 1203 + } 1204 + } 1205 + 1206 + updateOIDCClientSecret(clientId: string, secretHash: string): void { 1207 + const stmt = this.db.prepare('UPDATE apps SET client_secret = ? WHERE id = ?'); 1208 + stmt.run(secretHash, clientId); 1209 + } 1210 + 1211 + // ===== Session Admin Methods ===== 1212 + 1213 + getAllActiveSessions(appId?: string, did?: string, limit = 100): ActiveSession[] { 1214 + let sql = "SELECT * FROM sessions WHERE expires_at > datetime('now')"; 1215 + const params: unknown[] = []; 1216 + 1217 + if (appId) { 1218 + sql += ' AND app_id = ?'; 1219 + params.push(appId); 1220 + } 1221 + if (did) { 1222 + sql += ' AND did = ?'; 1223 + params.push(did); 1224 + } 1225 + 1226 + sql += ' ORDER BY created_at DESC LIMIT ?'; 1227 + params.push(limit); 1228 + 1229 + const stmt = this.db.prepare(sql); 1230 + const rows = stmt.all(...params) as Array<{ 1231 + id: string; 1232 + did: string; 1233 + handle: string; 1234 + user_id: number | null; 1235 + app_id: string; 1236 + refresh_token?: string; 1237 + created_at: string; 1238 + expires_at: string; 1239 + connection_state: SessionConnectionState; 1240 + last_activity: string; 1241 + client_info?: string; 1242 + }>; 1243 + 1244 + return rows.map((row) => ({ 1245 + ...row, 1246 + created_at: new Date(row.created_at), 1247 + expires_at: new Date(row.expires_at), 1248 + last_activity: new Date(row.last_activity), 1249 + connection_state: row.connection_state || 'pending', 1250 + })); 1251 + } 1252 + 1253 + revokeAllSessionsForUser(did: string, appId?: string): number { 1254 + let sql = 'DELETE FROM sessions WHERE did = ?'; 1255 + const params: unknown[] = [did]; 1256 + 1257 + if (appId) { 1258 + sql += ' AND app_id = ?'; 1259 + params.push(appId); 1260 + } 1261 + 1262 + const stmt = this.db.prepare(sql); 1263 + const result = stmt.run(...params); 1264 + return result.changes; 293 1265 } 294 1266 }
+297
gateway/src/services/email.ts
··· 1 + /** 2 + * Email Service 3 + * 4 + * Handles email verification, notifications, and account recovery 5 + * Supports SMTP and external providers (Resend, SendGrid, etc.) 6 + */ 7 + 8 + import crypto from 'crypto'; 9 + import nodemailer from 'nodemailer'; 10 + import type { Transporter } from 'nodemailer'; 11 + import type { DatabaseService } from './database.js'; 12 + import type { EmailProviderConfig, UserEmail } from '../types/email.js'; 13 + 14 + export interface EmailServiceConfig { 15 + provider: 'smtp' | 'resend' | 'sendgrid' | 'mailgun' | 'mock'; 16 + smtp?: { 17 + host: string; 18 + port: number; 19 + secure?: boolean; 20 + user?: string; 21 + pass?: string; 22 + }; 23 + apiKey?: string; 24 + from: string; 25 + codeExpiry?: number; // in seconds, default 15 minutes 26 + } 27 + 28 + export class EmailService { 29 + private transporter: Transporter | null = null; 30 + private config: EmailServiceConfig; 31 + private codeExpiry: number; 32 + 33 + constructor( 34 + private db: DatabaseService, 35 + config: EmailServiceConfig 36 + ) { 37 + this.config = config; 38 + this.codeExpiry = config.codeExpiry || 15 * 60; // 15 minutes default 39 + 40 + if (config.provider === 'smtp' && config.smtp) { 41 + this.transporter = nodemailer.createTransport({ 42 + host: config.smtp.host, 43 + port: config.smtp.port, 44 + secure: config.smtp.secure ?? config.smtp.port === 465, 45 + auth: config.smtp.user ? { 46 + user: config.smtp.user, 47 + pass: config.smtp.pass, 48 + } : undefined, 49 + }); 50 + } else if (config.provider === 'mock') { 51 + // Mock transport for testing 52 + this.transporter = nodemailer.createTransport({ 53 + host: 'localhost', 54 + port: 1025, 55 + ignoreTLS: true, 56 + }); 57 + } 58 + } 59 + 60 + /** 61 + * Add an email to a user's account 62 + */ 63 + async addEmail(did: string, email: string): Promise<{ success: boolean; error?: string }> { 64 + // Check if email is already registered to another user 65 + const existing = this.db.getUserByEmail(email); 66 + if (existing && existing.did !== did) { 67 + return { success: false, error: 'Email already registered to another account' }; 68 + } 69 + 70 + // Save email (unverified) 71 + const emails = this.db.getUserEmails(did); 72 + const isPrimary = emails.length === 0; 73 + 74 + this.db.saveUserEmail({ 75 + did, 76 + email, 77 + verified: false, 78 + is_primary: isPrimary, 79 + }); 80 + 81 + // Generate and send verification code 82 + const result = await this.sendVerificationCode(email, 'verify'); 83 + if (!result.success) { 84 + return result; 85 + } 86 + 87 + return { success: true }; 88 + } 89 + 90 + /** 91 + * Send verification code to email 92 + */ 93 + async sendVerificationCode( 94 + email: string, 95 + purpose: 'verify' | 'recovery' 96 + ): Promise<{ success: boolean; error?: string }> { 97 + // Generate 6-digit code 98 + const code = this.generateCode(); 99 + const codeHash = this.hashCode(code); 100 + 101 + // Store code 102 + this.db.saveEmailVerificationCode({ 103 + email, 104 + code_hash: codeHash, 105 + purpose, 106 + expires_at: new Date(Date.now() + this.codeExpiry * 1000), 107 + used: false, 108 + }); 109 + 110 + // Send email 111 + const subject = purpose === 'verify' 112 + ? 'Verify your email address' 113 + : 'Account recovery code'; 114 + 115 + const text = purpose === 'verify' 116 + ? `Your verification code is: ${code}\n\nThis code expires in ${Math.floor(this.codeExpiry / 60)} minutes.` 117 + : `Your recovery code is: ${code}\n\nThis code expires in ${Math.floor(this.codeExpiry / 60)} minutes.\n\nIf you did not request this, please ignore this email.`; 118 + 119 + try { 120 + await this.sendEmail(email, subject, text); 121 + return { success: true }; 122 + } catch (error) { 123 + console.error('[Email] Failed to send email:', error); 124 + return { success: false, error: 'Failed to send email' }; 125 + } 126 + } 127 + 128 + /** 129 + * Verify email code 130 + */ 131 + verifyEmail( 132 + did: string, 133 + email: string, 134 + code: string 135 + ): { success: boolean; error?: string } { 136 + const codeHash = this.hashCode(code); 137 + const verificationCode = this.db.getValidEmailVerificationCode(email, codeHash, 'verify'); 138 + 139 + if (!verificationCode) { 140 + return { success: false, error: 'Invalid or expired verification code' }; 141 + } 142 + 143 + // Mark code as used 144 + this.db.markEmailVerificationCodeUsed(verificationCode.id); 145 + 146 + // Mark email as verified 147 + this.db.verifyUserEmail(did, email); 148 + 149 + return { success: true }; 150 + } 151 + 152 + /** 153 + * Request account recovery 154 + */ 155 + async requestRecovery(email: string): Promise<{ success: boolean; error?: string }> { 156 + // Check if email is verified 157 + const user = this.db.getUserByEmail(email); 158 + if (!user) { 159 + // Don't reveal if email exists or not 160 + return { success: true }; 161 + } 162 + 163 + // Send recovery code 164 + return this.sendVerificationCode(email, 'recovery'); 165 + } 166 + 167 + /** 168 + * Verify recovery code 169 + */ 170 + verifyRecovery( 171 + email: string, 172 + code: string 173 + ): { success: boolean; did?: string; error?: string } { 174 + const codeHash = this.hashCode(code); 175 + const verificationCode = this.db.getValidEmailVerificationCode(email, codeHash, 'recovery'); 176 + 177 + if (!verificationCode) { 178 + return { success: false, error: 'Invalid or expired recovery code' }; 179 + } 180 + 181 + // Get user DID 182 + const user = this.db.getUserByEmail(email); 183 + if (!user) { 184 + return { success: false, error: 'No account found for this email' }; 185 + } 186 + 187 + // Mark code as used 188 + this.db.markEmailVerificationCodeUsed(verificationCode.id); 189 + 190 + return { success: true, did: user.did }; 191 + } 192 + 193 + /** 194 + * Get user's emails 195 + */ 196 + getUserEmails(did: string): UserEmail[] { 197 + return this.db.getUserEmails(did); 198 + } 199 + 200 + /** 201 + * Remove an email from user's account 202 + */ 203 + removeEmail(did: string, email: string): { success: boolean; error?: string } { 204 + const emails = this.db.getUserEmails(did); 205 + const emailToRemove = emails.find(e => e.email === email); 206 + 207 + if (!emailToRemove) { 208 + return { success: false, error: 'Email not found' }; 209 + } 210 + 211 + // Don't allow removing the primary email if there are other emails 212 + if (emailToRemove.is_primary && emails.length > 1) { 213 + return { success: false, error: 'Cannot remove primary email. Set another email as primary first.' }; 214 + } 215 + 216 + this.db.deleteUserEmail(did, email); 217 + return { success: true }; 218 + } 219 + 220 + /** 221 + * Set an email as primary 222 + */ 223 + setPrimaryEmail(did: string, email: string): { success: boolean; error?: string } { 224 + const emails = this.db.getUserEmails(did); 225 + const emailToSetPrimary = emails.find(e => e.email === email); 226 + 227 + if (!emailToSetPrimary) { 228 + return { success: false, error: 'Email not found' }; 229 + } 230 + 231 + if (!emailToSetPrimary.verified) { 232 + return { success: false, error: 'Only verified emails can be set as primary' }; 233 + } 234 + 235 + this.db.setPrimaryEmail(did, email); 236 + return { success: true }; 237 + } 238 + 239 + /** 240 + * Resend verification code 241 + */ 242 + async resendVerificationCode(email: string): Promise<{ success: boolean; error?: string }> { 243 + return this.sendVerificationCode(email, 'verify'); 244 + } 245 + 246 + /** 247 + * Generate a 6-digit verification code 248 + */ 249 + private generateCode(): string { 250 + return crypto.randomInt(100000, 999999).toString(); 251 + } 252 + 253 + /** 254 + * Hash a verification code for storage 255 + */ 256 + private hashCode(code: string): string { 257 + return crypto.createHash('sha256').update(code).digest('hex'); 258 + } 259 + 260 + /** 261 + * Send an email 262 + */ 263 + private async sendEmail(to: string, subject: string, text: string): Promise<void> { 264 + if (this.config.provider === 'mock') { 265 + console.log(`[Email] Mock send to ${to}: ${subject}`); 266 + console.log(`[Email] ${text}`); 267 + return; 268 + } 269 + 270 + if (!this.transporter) { 271 + throw new Error('Email transporter not configured'); 272 + } 273 + 274 + await this.transporter.sendMail({ 275 + from: this.config.from, 276 + to, 277 + subject, 278 + text, 279 + }); 280 + } 281 + 282 + /** 283 + * Verify email transporter is working 284 + */ 285 + async verify(): Promise<boolean> { 286 + if (!this.transporter) { 287 + return false; 288 + } 289 + 290 + try { 291 + await this.transporter.verify(); 292 + return true; 293 + } catch { 294 + return false; 295 + } 296 + } 297 + }
+253
gateway/src/services/mfa.ts
··· 1 + /** 2 + * MFA Service 3 + * 4 + * Handles TOTP (Time-based One-Time Password) setup, verification, 5 + * and backup code management 6 + */ 7 + 8 + import { TOTP, Secret } from 'otpauth'; 9 + import crypto from 'crypto'; 10 + import * as QRCode from 'qrcode'; 11 + import type { DatabaseService } from './database.js'; 12 + import type { TOTPSetupResponse, MFAStatus } from '../types/mfa.js'; 13 + 14 + export interface MFAConfig { 15 + issuer: string; 16 + encryptionKey: string; // 32-byte hex string for AES-256-GCM 17 + backupCodesCount?: number; 18 + } 19 + 20 + export class MFAService { 21 + private issuer: string; 22 + private encryptionKey: Buffer; 23 + private backupCodesCount: number; 24 + 25 + constructor( 26 + private db: DatabaseService, 27 + config: MFAConfig 28 + ) { 29 + this.issuer = config.issuer; 30 + this.encryptionKey = Buffer.from(config.encryptionKey, 'hex'); 31 + this.backupCodesCount = config.backupCodesCount || 10; 32 + 33 + if (this.encryptionKey.length !== 32) { 34 + throw new Error('MFA encryption key must be 32 bytes (64 hex characters)'); 35 + } 36 + } 37 + 38 + /** 39 + * Start TOTP setup for a user 40 + * Returns the secret and QR code for authenticator app 41 + */ 42 + async setupTOTP(did: string, handle: string): Promise<TOTPSetupResponse> { 43 + // Generate a new secret 44 + const secret = new Secret({ size: 20 }); // 160-bit secret 45 + 46 + // Create TOTP instance 47 + const totp = new TOTP({ 48 + issuer: this.issuer, 49 + label: handle || did, 50 + algorithm: 'SHA1', 51 + digits: 6, 52 + period: 30, 53 + secret, 54 + }); 55 + 56 + // Generate provisioning URI 57 + const uri = totp.toString(); 58 + 59 + // Generate QR code 60 + const qrCode = await QRCode.toDataURL(uri); 61 + 62 + // Encrypt and store the secret (not yet enabled) 63 + const encryptedSecret = this.encryptSecret(secret.base32); 64 + this.db.saveMFATOTP({ 65 + did, 66 + secret_encrypted: encryptedSecret, 67 + enabled: false, 68 + }); 69 + 70 + return { 71 + secret: secret.base32, 72 + qr_code: qrCode, 73 + manual_entry_key: secret.base32, 74 + issuer: this.issuer, 75 + account_name: handle || did, 76 + }; 77 + } 78 + 79 + /** 80 + * Verify TOTP code during setup and enable TOTP 81 + */ 82 + verifyAndEnableTOTP(did: string, code: string): boolean { 83 + const config = this.db.getMFATOTP(did); 84 + if (!config) { 85 + return false; 86 + } 87 + 88 + // Decrypt the secret 89 + const secretBase32 = this.decryptSecret(config.secret_encrypted); 90 + 91 + // Verify the code 92 + const totp = new TOTP({ 93 + issuer: this.issuer, 94 + algorithm: 'SHA1', 95 + digits: 6, 96 + period: 30, 97 + secret: Secret.fromBase32(secretBase32), 98 + }); 99 + 100 + const delta = totp.validate({ token: code, window: 1 }); 101 + 102 + if (delta !== null) { 103 + // Enable TOTP 104 + this.db.enableMFATOTP(did); 105 + return true; 106 + } 107 + 108 + return false; 109 + } 110 + 111 + /** 112 + * Verify TOTP code during login 113 + */ 114 + verifyTOTP(did: string, code: string): boolean { 115 + const config = this.db.getMFATOTP(did); 116 + if (!config || !config.enabled) { 117 + return false; 118 + } 119 + 120 + // Decrypt the secret 121 + const secretBase32 = this.decryptSecret(config.secret_encrypted); 122 + 123 + // Verify the code 124 + const totp = new TOTP({ 125 + issuer: this.issuer, 126 + algorithm: 'SHA1', 127 + digits: 6, 128 + period: 30, 129 + secret: Secret.fromBase32(secretBase32), 130 + }); 131 + 132 + const delta = totp.validate({ token: code, window: 1 }); 133 + return delta !== null; 134 + } 135 + 136 + /** 137 + * Disable TOTP for a user 138 + */ 139 + disableTOTP(did: string): void { 140 + this.db.disableMFATOTP(did); 141 + } 142 + 143 + /** 144 + * Check if user has TOTP enabled 145 + */ 146 + isTOTPEnabled(did: string): boolean { 147 + const config = this.db.getMFATOTP(did); 148 + return config?.enabled ?? false; 149 + } 150 + 151 + /** 152 + * Generate backup codes for a user 153 + */ 154 + generateBackupCodes(did: string): string[] { 155 + const codes: string[] = []; 156 + const codeHashes: string[] = []; 157 + 158 + for (let i = 0; i < this.backupCodesCount; i++) { 159 + // Generate 8-character alphanumeric code (split into 4-4 for readability) 160 + const part1 = crypto.randomBytes(2).toString('hex').toUpperCase(); 161 + const part2 = crypto.randomBytes(2).toString('hex').toUpperCase(); 162 + const code = `${part1}-${part2}`; 163 + codes.push(code); 164 + 165 + // Hash the code for storage 166 + const hash = crypto.createHash('sha256').update(code).digest('hex'); 167 + codeHashes.push(hash); 168 + } 169 + 170 + // Store hashed codes 171 + this.db.saveBackupCodes(did, codeHashes); 172 + 173 + return codes; 174 + } 175 + 176 + /** 177 + * Verify a backup code 178 + */ 179 + verifyBackupCode(did: string, code: string): boolean { 180 + // Hash the provided code 181 + const codeHash = crypto.createHash('sha256').update(code.toUpperCase()).digest('hex'); 182 + 183 + // Look up unused backup code 184 + const backupCode = this.db.getUnusedBackupCode(did, codeHash); 185 + if (!backupCode) { 186 + return false; 187 + } 188 + 189 + // Mark as used 190 + this.db.markBackupCodeUsed(backupCode.id); 191 + return true; 192 + } 193 + 194 + /** 195 + * Get MFA status for a user 196 + */ 197 + getMFAStatus(did: string, passkeyCount: number): MFAStatus { 198 + const totpConfig = this.db.getMFATOTP(did); 199 + const unusedBackupCodes = this.db.countUnusedBackupCodes(did); 200 + 201 + return { 202 + totp_enabled: totpConfig?.enabled ?? false, 203 + totp_verified_at: totpConfig?.verified_at?.toISOString(), 204 + passkey_count: passkeyCount, 205 + backup_codes_remaining: unusedBackupCodes, 206 + }; 207 + } 208 + 209 + /** 210 + * Verify MFA (TOTP or backup code) 211 + */ 212 + verifyMFA(did: string, code: string, type: 'totp' | 'backup_code'): boolean { 213 + if (type === 'totp') { 214 + return this.verifyTOTP(did, code); 215 + } else if (type === 'backup_code') { 216 + return this.verifyBackupCode(did, code); 217 + } 218 + return false; 219 + } 220 + 221 + /** 222 + * Encrypt a TOTP secret using AES-256-GCM 223 + */ 224 + private encryptSecret(secret: string): string { 225 + const iv = crypto.randomBytes(12); 226 + const cipher = crypto.createCipheriv('aes-256-gcm', this.encryptionKey, iv); 227 + 228 + let encrypted = cipher.update(secret, 'utf8', 'hex'); 229 + encrypted += cipher.final('hex'); 230 + const authTag = cipher.getAuthTag(); 231 + 232 + // Return iv:authTag:encrypted 233 + return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`; 234 + } 235 + 236 + /** 237 + * Decrypt a TOTP secret 238 + */ 239 + private decryptSecret(encryptedData: string): string { 240 + const [ivHex, authTagHex, encrypted] = encryptedData.split(':'); 241 + 242 + const iv = Buffer.from(ivHex, 'hex'); 243 + const authTag = Buffer.from(authTagHex, 'hex'); 244 + 245 + const decipher = crypto.createDecipheriv('aes-256-gcm', this.encryptionKey, iv); 246 + decipher.setAuthTag(authTag); 247 + 248 + let decrypted = decipher.update(encrypted, 'hex', 'utf8'); 249 + decrypted += decipher.final('utf8'); 250 + 251 + return decrypted; 252 + } 253 + }
+142
gateway/src/services/oidc/claims.ts
··· 1 + /** 2 + * OIDC Claims Builder 3 + * 4 + * Builds standard OIDC claims based on requested scopes 5 + */ 6 + 7 + import type { UserInfoResponse } from '../../types/index.js'; 8 + 9 + /** 10 + * Supported OIDC scopes 11 + */ 12 + export const SUPPORTED_SCOPES = ['openid', 'profile', 'email', 'offline_access'] as const; 13 + 14 + export type SupportedScope = (typeof SUPPORTED_SCOPES)[number]; 15 + 16 + /** 17 + * Check if a scope is supported 18 + */ 19 + export function isSupportedScope(scope: string): scope is SupportedScope { 20 + return SUPPORTED_SCOPES.includes(scope as SupportedScope); 21 + } 22 + 23 + /** 24 + * Parse and validate a scope string 25 + */ 26 + export function parseScopes(scopeString: string): string[] { 27 + return scopeString.split(' ').filter((s) => s.length > 0); 28 + } 29 + 30 + /** 31 + * Filter to only supported scopes 32 + */ 33 + export function filterSupportedScopes(scopes: string[]): string[] { 34 + return scopes.filter(isSupportedScope); 35 + } 36 + 37 + /** 38 + * Check if openid scope is included (required for OIDC) 39 + */ 40 + export function hasOpenIdScope(scopes: string[]): boolean { 41 + return scopes.includes('openid'); 42 + } 43 + 44 + /** 45 + * Check if offline_access scope is included (for refresh tokens) 46 + */ 47 + export function hasOfflineAccessScope(scopes: string[]): boolean { 48 + return scopes.includes('offline_access'); 49 + } 50 + 51 + /** 52 + * Build UserInfo response based on scopes 53 + */ 54 + export function buildUserInfo( 55 + user: { 56 + did: string; 57 + handle: string; 58 + email?: string; 59 + emailVerified?: boolean; 60 + }, 61 + scopes: string[] 62 + ): UserInfoResponse { 63 + // sub is always included 64 + const response: UserInfoResponse = { 65 + sub: user.did, 66 + }; 67 + 68 + // profile scope: name, preferred_username, etc. 69 + if (scopes.includes('profile')) { 70 + response.handle = user.handle; 71 + response.did = user.did; 72 + response.preferred_username = user.handle; 73 + // Extract name from handle (before the first .) 74 + const namePart = user.handle.split('.')[0]; 75 + if (namePart) { 76 + response.name = namePart; 77 + } 78 + } 79 + 80 + // email scope: email, email_verified 81 + // Note: Email requires user to have added and verified an email 82 + // This will be populated from user_emails table when available 83 + 84 + return response; 85 + } 86 + 87 + /** 88 + * Validate requested scopes against allowed scopes for a client 89 + */ 90 + export function validateScopes(requestedScopes: string[], allowedScopes: string[]): { 91 + valid: boolean; 92 + scopes: string[]; 93 + error?: string; 94 + } { 95 + const requested = parseScopes(requestedScopes.join(' ')); 96 + 97 + // Must include openid for OIDC 98 + if (!hasOpenIdScope(requested)) { 99 + return { 100 + valid: false, 101 + scopes: [], 102 + error: 'openid scope is required', 103 + }; 104 + } 105 + 106 + // Filter to only allowed and supported scopes 107 + const filtered = requested.filter( 108 + (scope) => allowedScopes.includes(scope) && isSupportedScope(scope) 109 + ); 110 + 111 + // Check if any requested scope was denied 112 + const denied = requested.filter( 113 + (scope) => !allowedScopes.includes(scope) || !isSupportedScope(scope) 114 + ); 115 + 116 + if (denied.length > 0) { 117 + // Return only allowed scopes, but don't fail 118 + return { 119 + valid: true, 120 + scopes: filtered, 121 + }; 122 + } 123 + 124 + return { 125 + valid: true, 126 + scopes: filtered, 127 + }; 128 + } 129 + 130 + /** 131 + * Get default scopes for a client 132 + */ 133 + export function getDefaultScopes(allowedScopes: string[]): string[] { 134 + // Default to openid + profile if allowed 135 + const defaults: string[] = ['openid']; 136 + 137 + if (allowedScopes.includes('profile')) { 138 + defaults.push('profile'); 139 + } 140 + 141 + return defaults; 142 + }
+92
gateway/src/services/oidc/index.ts
··· 1 + /** 2 + * OIDC Services 3 + * 4 + * Central orchestrator for OpenID Connect functionality 5 + */ 6 + 7 + export { KeyManager } from './keys.js'; 8 + export { TokenService } from './tokens.js'; 9 + export * from './pkce.js'; 10 + export * from './claims.js'; 11 + 12 + import { KeyManager } from './keys.js'; 13 + import { TokenService } from './tokens.js'; 14 + import type { DatabaseService } from '../database.js'; 15 + 16 + export interface OIDCConfig { 17 + issuer: string; 18 + keySecret: string; 19 + keyAlgorithm?: 'ES256' | 'RS256'; 20 + } 21 + 22 + /** 23 + * OIDC Service - main entry point for OIDC functionality 24 + */ 25 + export class OIDCService { 26 + public readonly keyManager: KeyManager; 27 + public readonly tokenService: TokenService; 28 + public readonly issuer: string; 29 + 30 + constructor(db: DatabaseService, config: OIDCConfig) { 31 + this.issuer = config.issuer; 32 + this.keyManager = new KeyManager(db, config.keySecret); 33 + this.tokenService = new TokenService(this.keyManager, config.issuer); 34 + } 35 + 36 + /** 37 + * Initialize OIDC - ensure signing key exists 38 + */ 39 + async initialize(algorithm: 'ES256' | 'RS256' = 'ES256'): Promise<void> { 40 + await this.keyManager.ensureSigningKey(algorithm); 41 + } 42 + 43 + /** 44 + * Get the OIDC discovery document 45 + */ 46 + getDiscoveryDocument(): Record<string, unknown> { 47 + return { 48 + issuer: this.issuer, 49 + authorization_endpoint: `${this.issuer}/oauth/authorize`, 50 + token_endpoint: `${this.issuer}/oauth/token`, 51 + userinfo_endpoint: `${this.issuer}/oauth/userinfo`, 52 + revocation_endpoint: `${this.issuer}/oauth/revoke`, 53 + end_session_endpoint: `${this.issuer}/oauth/end_session`, 54 + jwks_uri: `${this.issuer}/.well-known/jwks.json`, 55 + scopes_supported: ['openid', 'profile', 'email', 'offline_access'], 56 + response_types_supported: ['code'], 57 + response_modes_supported: ['query', 'fragment'], 58 + grant_types_supported: ['authorization_code', 'refresh_token'], 59 + subject_types_supported: ['public'], 60 + id_token_signing_alg_values_supported: ['ES256', 'RS256'], 61 + token_endpoint_auth_methods_supported: [ 62 + 'client_secret_basic', 63 + 'client_secret_post', 64 + 'none', 65 + ], 66 + code_challenge_methods_supported: ['S256', 'plain'], 67 + claims_supported: [ 68 + 'sub', 69 + 'iss', 70 + 'aud', 71 + 'exp', 72 + 'iat', 73 + 'nonce', 74 + 'at_hash', 75 + 'handle', 76 + 'did', 77 + 'name', 78 + 'preferred_username', 79 + ], 80 + }; 81 + } 82 + 83 + /** 84 + * Get the JWKS 85 + */ 86 + getJWKS(): { keys: Array<Record<string, unknown>> } { 87 + const jwks = this.keyManager.getJWKS(); 88 + return { 89 + keys: jwks.keys as unknown as Array<Record<string, unknown>>, 90 + }; 91 + } 92 + }
+247
gateway/src/services/oidc/keys.ts
··· 1 + /** 2 + * OIDC Key Management Service 3 + * 4 + * Handles generation, storage, and retrieval of signing keys for OIDC JWTs 5 + */ 6 + 7 + import crypto from 'crypto'; 8 + import type { DatabaseService } from '../database.js'; 9 + import type { OIDCKey, JWK, JWKS } from '../../types/index.js'; 10 + 11 + export class KeyManager { 12 + private encryptionKey: Buffer; 13 + 14 + constructor( 15 + private db: DatabaseService, 16 + encryptionSecret: string 17 + ) { 18 + // Derive a 32-byte key from the secret using SHA-256 19 + this.encryptionKey = crypto.createHash('sha256').update(encryptionSecret).digest(); 20 + } 21 + 22 + /** 23 + * Generate a new signing key pair 24 + */ 25 + async generateKey(algorithm: 'ES256' | 'RS256' = 'ES256'): Promise<string> { 26 + const kid = `key-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`; 27 + 28 + if (algorithm === 'ES256') { 29 + return this.generateES256Key(kid); 30 + } else { 31 + return this.generateRS256Key(kid); 32 + } 33 + } 34 + 35 + private generateES256Key(kid: string): string { 36 + const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { 37 + namedCurve: 'prime256v1', 38 + }); 39 + 40 + // Export private key as PEM and encrypt it 41 + const privatePem = privateKey.export({ type: 'pkcs8', format: 'pem' }) as string; 42 + const encryptedPrivateKey = this.encryptPrivateKey(privatePem); 43 + 44 + // Export public key as JWK 45 + const publicJwk = publicKey.export({ format: 'jwk' }) as { 46 + kty: string; 47 + crv: string; 48 + x: string; 49 + y: string; 50 + }; 51 + 52 + const jwk: JWK = { 53 + kty: publicJwk.kty, 54 + crv: publicJwk.crv, 55 + x: publicJwk.x, 56 + y: publicJwk.y, 57 + use: 'sig', 58 + alg: 'ES256', 59 + kid, 60 + }; 61 + 62 + // Mark current signing key as not for signing 63 + const currentKey = this.db.getCurrentSigningKey(); 64 + if (currentKey) { 65 + this.db.markKeyAsNotSigning(currentKey.kid); 66 + } 67 + 68 + // Save the new key 69 + this.db.saveOIDCKey({ 70 + kid, 71 + algorithm: 'ES256', 72 + private_key_encrypted: encryptedPrivateKey, 73 + public_key_jwk: JSON.stringify(jwk), 74 + is_active: true, 75 + use_for_signing: true, 76 + }); 77 + 78 + return kid; 79 + } 80 + 81 + private generateRS256Key(kid: string): string { 82 + const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { 83 + modulusLength: 2048, 84 + }); 85 + 86 + // Export private key as PEM and encrypt it 87 + const privatePem = privateKey.export({ type: 'pkcs8', format: 'pem' }) as string; 88 + const encryptedPrivateKey = this.encryptPrivateKey(privatePem); 89 + 90 + // Export public key as JWK 91 + const publicJwk = publicKey.export({ format: 'jwk' }) as { 92 + kty: string; 93 + n: string; 94 + e: string; 95 + }; 96 + 97 + const jwk: JWK = { 98 + kty: publicJwk.kty, 99 + n: publicJwk.n, 100 + e: publicJwk.e, 101 + use: 'sig', 102 + alg: 'RS256', 103 + kid, 104 + }; 105 + 106 + // Mark current signing key as not for signing 107 + const currentKey = this.db.getCurrentSigningKey(); 108 + if (currentKey) { 109 + this.db.markKeyAsNotSigning(currentKey.kid); 110 + } 111 + 112 + // Save the new key 113 + this.db.saveOIDCKey({ 114 + kid, 115 + algorithm: 'RS256', 116 + private_key_encrypted: encryptedPrivateKey, 117 + public_key_jwk: JSON.stringify(jwk), 118 + is_active: true, 119 + use_for_signing: true, 120 + }); 121 + 122 + return kid; 123 + } 124 + 125 + /** 126 + * Get the JWKS (JSON Web Key Set) containing all active public keys 127 + */ 128 + getJWKS(): JWKS { 129 + const activeKeys = this.db.getActiveOIDCKeys(); 130 + return { 131 + keys: activeKeys.map((key) => JSON.parse(key.public_key_jwk) as JWK), 132 + }; 133 + } 134 + 135 + /** 136 + * Get the current signing key 137 + */ 138 + getSigningKey(): { kid: string; privateKey: crypto.KeyObject; algorithm: 'ES256' | 'RS256' } | null { 139 + const key = this.db.getCurrentSigningKey(); 140 + if (!key) return null; 141 + 142 + const decryptedPem = this.decryptPrivateKey(key.private_key_encrypted); 143 + const privateKey = crypto.createPrivateKey(decryptedPem); 144 + 145 + return { 146 + kid: key.kid, 147 + privateKey, 148 + algorithm: key.algorithm, 149 + }; 150 + } 151 + 152 + /** 153 + * Get a specific key by kid 154 + */ 155 + getKeyByKid(kid: string): { privateKey: crypto.KeyObject; algorithm: 'ES256' | 'RS256' } | null { 156 + const key = this.db.getOIDCKey(kid); 157 + if (!key || !key.is_active) return null; 158 + 159 + const decryptedPem = this.decryptPrivateKey(key.private_key_encrypted); 160 + const privateKey = crypto.createPrivateKey(decryptedPem); 161 + 162 + return { 163 + privateKey, 164 + algorithm: key.algorithm, 165 + }; 166 + } 167 + 168 + /** 169 + * Rotate keys - generate a new key and mark the old one as not for signing 170 + * Old keys remain active for verification of existing tokens 171 + */ 172 + async rotateKeys(algorithm: 'ES256' | 'RS256' = 'ES256'): Promise<string> { 173 + return this.generateKey(algorithm); 174 + } 175 + 176 + /** 177 + * Deactivate a key (removes from JWKS, can't verify tokens signed with it) 178 + */ 179 + deactivateKey(kid: string): void { 180 + this.db.deactivateKey(kid); 181 + } 182 + 183 + /** 184 + * Delete a key permanently 185 + */ 186 + deleteKey(kid: string): void { 187 + this.db.deleteOIDCKey(kid); 188 + } 189 + 190 + /** 191 + * List all keys (for admin interface) 192 + */ 193 + listKeys(): OIDCKey[] { 194 + return this.db.getActiveOIDCKeys(); 195 + } 196 + 197 + /** 198 + * Ensure at least one signing key exists 199 + */ 200 + async ensureSigningKey(algorithm: 'ES256' | 'RS256' = 'ES256'): Promise<string> { 201 + const currentKey = this.db.getCurrentSigningKey(); 202 + if (currentKey) { 203 + return currentKey.kid; 204 + } 205 + return this.generateKey(algorithm); 206 + } 207 + 208 + /** 209 + * Encrypt a private key using AES-256-GCM 210 + */ 211 + private encryptPrivateKey(pem: string): string { 212 + const iv = crypto.randomBytes(16); 213 + const cipher = crypto.createCipheriv('aes-256-gcm', this.encryptionKey, iv); 214 + 215 + const encrypted = Buffer.concat([ 216 + cipher.update(pem, 'utf8'), 217 + cipher.final(), 218 + ]); 219 + 220 + const authTag = cipher.getAuthTag(); 221 + 222 + // Format: iv (16 bytes) + authTag (16 bytes) + encrypted data 223 + const combined = Buffer.concat([iv, authTag, encrypted]); 224 + return combined.toString('base64'); 225 + } 226 + 227 + /** 228 + * Decrypt a private key using AES-256-GCM 229 + */ 230 + private decryptPrivateKey(encryptedBase64: string): string { 231 + const data = Buffer.from(encryptedBase64, 'base64'); 232 + 233 + const iv = data.subarray(0, 16); 234 + const authTag = data.subarray(16, 32); 235 + const encrypted = data.subarray(32); 236 + 237 + const decipher = crypto.createDecipheriv('aes-256-gcm', this.encryptionKey, iv); 238 + decipher.setAuthTag(authTag); 239 + 240 + const decrypted = Buffer.concat([ 241 + decipher.update(encrypted), 242 + decipher.final(), 243 + ]); 244 + 245 + return decrypted.toString('utf8'); 246 + } 247 + }
+64
gateway/src/services/oidc/pkce.ts
··· 1 + /** 2 + * PKCE (Proof Key for Code Exchange) Utilities 3 + * 4 + * Handles PKCE code challenge verification for OAuth 2.0 5 + */ 6 + 7 + import crypto from 'crypto'; 8 + 9 + /** 10 + * Verify a PKCE code verifier against a code challenge 11 + */ 12 + export function verifyCodeChallenge( 13 + codeVerifier: string, 14 + codeChallenge: string, 15 + method: 'S256' | 'plain' = 'S256' 16 + ): boolean { 17 + if (method === 'plain') { 18 + return codeVerifier === codeChallenge; 19 + } 20 + 21 + // S256: SHA256(code_verifier) base64url encoded 22 + const hash = crypto.createHash('sha256').update(codeVerifier).digest(); 23 + const computed = hash.toString('base64url'); 24 + 25 + return computed === codeChallenge; 26 + } 27 + 28 + /** 29 + * Generate a code verifier for testing 30 + */ 31 + export function generateCodeVerifier(): string { 32 + return crypto.randomBytes(32).toString('base64url'); 33 + } 34 + 35 + /** 36 + * Generate a code challenge from a code verifier 37 + */ 38 + export function generateCodeChallenge(codeVerifier: string, method: 'S256' | 'plain' = 'S256'): string { 39 + if (method === 'plain') { 40 + return codeVerifier; 41 + } 42 + 43 + const hash = crypto.createHash('sha256').update(codeVerifier).digest(); 44 + return hash.toString('base64url'); 45 + } 46 + 47 + /** 48 + * Validate code verifier format 49 + * Must be 43-128 characters, containing only [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" 50 + */ 51 + export function isValidCodeVerifier(codeVerifier: string): boolean { 52 + if (codeVerifier.length < 43 || codeVerifier.length > 128) { 53 + return false; 54 + } 55 + 56 + return /^[A-Za-z0-9\-._~]+$/.test(codeVerifier); 57 + } 58 + 59 + /** 60 + * Validate code challenge method 61 + */ 62 + export function isValidCodeChallengeMethod(method: string): method is 'S256' | 'plain' { 63 + return method === 'S256' || method === 'plain'; 64 + }
+402
gateway/src/services/oidc/tokens.ts
··· 1 + /** 2 + * OIDC Token Service 3 + * 4 + * Handles creation and verification of OIDC JWTs (ID tokens, access tokens) 5 + */ 6 + 7 + import crypto from 'crypto'; 8 + import type { KeyManager } from './keys.js'; 9 + import type { IDTokenClaims, AccessTokenClaims, TokenResponse } from '../../types/index.js'; 10 + 11 + export class TokenService { 12 + constructor( 13 + private keyManager: KeyManager, 14 + private issuer: string 15 + ) {} 16 + 17 + /** 18 + * Create an ID Token 19 + */ 20 + createIdToken(claims: { 21 + sub: string; 22 + aud: string; 23 + nonce?: string; 24 + did: string; 25 + handle: string; 26 + accessToken?: string; 27 + expiresIn?: number; 28 + }): string { 29 + const signingKey = this.keyManager.getSigningKey(); 30 + if (!signingKey) { 31 + throw new Error('No signing key available'); 32 + } 33 + 34 + const now = Math.floor(Date.now() / 1000); 35 + const expiresIn = claims.expiresIn || 3600; 36 + 37 + const payload: IDTokenClaims = { 38 + iss: this.issuer, 39 + sub: claims.sub, 40 + aud: claims.aud, 41 + exp: now + expiresIn, 42 + iat: now, 43 + nonce: claims.nonce, 44 + handle: claims.handle, 45 + did: claims.did, 46 + }; 47 + 48 + // Add at_hash if access token is provided 49 + if (claims.accessToken) { 50 + payload.at_hash = this.computeAtHash(claims.accessToken, signingKey.algorithm); 51 + } 52 + 53 + return this.signJwt(payload as unknown as Record<string, unknown>, signingKey); 54 + } 55 + 56 + /** 57 + * Create an Access Token 58 + */ 59 + createAccessToken(claims: { 60 + sub: string; 61 + clientId: string; 62 + scope: string; 63 + expiresIn?: number; 64 + }): string { 65 + const signingKey = this.keyManager.getSigningKey(); 66 + if (!signingKey) { 67 + throw new Error('No signing key available'); 68 + } 69 + 70 + const now = Math.floor(Date.now() / 1000); 71 + const expiresIn = claims.expiresIn || 3600; 72 + 73 + const payload: AccessTokenClaims = { 74 + iss: this.issuer, 75 + sub: claims.sub, 76 + aud: this.issuer, 77 + exp: now + expiresIn, 78 + iat: now, 79 + jti: crypto.randomUUID(), 80 + client_id: claims.clientId, 81 + scope: claims.scope, 82 + }; 83 + 84 + return this.signJwt(payload as unknown as Record<string, unknown>, signingKey); 85 + } 86 + 87 + /** 88 + * Create a token response for the token endpoint 89 + */ 90 + createTokenResponse(params: { 91 + sub: string; 92 + clientId: string; 93 + scope: string; 94 + did: string; 95 + handle: string; 96 + nonce?: string; 97 + accessTokenTtl?: number; 98 + idTokenTtl?: number; 99 + includeRefreshToken?: boolean; 100 + refreshToken?: string; 101 + }): TokenResponse { 102 + const accessTokenTtl = params.accessTokenTtl || 3600; 103 + const idTokenTtl = params.idTokenTtl || 3600; 104 + 105 + const accessToken = this.createAccessToken({ 106 + sub: params.sub, 107 + clientId: params.clientId, 108 + scope: params.scope, 109 + expiresIn: accessTokenTtl, 110 + }); 111 + 112 + const response: TokenResponse = { 113 + access_token: accessToken, 114 + token_type: 'Bearer', 115 + expires_in: accessTokenTtl, 116 + scope: params.scope, 117 + }; 118 + 119 + // Include ID token if openid scope is requested 120 + if (params.scope.includes('openid')) { 121 + response.id_token = this.createIdToken({ 122 + sub: params.sub, 123 + aud: params.clientId, 124 + nonce: params.nonce, 125 + did: params.did, 126 + handle: params.handle, 127 + accessToken, 128 + expiresIn: idTokenTtl, 129 + }); 130 + } 131 + 132 + // Include refresh token if requested 133 + if (params.includeRefreshToken && params.refreshToken) { 134 + response.refresh_token = params.refreshToken; 135 + } 136 + 137 + return response; 138 + } 139 + 140 + /** 141 + * Verify an access token 142 + */ 143 + verifyAccessToken(token: string): AccessTokenClaims | null { 144 + try { 145 + const payload = this.verifyJwt(token); 146 + if (!payload) return null; 147 + 148 + // Check required claims 149 + if (!payload.iss || !payload.sub || !payload.exp || !payload.client_id) { 150 + return null; 151 + } 152 + 153 + // Check issuer 154 + if (payload.iss !== this.issuer) { 155 + return null; 156 + } 157 + 158 + // Check expiration 159 + const now = Math.floor(Date.now() / 1000); 160 + if (typeof payload.exp !== 'number' || payload.exp < now) { 161 + return null; 162 + } 163 + 164 + return payload as unknown as AccessTokenClaims; 165 + } catch { 166 + return null; 167 + } 168 + } 169 + 170 + /** 171 + * Verify an ID token 172 + */ 173 + verifyIdToken(token: string, expectedAudience?: string, expectedNonce?: string): IDTokenClaims | null { 174 + try { 175 + const payload = this.verifyJwt(token); 176 + if (!payload) return null; 177 + 178 + // Check required claims 179 + if (!payload.iss || !payload.sub || !payload.aud || !payload.exp) { 180 + return null; 181 + } 182 + 183 + // Check issuer 184 + if (payload.iss !== this.issuer) { 185 + return null; 186 + } 187 + 188 + // Check audience if provided 189 + if (expectedAudience && payload.aud !== expectedAudience) { 190 + return null; 191 + } 192 + 193 + // Check nonce if provided 194 + if (expectedNonce && payload.nonce !== expectedNonce) { 195 + return null; 196 + } 197 + 198 + // Check expiration 199 + const now = Math.floor(Date.now() / 1000); 200 + if (typeof payload.exp !== 'number' || payload.exp < now) { 201 + return null; 202 + } 203 + 204 + return payload as unknown as IDTokenClaims; 205 + } catch { 206 + return null; 207 + } 208 + } 209 + 210 + /** 211 + * Sign a JWT 212 + */ 213 + private signJwt( 214 + payload: Record<string, unknown>, 215 + signingKey: { kid: string; privateKey: crypto.KeyObject; algorithm: 'ES256' | 'RS256' } 216 + ): string { 217 + const header = { 218 + alg: signingKey.algorithm, 219 + typ: 'JWT', 220 + kid: signingKey.kid, 221 + }; 222 + 223 + const headerB64 = this.base64urlEncode(JSON.stringify(header)); 224 + const payloadB64 = this.base64urlEncode(JSON.stringify(payload)); 225 + const signingInput = `${headerB64}.${payloadB64}`; 226 + 227 + let signature: Buffer; 228 + if (signingKey.algorithm === 'ES256') { 229 + const sign = crypto.createSign('SHA256'); 230 + sign.update(signingInput); 231 + const derSignature = sign.sign(signingKey.privateKey); 232 + // Convert DER signature to raw r||s format for JWT 233 + signature = this.derToRaw(derSignature); 234 + } else { 235 + const sign = crypto.createSign('SHA256'); 236 + sign.update(signingInput); 237 + signature = sign.sign(signingKey.privateKey); 238 + } 239 + 240 + const signatureB64 = this.base64urlEncode(signature); 241 + return `${signingInput}.${signatureB64}`; 242 + } 243 + 244 + /** 245 + * Verify a JWT signature and return payload 246 + */ 247 + private verifyJwt(token: string): Record<string, unknown> | null { 248 + const parts = token.split('.'); 249 + if (parts.length !== 3) return null; 250 + 251 + const [headerB64, payloadB64, signatureB64] = parts; 252 + 253 + try { 254 + const header = JSON.parse(this.base64urlDecode(headerB64).toString()) as { 255 + alg: string; 256 + kid?: string; 257 + }; 258 + 259 + // Get the key 260 + let key: { privateKey: crypto.KeyObject; algorithm: 'ES256' | 'RS256' } | null = null; 261 + if (header.kid) { 262 + key = this.keyManager.getKeyByKid(header.kid); 263 + } 264 + if (!key) { 265 + const signingKey = this.keyManager.getSigningKey(); 266 + if (signingKey) { 267 + key = { privateKey: signingKey.privateKey, algorithm: signingKey.algorithm }; 268 + } 269 + } 270 + if (!key) return null; 271 + 272 + // Get public key from private key 273 + const publicKey = crypto.createPublicKey(key.privateKey); 274 + 275 + const signingInput = `${headerB64}.${payloadB64}`; 276 + const signature = this.base64urlDecode(signatureB64); 277 + 278 + let isValid: boolean; 279 + if (header.alg === 'ES256') { 280 + // Convert raw r||s signature back to DER for verification 281 + const derSignature = this.rawToDer(signature); 282 + const verify = crypto.createVerify('SHA256'); 283 + verify.update(signingInput); 284 + isValid = verify.verify(publicKey, derSignature); 285 + } else { 286 + const verify = crypto.createVerify('SHA256'); 287 + verify.update(signingInput); 288 + isValid = verify.verify(publicKey, signature); 289 + } 290 + 291 + if (!isValid) return null; 292 + 293 + return JSON.parse(this.base64urlDecode(payloadB64).toString()) as Record<string, unknown>; 294 + } catch { 295 + return null; 296 + } 297 + } 298 + 299 + /** 300 + * Compute at_hash for ID token 301 + */ 302 + private computeAtHash(accessToken: string, algorithm: 'ES256' | 'RS256'): string { 303 + const hashAlg = algorithm === 'ES256' ? 'sha256' : 'sha256'; 304 + const hash = crypto.createHash(hashAlg).update(accessToken).digest(); 305 + // Take the left half of the hash 306 + const leftHalf = hash.subarray(0, hash.length / 2); 307 + return this.base64urlEncode(leftHalf); 308 + } 309 + 310 + /** 311 + * Convert DER-encoded ECDSA signature to raw r||s format 312 + */ 313 + private derToRaw(derSignature: Buffer): Buffer { 314 + // DER format: 0x30 [total-length] 0x02 [r-length] [r] 0x02 [s-length] [s] 315 + let offset = 2; // Skip 0x30 and total length 316 + 317 + // Read r 318 + if (derSignature[offset] !== 0x02) throw new Error('Invalid DER signature'); 319 + offset++; 320 + const rLength = derSignature[offset]; 321 + offset++; 322 + let r = derSignature.subarray(offset, offset + rLength); 323 + offset += rLength; 324 + 325 + // Read s 326 + if (derSignature[offset] !== 0x02) throw new Error('Invalid DER signature'); 327 + offset++; 328 + const sLength = derSignature[offset]; 329 + offset++; 330 + let s = derSignature.subarray(offset, offset + sLength); 331 + 332 + // Remove leading zeros and pad to 32 bytes 333 + if (r.length > 32) r = r.subarray(r.length - 32); 334 + if (s.length > 32) s = s.subarray(s.length - 32); 335 + 336 + const raw = Buffer.alloc(64); 337 + r.copy(raw, 32 - r.length); 338 + s.copy(raw, 64 - s.length); 339 + 340 + return raw; 341 + } 342 + 343 + /** 344 + * Convert raw r||s format to DER-encoded ECDSA signature 345 + */ 346 + private rawToDer(rawSignature: Buffer): Buffer { 347 + if (rawSignature.length !== 64) throw new Error('Invalid raw signature length'); 348 + 349 + let r = rawSignature.subarray(0, 32); 350 + let s = rawSignature.subarray(32, 64); 351 + 352 + // Remove leading zeros 353 + while (r.length > 1 && r[0] === 0 && (r[1] & 0x80) === 0) { 354 + r = r.subarray(1); 355 + } 356 + while (s.length > 1 && s[0] === 0 && (s[1] & 0x80) === 0) { 357 + s = s.subarray(1); 358 + } 359 + 360 + // Add leading zero if high bit is set (to ensure positive integer) 361 + if (r[0] & 0x80) { 362 + r = Buffer.concat([Buffer.from([0]), r]); 363 + } 364 + if (s[0] & 0x80) { 365 + s = Buffer.concat([Buffer.from([0]), s]); 366 + } 367 + 368 + const rLen = r.length; 369 + const sLen = s.length; 370 + const totalLen = 2 + rLen + 2 + sLen; 371 + 372 + const der = Buffer.alloc(2 + totalLen); 373 + let offset = 0; 374 + 375 + der[offset++] = 0x30; // SEQUENCE 376 + der[offset++] = totalLen; 377 + der[offset++] = 0x02; // INTEGER 378 + der[offset++] = rLen; 379 + r.copy(der, offset); 380 + offset += rLen; 381 + der[offset++] = 0x02; // INTEGER 382 + der[offset++] = sLen; 383 + s.copy(der, offset); 384 + 385 + return der; 386 + } 387 + 388 + /** 389 + * Base64url encode 390 + */ 391 + private base64urlEncode(data: string | Buffer): string { 392 + const buffer = typeof data === 'string' ? Buffer.from(data) : data; 393 + return buffer.toString('base64url'); 394 + } 395 + 396 + /** 397 + * Base64url decode 398 + */ 399 + private base64urlDecode(str: string): Buffer { 400 + return Buffer.from(str, 'base64url'); 401 + } 402 + }
+304
gateway/src/services/passkey.ts
··· 1 + /** 2 + * Passkey Service 3 + * 4 + * Handles WebAuthn/FIDO2 passkey registration and authentication 5 + */ 6 + 7 + import { 8 + generateRegistrationOptions, 9 + verifyRegistrationResponse, 10 + generateAuthenticationOptions, 11 + verifyAuthenticationResponse, 12 + type VerifiedRegistrationResponse, 13 + type VerifiedAuthenticationResponse, 14 + } from '@simplewebauthn/server'; 15 + import type { 16 + RegistrationResponseJSON, 17 + AuthenticationResponseJSON, 18 + AuthenticatorTransportFuture, 19 + PublicKeyCredentialCreationOptionsJSON, 20 + PublicKeyCredentialRequestOptionsJSON, 21 + } from '@simplewebauthn/types'; 22 + import type { DatabaseService } from './database.js'; 23 + import type { PasskeyListItem } from '../types/passkey.js'; 24 + 25 + export interface PasskeyConfig { 26 + rpName: string; 27 + rpID: string; 28 + origin: string; 29 + } 30 + 31 + // In-memory challenge storage (in production, use Redis or database) 32 + const pendingChallenges = new Map<string, { challenge: string; expires: number }>(); 33 + 34 + export class PasskeyService { 35 + private rpName: string; 36 + private rpID: string; 37 + private origin: string; 38 + 39 + constructor( 40 + private db: DatabaseService, 41 + config: PasskeyConfig 42 + ) { 43 + this.rpName = config.rpName; 44 + this.rpID = config.rpID; 45 + this.origin = config.origin; 46 + } 47 + 48 + /** 49 + * Generate registration options for a new passkey 50 + */ 51 + async generateRegistrationOptions( 52 + did: string, 53 + handle: string 54 + ): Promise<PublicKeyCredentialCreationOptionsJSON> { 55 + // Get existing credentials for this user 56 + const existingCredentials = this.db.getPasskeyCredentialsByDid(did); 57 + 58 + const options = await generateRegistrationOptions({ 59 + rpName: this.rpName, 60 + rpID: this.rpID, 61 + userID: new TextEncoder().encode(did), 62 + userName: handle || did, 63 + userDisplayName: handle || did.split(':').pop() || did, 64 + attestationType: 'none', // We don't need attestation 65 + excludeCredentials: existingCredentials.map((cred) => ({ 66 + id: cred.id, 67 + transports: cred.transports as AuthenticatorTransportFuture[] | undefined, 68 + })), 69 + authenticatorSelection: { 70 + residentKey: 'preferred', 71 + userVerification: 'preferred', 72 + authenticatorAttachment: undefined, // Allow both platform and cross-platform 73 + }, 74 + supportedAlgorithmIDs: [-7, -257], // ES256, RS256 75 + }); 76 + 77 + // Store challenge for verification 78 + const challengeKey = `reg:${did}`; 79 + pendingChallenges.set(challengeKey, { 80 + challenge: options.challenge, 81 + expires: Date.now() + 5 * 60 * 1000, // 5 minutes 82 + }); 83 + 84 + return options; 85 + } 86 + 87 + /** 88 + * Verify registration response and store credential 89 + */ 90 + async verifyRegistration( 91 + did: string, 92 + handle: string, 93 + response: RegistrationResponseJSON, 94 + name?: string 95 + ): Promise<{ success: boolean; credentialId?: string; error?: string }> { 96 + // Get stored challenge 97 + const challengeKey = `reg:${did}`; 98 + const storedChallenge = pendingChallenges.get(challengeKey); 99 + 100 + if (!storedChallenge) { 101 + return { success: false, error: 'No registration challenge found' }; 102 + } 103 + 104 + if (Date.now() > storedChallenge.expires) { 105 + pendingChallenges.delete(challengeKey); 106 + return { success: false, error: 'Registration challenge expired' }; 107 + } 108 + 109 + try { 110 + const verification: VerifiedRegistrationResponse = await verifyRegistrationResponse({ 111 + response, 112 + expectedChallenge: storedChallenge.challenge, 113 + expectedOrigin: this.origin, 114 + expectedRPID: this.rpID, 115 + requireUserVerification: false, 116 + }); 117 + 118 + if (!verification.verified || !verification.registrationInfo) { 119 + return { success: false, error: 'Verification failed' }; 120 + } 121 + 122 + const { credentialID, credentialPublicKey, counter, credentialDeviceType, credentialBackedUp } = verification.registrationInfo; 123 + 124 + // Store the credential 125 + this.db.savePasskeyCredential({ 126 + id: credentialID, 127 + did, 128 + handle, 129 + public_key: Buffer.from(credentialPublicKey).toString('base64'), 130 + counter, 131 + device_type: credentialDeviceType === 'singleDevice' ? 'platform' : 'cross-platform', 132 + backed_up: credentialBackedUp, 133 + transports: response.response.transports as string[] | undefined ?? null, 134 + name: name || null, 135 + }); 136 + 137 + // Clean up challenge 138 + pendingChallenges.delete(challengeKey); 139 + 140 + return { success: true, credentialId: credentialID }; 141 + } catch (error) { 142 + console.error('[Passkey] Registration verification error:', error); 143 + return { success: false, error: error instanceof Error ? error.message : 'Verification failed' }; 144 + } 145 + } 146 + 147 + /** 148 + * Generate authentication options 149 + */ 150 + async generateAuthenticationOptions(did?: string): Promise<PublicKeyCredentialRequestOptionsJSON> { 151 + // Get user's credentials if DID provided 152 + let allowCredentials: { id: string; transports?: AuthenticatorTransportFuture[] }[] = []; 153 + if (did) { 154 + const credentials = this.db.getPasskeyCredentialsByDid(did); 155 + allowCredentials = credentials.map((cred) => ({ 156 + id: cred.id, 157 + transports: cred.transports as AuthenticatorTransportFuture[] | undefined, 158 + })); 159 + } 160 + 161 + const options = await generateAuthenticationOptions({ 162 + rpID: this.rpID, 163 + allowCredentials: allowCredentials.length > 0 ? allowCredentials : undefined, 164 + userVerification: 'preferred', 165 + }); 166 + 167 + // Store challenge for verification 168 + // Use a unique key since we might not know the DID yet 169 + const challengeKey = `auth:${options.challenge}`; 170 + pendingChallenges.set(challengeKey, { 171 + challenge: options.challenge, 172 + expires: Date.now() + 5 * 60 * 1000, // 5 minutes 173 + }); 174 + 175 + return options; 176 + } 177 + 178 + /** 179 + * Verify authentication response 180 + */ 181 + async verifyAuthentication( 182 + response: AuthenticationResponseJSON, 183 + expectedChallenge: string 184 + ): Promise<{ success: boolean; did?: string; handle?: string; error?: string }> { 185 + // Get stored challenge 186 + const challengeKey = `auth:${expectedChallenge}`; 187 + const storedChallenge = pendingChallenges.get(challengeKey); 188 + 189 + if (!storedChallenge) { 190 + return { success: false, error: 'No authentication challenge found' }; 191 + } 192 + 193 + if (Date.now() > storedChallenge.expires) { 194 + pendingChallenges.delete(challengeKey); 195 + return { success: false, error: 'Authentication challenge expired' }; 196 + } 197 + 198 + // Look up credential 199 + const credentialId = response.id; 200 + const credential = this.db.getPasskeyCredential(credentialId); 201 + 202 + if (!credential) { 203 + return { success: false, error: 'Unknown credential' }; 204 + } 205 + 206 + try { 207 + const verification: VerifiedAuthenticationResponse = await verifyAuthenticationResponse({ 208 + response, 209 + expectedChallenge: storedChallenge.challenge, 210 + expectedOrigin: this.origin, 211 + expectedRPID: this.rpID, 212 + authenticator: { 213 + credentialID: credential.id, 214 + credentialPublicKey: Buffer.from(credential.public_key, 'base64'), 215 + counter: credential.counter, 216 + transports: credential.transports as AuthenticatorTransportFuture[] | undefined, 217 + }, 218 + requireUserVerification: false, 219 + }); 220 + 221 + if (!verification.verified) { 222 + return { success: false, error: 'Verification failed' }; 223 + } 224 + 225 + // Update counter 226 + this.db.updatePasskeyCounter(credential.id, verification.authenticationInfo.newCounter); 227 + 228 + // Clean up challenge 229 + pendingChallenges.delete(challengeKey); 230 + 231 + return { 232 + success: true, 233 + did: credential.did, 234 + handle: credential.handle, 235 + }; 236 + } catch (error) { 237 + console.error('[Passkey] Authentication verification error:', error); 238 + return { success: false, error: error instanceof Error ? error.message : 'Verification failed' }; 239 + } 240 + } 241 + 242 + /** 243 + * List passkeys for a user 244 + */ 245 + listPasskeys(did: string): PasskeyListItem[] { 246 + const credentials = this.db.getPasskeyCredentialsByDid(did); 247 + return credentials.map((cred) => ({ 248 + id: cred.id, 249 + name: cred.name, 250 + device_type: cred.device_type, 251 + backed_up: cred.backed_up, 252 + last_used_at: cred.last_used_at?.toISOString() ?? null, 253 + created_at: cred.created_at.toISOString(), 254 + })); 255 + } 256 + 257 + /** 258 + * Rename a passkey 259 + */ 260 + renamePasskey(did: string, credentialId: string, name: string): boolean { 261 + const credential = this.db.getPasskeyCredential(credentialId); 262 + if (!credential || credential.did !== did) { 263 + return false; 264 + } 265 + this.db.renamePasskey(credentialId, name); 266 + return true; 267 + } 268 + 269 + /** 270 + * Delete a passkey 271 + */ 272 + deletePasskey(did: string, credentialId: string): boolean { 273 + const credential = this.db.getPasskeyCredential(credentialId); 274 + if (!credential || credential.did !== did) { 275 + return false; 276 + } 277 + this.db.deletePasskeyCredential(credentialId); 278 + return true; 279 + } 280 + 281 + /** 282 + * Get passkey count for a user 283 + */ 284 + getPasskeyCount(did: string): number { 285 + return this.db.countPasskeysByDid(did); 286 + } 287 + 288 + /** 289 + * Check if user has any passkeys 290 + */ 291 + hasPasskeys(did: string): boolean { 292 + return this.getPasskeyCount(did) > 0; 293 + } 294 + } 295 + 296 + // Cleanup expired challenges periodically 297 + setInterval(() => { 298 + const now = Date.now(); 299 + for (const [key, value] of pendingChallenges.entries()) { 300 + if (now > value.expires) { 301 + pendingChallenges.delete(key); 302 + } 303 + } 304 + }, 60 * 1000); // Every minute
+81
gateway/src/types/email.ts
··· 1 + /** 2 + * Email Verification Types 3 + * 4 + * Types for email verification and account recovery 5 + */ 6 + 7 + /** User email stored in database */ 8 + export interface UserEmail { 9 + id: number; 10 + did: string; 11 + email: string; 12 + verified: boolean; 13 + verified_at: Date | null; 14 + is_primary: boolean; 15 + created_at: Date; 16 + } 17 + 18 + /** Email verification code stored in database */ 19 + export interface EmailVerificationCode { 20 + id: number; 21 + email: string; 22 + code_hash: string; 23 + purpose: 'verify' | 'recovery'; 24 + expires_at: Date; 25 + used: boolean; 26 + created_at: Date; 27 + } 28 + 29 + /** Add email request */ 30 + export interface AddEmailRequest { 31 + email: string; 32 + } 33 + 34 + /** Verify email request */ 35 + export interface VerifyEmailRequest { 36 + email: string; 37 + code: string; 38 + } 39 + 40 + /** Account recovery request */ 41 + export interface RecoveryRequest { 42 + email: string; 43 + } 44 + 45 + /** Account recovery verify request */ 46 + export interface RecoveryVerifyRequest { 47 + email: string; 48 + code: string; 49 + } 50 + 51 + /** Email status response */ 52 + export interface EmailStatusResponse { 53 + emails: Array<{ 54 + email: string; 55 + verified: boolean; 56 + is_primary: boolean; 57 + verified_at?: string; 58 + created_at: string; 59 + }>; 60 + } 61 + 62 + /** Email verification result */ 63 + export interface EmailVerificationResult { 64 + success: boolean; 65 + email?: string; 66 + error?: string; 67 + } 68 + 69 + /** Email provider configuration */ 70 + export interface EmailProviderConfig { 71 + provider: 'smtp' | 'resend' | 'sendgrid' | 'mailgun'; 72 + smtp?: { 73 + host: string; 74 + port: number; 75 + user: string; 76 + pass: string; 77 + from: string; 78 + }; 79 + api_key?: string; 80 + from_address?: string; 81 + }
+6
gateway/src/types/index.ts
··· 4 4 * Defines the token format and interfaces for application authentication 5 5 */ 6 6 7 + // Re-export all types from sub-modules 8 + export * from './oidc.js'; 9 + export * from './passkey.js'; 10 + export * from './mfa.js'; 11 + export * from './email.js'; 12 + 7 13 export interface GatewayTokenPayload { 8 14 /** AT Protocol DID (e.g., "did:plc:xyz...") */ 9 15 did: string;
+65
gateway/src/types/mfa.ts
··· 1 + /** 2 + * MFA/TOTP Types 3 + * 4 + * Types for multi-factor authentication 5 + */ 6 + 7 + /** TOTP configuration stored in database */ 8 + export interface MFATOTPConfig { 9 + did: string; 10 + secret_encrypted: string; 11 + enabled: boolean; 12 + verified_at: Date | null; 13 + created_at: Date; 14 + } 15 + 16 + /** Backup code stored in database */ 17 + export interface MFABackupCode { 18 + id: number; 19 + did: string; 20 + code_hash: string; 21 + used: boolean; 22 + used_at: Date | null; 23 + created_at: Date; 24 + } 25 + 26 + /** TOTP setup response */ 27 + export interface TOTPSetupResponse { 28 + secret: string; 29 + qr_code: string; 30 + manual_entry_key: string; 31 + issuer: string; 32 + account_name: string; 33 + } 34 + 35 + /** TOTP verify setup request */ 36 + export interface TOTPVerifySetupRequest { 37 + code: string; 38 + } 39 + 40 + /** TOTP verify request (during login) */ 41 + export interface TOTPVerifyRequest { 42 + did: string; 43 + code: string; 44 + } 45 + 46 + /** Backup codes response */ 47 + export interface BackupCodesResponse { 48 + codes: string[]; 49 + generated_at: string; 50 + } 51 + 52 + /** MFA status for a user */ 53 + export interface MFAStatus { 54 + totp_enabled: boolean; 55 + totp_verified_at?: string; 56 + passkey_count: number; 57 + backup_codes_remaining: number; 58 + } 59 + 60 + /** MFA verification result */ 61 + export interface MFAVerificationResult { 62 + success: boolean; 63 + method?: 'totp' | 'backup_code' | 'passkey'; 64 + error?: string; 65 + }
+189
gateway/src/types/oidc.ts
··· 1 + /** 2 + * OIDC Types 3 + * 4 + * Types for OpenID Connect provider functionality 5 + */ 6 + 7 + /** OIDC Client types */ 8 + export type OIDCClientType = 'legacy' | 'oidc'; 9 + 10 + /** Token endpoint authentication methods */ 11 + export type TokenEndpointAuthMethod = 12 + | 'client_secret_basic' 13 + | 'client_secret_post' 14 + | 'none'; 15 + 16 + /** Extended app config for OIDC clients */ 17 + export interface OIDCClientConfig { 18 + id: string; 19 + name: string; 20 + client_type: OIDCClientType; 21 + /** HMAC secret for legacy clients */ 22 + hmac_secret?: string; 23 + /** Client secret for OIDC clients (hashed) */ 24 + client_secret?: string; 25 + /** JSON array of redirect URIs */ 26 + redirect_uris: string[]; 27 + /** JSON array of grant types */ 28 + grant_types: string[]; 29 + /** JSON array of allowed scopes */ 30 + allowed_scopes: string[]; 31 + token_ttl_seconds: number; 32 + id_token_ttl_seconds: number; 33 + access_token_ttl_seconds: number; 34 + refresh_token_ttl_seconds: number; 35 + require_pkce: boolean; 36 + token_endpoint_auth_method: TokenEndpointAuthMethod; 37 + created_at: Date; 38 + } 39 + 40 + /** OIDC Signing Key */ 41 + export interface OIDCKey { 42 + kid: string; 43 + algorithm: 'ES256' | 'RS256'; 44 + private_key_encrypted: string; 45 + public_key_jwk: string; 46 + created_at: Date; 47 + expires_at?: Date; 48 + is_active: boolean; 49 + use_for_signing: boolean; 50 + } 51 + 52 + /** JWK (JSON Web Key) for public key exposure */ 53 + export interface JWK { 54 + kty: string; 55 + crv?: string; 56 + x?: string; 57 + y?: string; 58 + n?: string; 59 + e?: string; 60 + use: string; 61 + alg: string; 62 + kid: string; 63 + } 64 + 65 + /** JWKS (JSON Web Key Set) */ 66 + export interface JWKS { 67 + keys: JWK[]; 68 + } 69 + 70 + /** Authorization Code */ 71 + export interface AuthorizationCode { 72 + code: string; 73 + client_id: string; 74 + redirect_uri: string; 75 + scope: string; 76 + nonce?: string; 77 + code_challenge?: string; 78 + code_challenge_method?: 'S256' | 'plain'; 79 + did: string; 80 + handle: string; 81 + user_id?: number; 82 + created_at: number; 83 + expires_at: number; 84 + used: boolean; 85 + } 86 + 87 + /** Refresh Token */ 88 + export interface RefreshToken { 89 + token_hash: string; 90 + client_id: string; 91 + did: string; 92 + handle: string; 93 + user_id?: number; 94 + scope: string; 95 + created_at: Date; 96 + expires_at: Date; 97 + last_used_at?: Date; 98 + revoked: boolean; 99 + family_id?: string; 100 + } 101 + 102 + /** OIDC Discovery Document */ 103 + export interface OIDCDiscoveryDocument { 104 + issuer: string; 105 + authorization_endpoint: string; 106 + token_endpoint: string; 107 + userinfo_endpoint: string; 108 + revocation_endpoint: string; 109 + end_session_endpoint: string; 110 + jwks_uri: string; 111 + scopes_supported: string[]; 112 + response_types_supported: string[]; 113 + response_modes_supported: string[]; 114 + grant_types_supported: string[]; 115 + subject_types_supported: string[]; 116 + id_token_signing_alg_values_supported: string[]; 117 + token_endpoint_auth_methods_supported: string[]; 118 + code_challenge_methods_supported: string[]; 119 + claims_supported: string[]; 120 + } 121 + 122 + /** ID Token Claims */ 123 + export interface IDTokenClaims { 124 + iss: string; 125 + sub: string; 126 + aud: string; 127 + exp: number; 128 + iat: number; 129 + nonce?: string; 130 + at_hash?: string; 131 + /** AT Protocol specific claims */ 132 + handle: string; 133 + did: string; 134 + } 135 + 136 + /** Access Token Claims */ 137 + export interface AccessTokenClaims { 138 + iss: string; 139 + sub: string; 140 + aud: string; 141 + exp: number; 142 + iat: number; 143 + jti: string; 144 + client_id: string; 145 + scope: string; 146 + } 147 + 148 + /** UserInfo Response */ 149 + export interface UserInfoResponse { 150 + sub: string; 151 + handle?: string; 152 + did?: string; 153 + name?: string; 154 + preferred_username?: string; 155 + } 156 + 157 + /** Token Response */ 158 + export interface TokenResponse { 159 + access_token: string; 160 + token_type: 'Bearer'; 161 + expires_in: number; 162 + refresh_token?: string; 163 + id_token?: string; 164 + scope?: string; 165 + } 166 + 167 + /** Token Request */ 168 + export interface TokenRequest { 169 + grant_type: 'authorization_code' | 'refresh_token'; 170 + code?: string; 171 + redirect_uri?: string; 172 + client_id?: string; 173 + client_secret?: string; 174 + code_verifier?: string; 175 + refresh_token?: string; 176 + scope?: string; 177 + } 178 + 179 + /** Authorization Request */ 180 + export interface AuthorizationRequest { 181 + response_type: string; 182 + client_id: string; 183 + redirect_uri: string; 184 + scope: string; 185 + state: string; 186 + nonce?: string; 187 + code_challenge?: string; 188 + code_challenge_method?: 'S256' | 'plain'; 189 + }
+93
gateway/src/types/passkey.ts
··· 1 + /** 2 + * Passkey/WebAuthn Types 3 + * 4 + * Types for WebAuthn/FIDO2 passkey authentication 5 + */ 6 + 7 + /** Passkey credential stored in database */ 8 + export interface PasskeyCredential { 9 + id: string; 10 + did: string; 11 + handle: string; 12 + public_key: string; 13 + counter: number; 14 + device_type: 'platform' | 'cross-platform' | null; 15 + backed_up: boolean; 16 + transports: string[] | null; 17 + name: string | null; 18 + created_at: Date; 19 + last_used_at: Date | null; 20 + } 21 + 22 + /** Passkey list item (for API response) */ 23 + export interface PasskeyListItem { 24 + id: string; 25 + name: string | null; 26 + device_type: string | null; 27 + backed_up: boolean; 28 + last_used_at: string | null; 29 + created_at: string; 30 + } 31 + 32 + /** Registration options request */ 33 + export interface PasskeyRegistrationOptionsRequest { 34 + /** User's DID */ 35 + did: string; 36 + /** User's handle */ 37 + handle: string; 38 + } 39 + 40 + /** Registration verification request */ 41 + export interface PasskeyRegistrationVerifyRequest { 42 + credential: { 43 + id: string; 44 + rawId: string; 45 + response: { 46 + clientDataJSON: string; 47 + attestationObject: string; 48 + transports?: string[]; 49 + }; 50 + type: 'public-key'; 51 + clientExtensionResults?: Record<string, unknown>; 52 + authenticatorAttachment?: string; 53 + }; 54 + name?: string; 55 + } 56 + 57 + /** Authentication options request */ 58 + export interface PasskeyAuthenticationOptionsRequest { 59 + /** Optional: User's DID for conditional UI */ 60 + did?: string; 61 + } 62 + 63 + /** Authentication verification request */ 64 + export interface PasskeyAuthenticationVerifyRequest { 65 + credential: { 66 + id: string; 67 + rawId: string; 68 + response: { 69 + clientDataJSON: string; 70 + authenticatorData: string; 71 + signature: string; 72 + userHandle?: string; 73 + }; 74 + type: 'public-key'; 75 + clientExtensionResults?: Record<string, unknown>; 76 + authenticatorAttachment?: string; 77 + }; 78 + } 79 + 80 + /** Registration result */ 81 + export interface PasskeyRegistrationResult { 82 + success: boolean; 83 + passkey_id?: string; 84 + error?: string; 85 + } 86 + 87 + /** Authentication result */ 88 + export interface PasskeyAuthenticationResult { 89 + success: boolean; 90 + did?: string; 91 + handle?: string; 92 + error?: string; 93 + }
+183
gateway/tekton/README.md
··· 1 + # Tekton CI/CD Pipeline for ATAuth Gateway 2 + 3 + This directory contains Tekton pipeline resources for building and deploying ATAuth Gateway on a k3s cluster. 4 + 5 + ## Prerequisites 6 + 7 + 1. **Tekton Pipelines** installed on your k3s cluster: 8 + ```bash 9 + kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml 10 + ``` 11 + 12 + 2. **Tekton Triggers** for webhook support: 13 + ```bash 14 + kubectl apply -f https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml 15 + kubectl apply -f https://storage.googleapis.com/tekton-releases/triggers/latest/interceptors.yaml 16 + ``` 17 + 18 + 3. **Tekton Dashboard** (optional but recommended): 19 + ```bash 20 + kubectl apply -f https://storage.googleapis.com/tekton-releases/dashboard/latest/release.yaml 21 + ``` 22 + 23 + ## Setup 24 + 25 + ### 1. Configure Registry Credentials 26 + 27 + Create a secret for your container registry: 28 + 29 + ```bash 30 + kubectl create secret docker-registry registry-credentials \ 31 + --docker-server=gitea.cloudforest-basilisk.ts.net \ 32 + --docker-username=<your-username> \ 33 + --docker-password=<your-password-or-token> \ 34 + -n tekton-pipelines 35 + ``` 36 + 37 + Or edit `secrets.yaml` with your credentials and apply. 38 + 39 + ### 2. Configure Git Credentials (if private repo) 40 + 41 + ```bash 42 + kubectl create secret generic git-credentials \ 43 + --from-literal=username=<your-username> \ 44 + --from-literal=password=<your-token> \ 45 + -n tekton-pipelines 46 + ``` 47 + 48 + ### 3. Apply Pipeline Resources 49 + 50 + ```bash 51 + # Apply all Tekton resources 52 + kubectl apply -k tekton/ 53 + 54 + # Or apply individually 55 + kubectl apply -f tekton/rbac.yaml 56 + kubectl apply -f tekton/pipeline.yaml 57 + kubectl apply -f tekton/trigger.yaml 58 + kubectl apply -f tekton/ingress.yaml 59 + ``` 60 + 61 + ### 4. Configure Gitea Webhook 62 + 63 + In your Gitea repository settings, add a webhook: 64 + 65 + - **URL**: `https://tekton-webhook.cloudforest-basilisk.ts.net/atauth` 66 + - **Content Type**: `application/json` 67 + - **Secret**: (optional, add CEL interceptor if using) 68 + - **Events**: Push events 69 + 70 + ## Usage 71 + 72 + ### Manual Pipeline Run 73 + 74 + Trigger a build manually: 75 + 76 + ```bash 77 + kubectl create -f tekton/pipelinerun.yaml 78 + ``` 79 + 80 + Or with custom parameters: 81 + 82 + ```bash 83 + cat <<EOF | kubectl create -f - 84 + apiVersion: tekton.dev/v1beta1 85 + kind: PipelineRun 86 + metadata: 87 + generateName: atauth-gateway-build- 88 + namespace: tekton-pipelines 89 + spec: 90 + pipelineRef: 91 + name: atauth-gateway-build 92 + params: 93 + - name: git-revision 94 + value: "develop" 95 + - name: image-tag 96 + value: "dev-$(date +%Y%m%d-%H%M%S)" 97 + workspaces: 98 + - name: source 99 + volumeClaimTemplate: 100 + spec: 101 + accessModes: ["ReadWriteOnce"] 102 + resources: 103 + requests: 104 + storage: 1Gi 105 + - name: docker-credentials 106 + secret: 107 + secretName: registry-credentials 108 + EOF 109 + ``` 110 + 111 + ### Monitor Pipeline Runs 112 + 113 + ```bash 114 + # List pipeline runs 115 + kubectl get pipelineruns -n tekton-pipelines 116 + 117 + # Watch logs 118 + kubectl logs -f -n tekton-pipelines -l tekton.dev/pipelineRun=<run-name> 119 + 120 + # Or use tkn CLI 121 + tkn pipelinerun logs -f -n tekton-pipelines 122 + ``` 123 + 124 + ### Tekton Dashboard 125 + 126 + If installed, access the dashboard: 127 + 128 + ```bash 129 + kubectl port-forward -n tekton-pipelines svc/tekton-dashboard 9097:9097 130 + ``` 131 + 132 + Then open http://localhost:9097 133 + 134 + ## Pipeline Overview 135 + 136 + ``` 137 + ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ 138 + │ fetch-source │────▶│ build-push │────▶│ deploy │ 139 + │ (git-clone) │ │ (kaniko) │ │ (kubectl apply) │ 140 + └─────────────────┘ └─────────────────┘ └─────────────────┘ 141 + ``` 142 + 143 + 1. **fetch-source**: Clones the Git repository 144 + 2. **build-push**: Builds Docker image with Kaniko and pushes to registry 145 + 3. **deploy**: Applies Kubernetes manifests with new image tag 146 + 147 + ## Files 148 + 149 + | File | Description | 150 + |------|-------------| 151 + | `pipeline.yaml` | Main pipeline definition | 152 + | `pipelinerun.yaml` | Example manual run | 153 + | `trigger.yaml` | Webhook trigger configuration | 154 + | `rbac.yaml` | Service accounts and permissions | 155 + | `secrets.yaml` | Registry and Git credentials template | 156 + | `ingress.yaml` | Ingress for webhook endpoint | 157 + | `kustomization.yaml` | Kustomize configuration | 158 + 159 + ## Troubleshooting 160 + 161 + ### Build fails with permission denied 162 + 163 + Ensure the PVC has correct permissions: 164 + ```yaml 165 + podTemplate: 166 + securityContext: 167 + fsGroup: 65532 168 + ``` 169 + 170 + ### Kaniko can't push to registry 171 + 172 + Verify registry credentials: 173 + ```bash 174 + kubectl get secret registry-credentials -n tekton-pipelines -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d 175 + ``` 176 + 177 + ### Webhook not triggering 178 + 179 + Check EventListener pods: 180 + ```bash 181 + kubectl get pods -n tekton-pipelines -l eventlistener=atauth-gateway-listener 182 + kubectl logs -n tekton-pipelines -l eventlistener=atauth-gateway-listener 183 + ```
+28
gateway/tekton/ingress.yaml
··· 1 + --- 2 + # Ingress for Tekton EventListener webhook 3 + # This allows Gitea to trigger builds via webhook 4 + apiVersion: networking.k8s.io/v1 5 + kind: Ingress 6 + metadata: 7 + name: tekton-atauth-webhook 8 + namespace: tekton-pipelines 9 + annotations: 10 + # Traefik (k3s default) 11 + traefik.ingress.kubernetes.io/router.entrypoints: websecure 12 + traefik.ingress.kubernetes.io/router.tls: "true" 13 + spec: 14 + rules: 15 + - host: tekton-webhook.cloudforest-basilisk.ts.net 16 + http: 17 + paths: 18 + - path: /atauth 19 + pathType: Prefix 20 + backend: 21 + service: 22 + name: el-atauth-gateway-listener 23 + port: 24 + number: 8080 25 + tls: 26 + - hosts: 27 + - tekton-webhook.cloudforest-basilisk.ts.net 28 + secretName: tekton-webhook-tls
+13
gateway/tekton/kustomization.yaml
··· 1 + apiVersion: kustomize.config.k8s.io/v1beta1 2 + kind: Kustomization 3 + 4 + namespace: tekton-pipelines 5 + 6 + resources: 7 + - rbac.yaml 8 + - secrets.yaml 9 + - pipeline.yaml 10 + - trigger.yaml 11 + - ingress.yaml 12 + 13 + # Don't include pipelinerun.yaml - it's for manual runs
+104
gateway/tekton/pipeline.yaml
··· 1 + apiVersion: tekton.dev/v1beta1 2 + kind: Pipeline 3 + metadata: 4 + name: atauth-gateway-build 5 + namespace: tekton-pipelines 6 + spec: 7 + description: Build and deploy ATAuth Gateway 8 + params: 9 + - name: git-url 10 + type: string 11 + description: Git repository URL 12 + default: "https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth.git" 13 + - name: git-revision 14 + type: string 15 + description: Git revision to build 16 + default: "main" 17 + - name: image-registry 18 + type: string 19 + description: Container registry 20 + default: "gitea.cloudforest-basilisk.ts.net" 21 + - name: image-name 22 + type: string 23 + description: Image name 24 + default: "arcnode.xyz/atauth-gateway" 25 + - name: image-tag 26 + type: string 27 + description: Image tag 28 + default: "latest" 29 + - name: deploy-namespace 30 + type: string 31 + description: Kubernetes namespace for deployment 32 + default: "atauth" 33 + 34 + workspaces: 35 + - name: source 36 + description: Workspace for source code 37 + - name: docker-credentials 38 + description: Docker registry credentials 39 + 40 + tasks: 41 + - name: fetch-source 42 + taskRef: 43 + name: git-clone 44 + kind: ClusterTask 45 + workspaces: 46 + - name: output 47 + workspace: source 48 + params: 49 + - name: url 50 + value: $(params.git-url) 51 + - name: revision 52 + value: $(params.git-revision) 53 + - name: depth 54 + value: "1" 55 + 56 + - name: build-push 57 + taskRef: 58 + name: kaniko 59 + kind: ClusterTask 60 + runAfter: 61 + - fetch-source 62 + workspaces: 63 + - name: source 64 + workspace: source 65 + - name: dockerconfig 66 + workspace: docker-credentials 67 + params: 68 + - name: IMAGE 69 + value: $(params.image-registry)/$(params.image-name):$(params.image-tag) 70 + - name: CONTEXT 71 + value: ./gateway 72 + - name: DOCKERFILE 73 + value: ./gateway/Dockerfile 74 + - name: EXTRA_ARGS 75 + value: 76 + - --cache=true 77 + - --cache-repo=$(params.image-registry)/$(params.image-name)/cache 78 + 79 + - name: deploy 80 + taskRef: 81 + name: kubernetes-actions 82 + kind: ClusterTask 83 + runAfter: 84 + - build-push 85 + workspaces: 86 + - name: manifest-dir 87 + workspace: source 88 + params: 89 + - name: script 90 + value: | 91 + cd gateway/k8s 92 + 93 + # Create namespace if not exists 94 + kubectl create namespace $(params.deploy-namespace) --dry-run=client -o yaml | kubectl apply -f - 95 + 96 + # Apply kustomization with new image 97 + kubectl kustomize . | \ 98 + sed "s|atauth-gateway:latest|$(params.image-registry)/$(params.image-name):$(params.image-tag)|g" | \ 99 + kubectl apply -f - 100 + 101 + # Wait for deployment 102 + kubectl -n $(params.deploy-namespace) rollout status deployment/atauth-gateway --timeout=300s 103 + 104 + echo "Deployment complete!"
+36
gateway/tekton/pipelinerun.yaml
··· 1 + apiVersion: tekton.dev/v1beta1 2 + kind: PipelineRun 3 + metadata: 4 + generateName: atauth-gateway-build- 5 + namespace: tekton-pipelines 6 + spec: 7 + pipelineRef: 8 + name: atauth-gateway-build 9 + params: 10 + - name: git-url 11 + value: "https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth.git" 12 + - name: git-revision 13 + value: "main" 14 + - name: image-registry 15 + value: "gitea.cloudforest-basilisk.ts.net" 16 + - name: image-name 17 + value: "arcnode.xyz/atauth-gateway" 18 + - name: image-tag 19 + value: "latest" 20 + - name: deploy-namespace 21 + value: "atauth" 22 + workspaces: 23 + - name: source 24 + volumeClaimTemplate: 25 + spec: 26 + accessModes: 27 + - ReadWriteOnce 28 + resources: 29 + requests: 30 + storage: 1Gi 31 + - name: docker-credentials 32 + secret: 33 + secretName: registry-credentials 34 + podTemplate: 35 + securityContext: 36 + fsGroup: 65532
+71
gateway/tekton/rbac.yaml
··· 1 + --- 2 + apiVersion: v1 3 + kind: ServiceAccount 4 + metadata: 5 + name: tekton-triggers-sa 6 + namespace: tekton-pipelines 7 + secrets: 8 + - name: registry-credentials 9 + - name: git-credentials 10 + --- 11 + apiVersion: rbac.authorization.k8s.io/v1 12 + kind: Role 13 + metadata: 14 + name: tekton-triggers-role 15 + namespace: tekton-pipelines 16 + rules: 17 + - apiGroups: ["triggers.tekton.dev"] 18 + resources: ["eventlisteners", "triggerbindings", "triggertemplates", "triggers"] 19 + verbs: ["get", "list", "watch"] 20 + - apiGroups: ["tekton.dev"] 21 + resources: ["pipelineruns", "pipelineresources"] 22 + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] 23 + - apiGroups: [""] 24 + resources: ["configmaps", "secrets"] 25 + verbs: ["get", "list", "watch"] 26 + --- 27 + apiVersion: rbac.authorization.k8s.io/v1 28 + kind: RoleBinding 29 + metadata: 30 + name: tekton-triggers-rolebinding 31 + namespace: tekton-pipelines 32 + subjects: 33 + - kind: ServiceAccount 34 + name: tekton-triggers-sa 35 + namespace: tekton-pipelines 36 + roleRef: 37 + kind: Role 38 + name: tekton-triggers-role 39 + apiGroup: rbac.authorization.k8s.io 40 + --- 41 + # ClusterRole for deploying to other namespaces 42 + apiVersion: rbac.authorization.k8s.io/v1 43 + kind: ClusterRole 44 + metadata: 45 + name: tekton-deploy-role 46 + rules: 47 + - apiGroups: [""] 48 + resources: ["namespaces"] 49 + verbs: ["create", "get", "list"] 50 + - apiGroups: [""] 51 + resources: ["configmaps", "secrets", "services", "persistentvolumeclaims"] 52 + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] 53 + - apiGroups: ["apps"] 54 + resources: ["deployments"] 55 + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] 56 + - apiGroups: ["networking.k8s.io"] 57 + resources: ["ingresses"] 58 + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] 59 + --- 60 + apiVersion: rbac.authorization.k8s.io/v1 61 + kind: ClusterRoleBinding 62 + metadata: 63 + name: tekton-deploy-rolebinding 64 + subjects: 65 + - kind: ServiceAccount 66 + name: tekton-triggers-sa 67 + namespace: tekton-pipelines 68 + roleRef: 69 + kind: ClusterRole 70 + name: tekton-deploy-role 71 + apiGroup: rbac.authorization.k8s.io
+40
gateway/tekton/secrets.yaml
··· 1 + --- 2 + # Registry credentials for Kaniko to push images 3 + # Create with: kubectl create secret docker-registry registry-credentials \ 4 + # --docker-server=gitea.cloudforest-basilisk.ts.net \ 5 + # --docker-username=<username> \ 6 + # --docker-password=<password> \ 7 + # -n tekton-pipelines 8 + apiVersion: v1 9 + kind: Secret 10 + metadata: 11 + name: registry-credentials 12 + namespace: tekton-pipelines 13 + annotations: 14 + tekton.dev/docker-0: https://gitea.cloudforest-basilisk.ts.net 15 + type: kubernetes.io/dockerconfigjson 16 + stringData: 17 + # Replace with actual credentials or use kubectl create secret 18 + .dockerconfigjson: | 19 + { 20 + "auths": { 21 + "gitea.cloudforest-basilisk.ts.net": { 22 + "username": "REPLACE_WITH_USERNAME", 23 + "password": "REPLACE_WITH_PASSWORD", 24 + "auth": "REPLACE_WITH_BASE64_USER_PASS" 25 + } 26 + } 27 + } 28 + --- 29 + # Git credentials for private repos (if needed) 30 + apiVersion: v1 31 + kind: Secret 32 + metadata: 33 + name: git-credentials 34 + namespace: tekton-pipelines 35 + annotations: 36 + tekton.dev/git-0: https://gitea.cloudforest-basilisk.ts.net 37 + type: kubernetes.io/basic-auth 38 + stringData: 39 + username: REPLACE_WITH_USERNAME 40 + password: REPLACE_WITH_TOKEN
+92
gateway/tekton/trigger.yaml
··· 1 + --- 2 + apiVersion: triggers.tekton.dev/v1beta1 3 + kind: TriggerTemplate 4 + metadata: 5 + name: atauth-gateway-trigger-template 6 + namespace: tekton-pipelines 7 + spec: 8 + params: 9 + - name: git-revision 10 + description: The git revision 11 + default: main 12 + - name: git-url 13 + description: The git repository url 14 + resourcetemplates: 15 + - apiVersion: tekton.dev/v1beta1 16 + kind: PipelineRun 17 + metadata: 18 + generateName: atauth-gateway-build- 19 + spec: 20 + pipelineRef: 21 + name: atauth-gateway-build 22 + params: 23 + - name: git-url 24 + value: $(tt.params.git-url) 25 + - name: git-revision 26 + value: $(tt.params.git-revision) 27 + - name: image-tag 28 + value: $(tt.params.git-revision) 29 + workspaces: 30 + - name: source 31 + volumeClaimTemplate: 32 + spec: 33 + accessModes: 34 + - ReadWriteOnce 35 + resources: 36 + requests: 37 + storage: 1Gi 38 + - name: docker-credentials 39 + secret: 40 + secretName: registry-credentials 41 + podTemplate: 42 + securityContext: 43 + fsGroup: 65532 44 + --- 45 + apiVersion: triggers.tekton.dev/v1beta1 46 + kind: TriggerBinding 47 + metadata: 48 + name: atauth-gateway-trigger-binding 49 + namespace: tekton-pipelines 50 + spec: 51 + params: 52 + - name: git-revision 53 + value: $(body.ref) 54 + - name: git-url 55 + value: $(body.repository.clone_url) 56 + --- 57 + apiVersion: triggers.tekton.dev/v1beta1 58 + kind: EventListener 59 + metadata: 60 + name: atauth-gateway-listener 61 + namespace: tekton-pipelines 62 + spec: 63 + serviceAccountName: tekton-triggers-sa 64 + triggers: 65 + - name: gitea-push 66 + interceptors: 67 + - ref: 68 + name: "cel" 69 + params: 70 + - name: "filter" 71 + value: "body.ref in ['refs/heads/main', 'refs/heads/develop']" 72 + - name: "overlays" 73 + value: 74 + - key: truncated_sha 75 + expression: "body.after.truncate(7)" 76 + bindings: 77 + - ref: atauth-gateway-trigger-binding 78 + template: 79 + ref: atauth-gateway-trigger-template 80 + --- 81 + apiVersion: v1 82 + kind: Service 83 + metadata: 84 + name: el-atauth-gateway-listener 85 + namespace: tekton-pipelines 86 + spec: 87 + type: ClusterIP 88 + ports: 89 + - port: 8080 90 + targetPort: 8080 91 + selector: 92 + eventlistener: atauth-gateway-listener