Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
136dd7e
chore(deps): update dependency postcss to ^8.5.7
renovate[bot] Mar 2, 2026
9d6d2cb
Merge pull request #793 from Wikid82/renovate/feature/beta-release-no…
Wikid82 Mar 2, 2026
938b170
Merge branch 'development' into feature/beta-release
Wikid82 Mar 2, 2026
6cfe8ca
chore(deps): update dependency postcss to ^8.5.8
renovate[bot] Mar 3, 2026
9c20391
Merge pull request #795 from Wikid82/renovate/feature/beta-release-no…
Wikid82 Mar 3, 2026
a1a9ab2
chore(docs): archive uptime monitoring regression investigation plan …
actions-user Mar 2, 2026
3632d0d
fix: user roles to use UserRole type and update related tests
actions-user Mar 3, 2026
a681d6a
feat: remove Account page and add PassthroughLanding page
actions-user Mar 3, 2026
3f12ca0
feat: implement role-based access for settings route and add focus tr…
actions-user Mar 3, 2026
4e4c458
fix: update Caddy Server version to 2.11.1 in architecture documentation
actions-user Mar 3, 2026
e92e7ed
fix: prevent stale-SHA checkout and pin caddy-security in weekly secu…
actions-user Mar 3, 2026
6f408f6
fix: prevent stale-SHA checkout in scheduled CodeQL security scan
actions-user Mar 3, 2026
a3d1ae3
fix: update checkout ref to use full GitHub ref path for accurate bra…
actions-user Mar 3, 2026
0fd0057
feat: Add passthrough role support and related tests
actions-user Mar 3, 2026
a570a33
fix: update opentelemetry http instrumentation to v0.66.0
actions-user Mar 3, 2026
ad2d30b
fix: update postcss to version 8.5.8 for improved stability
actions-user Mar 3, 2026
afbd50b
fix: update @floating-ui and caniuse-lite packages to latest versions…
actions-user Mar 3, 2026
ed27fb0
fix(e2e): update account navigation locator and skip legacy Account.t…
actions-user Mar 3, 2026
fc404da
fix(e2e): resolve shard 4 failures from 3-tier role model changes
actions-user Mar 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/instructions/ARCHITECTURE.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ graph TB
| **HTTP Framework** | Gin | Latest | Routing, middleware, HTTP handling |
| **Database** | SQLite | 3.x | Embedded database |
| **ORM** | GORM | Latest | Database abstraction layer |
| **Reverse Proxy** | Caddy Server | 2.11.0-beta.2 | Embedded HTTP/HTTPS proxy |
| **Reverse Proxy** | Caddy Server | 2.11.1 | Embedded HTTP/HTTPS proxy |
| **WebSocket** | gorilla/websocket | Latest | Real-time log streaming |
| **Crypto** | golang.org/x/crypto | Latest | Password hashing, encryption |
| **Metrics** | Prometheus Client | Latest | Application metrics |
Expand Down
13 changes: 13 additions & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@
"platformAutomerge": true,

"customManagers": [
{
"customType": "regex",
"description": "Track caddy-security plugin version in Dockerfile",
"managerFilePatterns": [
"/^Dockerfile$/"
],
"matchStrings": [
"ARG CADDY_SECURITY_VERSION=(?<currentValue>[^\\s]+)"
],
"depNameTemplate": "github.com/greenpau/caddy-security",
"datasourceTemplate": "go",
"versioningTemplate": "semver"
},
{
"customType": "regex",
"description": "Track Go dependencies patched in Dockerfile for Caddy CVE fixes",
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ jobs:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.sha }}
# Use github.ref (full ref path) instead of github.ref_name:
# - push/schedule: resolves to refs/heads/<branch>, checking out latest HEAD
# - pull_request: resolves to refs/pull/<n>/merge, the correct PR merge ref
# github.ref_name fails for PRs because it yields "<n>/merge" which checkout
# interprets as a branch name (refs/heads/<n>/merge) that does not exist.
ref: ${{ github.ref }}

- name: Verify CodeQL parity guard
if: matrix.language == 'go'
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/security-weekly-rebuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
# Explicitly fetch the current HEAD of the ref at run time, not the
# SHA that was frozen when this scheduled job was queued. Without this,
# a queued job can run days later with stale code.
ref: ${{ github.ref_name }}

