Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions backend/internal/huma/handlers/appimages.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func RegisterAppImages(api huma.API, appImagesService *services.ApplicationImage
Summary: "Get application logo",
Description: "Get the application logo image",
Tags: []string{"Application Images"},
Security: []map[string][]string{},
}, h.GetLogo)

huma.Register(api, huma.Operation{
Expand All @@ -66,6 +67,7 @@ func RegisterAppImages(api huma.API, appImagesService *services.ApplicationImage
Summary: "Get application logo for email",
Description: "Get the application logo image in PNG format for emails",
Tags: []string{"Application Images"},
Security: []map[string][]string{},
}, h.GetLogoEmail)

huma.Register(api, huma.Operation{
Expand All @@ -75,6 +77,7 @@ func RegisterAppImages(api huma.API, appImagesService *services.ApplicationImage
Summary: "Get application favicon",
Description: "Get the application favicon image",
Tags: []string{"Application Images"},
Security: []map[string][]string{},
}, h.GetFavicon)

huma.Register(api, huma.Operation{
Expand All @@ -84,6 +87,7 @@ func RegisterAppImages(api huma.API, appImagesService *services.ApplicationImage
Summary: "Get default profile image",
Description: "Get the default user profile image",
Tags: []string{"Application Images"},
Security: []map[string][]string{},
}, h.GetDefaultProfile)

huma.Register(api, huma.Operation{
Expand All @@ -93,6 +97,7 @@ func RegisterAppImages(api huma.API, appImagesService *services.ApplicationImage
Summary: "Get PWA icon",
Description: "Get a Progressive Web App icon image",
Tags: []string{"Application Images"},
Security: []map[string][]string{},
}, h.GetPWAIcon)
}

Expand Down
3 changes: 3 additions & 0 deletions backend/internal/huma/handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func RegisterAuth(api huma.API, userService *services.UserService, authService *
Summary: "Login",
Description: "Authenticate a user with username and password",
Tags: []string{"Auth"},
Security: []map[string][]string{},
}, h.Login)

huma.Register(api, huma.Operation{
Expand All @@ -85,6 +86,7 @@ func RegisterAuth(api huma.API, userService *services.UserService, authService *
Summary: "Logout",
Description: "Clear authentication session",
Tags: []string{"Auth"},
Security: []map[string][]string{},
}, h.Logout)

huma.Register(api, huma.Operation{
Expand All @@ -107,6 +109,7 @@ func RegisterAuth(api huma.API, userService *services.UserService, authService *
Summary: "Refresh token",
Description: "Obtain a new access token using a refresh token",
Tags: []string{"Auth"},
Security: []map[string][]string{},
}, h.RefreshToken)

huma.Register(api, huma.Operation{
Expand Down
1 change: 1 addition & 0 deletions backend/internal/huma/handlers/environments.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ func RegisterEnvironments(api huma.API, environmentService *services.Environment
Description: "Agent sends API key to complete environment pairing",
Tags: []string{"Environments"},
MaxBodyBytes: 1024,
Security: []map[string][]string{},
}, h.PairEnvironment)

huma.Register(api, huma.Operation{
Expand Down
2 changes: 2 additions & 0 deletions backend/internal/huma/handlers/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func RegisterHealth(api huma.API) {
Summary: "Health check",
Description: "Check if the API is healthy",
Tags: []string{"Health"},
Security: []map[string][]string{},
}, func(ctx context.Context, input *struct{}) (*HealthOutput, error) {
return &HealthOutput{
Body: system.HealthResponse{
Expand All @@ -37,6 +38,7 @@ func RegisterHealth(api huma.API) {
Summary: "Health check (HEAD)",
Description: "Check if the API is healthy (HEAD request)",
Tags: []string{"Health"},
Security: []map[string][]string{},
}, func(ctx context.Context, input *struct{}) (*struct{}, error) {
return nil, nil
})
Expand Down
6 changes: 6 additions & 0 deletions backend/internal/huma/handlers/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func RegisterOidc(api huma.API, authService *services.AuthService, oidcService *
Summary: "Get OIDC status",
Description: "Get the current OIDC configuration status",
Tags: []string{"OIDC"},
Security: []map[string][]string{},
}, h.GetOidcStatus)

huma.Register(api, huma.Operation{
Expand All @@ -108,6 +109,7 @@ func RegisterOidc(api huma.API, authService *services.AuthService, oidcService *
Summary: "Get OIDC config",
Description: "Get the OIDC client configuration",
Tags: []string{"OIDC"},
Security: []map[string][]string{},
}, h.GetOidcConfig)

huma.Register(api, huma.Operation{
Expand All @@ -117,6 +119,7 @@ func RegisterOidc(api huma.API, authService *services.AuthService, oidcService *
Summary: "Get OIDC auth URL",
Description: "Generate an OIDC authorization URL for login",
Tags: []string{"OIDC"},
Security: []map[string][]string{},
}, h.GetOidcAuthUrl)

huma.Register(api, huma.Operation{
Expand All @@ -126,6 +129,7 @@ func RegisterOidc(api huma.API, authService *services.AuthService, oidcService *
Summary: "Handle OIDC callback",
Description: "Process the OIDC callback and complete authentication",
Tags: []string{"OIDC"},
Security: []map[string][]string{},
}, h.HandleOidcCallback)

huma.Register(api, huma.Operation{
Expand All @@ -135,6 +139,7 @@ func RegisterOidc(api huma.API, authService *services.AuthService, oidcService *
Summary: "Initiate OIDC device authorization",
Description: "Start the device authorization flow for CLI authentication",
Tags: []string{"OIDC"},
Security: []map[string][]string{},
}, h.InitiateDeviceAuth)

huma.Register(api, huma.Operation{
Expand All @@ -144,6 +149,7 @@ func RegisterOidc(api huma.API, authService *services.AuthService, oidcService *
Summary: "Exchange device code for tokens",
Description: "Exchange a device code for authentication tokens",
Tags: []string{"OIDC"},
Security: []map[string][]string{},
}, h.ExchangeDeviceToken)
}

Expand Down
79 changes: 43 additions & 36 deletions backend/internal/huma/handlers/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/getarcaneapp/arcane/backend/internal/common"
"github.com/getarcaneapp/arcane/backend/internal/config"
humamw "github.com/getarcaneapp/arcane/backend/internal/huma/middleware"
"github.com/getarcaneapp/arcane/backend/internal/models"
"github.com/getarcaneapp/arcane/backend/internal/services"
"github.com/getarcaneapp/arcane/backend/pkg/projects"
"github.com/getarcaneapp/arcane/backend/pkg/utils/mapper"
Expand Down Expand Up @@ -68,11 +69,11 @@ type GetCategoriesOutput struct {
Body []category.Category
}

// validateProjectsDirectoryValue validates a projects directory value allowing:
// validateProjectsDirectoryValueInternal validates a projects directory value allowing:
// - Unix absolute paths (/...)
// - Windows drive paths (C:/..., C:\...)
// - Mapping format "container:host" where container is absolute Unix or Windows path
func validateProjectsDirectoryValue(path string) error {
func validateProjectsDirectoryValueInternal(path string) error {
switch {
case projects.IsWindowsDrivePath(path):
return nil
Expand All @@ -94,10 +95,10 @@ func validateProjectsDirectoryValue(path string) error {
}
}

// validateAbsoluteDirectoryPath validates a plain absolute directory path allowing:
// validateAbsoluteDirectoryPathInternal validates a plain absolute directory path allowing:
// - Unix absolute paths (/...)
// - Windows drive paths (C:/..., C:\...)
func validateAbsoluteDirectoryPath(path string) error {
func validateAbsoluteDirectoryPathInternal(path string) error {
switch {
case projects.IsWindowsDrivePath(path):
return nil
Expand Down Expand Up @@ -125,6 +126,7 @@ func RegisterSettings(api huma.API, settingsService *services.SettingsService, s
Summary: "Get public settings",
Description: "Get all public settings for an environment",
Tags: []string{"Settings"},
Security: []map[string][]string{},
}, h.GetPublicSettings)

huma.Register(api, huma.Operation{
Expand Down Expand Up @@ -205,14 +207,14 @@ func (h *SettingsHandler) GetPublicSettings(ctx context.Context, input *GetPubli
return &GetPublicSettingsOutput{Body: settingsDto}, nil
}

settingsList := h.settingsService.ListSettings(false)
settingsList := h.settingsService.ListSettings(models.SettingVisibilityPublic)

var settingsDto []settings.PublicSetting
if err := mapper.MapStructList(settingsList, &settingsDto); err != nil {
return nil, huma.Error500InternalServerError((&common.SettingsMappingError{Err: err}).Error())
}

return &GetPublicSettingsOutput{Body: h.appendRuntimeSettings(settingsDto)}, nil
return &GetPublicSettingsOutput{Body: h.appendRuntimeSettings(settingsDto, false)}, nil
}

// GetSettings returns all settings for an environment.
Expand Down Expand Up @@ -241,46 +243,51 @@ func (h *SettingsHandler) GetSettings(ctx context.Context, input *GetSettingsInp
return &GetSettingsOutput{Body: settingsDto}, nil
}

showAll := isAdmin
settingsList := h.settingsService.ListSettings(showAll)
visibility := models.SettingVisibilityNonAdmin
if isAdmin {
visibility = models.SettingVisibilityAll
}
settingsList := h.settingsService.ListSettings(visibility)

var settingsDto []settings.PublicSetting
if err := mapper.MapStructList(settingsList, &settingsDto); err != nil {
Comment thread
greptile-apps[bot] marked this conversation as resolved.
return nil, huma.Error500InternalServerError((&common.SettingsMappingError{Err: err}).Error())
}

return &GetSettingsOutput{Body: h.appendRuntimeSettings(settingsDto)}, nil
return &GetSettingsOutput{Body: h.appendRuntimeSettings(settingsDto, true)}, nil
}

func (h *SettingsHandler) appendRuntimeSettings(settingsDto []settings.PublicSetting) []settings.PublicSetting {
func (h *SettingsHandler) appendRuntimeSettings(settingsDto []settings.PublicSetting, includeAuthenticatedOnly bool) []settings.PublicSetting {
uiConfigDisabled := false
if h.cfg != nil {
if includeAuthenticatedOnly && h.cfg != nil {
uiConfigDisabled = h.cfg.UIConfigurationDisabled
}
settingsDto = append(settingsDto, settings.PublicSetting{
Key: "uiConfigDisabled",
Value: strconv.FormatBool(uiConfigDisabled),
Type: "boolean",
})

backupVolumeName := "arcane-backups"
if h.cfg != nil && strings.TrimSpace(h.cfg.BackupVolumeName) != "" {
backupVolumeName = h.cfg.BackupVolumeName
}
settingsDto = append(settingsDto, settings.PublicSetting{
Key: "backupVolumeName",
Value: backupVolumeName,
Type: "string",
})

if h.settingsService != nil {
cfg := h.settingsService.GetSettingsConfig()
depotConfigured := strings.TrimSpace(cfg.DepotProjectId.Value) != "" && strings.TrimSpace(cfg.DepotToken.Value) != ""
if includeAuthenticatedOnly {
settingsDto = append(settingsDto, settings.PublicSetting{
Key: "depotConfigured",
Value: strconv.FormatBool(depotConfigured),
Key: "uiConfigDisabled",
Value: strconv.FormatBool(uiConfigDisabled),
Type: "boolean",
})

backupVolumeName := "arcane-backups"
if h.cfg != nil && strings.TrimSpace(h.cfg.BackupVolumeName) != "" {
backupVolumeName = h.cfg.BackupVolumeName
}
settingsDto = append(settingsDto, settings.PublicSetting{
Key: "backupVolumeName",
Value: backupVolumeName,
Type: "string",
})

if h.settingsService != nil {
cfg := h.settingsService.GetSettingsConfig()
depotConfigured := strings.TrimSpace(cfg.DepotProjectId.Value) != "" && strings.TrimSpace(cfg.DepotToken.Value) != ""
settingsDto = append(settingsDto, settings.PublicSetting{
Key: "depotConfigured",
Value: strconv.FormatBool(depotConfigured),
Type: "boolean",
})
}
}

return settingsDto
Expand Down Expand Up @@ -315,7 +322,7 @@ func (h *SettingsHandler) validateSettingsUpdateInput(input settings.Update) err
if input.ProjectsDirectory != nil && *input.ProjectsDirectory != "" {
currentDir := h.settingsService.GetSettingsConfig().ProjectsDirectory.Value
if *input.ProjectsDirectory != currentDir {
if err := validateProjectsDirectoryValue(*input.ProjectsDirectory); err != nil {
if err := validateProjectsDirectoryValueInternal(*input.ProjectsDirectory); err != nil {
return huma.Error400BadRequest(err.Error())
}
}
Expand All @@ -324,7 +331,7 @@ func (h *SettingsHandler) validateSettingsUpdateInput(input settings.Update) err
if input.SwarmStackSourcesDirectory != nil && *input.SwarmStackSourcesDirectory != "" {
currentDir := h.settingsService.GetSettingsConfig().SwarmStackSourcesDirectory.Value
if *input.SwarmStackSourcesDirectory != currentDir {
if err := validateAbsoluteDirectoryPath(*input.SwarmStackSourcesDirectory); err != nil {
if err := validateAbsoluteDirectoryPathInternal(*input.SwarmStackSourcesDirectory); err != nil {
return huma.Error400BadRequest("swarmStackSourcesDirectory " + err.Error())
}
}
Expand All @@ -339,7 +346,7 @@ func (h *SettingsHandler) updateSettingsForRemoteEnvironment(ctx context.Context
}

// Check if trying to update auth settings on non-local environment.
if hasAuthSettingsUpdate(input.Body) {
if hasAuthSettingsUpdateInternal(input.Body) {
return nil, huma.Error403Forbidden((&common.AuthSettingsUpdateError{}).Error())
}

Expand Down Expand Up @@ -389,7 +396,7 @@ func (h *SettingsHandler) updateSettingsForLocalEnvironment(ctx context.Context,
}, nil
}

func hasAuthSettingsUpdate(req settings.Update) bool {
func hasAuthSettingsUpdateInternal(req settings.Update) bool {
return req.AuthLocalEnabled != nil || req.OidcEnabled != nil ||
req.AuthSessionTimeout != nil || req.AuthPasswordPolicy != nil ||
req.AuthOidcConfig != nil || req.OidcClientId != nil ||
Expand Down
40 changes: 40 additions & 0 deletions backend/internal/huma/handlers/settings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package handlers

import (
"testing"

"github.com/getarcaneapp/arcane/backend/internal/config"
apitypes "github.com/getarcaneapp/arcane/types/settings"
"github.com/stretchr/testify/require"
)

func TestSettingsHandler_AppendRuntimeSettings(t *testing.T) {
handler := &SettingsHandler{
cfg: &config.Config{
UIConfigurationDisabled: true,
BackupVolumeName: "custom-backups",
},
}

publicSettings := handler.appendRuntimeSettings(nil, false)
publicKeys := runtimeSettingKeysInternal(publicSettings)
require.NotContains(t, publicKeys, "uiConfigDisabled")
require.NotContains(t, publicKeys, "backupVolumeName")
require.NotContains(t, publicKeys, "depotConfigured")

authenticatedSettings := handler.appendRuntimeSettings(nil, true)
authenticatedKeys := runtimeSettingKeysInternal(authenticatedSettings)
require.Contains(t, authenticatedKeys, "uiConfigDisabled")
require.Contains(t, authenticatedKeys, "backupVolumeName")
require.Equal(t, "true", authenticatedKeys["uiConfigDisabled"])
require.Equal(t, "custom-backups", authenticatedKeys["backupVolumeName"])
}

func runtimeSettingKeysInternal(settings []apitypes.PublicSetting) map[string]string {
keys := make(map[string]string, len(settings))
for _, setting := range settings {
keys[setting.Key] = setting.Value
}

return keys
}
2 changes: 1 addition & 1 deletion backend/internal/huma/handlers/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ type UpdateGlobalVariablesOutput struct {
func RegisterTemplates(api huma.API, templateService *services.TemplateService, environmentService *services.EnvironmentService) {
h := &TemplateHandler{templateService: templateService, environmentService: environmentService}

// Public endpoints (no auth required in original)
// Template registry endpoint.
huma.Register(api, huma.Operation{
OperationID: "fetchTemplateRegistry",
Method: "GET",
Expand Down
Loading
Loading