mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-18 16:01:18 +00:00
117 lines
3.8 KiB
Go
117 lines
3.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/warmbly/warmbly/internal/api/middleware"
|
|
"github.com/warmbly/warmbly/internal/app/instancecheck"
|
|
"github.com/warmbly/warmbly/internal/app/instanceconfig"
|
|
"github.com/warmbly/warmbly/internal/app/instancesettings"
|
|
"github.com/warmbly/warmbly/internal/errx"
|
|
)
|
|
|
|
// The Instance surface answers the two questions a self-hoster cannot answer
|
|
// from inside the product today: what did my environment actually resolve to,
|
|
// and what is wrong with this deployment right now.
|
|
|
|
// AdminInstanceConfig returns the resolved effective configuration.
|
|
//
|
|
// Read-only by design: the environment is authoritative, so no API ever writes
|
|
// a key on this page. Sensitive values never leave the process; they carry a
|
|
// short fingerprint instead, which is enough to confirm two services hold the
|
|
// same AUTH_SECRET without disclosing either.
|
|
func (h *Handler) AdminInstanceConfig(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{"entries": instanceconfig.Entries(h.InstanceRuntime)})
|
|
}
|
|
|
|
// AdminInstanceLimits returns the effective product limits. Every value is a
|
|
// compiled constant, so this exists for visibility, not for editing.
|
|
func (h *Handler) AdminInstanceLimits(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{"groups": instanceconfig.Limits()})
|
|
}
|
|
|
|
// AdminInstanceHealth runs the setup and health checks and returns only the
|
|
// findings. Checks run in parallel with a per-check timeout, so this is safe
|
|
// to poll, and a check whose input is unavailable is skipped rather than
|
|
// reported as a failure.
|
|
func (h *Handler) AdminInstanceHealth(c *gin.Context) {
|
|
registry := h.InstanceChecks
|
|
if registry == nil {
|
|
// Still answer with the environment-only checks rather than an empty
|
|
// page: a page that says nothing is indistinguishable from a healthy one.
|
|
registry = instancecheck.New(instancecheck.Deps{
|
|
Runtime: h.InstanceRuntime,
|
|
Transport: h.MailTransportRef,
|
|
})
|
|
}
|
|
|
|
checks, summary := registry.Run(c.Request.Context(), instancecheck.Input{
|
|
Host: c.Request.Host,
|
|
Origin: c.GetHeader("Origin"),
|
|
Forwarded: c.GetHeader("X-Forwarded-For") != "",
|
|
})
|
|
|
|
c.JSON(http.StatusOK, gin.H{"checks": checks, "summary": summary})
|
|
}
|
|
|
|
// AdminGetInstanceSettings returns the database-backed settings document.
|
|
func (h *Handler) AdminGetInstanceSettings(c *gin.Context) {
|
|
if h.InstanceSettings == nil {
|
|
c.JSON(http.StatusOK, instancesettings.Defaults())
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, h.InstanceSettings.Get(c.Request.Context()))
|
|
}
|
|
|
|
// AdminPutInstanceSettings validates, clamps and stores the settings document.
|
|
// Absent fields keep their stored value, so a client that does not know about
|
|
// a key cannot clear it.
|
|
func (h *Handler) AdminPutInstanceSettings(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
if h.InstanceSettings == nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "Instance settings are not available on this deployment."))
|
|
return
|
|
}
|
|
|
|
var patch instancesettings.Patch
|
|
if err := c.ShouldBindJSON(&patch); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
doc, err := h.InstanceSettings.Put(c.Request.Context(), patch, adminID)
|
|
if err != nil {
|
|
errx.JSON(c, errx.InternalError())
|
|
return
|
|
}
|
|
|
|
if h.AdminService != nil {
|
|
h.AdminService.LogAdminAction(
|
|
c.Request.Context(),
|
|
*adminID,
|
|
"update_instance_settings",
|
|
"instance",
|
|
nil,
|
|
instanceSettingsAuditDetails(doc),
|
|
c.ClientIP(),
|
|
c.Request.UserAgent(),
|
|
)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, doc)
|
|
}
|
|
|
|
func instanceSettingsAuditDetails(doc instancesettings.Document) map[string]any {
|
|
details := map[string]any{
|
|
"invitations_links_enabled": doc.Invitations.LinksEnabled,
|
|
"invitations_ttl_hours": doc.Invitations.TTLHours,
|
|
"access_allow_invited_signup": doc.Access.AllowInvitedSignup,
|
|
}
|
|
return details
|
|
}
|