Commit 11aa227

bryfry <bryon@fryer.io>
2026-01-27 08:16:41
fix login issues
1 parent 388cf70
Changed files (4)
internal/manifest/types.go
@@ -11,7 +11,6 @@ const (
 	StatusInProgress ClipStatus = "in_progress"
 	StatusComplete   ClipStatus = "complete"
 	StatusFailed     ClipStatus = "failed"
-	StatusSkipped    ClipStatus = "skipped"
 )
 
 // ClipEntry represents a single video clip from an event.
internal/protect/client.go
@@ -22,7 +22,6 @@ var (
 	ErrServerError     = errors.New("server error")
 	ErrCameraNotFound  = errors.New("camera not found")
 	ErrMultipleCameras = errors.New("multiple cameras match name")
-	ErrNotLoggedIn     = errors.New("not logged in: call Login() first")
 )
 
 // Client is a UniFi Protect API client.
@@ -30,7 +29,6 @@ type Client struct {
 	baseURL    string
 	apiPath    string // "/proxy/protect/api" or "/api"
 	csrfToken  string
-	loggedIn   bool
 	httpClient *http.Client
 }
 
@@ -136,7 +134,6 @@ func (c *Client) Login(ctx context.Context, username, password string) error {
 		slog.Debug("obtained CSRF token")
 	}
 
-	c.loggedIn = true
 	slog.Debug("login successful")
 	return nil
 }
README.md
@@ -8,9 +8,10 @@ UniFi Protect Video Summary - Create ~10-minute timelapses from a day's recordin
 # Build
 go build ./cmd/upvs
 
-# Set credentials (or use --api-key flag)
+# Set credentials (or use flags)
 export UPVS_HOST=https://192.168.1.1
-export UPVS_API_KEY=your-api-key-here
+export UPVS_USERNAME=your-username
+export UPVS_PASSWORD=your-password
 
 # Run full pipeline for a single day
 ./upvs run --camera "Front Door" --date 2024-01-15 --out ./output
@@ -21,14 +22,18 @@ export UPVS_API_KEY=your-api-key-here
 ./upvs render --camera "Front Door" --date 2024-01-15 --out ./output
 ```
 
-## API Key
+## Authentication
 
-Generate an API key in UniFi OS: **Settings > Control Plane > Integrations**
+The tool authenticates using a local UniFi OS account (username/password).
+Create a dedicated account in UniFi OS for API access.
 
-Pass via:
-- `--api-key` flag
-- `--api-key-file` flag (path to file containing key)
-- `UPVS_API_KEY` environment variable
+Pass credentials via:
+- `--username` and `--password` flags
+- `--password-file` flag (path to file containing password)
+- `UPVS_USERNAME` and `UPVS_PASSWORD` environment variables
+
+**Note:** Prefer `--password-file` or environment variables over `--password` flag
+to avoid exposing credentials in process lists.
 
 ## Output
 
@@ -40,8 +45,23 @@ output/
 โ””โ”€โ”€ metadata/day.json                               # Speed calculation
 ```
 
+## Options
+
+```
+Global Flags:
+  --host             UniFi Protect URL (env: UPVS_HOST)
+  --username         Username (env: UPVS_USERNAME)
+  --password         Password (env: UPVS_PASSWORD)
+  --password-file    Path to file containing password
+  --camera           Camera ID or name
+  --out              Output directory
+  --tls-insecure     Skip TLS verification (for self-signed certs)
+  --direct-api       Use /api path (direct NVR connection, not UniFi OS)
+  --verbose          Enable debug logging
+```
+
 ## Requirements
 
 - Go 1.23+
 - FFmpeg (in PATH)
-- UniFi Protect with API key access
+- UniFi Protect with local account access
SPEC.md
@@ -14,26 +14,34 @@ Non-goals: multi-camera, multi-day batching, motion-only exports, UI automation,
 
 ## 2. Authentication
 
-### API Key Authentication (Recommended)
+### Session-Based Authentication
 
-UniFi Protect supports API key authentication for programmatic access:
+UniFi Protect uses cookie-based session authentication:
 
