From 21d0ce22d512cc46bd3a8fc112f599a4d54c2e67 Mon Sep 17 00:00:00 2001 From: Doug Horner Date: Fri, 20 Mar 2026 15:18:23 -0400 Subject: [PATCH 1/2] feat: Add quick login button for dev mode - Add split-button dropdown with 'Login as Admin' and 'Login as Standard User' options - Show quick login only when no .env file exists (dev mode) - Auto-create test user and log in with one click - First registered user automatically becomes admin - Show 'No users exist' message on login page when database is empty - Add Playwright tests for quick login and API key creation - Simplify README with developer-focused quick start guide - Update server startup message with clear ready banner --- .gitignore | 3 + create-a-container/README.md | 640 ++-------- create-a-container/package-lock.json | 1171 +++++++++++++++++- create-a-container/package.json | 6 +- create-a-container/playwright.config.js | 28 + create-a-container/routers/login.js | 84 +- create-a-container/routers/register.js | 4 + create-a-container/server.js | 8 +- create-a-container/tests/quick-login.spec.js | 72 ++ create-a-container/views/login.ejs | 47 + 10 files changed, 1453 insertions(+), 610 deletions(-) create mode 100644 create-a-container/playwright.config.js create mode 100644 create-a-container/tests/quick-login.spec.js diff --git a/.gitignore b/.gitignore index 37d7e734..262b15e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ node_modules .env +.playwright-mcp/ +playwright-report/ +test-results/ diff --git a/create-a-container/README.md b/create-a-container/README.md index 4c135ef0..9af6149a 100644 --- a/create-a-container/README.md +++ b/create-a-container/README.md @@ -1,624 +1,140 @@ # Create-a-Container -A web application for managing LXC container creation, configuration, and lifecycle on Proxmox VE infrastructure. Provides a user-friendly interface and REST API for container management with automated database tracking and nginx reverse proxy configuration generation. +Web application for managing LXC containers on Proxmox VE with a user-friendly interface and REST API. -## Data Model - -```mermaid -erDiagram - Node ||--o{ Container : "hosts" - Container ||--o{ Service : "exposes" - - Node { - int id PK - string name UK "Proxmox node name" - string apiUrl "Proxmox API URL" - boolean tlsVerify "Verify TLS certificates" - datetime createdAt - datetime updatedAt - } - - Container { - int id PK - string hostname UK "FQDN hostname" - string username "Owner username" - string status "pending,creating,running,failed" - string template "Template name" - int creationJobId FK "References Job" - int nodeId FK "References Node" - int containerId UK "Proxmox VMID" - string macAddress UK "MAC address (nullable)" - string ipv4Address UK "IPv4 address (nullable)" - string aiContainer "Node type flag" - datetime createdAt - datetime updatedAt - } - - Service { - int id PK - int containerId FK "References Container" - enum type "tcp, udp, or http" - int internalPort "Port inside container" - int externalPort "External port (tcp/udp only)" - boolean tls "TLS enabled (tcp only)" - string externalHostname UK "Public hostname (http only)" - datetime createdAt - datetime updatedAt - } -``` - -**Key Constraints:** -- `(Node.name)` - Unique -- `(Container.hostname)` - Unique -- `(Container.nodeId, Container.containerId)` - Unique (same VMID can exist on different nodes) -- `(Service.externalHostname)` - Unique when type='http' -- `(Service.type, Service.externalPort)` - Unique when type='tcp' or type='udp' +## Quick Start (Local Development) -## Features - -- **User Authentication** - Proxmox VE authentication integration -- **Container Management** - Create, list, and track LXC containers -- **Docker/OCI Support** - Pull and deploy containers from Docker Hub, GHCR, or any OCI registry -- **Service Registry** - Track HTTP/TCP/UDP services running on containers -- **Dynamic Nginx Config** - Generate nginx reverse proxy configurations on-demand -- **Real-time Progress** - SSE (Server-Sent Events) for container creation progress -- **User Registration** - Self-service account request system with email notifications -- **Rate Limiting** - Protection against abuse (100 requests per 15 minutes) - -## Prerequisites - -### System Requirements -- **Node.js** 18.x or higher -- **PostgreSQL** 16 or higher -- **Proxmox VE** cluster with API access -- **SMTP server** for email notifications (optional) - -### Services ```bash -# Install Node.js (Debian/Ubuntu) -curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - -sudo apt-get install -y nodejs - -# Install PostgreSQL -sudo apt-get install postgresql -y +cd create-a-container +npm install +npx sequelize-cli db:migrate +npx sequelize-cli db:seed:all +node server.js ``` -## Installation - -### 1. Clone Repository -```bash -cd /opt -sudo git clone https://github.com/mieweb/opensource-server.git -cd opensource-server/create-a-container -``` +Open **http://localhost:3000** — the login page shows "No users exist yet." -### 2. Install Dependencies -```bash -npm install -``` +### First User = Admin -### 3. Database Setup +1. Click **Register** and create an account +2. Log in immediately (first user is auto-approved as admin) +3. Subsequent users require admin approval or email invite -#### Create Database and User -```sql -CREATE DATABASE opensource_containers CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -CREATE USER 'container_manager'@'localhost' IDENTIFIED BY 'secure_password_here'; -GRANT ALL PRIVILEGES ON opensource_containers.* TO 'container_manager'@'localhost'; -FLUSH PRIVILEGES; -``` +### Reset Database -#### Run Migrations ```bash -npm run db:migrate +rm data/database.sqlite +npx sequelize-cli db:migrate +npx sequelize-cli db:seed:all ``` -This creates the following tables: -- `Containers` - Container records (hostname, IP, MAC, OS, etc.) -- `Services` - Service mappings (ports, protocols, hostnames) +## Features + +- **Container Management** — Create, list, and track LXC containers +- **Docker/OCI Support** — Deploy from Docker Hub, GHCR, or any OCI registry +- **Service Registry** — Track HTTP/TCP/UDP services per container +- **Dynamic Nginx Config** — Auto-generate reverse proxy configurations +- **User Management** — Self-service registration with admin approval +- **Push Notification 2FA** — Optional two-factor authentication -### 4. Configuration +## Configuration -Create a `.env` file in the `create-a-container` directory: +SQLite is used by default for local development. For production, create a `.env` file: ```bash -# Database Configuration +# PostgreSQL (production) +DATABASE_DIALECT=postgres POSTGRES_HOST=localhost POSTGRES_USER=cluster_manager -POSTGRES_PASSWORD=secure_password_here +POSTGRES_PASSWORD=secure_password POSTGRES_DATABASE=cluster_manager -DATABASE_DIALECT=postgres -# Session Configuration -SESSION_SECRET=generate_random_secret_here +# Session (required for production) +SESSION_SECRET=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))") -# Application NODE_ENV=production ``` -#### Generate Session Secret -```bash -node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" -``` +## Production Deployment -### 5. Start Application +### Systemd Service -#### Development Mode (with auto-reload) -```bash -npm run dev -``` - -#### Production Mode -```bash -node server.js -``` - -#### As a System Service -Create `/etc/systemd/system/create-a-container.service`: -```ini -[Unit] -Description=Create-a-Container Service -After=network.target mariadb.service - -[Service] -Type=simple -User=www-data -WorkingDirectory=/opt/opensource-server/create-a-container -Environment=NODE_ENV=production -ExecStart=/usr/bin/node server.js -Restart=always -RestartSec=10 - -[Install] -WantedBy=multi-user.target -``` - -Enable and start: ```bash +sudo cp systemd/create-a-container.service /etc/systemd/system/ sudo systemctl daemon-reload -sudo systemctl enable create-a-container -sudo systemctl start create-a-container -sudo systemctl status create-a-container -``` - -## API Routes - -### Authentication Routes - -#### `GET /login` -Display login page - -#### `POST /login` -Authenticate user with Proxmox VE credentials -- **Body**: `{ username, password }` -- **Returns**: `{ success: true, redirect: "/" }` - -#### `POST /logout` -End user session - -### Container Management Routes - -#### `GET /` -Redirect to `/containers` - -#### `GET /containers` (Auth Required) -List all containers for authenticated user -- **Returns**: HTML page with container list - -#### `GET /containers/new` (Auth Required) -Display container creation form - -#### `POST /containers` -Create a container asynchronously via a background job -- **Body**: `{ hostname, template, customTemplate, services }` where: - - `hostname`: Container hostname - - `template`: Template selection in format "nodeName,vmid" OR "custom" for Docker images - - `customTemplate`: Docker image reference when template="custom" (e.g., `nginx`, `nginx:alpine`, `myorg/myapp:v1`, `ghcr.io/org/image:tag`) - - `services`: Object of service definitions -- **Returns**: Redirect to containers list with flash message -- **Process**: Creates pending container, services, and job in a single transaction. Docker image references are normalized to full format (`host/org/image:tag`). The job-runner executes the actual Proxmox operations. - -#### `DELETE /containers/:id` (Auth Required) -Delete a container from both Proxmox and the database -- **Path Parameter**: `id` - Container database ID -- **Authorization**: User can only delete their own containers -- **Process**: - 1. Verifies container ownership - 2. Deletes container from Proxmox via API - 3. On success, removes container record from database (cascades to services) -- **Returns**: `{ success: true, message: "Container deleted successfully" }` -- **Errors**: - - `404` - Container not found - - `403` - User doesn't own the container - - `500` - Proxmox API deletion failed or node not configured - -#### `GET /status/:jobId` (Auth Required) -View container creation progress page - -#### `GET /api/stream/:jobId` -SSE stream for real-time container creation progress -- **Returns**: Server-Sent Events stream - -### Job Runner & Jobs API Routes - -#### `POST /jobs` (Admin Auth Required) -Enqueue a job for background execution -- **Body**: `{ "command": "" }` -- **Response**: `201 { id, status }` -- **Authorization**: Admin only (prevents arbitrary command execution) -- **Behavior**: Admin's username is recorded in `createdBy` column for audit trail - -#### `GET /jobs/:id` (Auth Required) -Fetch job metadata (command, status, timestamps) -- **Response**: `{ id, command, status, createdAt, updatedAt, createdBy }` -- **Authorization**: Only the job owner or admins may view -- **Returns**: `404` if unauthorized (prevents information leakage) - -#### `GET /jobs/:id/status` (Auth Required) -Fetch job output rows with offset/limit pagination -- **Query Params**: - - `offset` (optional, default 0) - Skip first N rows - - `limit` (optional, max 1000) - Return up to N rows -- **Response**: Array of JobStatus objects `[{ id, jobId, output, createdAt, updatedAt }, ...]` -- **Authorization**: Only the job owner or admins may view -- **Returns**: `404` if unauthorized - -### Job Runner System - -#### Background Job Execution -The job runner (`job-runner.js`) is a background Node.js process that: -1. Polls the `Jobs` table for `pending` status records -2. Claims a job transactionally (sets status to `running` and acquires row lock) -3. Spawns the job command in a shell subprocess -4. Streams stdout/stderr into the `JobStatuses` table in real-time -5. Updates job status to `success` or `failure` on process exit -6. Gracefully cancels running jobs on shutdown (SIGTERM/SIGINT) and marks them `cancelled` - -#### Data Models - -**Job Model** (`models/job.js`) -``` -id INT PRIMARY KEY AUTO_INCREMENT -command VARCHAR(2000) NOT NULL - shell command to execute -createdBy VARCHAR(255) - username of admin who enqueued (nullable for legacy jobs) -status ENUM('pending', 'running', 'success', 'failure', 'cancelled') -createdAt DATETIME -updatedAt DATETIME -``` - -**JobStatus Model** (`models/jobstatus.js`) -``` -id INT PRIMARY KEY AUTO_INCREMENT -jobId INT NOT NULL (FK → Jobs.id, CASCADE delete) -output TEXT - chunk of stdout/stderr from the job -createdAt DATETIME -updatedAt DATETIME +sudo systemctl enable --now create-a-container ``` -**Migrations** -- `migrations/20251117120000-create-jobs.js` -- `migrations/20251117120001-create-jobstatuses.js` (includes `updatedAt`) -- `migrations/20251117120002-add-job-createdby.js` (adds nullable `createdBy` column + index) +### Job Runner (Background Tasks) -#### Running the Job Runner +The job runner processes container creation tasks asynchronously: -**Development (foreground, logs to stdout)** ```bash -cd create-a-container +# Development npm run job-runner -``` -**Production (systemd service)** -Copy `systemd/job-runner.service` to `/etc/systemd/system/job-runner.service`: -```bash +# Production sudo cp systemd/job-runner.service /etc/systemd/system/ -sudo systemctl daemon-reload -sudo systemctl enable --now job-runner.service -sudo systemctl status job-runner.service -``` - -#### Configuration - -**Database** (via `.env`) -- `POSTGRES_HOST`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DATABASE`, `DATABASE_DIALECT` - -**Runner Behavior** (environment variables) -- `JOB_RUNNER_POLL_MS` (default 2000) - Polling interval in milliseconds -- `JOB_RUNNER_CWD` (default cwd) - Working directory for spawned commands -- `NODE_ENV=production` - Recommended for production - -**Systemd Setup** (recommended for production) -Create `/etc/default/container-creator` with DB credentials: -```bash -POSTGRES_HOST=localhost -POSTGRES_USER=cluster_manager -POSTGRES_PASSWORD=secure_password_here -POSTGRES_DATABASE=cluster_manager -DATABASE_DIALECT=postgres -``` - -Update `job-runner.service` to include: -```ini -EnvironmentFile=/etc/default/container-creator +sudo systemctl enable --now job-runner ``` -#### Security Considerations - -1. **Command Injection Risk**: The runner spawns commands via shell. Only admins can enqueue jobs via the API. Do not expose `POST /jobs` to untrusted users. -2. **Job Ownership**: Jobs are scoped by `createdBy`. Only the admin who created the job (or other admins) can view its metadata and output. Non-owners receive `404` (not `403`) to prevent information leakage. -3. **Legacy Jobs**: Jobs created before the `createdBy` migration will have `createdBy = NULL` and are visible only to admins. -4. **Graceful Shutdown**: On SIGTERM/SIGINT, the runner kills all running child processes and marks their jobs as `cancelled`. - -#### Testing & Troubleshooting - -**Insert a test job (SQL)** -```sql -INSERT INTO Jobs (command, status, createdAt, updatedAt) -VALUES ('echo "Hello" && sleep 5 && echo "World"', 'pending', NOW(), NOW()); -``` - -**Inspect job status** -```sql -SELECT id, status, updatedAt FROM Jobs ORDER BY id DESC LIMIT 10; -``` - -**View job output** -```sql -SELECT id, output, createdAt FROM JobStatuses WHERE jobId = 1 ORDER BY id ASC; -``` - -**Long-running test (5 minutes)** -1. Stop runner to keep job pending -```bash -sudo systemctl stop job-runner.service -``` -2. Insert job -```bash -psql -c "INSERT INTO \"Jobs\" (command, status, \"createdAt\", \"updatedAt\") VALUES ('for i in \$(seq 1 300); do echo \"line \$i\"; sleep 1; done', 'pending', NOW(), NOW()) RETURNING id;" -``` -3. Start runner and monitor -```bash -node job-runner.js -# In another terminal: -while sleep 15; do - psql -c "SELECT id, output FROM \"JobStatuses\" WHERE \"jobId\"= ORDER BY id ASC;" -done -``` -4. Check final status -```sql -SELECT id, status FROM Jobs WHERE id = ; -``` - -#### Deployment Checklist - -- [ ] Run migrations: `npm run db:migrate` -- [ ] Deploy `job-runner.js` to target host (e.g., `/opt/container-creator/`) -- [ ] Copy `systemd/job-runner.service` to `/etc/systemd/system/` -- [ ] Create `/etc/default/container-creator` with DB env vars -- [ ] Reload systemd: `sudo systemctl daemon-reload` -- [ ] Enable and start: `sudo systemctl enable --now job-runner.service` -- [ ] Verify runner is running: `sudo systemctl status job-runner.service` -- [ ] Test API by creating a job via `POST /jobs` (admin user) - -#### Future Enhancements - -- Replace raw `command` API with safe task names and parameter mapping -- Add SSE or WebSocket streaming endpoint (`/jobs/:id/stream`) to push log lines to frontend in real-time -- Add batching or file-based logs for high-volume output to reduce DB pressure -- Implement job timeout/deadline and automatic cancellation - -### Configuration Routes - -#### `GET /sites/:siteId/nginx` -Generate nginx configuration for all registered services -- **Returns**: `text/plain` - Complete nginx configuration with all server blocks - -### User Registration Routes - -#### `GET /register` -Display account request form - -#### `POST /register` -Submit account request (sends email to admins) -- **Body**: `{ name, email, username, reason }` -- **Returns**: Success message - -### Utility Routes - -#### `GET /send-test-email` (Dev Only) -Test email configuration (development/testing) - -## Database Schema - -### Containers Table -```sql -id INT PRIMARY KEY AUTO_INCREMENT -hostname VARCHAR(255) UNIQUE NOT NULL -username VARCHAR(255) NOT NULL -status VARCHAR(20) NOT NULL DEFAULT 'pending' -template VARCHAR(255) -creationJobId INT FOREIGN KEY REFERENCES Jobs(id) -nodeId INT FOREIGN KEY REFERENCES Nodes(id) -containerId INT UNSIGNED NOT NULL -macAddress VARCHAR(17) UNIQUE -ipv4Address VARCHAR(45) UNIQUE -aiContainer VARCHAR(50) DEFAULT 'N' -createdAt DATETIME -updatedAt DATETIME -``` - -### Services Table -```sql -id INT PRIMARY KEY AUTO_INCREMENT -containerId INT FOREIGN KEY REFERENCES Containers(id) -type ENUM('tcp', 'udp', 'http') NOT NULL -internalPort INT NOT NULL -externalPort INT -tls BOOLEAN DEFAULT FALSE -externalHostname VARCHAR(255) -createdAt DATETIME -updatedAt DATETIME -``` - -## Configuration Files - -### `config/config.js` -Sequelize database configuration (reads from `.env`) - -### `models/` -- `container.js` - Container model definition -- `service.js` - Service model definition -- `index.js` - Sequelize initialization - -### `data/services.json` -Service type definitions and port mappings - -### `views/` -- `login.html` - Login form -- `form.html` - Container creation form -- `request-account.html` - Account request form -- `status.html` - Container creation progress viewer -- `containers.ejs` - Container list (EJS template) -- `nginx-conf.ejs` - Nginx config generator (EJS template) - -### `public/` -- `style.css` - Application styles - -### `migrations/` -Database migration files for schema management - -## Environment Variables - -### Required -- `POSTGRES_HOST` - Database host (default: localhost) -- `POSTGRES_USER` - Database username -- `POSTGRES_PASSWORD` - Database password -- `POSTGRES_DATABASE` - Database name -- `SESSION_SECRET` - Express session secret (cryptographically random string) - -### Optional -- `NODE_ENV` - Environment (development/production, default: development) - -## Security - -### Authentication -- Proxmox VE integration via API -- Session-based authentication with secure cookies -- Per-route authentication middleware - -### Rate Limiting -- 100 requests per 15-minute window per IP -- Protects against brute force and abuse - -### Session Security -- Session secret required for cookie signing -- Secure cookie flag enabled -- Session data server-side only - -### Input Validation -- URL encoding for all parameters -- Sequelize ORM prevents SQL injection -- Form data validation - -## Troubleshooting - -### Database Connection Issues -```bash -# Test database connection -psql -h localhost -U cluster_manager -d cluster_manager - -# Check if migrations ran -npm run db:migrate - -# Verify tables exist -psql -h localhost -U cluster_manager -d cluster_manager -c "\dt" -``` - -### Application Won't Start -```bash -# Check Node.js version -node --version # Should be 18.x or higher - -# Verify .env file exists and is readable -cat .env - -# Check for syntax errors -node -c server.js - -# Run with verbose logging -NODE_ENV=development node server.js -``` - -### Authentication Failing -```bash -# Verify Proxmox API is accessible -curl -k https://10.15.0.4:8006/api2/json/version +## Data Model -# Check if certificate validation is working -# Edit server.js if using self-signed certs +```mermaid +erDiagram + Site ||--o{ Node : contains + Node ||--o{ Container : hosts + Container ||--o{ Service : exposes + + Site { int id PK; string name; string internalDomain } + Node { int id PK; string name UK; string apiUrl; int siteId FK } + Container { int id PK; string hostname UK; string status; int nodeId FK } + Service { int id PK; string type; int internalPort; int containerId FK } ``` -### Email Not Sending -```bash -# Test SMTP connection -telnet mail.example.com 25 +## API Reference -# Test route (development only) -curl http://localhost:3000/send-test-email -``` +See [openapi.yaml](openapi.yaml) for the complete API specification. -### Port Already in Use -```bash -# Find process using port 3000 -sudo lsof -i :3000 +### Key Endpoints -# Change port in .env or kill conflicting process -kill -9 -``` +| Method | Path | Description | +|--------|------|-------------| +| `GET` | `/login` | Login page | +| `POST` | `/login` | Authenticate user | +| `GET` | `/sites` | List all sites | +| `GET` | `/sites/:id/nodes` | List nodes in a site | +| `GET` | `/sites/:id/containers` | List containers in a site | +| `POST` | `/containers` | Create container (async job) | +| `DELETE` | `/containers/:id` | Delete container | +| `GET` | `/sites/:siteId/nginx` | Generate nginx config | ## Development ### Database Migrations + ```bash -# Create new migration +# Create migration npx sequelize-cli migration:generate --name description-here # Run migrations -npm run db:migrate +npx sequelize-cli db:migrate # Undo last migration npx sequelize-cli db:migrate:undo ``` -### Code Structure +### Project Structure + ``` create-a-container/ -├── server.js # Main Express application -├── package.json # Dependencies and scripts -├── .env # Environment configuration (gitignored) -├── config/ # Sequelize configuration -├── models/ # Database models -├── migrations/ # Database migrations -├── views/ # HTML templates -├── public/ # Static assets -├── data/ # JSON data files -└── bin/ # Utility scripts +├── server.js # Express application +├── config/ # Database configuration +├── models/ # Sequelize models +├── migrations/ # Database migrations +├── seeders/ # Initial data +├── routers/ # Route handlers +├── views/ # EJS templates +├── public/ # Static assets +└── systemd/ # Service files ``` - -## Integration with Nginx Reverse Proxy - -This application generates nginx configurations consumed by the `nginx-reverse-proxy` component: - -1. Containers register their services in the database -2. The `/sites/:siteId/nginx` endpoint generates complete nginx configs -3. The reverse proxy polls this endpoint via cron -4. Nginx automatically reloads with updated configurations - -See `../nginx-reverse-proxy/README.md` for reverse proxy setup. - -## License - -See the main repository LICENSE file. - -## Support - -For issues, questions, or contributions, see the main opensource-server repository. diff --git a/create-a-container/package-lock.json b/create-a-container/package-lock.json index a5c1b3a8..0372ecdd 100644 --- a/create-a-container/package-lock.json +++ b/create-a-container/package-lock.json @@ -21,10 +21,12 @@ "qrcode": "^1.5.4", "sequelize": "^6.37.8", "sequelize-cli": "^6.6.3", + "sqlite3": "^6.0.1", "swagger-ui-express": "^5.0.1", "yamljs": "^0.3.0" }, "devDependencies": { + "@playwright/test": "^1.58.2", "nodemon": "^3.1.10" } }, @@ -33,6 +35,16 @@ "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==" }, + "node_modules/@gar/promise-retry": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", + "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -50,6 +62,58 @@ "node": ">=12" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "license": "ISC", + "optional": true, + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "license": "ISC", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", + "license": "ISC", + "optional": true, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/@one-ini/wasm": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", @@ -74,6 +138,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@scarf/scarf": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", @@ -132,6 +212,16 @@ "node": ">= 0.6" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -185,14 +275,6 @@ "node": ">=16.17.0" } }, - "node_modules/argon2/node_modules/node-addon-api": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", - "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -240,6 +322,26 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -271,6 +373,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -300,21 +422,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -338,6 +445,30 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -346,6 +477,102 @@ "node": ">= 0.8" } }, + "node_modules/cacache": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", + "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/cacache/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "optional": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -407,6 +634,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -644,6 +880,30 @@ "node": ">=0.10.0" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -661,6 +921,15 @@ "node": ">= 0.8" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dijkstrajs": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", @@ -779,6 +1048,25 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -843,6 +1131,22 @@ "node": ">= 0.6" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0", + "optional": true + }, "node_modules/express": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", @@ -945,6 +1249,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -1106,6 +1416,12 @@ "node": ">= 0.8" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -1121,6 +1437,19 @@ "node": ">=10" } }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1194,6 +1523,12 @@ "node": ">= 0.4" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -1314,6 +1649,13 @@ "node": ">= 0.4" } }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause", + "optional": true + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -1333,6 +1675,70 @@ "url": "https://opencollective.com/express" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -1561,6 +1967,40 @@ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, + "node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/make-fetch-happen": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz", + "integrity": "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1646,6 +2086,18 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -1658,15 +2110,152 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.0.3" + }, "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/minipass-fetch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", + "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/minipass-sized": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -1736,22 +2325,126 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "engines": { - "node": ">= 0.6" + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.89.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.6.0.tgz", + "integrity": "sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-gyp": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", + "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "license": "ISC", + "optional": true, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^4.0.0" + }, "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/nodemailer": { @@ -1881,6 +2574,19 @@ "node": ">=8" } }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -2076,6 +2782,53 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/pngjs": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", @@ -2124,6 +2877,43 @@ "node": ">=0.10.0" } }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -2155,6 +2945,16 @@ "dev": true, "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/qrcode": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", @@ -2339,19 +3139,33 @@ "node": ">= 0.10" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">= 6" } }, "node_modules/readdirp": { @@ -2699,6 +3513,51 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -2712,6 +3571,47 @@ "node": ">=10" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -2727,6 +3627,46 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "license": "BSD-3-Clause" }, + "node_modules/sqlite3": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-6.0.1.tgz", + "integrity": "sha512-X0czUUMG2tmSqJpEQa3tCuZSHKIx8PwM53vLZzKp/o6Rpy25fiVfjdbnZ988M8+O3ZWR1ih0K255VumCb3MAnQ==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^8.0.0", + "prebuild-install": "^7.1.3", + "tar": "^7.5.10" + }, + "engines": { + "node": ">=20.17.0" + }, + "optionalDependencies": { + "node-gyp": "12.x" + }, + "peerDependencies": { + "node-gyp": "12.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/ssri": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -2735,6 +3675,15 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -2831,6 +3780,15 @@ "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -2880,6 +3838,104 @@ "express": ">=4.0.0 || >=5.0.0-beta" } }, + "node_modules/tar": { + "version": "7.5.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.12.tgz", + "integrity": "sha512-9TsuLcdhOn4XztcQqhNyq1KOwOOED/3k58JAvtULiYqbO8B/0IBAAIE1hj0Svmm58k27TmcigyDI0deMlgG3uw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2917,6 +3973,18 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -2983,6 +4051,12 @@ "node": ">= 0.8" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -3152,6 +4226,15 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/yamljs": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", diff --git a/create-a-container/package.json b/create-a-container/package.json index b8fdf19d..6a0e3752 100644 --- a/create-a-container/package.json +++ b/create-a-container/package.json @@ -7,7 +7,9 @@ "scripts": { "dev": "nodemon server.js", "db:migrate": "sequelize db:migrate && sequelize db:seed:all", - "job-runner": "node job-runner.js" + "job-runner": "node job-runner.js", + "test": "npx playwright test", + "test:ui": "npx playwright test --ui" }, "dependencies": { "argon2": "^0.44.0", @@ -26,10 +28,12 @@ "qrcode": "^1.5.4", "sequelize": "^6.37.8", "sequelize-cli": "^6.6.3", + "sqlite3": "^6.0.1", "swagger-ui-express": "^5.0.1", "yamljs": "^0.3.0" }, "devDependencies": { + "@playwright/test": "^1.58.2", "nodemon": "^3.1.10" } } diff --git a/create-a-container/playwright.config.js b/create-a-container/playwright.config.js new file mode 100644 index 00000000..45674c19 --- /dev/null +++ b/create-a-container/playwright.config.js @@ -0,0 +1,28 @@ +// @ts-check +const { defineConfig } = require('@playwright/test'); + +module.exports = defineConfig({ + testDir: './tests', + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: 1, + reporter: 'html', + use: { + baseURL: 'http://localhost:3000', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + }, + projects: [ + { + name: 'chromium', + use: { browserName: 'chromium' }, + }, + ], + webServer: { + command: 'node server.js', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + timeout: 120000, + }, +}); diff --git a/create-a-container/routers/login.js b/create-a-container/routers/login.js index e67f683b..47b439ab 100644 --- a/create-a-container/routers/login.js +++ b/create-a-container/routers/login.js @@ -1,18 +1,98 @@ const express = require('express'); const router = express.Router(); +const fs = require('fs'); +const path = require('path'); const { User, Setting } = require('../models'); const { isSafeRelativeUrl } = require('../utils'); +// Check if we're in dev mode (no .env file or NODE_ENV !== 'production') +function isDevMode() { + const envPath = path.join(__dirname, '..', '.env'); + const hasEnvFile = fs.existsSync(envPath); + return !hasEnvFile || process.env.NODE_ENV !== 'production'; +} + // GET / - Display login form -router.get('/', (req, res) => { +router.get('/', async (req, res) => { + const userCount = await User.count(); + const devMode = isDevMode(); res.render('login', { successMessages: req.flash('success'), errorMessages: req.flash('error'), warningMessages: req.flash('warning'), - redirect: req.query.redirect || '/' + redirect: req.query.redirect || '/', + noUsers: userCount === 0, + showQuickLogin: devMode }); }); +// POST /quick - Create test user and auto-login (dev mode only) +router.post('/quick', async (req, res) => { + // Only allow in dev mode + if (!isDevMode()) { + await req.flash('error', 'Quick login is only available in development mode'); + return res.redirect('/login'); + } + + const role = req.body.role || 'admin'; + const isAdmin = role === 'admin'; + const username = isAdmin ? 'admin' : 'testuser'; + const displayName = isAdmin ? 'Admin User' : 'Test User'; + + try { + // Find existing user or create new one + let user = await User.findOne({ + where: { uid: username }, + include: [{ association: 'groups' }] + }); + + if (!user) { + user = await User.create({ + uidNumber: await User.nextUidNumber(), + uid: username, + givenName: isAdmin ? 'Admin' : 'Test', + sn: 'User', + cn: displayName, + mail: `${username}@localhost`, + userPassword: 'test', + status: 'active', + homeDirectory: `/home/${username}`, + }); + + // For admin users, ensure they're in sysadmins group + if (isAdmin) { + const { Group } = require('../models'); + const sysadminsGroup = await Group.findByPk(2000); + if (sysadminsGroup) { + await user.addGroup(sysadminsGroup); + } + } + // Reload user with groups + user = await User.findOne({ + where: { uid: username }, + include: [{ association: 'groups' }] + }); + } + + // Auto-login: set session variables based on user's actual groups + const userIsAdmin = user.groups?.some(g => g.isAdmin) || false; + req.session.user = user.uid; + req.session.isAdmin = userIsAdmin; + + // Save session and redirect to app + req.session.save((err) => { + if (err) { + console.error('Session save error:', err); + } + return res.redirect('/'); + }); + } catch (err) { + console.error('Quick login error:', err); + await req.flash('error', 'Failed to create user: ' + err.message); + return res.redirect('/login'); + } +}); + // POST / - Handle login submission router.post('/', async (req, res) => { const { username, password } = req.body; diff --git a/create-a-container/routers/register.js b/create-a-container/routers/register.js index dc000f7b..756dc214 100644 --- a/create-a-container/routers/register.js +++ b/create-a-container/routers/register.js @@ -74,8 +74,10 @@ router.post('/', async (req, res) => { // Determine user status let status; + let isFirstUser = false; if (await User.count() === 0) { status = 'active'; // First user is always active + isFirstUser = true; } else if (isInvitedUser) { status = 'active'; // Invited users are auto-activated } else { @@ -132,6 +134,8 @@ router.post('/', async (req, res) => { await req.flash('warning', `Account created, but 2FA invite failed: ${inviteResult.error}`); } await req.flash('success', 'Account created successfully! You can now log in.'); + } else if (isFirstUser) { + await req.flash('success', 'Admin account created successfully! You can now log in.'); } else { await req.flash('success', 'Account registered successfully. You will be notified via email once approved.'); } diff --git a/create-a-container/server.js b/create-a-container/server.js index 252be879..dc8b4261 100644 --- a/create-a-container/server.js +++ b/create-a-container/server.js @@ -156,7 +156,13 @@ async function main() { // --- Routes --- const PORT = 3000; - app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`)); + app.listen(PORT, () => { + console.log(''); + console.log('='.repeat(50)); + console.log(' Server ready at http://localhost:' + PORT); + console.log('='.repeat(50)); + console.log(''); + }); // Handles logout app.post('/logout', (req, res) => { diff --git a/create-a-container/tests/quick-login.spec.js b/create-a-container/tests/quick-login.spec.js new file mode 100644 index 00000000..a73d3c74 --- /dev/null +++ b/create-a-container/tests/quick-login.spec.js @@ -0,0 +1,72 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +test.describe('Quick Login and API Key', () => { + test.beforeEach(async ({ page }) => { + // Start from the login page + await page.goto('/login'); + }); + + test('should login as admin using quick login', async ({ page }) => { + // Click the quick login button (Login as Admin) + await page.getByRole('button', { name: 'Login as Admin' }).click(); + + // Should redirect to sites page + await expect(page).toHaveURL(/\/sites/); + + // Should show admin menu items + await expect(page.getByRole('link', { name: 'Users' })).toBeVisible(); + await expect(page.getByRole('link', { name: 'Settings' })).toBeVisible(); + + // Should show New Site button (admin only) + await expect(page.getByRole('button', { name: 'New Site' })).toBeVisible(); + }); + + test('should login as standard user using quick login dropdown', async ({ page }) => { + // Click dropdown toggle + await page.getByRole('button', { name: '▼' }).click(); + + // Select "Login as Standard User" + await page.getByRole('link', { name: 'Login as Standard User' }).click(); + + // Click the button (now labeled "Login as Standard User") + await page.getByRole('button', { name: 'Login as Standard User' }).click(); + + // Should redirect to sites page + await expect(page).toHaveURL(/\/sites/); + + // Should NOT show admin menu items + await expect(page.getByRole('link', { name: 'Users' })).not.toBeVisible(); + await expect(page.getByRole('link', { name: 'Settings' })).not.toBeVisible(); + }); + + test('should create an API key after quick login', async ({ page }) => { + // Quick login as admin + await page.getByRole('button', { name: 'Login as Admin' }).click(); + await expect(page).toHaveURL(/\/sites/); + + // Navigate to API Keys + await page.getByRole('link', { name: 'API Keys' }).click(); + await expect(page).toHaveURL(/\/apikeys/); + + // Click "New API Key" button + await page.getByRole('button', { name: 'Create new API key' }).click(); + await expect(page).toHaveURL(/\/apikeys\/new/); + + // Fill in description + await page.getByRole('textbox', { name: 'Description' }).fill('Test API Key from Playwright'); + + // Generate the key + await page.getByRole('button', { name: 'Generate API Key' }).click(); + + // Should show success message with the API key + // The key is displayed only once, so we check for the success indication + await expect(page.getByText(/API key created/i).or(page.getByText(/Your new API key/i))).toBeVisible(); + + // Navigate back to API keys list + await page.getByRole('link', { name: 'API Keys' }).first().click(); + + // Should see the new key in the list (use the table cell to be specific) + await expect(page.getByRole('cell', { name: 'Test API Key from Playwright' })).toBeVisible(); + }); +}); diff --git a/create-a-container/views/login.ejs b/create-a-container/views/login.ejs index a241f220..bcedd878 100644 --- a/create-a-container/views/login.ejs +++ b/create-a-container/views/login.ejs @@ -34,6 +34,53 @@ <% }) %> <% } %> + + <% if (typeof noUsers !== 'undefined' && noUsers) { %> +
+ Welcome! No users exist yet. Register to create the first admin account. +
+ <% } %> + <% if (typeof showQuickLogin !== 'undefined' && showQuickLogin) { %> + +
+ + <% } %>
From 3718f3f66d9b020a51fbb282af889ba1590dc3e7 Mon Sep 17 00:00:00 2001 From: Doug Horner Date: Fri, 20 Mar 2026 15:31:23 -0400 Subject: [PATCH 2/2] fix: use .first() for cell selector in API key test --- create-a-container/tests/quick-login.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/create-a-container/tests/quick-login.spec.js b/create-a-container/tests/quick-login.spec.js index a73d3c74..91dbd68f 100644 --- a/create-a-container/tests/quick-login.spec.js +++ b/create-a-container/tests/quick-login.spec.js @@ -66,7 +66,7 @@ test.describe('Quick Login and API Key', () => { // Navigate back to API keys list await page.getByRole('link', { name: 'API Keys' }).first().click(); - // Should see the new key in the list (use the table cell to be specific) - await expect(page.getByRole('cell', { name: 'Test API Key from Playwright' })).toBeVisible(); + // Should see the new key in the list (use first() since name may appear in multiple columns) + await expect(page.getByRole('cell', { name: 'Test API Key from Playwright' }).first()).toBeVisible(); }); });