- name: Normalize image name
run: |
Expand Down
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ ARG CADDY_VERSION=2.11.1
ARG CADDY_CANDIDATE_VERSION=2.11.1
ARG CADDY_USE_CANDIDATE=0
ARG CADDY_PATCH_SCENARIO=B
# renovate: datasource=go depName=github.com/greenpau/caddy-security
ARG CADDY_SECURITY_VERSION=1.1.36
## When an official caddy image tag isn't available on the host, use a
## plain Alpine base image and overwrite its caddy binary with our
## xcaddy-built binary in the later COPY step. This avoids relying on
Expand Down Expand Up @@ -202,6 +204,7 @@ ARG CADDY_VERSION
ARG CADDY_CANDIDATE_VERSION
ARG CADDY_USE_CANDIDATE
ARG CADDY_PATCH_SCENARIO
ARG CADDY_SECURITY_VERSION
# renovate: datasource=go depName=github.com/caddyserver/xcaddy
ARG XCADDY_VERSION=0.4.5

Expand Down Expand Up @@ -229,7 +232,8 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
echo "Stage 1: Generate go.mod with xcaddy..."; \
# Run xcaddy to generate the build directory and go.mod
GOOS=$TARGETOS GOARCH=$TARGETARCH xcaddy build v${CADDY_TARGET_VERSION} \
--with github.com/greenpau/caddy-security \
--with github.com/caddyserver/caddy/v2@v${CADDY_TARGET_VERSION} \
--with github.com/greenpau/caddy-security@v${CADDY_SECURITY_VERSION} \
--with github.com/corazawaf/coraza-caddy/v2 \
--with github.com/hslatman/caddy-crowdsec-bouncer@v0.10.0 \
--with github.com/zhangjiayin/caddy-geoip2 \
Expand Down
4 changes: 2 additions & 2 deletions backend/cmd/api/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestResetPasswordCommand_Succeeds(t *testing.T) {
}