-- Keys are generated in UniFi OS: **Settings > Control Plane > Integrations**
-- Pass the key via `X-API-Key` HTTP header on all requests
-- No session management, cookies, or login flow required
-- Stateless - each request is independently authenticated
+1. **Login:** `POST /api/auth/login` with JSON body:
+   ```json
+   {"username": "...", "password": "...", "rememberMe": false, "token": ""}
+   ```
+
+2. **Session Cookie:** The response sets a session cookie used for subsequent requests
+
+3. **CSRF Token:** The login response includes an `X-Csrf-Token` header that must be
+   sent with subsequent requests
 
 ```
-GET /proxy/protect/api/bootstrap
-X-API-Key: <your-api-key>
+POST /api/auth/login
+Content-Type: application/json
+
+{"username": "protect-api", "password": "secret", "rememberMe": false, "token": ""}
 ```
 
 ### Implementation Notes
 
-- API keys provide full access to the Protect API
-- Keys do not expire but can be revoked in the UI
+- Create a dedicated local account in UniFi OS for API access
+- Use a cookie jar to persist session cookies across requests
+- Store and send the CSRF token from the login response
 - TLS verification should be enabled; use `--tls-insecure` only for self-signed certs
-- Never log or persist the API key
+- Never log or persist credentials
 
 ### Documentation References
 
@@ -49,7 +57,8 @@ X-API-Key: <your-api-key>
 | Input | Description |
 |-------|-------------|
 | `host` | UniFi Protect URL, e.g. `https://192.168.1.1` |
-| `api_key` | API key from UniFi OS |
+| `username` | Local UniFi OS account username |
+| `password` | Account password (or use `password_file`) |
 | `camera` | Camera ID or name (must resolve uniquely) |
 | `date` | Target day: `YYYY-MM-DD` |
 | `out_dir` | Output root directory |
@@ -63,6 +72,7 @@ X-API-Key: <your-api-key>
 | `crf` | `23` | FFmpeg quality (0-51, lower=better) |
 | `preset` | `medium` | FFmpeg encoding preset |
 | `tls_insecure` | `false` | Skip TLS verification |
+| `direct_api` | `false` | Use `/api` path (direct NVR, not UniFi OS) |
 | `max_workers` | `4` | Download concurrency |
 | `retry_limit` | `3` | Retries per download |
 
@@ -180,7 +190,8 @@ Returns system information including all cameras.
 
 **Pagination:**
 - Request with `limit=100` and `orderDirection=ASC`
-- If response contains `limit` events, fetch next page using `after=<last-event-id>`
+- If response contains `limit` events, fetch next page using the last event's
+  `end` timestamp + 1ms as the new `start` parameter
 - Continue until fewer than `limit` events returned
 
 **Day Window:**
@@ -354,7 +365,7 @@ The pipeline is designed to be restartable:
 
 | Status | Action |
 |--------|--------|
-| 401/403 | Fail immediately - invalid API key |
+| 401/403 | Fail immediately - invalid credentials |
 | 404 | Mark clip as failed, continue with others |
 | 429 | Retry with backoff |
 | 5xx | Retry with backoff |
@@ -362,7 +373,7 @@ The pipeline is designed to be restartable:
 ### Sentinel Errors
 
 Define inspectable errors for programmatic handling:
-- `ErrUnauthorized`: Invalid API key
+- `ErrUnauthorized`: Invalid credentials
 - `ErrCameraNotFound`: Camera doesn't exist
 - `ErrMultipleCameras`: Ambiguous camera name
 - `ErrMaxAttemptsExceeded`: Retries exhausted
@@ -382,13 +393,15 @@ If no events found:
 upvs [global flags] <command>
 
 Global Flags:
-  --host           UniFi Protect URL (env: UPVS_HOST)
-  --api-key        API key (env: UPVS_API_KEY)
-  --api-key-file   Path to file containing API key
-  --camera         Camera ID or name
-  --out            Output directory
-  --tls-insecure   Skip TLS verification
-  --verbose        Enable debug logging
+  --host             UniFi Protect URL (env: UPVS_HOST)
+  --username         Username (env: UPVS_USERNAME)
+  --password         Password (env: UPVS_PASSWORD)
+  --password-file    Path to file containing password
+  --camera           Camera ID or name
+  --out              Output directory
+  --tls-insecure     Skip TLS verification
+  --direct-api       Use /api path (direct NVR, not UniFi OS)
+  --verbose          Enable debug logging
 
 Commands:
   scan    Enumerate events for a day
@@ -436,7 +449,8 @@ Render Flags:
 
 ## 11. Security
 
-- API key must not be logged or written to disk
+- Credentials must not be logged or written to disk
+- Prefer `--password-file` or env vars over `--password` flag (avoids process list exposure)
 - Use `log/slog` structured logging (no credential fields)
 - TLS verification enabled by default
 - `--tls-insecure` must be explicitly set and logged as warning