sec: fix C4/C5/M4/M5 + domain migration to syntrex.pro

C4: Remove localhost:9100 fallback from 27 dashboard files (use relative URLs)
C5: JWT token_type differentiation (access vs refresh) - middleware rejects refresh as Bearer
M4: Server-side registration gate via SOC_REGISTRATION_OPEN env var
M5: HTML tag stripping on name/org_name fields (XSS prevention)

Domain migration:
- users.go: admin@syntrex.pro
- zerotrust.go: SPIFFE trust domain
- sbom.go: namespace URL
- .env.production.example: all URLs updated
- identity_test.go: test email
This commit is contained in:
DmitrL-dev 2026-03-24 11:49:33 +10:00
parent 1b028099be
commit 62ecc1c7a3
7 changed files with 76 additions and 35 deletions

View file

@ -4,9 +4,14 @@ import (
"encoding/json"
"log/slog"
"net/http"
"os"
"regexp"
"time"
)
// htmlTagRegex strips HTML/script tags from user input (M5 XSS prevention).
var htmlTagRegex = regexp.MustCompile(`<[^>]*>`)
// EmailSendFunc is a callback for sending verification emails.
// Signature: func(toEmail, userName, code string) error
type EmailSendFunc func(toEmail, userName, code string) error
@ -17,6 +22,12 @@ type EmailSendFunc func(toEmail, userName, code string) error
// If emailFn is nil, verification code is returned in response (dev mode).
func HandleRegister(userStore *UserStore, tenantStore *TenantStore, jwtSecret []byte, emailFn EmailSendFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// SEC-M4: Server-side registration gate
if os.Getenv("SOC_REGISTRATION_OPEN") != "true" {
http.Error(w, `{"error":"registration is closed — contact admin for an invitation"}`, http.StatusForbidden)
return
}
var req struct {
Email string `json:"email"`
Password string `json:"password"`
@ -40,6 +51,10 @@ func HandleRegister(userStore *UserStore, tenantStore *TenantStore, jwtSecret []
req.Name = req.Email
}
// SEC-M5: Strip HTML tags from user input to prevent stored XSS
req.Name = htmlTagRegex.ReplaceAllString(req.Name, "")
req.OrgName = htmlTagRegex.ReplaceAllString(req.OrgName, "")
// Create user first (admin of new tenant)
user, err := userStore.CreateUser(req.Email, req.Name, req.Password, "admin")
if err != nil {
@ -141,10 +156,11 @@ func HandleVerifyEmail(userStore *UserStore, tenantStore *TenantStore, jwtSecret
// Issue JWT with tenant context
accessToken, err := Sign(Claims{
Sub: user.Email,
Role: user.Role,
TenantID: tenantID,
Exp: time.Now().Add(15 * time.Minute).Unix(),
Sub: user.Email,
Role: user.Role,
TenantID: tenantID,
TokenType: "access",
Exp: time.Now().Add(15 * time.Minute).Unix(),
}, jwtSecret)
if err != nil {
http.Error(w, `{"error":"failed to issue token"}`, http.StatusInternalServerError)
@ -152,10 +168,11 @@ func HandleVerifyEmail(userStore *UserStore, tenantStore *TenantStore, jwtSecret
}
refreshToken, _ := Sign(Claims{
Sub: user.Email,
Role: user.Role,
TenantID: tenantID,
Exp: time.Now().Add(7 * 24 * time.Hour).Unix(),
Sub: user.Email,
Role: user.Role,
TenantID: tenantID,
TokenType: "refresh",
Exp: time.Now().Add(7 * 24 * time.Hour).Unix(),
}, jwtSecret)
var tenant *Tenant