Commit Graph

65 Commits

Author SHA1 Message Date
user
e42f80814c fix: address noinlineerr lint warning 2026-02-15 21:43:00 -08:00
user
97a5aae2f7 simplify: replace mutex + ON CONFLICT with a single DB transaction
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.
2026-02-15 21:41:52 -08:00
763e722607 fix: prevent setup endpoint race condition (closes #26)
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).
2026-02-15 21:35:16 -08:00
97ee1e212f Merge pull request 'Wait for final log flush before closing deploymentLogWriter (closes #4)' (#9) from clawbot/upaas:fix/issue-4 into main
Reviewed-on: #9
2026-02-16 06:29:18 +01:00
3e8f424129 Merge pull request 'Add rate limiting to login endpoint to prevent brute force (closes #12)' (#14) from clawbot/upaas:fix/issue-12 into main
Reviewed-on: #14
2026-02-16 06:15:48 +01:00
ef0786c4b4 fix: extract real client IP from proxy headers (X-Real-IP / X-Forwarded-For)
Behind a reverse proxy like Traefik, RemoteAddr always contains the
proxy's IP. Add realIP() helper that checks X-Real-IP first, then the
first entry of X-Forwarded-For, falling back to RemoteAddr.

Update both LoginRateLimit and Logging middleware to use realIP().
Add comprehensive tests for the new function.

Fixes #12
2026-02-15 21:14:12 -08:00
dcdecafc61 Merge pull request 'Add ownership verification on resource deletion (closes #19)' (#28) from clawbot/upaas:fix/ownership-verification-on-delete into main
Reviewed-on: #28
2026-02-16 06:12:52 +01:00
867cdf01ab fix: add ownership verification on env var, label, volume, and port deletion
Verify that the resource's AppID matches the URL path app ID before
allowing deletion. Without this check, any authenticated user could
delete resources belonging to any app by providing the target resource's
ID in the URL regardless of the app ID in the path (IDOR vulnerability).

Closes #19
2026-02-15 21:02:46 -08:00
user
a1b06219e7 fix: add eviction for stale IP rate limiter entries and Retry-After header
- Store lastSeen timestamp per IP limiter entry
- Lazy sweep removes entries older than 10 minutes on each request
- Add Retry-After header to 429 responses
- Add test for stale entry eviction

Fixes memory leak under sustained attack from many IPs.
2026-02-15 21:01:11 -08:00
clawbot
66661d1b1d Add rate limiting to login endpoint to prevent brute force
Apply per-IP rate limiting (5 attempts/minute) to POST /login using
golang.org/x/time/rate. Returns 429 Too Many Requests when exceeded.

Closes #12
2026-02-15 21:01:11 -08:00
6475389280 test: add IDOR tests for resource deletion ownership verification
Tests demonstrate that env vars, labels, volumes, and ports can be
deleted via another app's URL path without ownership checks.

All 4 tests fail, confirming the vulnerability described in #19.
2026-02-15 21:00:41 -08:00
3a2bd0e51d Merge pull request 'Set Secure flag on session cookie in production mode (closes #5)' (#10) from clawbot/upaas:fix/issue-5 into main
Reviewed-on: #10
2026-02-16 05:58:22 +01:00
79a3165f90 Merge pull request 'Clean up Docker container when deleting an app (closes #2)' (#7) from clawbot/upaas:fix/issue-2 into main
Reviewed-on: #7
2026-02-16 05:56:56 +01:00
86491b1367 Merge pull request 'Limit webhook request body size to 1MB to prevent DoS (closes #1)' (#6) from clawbot/upaas:fix/issue-1 into main
Reviewed-on: #6
2026-02-16 05:56:14 +01:00
98b8403e8b Merge branch 'main' into fix/issue-1 2026-02-16 05:56:06 +01:00
076442923c Merge pull request 'Use hashed webhook secrets for constant-time comparison (closes #13)' (#15) from clawbot/upaas:fix/issue-13 into main
Reviewed-on: #15
2026-02-16 05:55:46 +01:00
57ea724419 Merge branch 'main' into fix/issue-13 2026-02-16 05:55:17 +01:00
39bcfb7456 Merge pull request 'Add CSRF protection to state-changing POST endpoints (closes #11)' (#16) from clawbot/upaas:fix/issue-11 into main
Reviewed-on: #16
2026-02-16 05:53:37 +01:00
4247162d30 Merge branch 'main' into fix/issue-11 2026-02-16 05:51:25 +01:00
4bad74081b Merge pull request 'rewrite log viewer panes (closes #17)' (#27) from clawbot/upaas:fix/log-viewer-rewrite into main
Reviewed-on: #27
2026-02-16 05:51:12 +01:00
clawbot
be6080280e rewrite log viewer panes: smart auto-scroll with follow button
- Track scroll position per log pane (container logs, build logs, deployment cards)
- Auto-scroll to bottom only when user is already at bottom (tail-follow)
- When user scrolls up to review earlier output, pause auto-scroll
- Show a '↓ Follow' button when auto-scroll is paused; clicking resumes
- Only scroll on actual content changes (skip no-op updates)
- Use overflow-y: auto for proper scrollable containers
- Add break-words to prevent horizontal overflow on long lines

Closes #17
2026-02-15 20:48:43 -08:00
clawbot
b1dc8fcc4e Add CSRF protection to state-changing POST endpoints
Add gorilla/csrf middleware to protect all HTML-serving routes against
cross-site request forgery attacks. The webhook endpoint is excluded
since it uses secret-based authentication.

Changes:
- Add gorilla/csrf v1.7.3 dependency
- Add CSRF() middleware method using session secret as key
- Apply CSRF middleware to all HTML route groups in routes.go
- Pass CSRF token to all templates via addGlobals helper
- Add {{ .CSRFField }} / {{ $.CSRFField }} hidden inputs to all forms

Closes #11
2026-02-15 14:17:55 -08:00
clawbot
72786a9feb fix: use hashed webhook secrets for constant-time comparison
Store a SHA-256 hash of the webhook secret in a new webhook_secret_hash
column. FindAppByWebhookSecret now hashes the incoming secret and queries
by hash, eliminating the SQL string comparison timing side-channel.

- Add migration 005_add_webhook_secret_hash.sql
- Add database.HashWebhookSecret() helper
- Backfill existing secrets on startup
- Update App model to include WebhookSecretHash in all queries
- Update app creation to compute hash at insert time
- Add TestHashWebhookSecret unit test
- Update all test fixtures to set WebhookSecretHash

Closes #13
2026-02-15 14:06:53 -08:00
clawbot
185daab909 fix: set Secure flag on session cookie in production mode (closes #5) 2026-02-08 12:05:09 -08:00
clawbot
69456abd25 fix: wait for final log flush before closing deploymentLogWriter (closes #4) 2026-02-08 12:04:37 -08:00
clawbot
ed4ddc5536 fix: clean up Docker container when deleting an app (closes #2) 2026-02-08 12:02:56 -08:00
clawbot
e212910143 fix: limit webhook request body size to 1MB to prevent DoS (closes #1) 2026-02-08 12:02:06 -08:00
d4eae284b5 Fix build logs auto-scroll with double RAF and change detection
Use double requestAnimationFrame to ensure DOM has fully reflowed before
scrolling, and only scroll when log content actually changes.
2026-01-08 10:31:19 -08:00
ee4afbde80 Fix build logs overflow by using Alpine.js reactive bindings
Replaced manual DOM manipulation with a deploymentCard Alpine component
that uses x-text bindings and x-ref for proper scrolling, matching the
working container logs implementation.
2026-01-08 10:21:16 -08:00
aaa55fd153 Fix app status not updated when deployment fails or service restarts
- Update app status to error when health check fails with an error
- Update app status to error in cleanupStuckDeployments for apps stuck in building state
- This fixes the inconsistency where app shows "building" but deployment is "failed"
2026-01-01 06:59:03 -08:00
a71bf07dbf Fix deploy button strobing by using deployment status
Use latestDeploymentStatus instead of app status to determine if
a deployment is in progress. The deployment status is more reliable
during state transitions and prevents the button from flickering.
2026-01-01 06:52:43 -08:00
ee34f3b70c Change Docker image naming to upaas-appname:deploymentID
- Use upaas-appname format instead of upaas/appname
- Tag with deployment number instead of 'latest'
- Example: upaas-myapp:42
2026-01-01 06:50:54 -08:00
ba4893a98a Simplify logs pane structure to fix scrolling
- Remove details/summary element that may have caused layout issues
- Use simpler layout with header row and logs block below
- Change overflow-auto to overflow-y-scroll to force scrollbar
- Add break-all to handle long lines
2026-01-01 06:48:06 -08:00
c01bfcc9a9 Fix logs pane overflow by moving background/padding to wrapper
Move bg-gray-900, rounded-lg, and p-4 from the pre element to the
wrapper div. This matches the working structure in app_detail.html
and allows overflow-auto to work correctly.
2026-01-01 06:40:31 -08:00
2b63219f66 Fix realtime build logs scrolling in deployment history
- Reload page when new deployment starts but no card exists in DOM
- Only update logs content when it changes to avoid unnecessary reflows
- Use double requestAnimationFrame for reliable scroll-to-bottom timing
2026-01-01 06:10:25 -08:00
2cbcd3d72a Add build log file storage and download functionality
- Write deployment logs to files when deployment finishes (success or failure)
- Log files stored in DataDir/logs/<hostname>/<appname>/<appname>_<sha>_<timestamp>.log.txt
- Capture commit SHA for manual deploys by parsing git rev-parse HEAD after clone
- Add download endpoint for log files at /apps/{id}/deployments/{deploymentID}/download
- Add download link in deployment history view for finished deployments
2026-01-01 06:08:00 -08:00
c4362c3143 Add commit URL to Slack notifications with link and backtick formatting
- Add commit_url column to webhook_events and deployments tables
- Extract commit URL from webhook payload (from commit object or repo URL)
- Format Slack messages with backticks for branch and commit SHA
- Link commit SHA to the actual commit URL on the git server
- Keep plain text format for ntfy notifications
2025-12-31 16:29:22 -08:00
4cd12d717c Remove separate live logs section, update logs in deployment card 2025-12-31 15:06:35 -08:00
a2539ebf3a Fix finished time showing for in-progress deployments 2025-12-31 14:58:41 -08:00
58687e77f5 Always scroll logs to bottom on every update 2025-12-31 14:56:56 -08:00
83986626a4 Fix container name conflict on redeployment
Remove old container before creating new one instead of trying to keep
it for rollback. Rollback isn't safe anyway because database migrations
may have been applied by the new container.

The old stop-then-rollback approach failed because Docker doesn't allow
two containers with the same name, even if one is stopped.
2025-12-31 14:48:16 -08:00
d2f2747ae6 Fix real-time build log streaming and scroll behavior
- Use line-by-line reading for Docker build output instead of io.Copy
  to ensure each log line is written immediately without buffering
- Add isNearBottom() helper to check scroll position before auto-scroll
- Only auto-scroll logs if user was already near bottom (better UX)
- Use requestAnimationFrame for smoother scroll-to-bottom animation
2025-12-31 14:44:15 -08:00
f1cc7d65a6 Integrate Alpine.js for reactive UI
- Add Alpine.js (self-hosted, embedded in static/)
- Refactor app.js to use Alpine.js stores and components
- Update templates to use x-data, x-bind, x-show, x-text directives
- Add reactive deploy button state, live logs, status badges
- Add auto-dismiss alerts with close button and transitions
- Add copy-to-clipboard component with feedback
- Add confirm dialog component for destructive actions
- Add relative time component with auto-update
- Add prettier to make fmt target for JS formatting
2026-01-01 05:37:46 +07:00
ab7e917b03 Add real-time deployment updates and refactor JavaScript
- Add deploy stats (last deploy time, total count) to dashboard
- Add recent-deployments API endpoint for real-time updates
- Add live build logs to deployments history page
- Fix git clone regression (preserve entrypoint for simple clones)
- Refactor JavaScript into shared app.js with page init functions
- Deploy button disables immediately on click
- Auto-refresh deployment list and logs during builds
- Format JavaScript with Prettier (4-space indent)
2026-01-01 05:22:56 +07:00
307955dae1 Improve footer styling and fix license to WTFPL 2025-12-30 15:08:22 +07:00
b3ac3c60c2 Add deployment improvements and UI enhancements
- Clone specific commit SHA from webhook instead of just branch HEAD
- Log webhook payload in deployment logs
- Add build/deploy timing to ntfy and Slack notifications
- Implement container rollback on deploy failure
- Remove old container only after successful deployment
- Show relative times in deployment history (hover for full date)
- Update port mappings UI with labeled text inputs
- Add footer with version info, license, and repo link
- Format deploy key comment as upaas_DATE_appname
2025-12-30 15:05:26 +07:00
bc275f7b9c Add TCP/UDP port mapping support
- Add app_ports table for storing port mappings per app
- Add Port model with CRUD operations
- Add handlers for adding/deleting port mappings
- Add ports section to app detail template
- Update Docker client to configure port bindings when creating containers
- Support both TCP and UDP protocols
2025-12-30 12:11:57 +07:00
4ece7431af Use app name and deployment ID in build directory structure
Change build directory from builds/<app-id>-<random> to
builds/<appname>/<deployment-id>-<random> for better organization
and easier debugging.
2025-12-30 11:57:02 +07:00
2ed23912a9 Update branding to µPaaS by @sneak
Change all references from "upaas" to "µPaaS" in page titles,
headers, and README. Add attribution link to sneak.berlin in
the navigation bar.
2025-12-29 17:04:53 +07:00
dc6500eac6 Fix repository cloning when running inside a container
Use DataDir/builds instead of /tmp for clone directories so that bind
mounts work correctly when upaas itself runs in a Docker container.
The /tmp directory inside the upaas container isn't accessible to the
Docker daemon on the host, causing bind mount failures.

Also fix test setups to pass Config to deploy service and add delay
to webhook test to avoid temp directory cleanup race with async
deployment goroutine.
2025-12-29 17:02:01 +07:00