Files
Matthew Meszaros 734cb5fe08 feat: make self-hosted onboarding survivable by fixing invite_only, which could not onboard anyone (the accept route is JWT-only, so redeeming the invitation that would create your account required already having one, making the self-host default silently identical to fully closed), threading the invitation token through registration so an invited person lands in the inviting organization instead of a stray workspace, gating SSO just-in-time provisioning behind DISABLE_REGISTRATION (it bypassed the gate entirely, so an instance set to true was still open to anyone the IdP would assert) with SSO_AUTO_PROVISION as the opt-out, correcting the OIDC redirect URL that pointed at /api/v1 against a route at /v1 and 404'd every SSO login, scoping the first-launch exemption so it no longer overrides an explicit lockdown, preserving the remaining TTL when restoring a losing setup token so a public endpoint cannot hold the claim window open forever, replacing a generic 403 with typed registration_invite_only, registration_closed, invitation_invalid, setup_token_invalid and setup_already_complete codes that name the next step, logging why no claim link was issued on an already-claimed instance instead of staying silent, adding a warmblyctl operator CLI (status with health checks and a non-zero exit, reissuable setup-link, user create/list/reset-password/grant-admin/revoke-admin/disable-2fa, hash-password) so a locked-out operator no longer needs hand-written psql, adding read-only instance configuration over 104 environment variables with structural secret redaction and fingerprints, 35 health checks, a database-backed settings tier for the three keys no environment variable owns, hiding the signup form when the config already says invite_only rather than failing the whole form with a toast, and documenting first run, accounts and access, configuration, instance health and troubleshooting alongside the root .env.example the README told operators to write but never shipped (#114)
2026-08-16 05:58:11 +02:00

46 lines
1.3 KiB
Go

package instanceconfig
import (
"crypto/sha256"
"encoding/hex"
"strings"
)
// sensitiveMarkers make redaction structural rather than per-field: a new
// variable is protected by its name, not by someone remembering to list it.
var sensitiveMarkers = []string{"SECRET", "PASSWORD", "KEY", "TOKEN", "DSN", "_PASS"}
// sensitiveKeys are connection strings: every one of them routinely carries
// user:password inline, and none of them matches a marker.
var sensitiveKeys = map[string]bool{
"PRIMARY_DB": true,
"REDIS": true,
"NATS_URL": true,
"SCHEMA_REGISTRY_URL": true,
}
// Sensitive reports whether a variable's value must never be returned.
func Sensitive(key string) bool {
if sensitiveKeys[key] {
return true
}
upper := strings.ToUpper(key)
for _, marker := range sensitiveMarkers {
if strings.Contains(upper, marker) {
return true
}
}
return false
}
// Fingerprint is the first 4 hex characters of SHA-256 of a value, empty when
// the value is unset. It exists so an operator can confirm the backend and the
// realtime service hold the same AUTH_SECRET without either being disclosed.
func Fingerprint(value string) string {
if value == "" {
return ""
}
sum := sha256.Sum256([]byte(value))
return hex.EncodeToString(sum[:])[:4]
}