email := "user@example.com"
user := models.User{UUID: "u-1", Email: email, Name: "User", Role: "admin", Enabled: true}
user := models.User{UUID: "u-1", Email: email, Name: "User", Role: models.RoleAdmin, Enabled: true}
user.PasswordHash = "$2a$10$example_hashed_password"
if err = db.Create(&user).Error; err != nil {
t.Fatalf("seed user: %v", err)
Expand Down Expand Up @@ -257,7 +257,7 @@ func TestMain_ResetPasswordCommand_InProcess(t *testing.T) {
}

email := "user@example.com"
user := models.User{UUID: "u-1", Email: email, Name: "User", Role: "admin", Enabled: true}
user := models.User{UUID: "u-1", Email: email, Name: "User", Role: models.RoleAdmin, Enabled: true}
user.PasswordHash = "$2a$10$example_hashed_password"
user.FailedLoginAttempts = 3
if err = db.Create(&user).Error; err != nil {
Expand Down
4 changes: 2 additions & 2 deletions backend/cmd/seed/seed_smoke_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func TestSeedMain_ForceAdminUpdatesExistingUserPassword(t *testing.T) {
UUID: "existing-user",
Email: "admin@localhost",
Name: "Old Name",
Role: "viewer",
Role: models.RolePassthrough,
Enabled: false,
PasswordHash: "$2a$10$example_hashed_password",
}
Expand Down Expand Up @@ -134,7 +134,7 @@ func TestSeedMain_ForceAdminWithoutPasswordUpdatesMetadata(t *testing.T) {
UUID: "existing-user-no-pass",
Email: "admin@localhost",
Name: "Old Name",
Role: "viewer",
Role: models.RolePassthrough,
Enabled: false,
PasswordHash: "$2a$10$example_hashed_password",
}
Expand Down
2 changes: 1 addition & 1 deletion backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ require (
github.com/ugorji/go/codec v1.3.1 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 // indirect
go.opentelemetry.io/otel v1.41.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect
Expand Down
12 changes: 6 additions & 6 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 h1:PnV4kVnw0zOmwwFkAzCN5O07fw1YOIQor120zrh0AVo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0/go.mod h1:ofAwF4uinaf8SXdVzzbL4OsxJ3VfeEg3f/F6CeF49/Y=
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
Expand All @@ -186,10 +186,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=
go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y=
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/api/handlers/auth_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ func (h *AuthHandler) Verify(c *gin.Context) {

// Set headers for downstream services
c.Header("X-Forwarded-User", user.Email)
c.Header("X-Forwarded-Groups", user.Role)
c.Header("X-Forwarded-Groups", string(user.Role))
c.Header("X-Forwarded-Name", user.Name)

// Return 200 OK - access granted
Expand Down
26 changes: 13 additions & 13 deletions backend/internal/api/handlers/auth_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ func TestAuthHandler_Me(t *testing.T) {
UUID: uuid.NewString(),
Email: "me@example.com",
Name: "Me User",
Role: "admin",
Role: models.RoleAdmin,
}
db.Create(user)

Expand Down Expand Up @@ -630,7 +630,7 @@ func TestAuthHandler_Verify_ValidToken(t *testing.T) {
UUID: uuid.NewString(),
Email: "test@example.com",
Name: "Test User",
Role: "user",
Role: models.RoleUser,
Enabled: true,
}
_ = user.SetPassword("password123")
Expand Down Expand Up @@ -661,7 +661,7 @@ func TestAuthHandler_Verify_BearerToken(t *testing.T) {
UUID: uuid.NewString(),
Email: "bearer@example.com",
Name: "Bearer User",
Role: "admin",
Role: models.RoleAdmin,
Enabled: true,
}
_ = user.SetPassword("password123")
Expand Down Expand Up @@ -690,7 +690,7 @@ func TestAuthHandler_Verify_DisabledUser(t *testing.T) {
UUID: uuid.NewString(),
Email: "disabled@example.com",
Name: "Disabled User",
Role: "user",
Role: models.RoleUser,
}
_ = user.SetPassword("password123")
db.Create(user)
Expand Down Expand Up @@ -730,7 +730,7 @@ func TestAuthHandler_Verify_ForwardAuthDenied(t *testing.T) {
UUID: uuid.NewString(),
Email: "denied@example.com",
Name: "Denied User",
Role: "user",
Role: models.RoleUser,
Enabled: true,
PermissionMode: models.PermissionModeDenyAll,
}
Expand Down Expand Up @@ -795,7 +795,7 @@ func TestAuthHandler_VerifyStatus_Authenticated(t *testing.T) {
UUID: uuid.NewString(),
Email: "status@example.com",
Name: "Status User",
Role: "user",
Role: models.RoleUser,
Enabled: true,
}
_ = user.SetPassword("password123")
Expand Down Expand Up @@ -828,7 +828,7 @@ func TestAuthHandler_VerifyStatus_DisabledUser(t *testing.T) {
UUID: uuid.NewString(),
Email: "disabled2@example.com",
Name: "Disabled User 2",
Role: "user",
Role: models.RoleUser,
}
_ = user.SetPassword("password123")
db.Create(user)
Expand Down Expand Up @@ -880,7 +880,7 @@ func TestAuthHandler_GetAccessibleHosts_AllowAll(t *testing.T) {
UUID: uuid.NewString(),
Email: "allowall@example.com",
Name: "Allow All User",
Role: "user",
Role: models.RoleUser,
Enabled: true,
PermissionMode: models.PermissionModeAllowAll,
}
Expand Down Expand Up @@ -917,7 +917,7 @@ func TestAuthHandler_GetAccessibleHosts_DenyAll(t *testing.T) {
UUID: uuid.NewString(),
Email: "denyall@example.com",
Name: "Deny All User",
Role: "user",
Role: models.RoleUser,
Enabled: true,
PermissionMode: models.PermissionModeDenyAll,
}
Expand Down Expand Up @@ -956,7 +956,7 @@ func TestAuthHandler_GetAccessibleHosts_PermittedHosts(t *testing.T) {
UUID: uuid.NewString(),
Email: "permitted@example.com",
Name: "Permitted User",
Role: "user",
Role: models.RoleUser,
Enabled: true,
PermissionMode: models.PermissionModeDenyAll,
PermittedHosts: []models.ProxyHost{*host1}, // Only host1
Expand Down Expand Up @@ -1111,7 +1111,7 @@ func TestAuthHandler_Logout_InvalidatesBearerSession(t *testing.T) {
UUID: uuid.NewString(),
Email: "logout-session@example.com",
Name: "Logout Session",
Role: "admin",
Role: models.RoleAdmin,
Enabled: true,
}
_ = user.SetPassword("password123")
Expand Down Expand Up @@ -1242,7 +1242,7 @@ func TestAuthHandler_Refresh(t *testing.T) {

handler, db := setupAuthHandler(t)

user := &models.User{UUID: uuid.NewString(), Email: "refresh@example.com", Name: "Refresh User", Role: "user", Enabled: true}
user := &models.User{UUID: uuid.NewString(), Email: "refresh@example.com", Name: "Refresh User", Role: models.RoleUser, Enabled: true}
require.NoError(t, user.SetPassword("password123"))
require.NoError(t, db.Create(user).Error)

Expand Down Expand Up @@ -1332,7 +1332,7 @@ func TestAuthHandler_Verify_UsesOriginalHostFallback(t *testing.T) {
UUID: uuid.NewString(),
Email: "originalhost@example.com",
Name: "Original Host User",
Role: "user",
Role: models.RoleUser,
Enabled: true,
PermissionMode: models.PermissionModeAllowAll,
}
Expand Down
20 changes: 4 additions & 16 deletions backend/internal/api/handlers/emergency_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,10 +384,7 @@ func (h *EmergencyHandler) syncSecurityState(ctx context.Context) {
// POST /api/v1/emergency/token/generate
// Requires admin authentication
func (h *EmergencyHandler) GenerateToken(c *gin.Context) {
// Check admin role
role, exists := c.Get("role")
if !exists || role != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "Admin access required"})
if !requireAdmin(c) {
return
}

Expand Down Expand Up @@ -437,10 +434,7 @@ func (h *EmergencyHandler) GenerateToken(c *gin.Context) {
// GET /api/v1/emergency/token/status
// Requires admin authentication
func (h *EmergencyHandler) GetTokenStatus(c *gin.Context) {
// Check admin role
role, exists := c.Get("role")
if !exists || role != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "Admin access required"})
if !requireAdmin(c) {
return
}

Expand All @@ -458,10 +452,7 @@ func (h *EmergencyHandler) GetTokenStatus(c *gin.Context) {
// DELETE /api/v1/emergency/token
// Requires admin authentication
func (h *EmergencyHandler) RevokeToken(c *gin.Context) {
// Check admin role
role, exists := c.Get("role")
if !exists || role != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "Admin access required"})
if !requireAdmin(c) {
return
}

Expand All @@ -485,10 +476,7 @@ func (h *EmergencyHandler) RevokeToken(c *gin.Context) {
// PATCH /api/v1/emergency/token/expiration
// Requires admin authentication
func (h *EmergencyHandler) UpdateTokenExpiration(c *gin.Context) {
// Check admin role
role, exists := c.Get("role")
if !exists || role != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "Admin access required"})
if !requireAdmin(c) {
return
}

Expand Down
4 changes: 1 addition & 3 deletions backend/internal/api/handlers/permission_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ func requireAuthenticatedAdmin(c *gin.Context) bool {
}

func isAdmin(c *gin.Context) bool {
role, _ := c.Get("role")
roleStr, _ := role.(string)
return roleStr == "admin"
return c.GetString("role") == string(models.RoleAdmin)
}

func respondPermissionError(c *gin.Context, securityService *services.SecurityService, action string, err error, path string) bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ func TestSecurityEventIntakeR6Intact(t *testing.T) {
Email: "admin@example.com",
Name: "Admin User",
PasswordHash: "$2a$10$abcdefghijklmnopqrstuvwxyz", // Dummy bcrypt hash
Role: "admin",
Role: models.RoleAdmin,
Enabled: true,
}
require.NoError(t, db.Create(adminUser).Error)
Expand Down
5 changes: 1 addition & 4 deletions backend/internal/api/handlers/security_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1075,10 +1075,7 @@ func (h *SecurityHandler) PatchRateLimit(c *gin.Context) {
// toggleSecurityModule is a helper function that handles enabling/disabling security modules
// It updates the setting, invalidates cache, and triggers Caddy config reload
func (h *SecurityHandler) toggleSecurityModule(c *gin.Context, settingKey string, enabled bool) {
// Check admin role
role, exists := c.Get("role")
if !exists || role != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "Admin access required"})
if !requireAdmin(c) {
return
}

Expand Down
Loading
Loading