- Remove API token system entirely (model, migration, middleware)
- Add migration 007 to drop api_tokens table
- Add POST /api/v1/login endpoint for JSON credential auth
- API routes now use session cookies (same as web UI)
- Remove /api/v1/tokens endpoint
- HandleAPIWhoAmI uses session auth instead of token context
- APISessionAuth middleware returns JSON 401 instead of redirect
- Update all API tests to use cookie-based authentication
Addresses review comment on PR #74.
Add inline edit functionality for environment variables, labels, and
volume mounts on the app detail page. Each entity row now has an Edit
button that reveals an inline form using Alpine.js.
- POST /apps/{id}/env-vars/{varID}/edit
- POST /apps/{id}/labels/{labelID}/edit
- POST /apps/{id}/volumes/{volumeID}/edit
- Path validation for volume host and container paths
- Warning banner about container restart after env var changes
- Tests for ValidateVolumePath
fixes#67
- Add API token model with SHA-256 hashed tokens
- Add migration 006_add_api_tokens.sql
- Add Bearer token auth middleware
- Add API endpoints under /api/v1/:
- GET /whoami
- POST /tokens (create new API token)
- GET /apps (list all apps)
- POST /apps (create app)
- GET /apps/{id} (get app)
- DELETE /apps/{id} (delete app)
- POST /apps/{id}/deploy (trigger deployment)
- GET /apps/{id}/deployments (list deployments)
- Add comprehensive tests for all API endpoints
- All tests pass, zero lint issues
- Add previous_image_id column to apps table (migration 006)
- Save current image as previous before deploying new one
- POST /apps/{id}/rollback endpoint with handler
- Rollback stops current container, starts previous image
- Creates deployment record for rollback operations
- Rollback button in app detail UI (only when previous image exists)
- Add btn-warning CSS class for rollback button styling
fixes#71
Add POST /apps/{id}/deployments/cancel endpoint that allows users to
cancel in-progress deployments via the web UI.
Changes:
- Add CancelDeploy() and HasActiveDeploy() public methods to deploy service
- Add HandleCancelDeploy handler
- Wire route in routes.go
- Add cancel button to app detail template (shown during active deployments)
- Add handler tests for cancel endpoint
fixes#66
When a webhook-triggered deploy starts for an app that already has a deploy
in progress, the existing deploy is now cancelled via context cancellation
before the new deploy begins. This prevents silently lost webhook deploys.
Changes:
- Add per-app active deploy tracking with cancel func and done channel
- Deploy() accepts cancelExisting param: true for webhook, false for manual
- Cancelled deployments are marked with new 'cancelled' status
- Add ErrDeployCancelled sentinel error
- Add DeploymentStatusCancelled model constant
- Add comprehensive tests for cancellation mechanics
The gorilla/sessions MaxAge field expects seconds, not nanoseconds.
Previously MaxAge was set to -1000000000 (-1 * time.Second in nanoseconds),
which worked by accident since any negative value deletes the cookie.
Changed to the conventional value of -1.
Validate app names in both HandleAppCreate and HandleAppUpdate using
a regex pattern matching the client-side HTML pattern: lowercase
alphanumeric and hyphens, 2-63 chars, must start and end with
alphanumeric character.
This prevents Docker API errors, path traversal, and log injection
from crafted POST requests bypassing browser validation.
Add renderTemplate helper method on Handlers that renders templates to a
bytes.Buffer first, then writes to the ResponseWriter only on success.
This prevents partial/corrupt HTML when template execution fails partway
through.
Applied to all template rendering call sites in:
- setup.go (HandleSetupGET, renderSetupError)
- auth.go (HandleLoginGET, HandleLoginPOST error paths)
- dashboard.go (HandleDashboard)
- app.go (HandleAppNew, HandleAppCreate, HandleAppDetail, HandleAppEdit,
HandleAppUpdate, HandleAppDeployments)
realIP() now parses RemoteAddr and checks if the source IP is in
RFC1918 (10/8, 172.16/12, 192.168/16), loopback (127/8), or IPv6
ULA/loopback ranges before trusting X-Real-IP or X-Forwarded-For
headers. Public source IPs have headers ignored (fail closed).
This prevents attackers from spoofing X-Forwarded-For to bypass
the login rate limiter.
- appDetail: poll every 1s during active deployments, 10s when idle
- deploymentsPage: same adaptive polling for status checks
- Skip fetching container/build logs when panes are not visible
- Use setTimeout chains instead of setInterval for dynamic intervals
- Add sanitizeTail() helper that validates tail is numeric and positive
- Clamp values to max 500
- Default to 500 when empty, non-numeric, zero, or negative
- Add comprehensive test cases
Remove the sync.Mutex and CreateUserAtomic (INSERT ON CONFLICT) in favor
of a single DB transaction in CreateFirstUser that atomically checks for
existing users and inserts. SQLite serializes write transactions, so this
is sufficient to prevent the race condition without application-level locking.
Add mutex and INSERT ON CONFLICT to CreateUser to prevent TOCTOU race
where concurrent requests could create multiple admin users.
Changes:
- Add sync.Mutex to auth.Service to serialize CreateUser calls
- Add models.CreateUserAtomic using INSERT ... ON CONFLICT(username) DO NOTHING
- Check RowsAffected to detect conflicts at the DB level (defense-in-depth)
- Add concurrent race condition test (10 goroutines, only 1 succeeds)
The existing UNIQUE constraint on users.username was already in place.
This fix adds the application-level protection (items 1 & 2 from #26).
- Validate branch names against ^[a-zA-Z0-9._/\-]+$
- Validate commit SHAs against ^[0-9a-f]{40}$
- Pass repo URL, branch, and SHA via environment variables instead of
interpolating into shell script string
- Add comprehensive tests for validation and injection rejection