refactor(canary): consolidate realIP into middleware across 5 generators

Closes deferred Phase-9 item 8. Phase 9 promoted realIP / lastNonEmptyXFF /
OptionalHeader to internal/middleware and refactored webbug only; the
other generators kept their byte-identical local copies. Sweeping them
all over now while the touch surface is already open for Phase 10.

- docx, envfile, kubeconfig, pdf, slowredirect: drop the local
  realIP / lastNonEmptyXFF / optionalHeader functions plus the
  CF-Connecting-IP / X-Forwarded-For / X-Real-IP header constants.
  Trigger paths now call middleware.RealIP / middleware.OptionalHeader
  directly. Drops the unused "net" import from each.
- slowredirect/fingerprint_handler.go: same treatment for the
  AttachFingerprint call site.

No behavior change — middleware.RealIP is byte-identical to what was
previously inlined per generator; verified by re-running the per-package
trigger tests post-refactor.
This commit is contained in:
CarterPerez-dev 2026-05-14 00:31:49 -04:00
parent f9b012699a
commit 2799045ee7
6 changed files with 39 additions and 239 deletions

View File

@ -10,23 +10,20 @@ import (
_ "embed"
"fmt"
"io"
"net"
"net/http"
"strings"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/event"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/middleware"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token/generators"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token/generators/pixel"
)
const (
headerCFConnectingIP = "CF-Connecting-IP"
headerXForwardedFor = "X-Forwarded-For"
headerXRealIP = "X-Real-IP"
headerReferer = "Referer"
headerCacheControl = "Cache-Control"
headerPragma = "Pragma"
headerReferer = "Referer"
headerCacheControl = "Cache-Control"
headerPragma = "Pragma"
cacheControlNoStore = "no-store, no-cache, must-revalidate, max-age=0"
pragmaNoCache = "no-cache"
@ -90,9 +87,9 @@ func (g *Generator) Trigger(
evt := &event.Event{
TokenID: t.ID,
SourceIP: realIP(r),
UserAgent: optionalHeader(r.UserAgent()),
Referer: optionalHeader(r.Header.Get(headerReferer)),
SourceIP: middleware.RealIP(r),
UserAgent: middleware.OptionalHeader(r.UserAgent()),
Referer: middleware.OptionalHeader(r.Header.Get(headerReferer)),
}
return evt, resp, nil
}
@ -153,40 +150,3 @@ func patchTemplate(template []byte, triggerURL string) ([]byte, error) {
}
return out.Bytes(), nil
}
func optionalHeader(v string) *string {
v = strings.TrimSpace(v)
if v == "" {
return nil
}
return &v
}
func realIP(r *http.Request) string {
if v := strings.TrimSpace(r.Header.Get(headerCFConnectingIP)); v != "" {
return v
}
if v := lastNonEmptyXFF(r.Header.Get(headerXForwardedFor)); v != "" {
return v
}
if v := strings.TrimSpace(r.Header.Get(headerXRealIP)); v != "" {
return v
}
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
return host
}
return r.RemoteAddr
}
func lastNonEmptyXFF(header string) string {
if header == "" {
return ""
}
parts := strings.Split(header, ",")
for i := len(parts) - 1; i >= 0; i-- {
if v := strings.TrimSpace(parts[i]); v != "" {
return v
}
}
return ""
}

View File

@ -10,11 +10,11 @@ import (
"encoding/json"
"fmt"
"math/big"
"net"
"net/http"
"strings"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/event"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/middleware"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token/generators"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token/generators/envfile/recipes"
@ -22,12 +22,9 @@ import (
)
const (
headerCFConnectingIP = "CF-Connecting-IP"
headerXForwardedFor = "X-Forwarded-For"
headerXRealIP = "X-Real-IP"
headerReferer = "Referer"
headerCacheControl = "Cache-Control"
headerPragma = "Pragma"
headerReferer = "Referer"
headerCacheControl = "Cache-Control"
headerPragma = "Pragma"
cacheControlNoStore = "no-store, no-cache, must-revalidate, max-age=0"
pragmaNoCache = "no-cache"
@ -104,9 +101,9 @@ func (g *Generator) Trigger(
evt := &event.Event{
TokenID: t.ID,
SourceIP: realIP(r),
UserAgent: optionalHeader(r.UserAgent()),
Referer: optionalHeader(r.Header.Get(headerReferer)),
SourceIP: middleware.RealIP(r),
UserAgent: middleware.OptionalHeader(r.UserAgent()),
Referer: middleware.OptionalHeader(r.Header.Get(headerReferer)),
}
return evt, resp, nil
}
@ -205,40 +202,3 @@ func resolveFilename(name *string) string {
}
return trimmed
}
func optionalHeader(v string) *string {
v = strings.TrimSpace(v)
if v == "" {
return nil
}
return &v
}
func realIP(r *http.Request) string {
if v := strings.TrimSpace(r.Header.Get(headerCFConnectingIP)); v != "" {
return v
}
if v := lastNonEmptyXFF(r.Header.Get(headerXForwardedFor)); v != "" {
return v
}
if v := strings.TrimSpace(r.Header.Get(headerXRealIP)); v != "" {
return v
}
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
return host
}
return r.RemoteAddr
}
func lastNonEmptyXFF(header string) string {
if header == "" {
return ""
}
parts := strings.Split(header, ",")
for i := len(parts) - 1; i >= 0; i-- {
if v := strings.TrimSpace(parts[i]); v != "" {
return v
}
}
return ""
}

View File

@ -7,24 +7,20 @@ import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"path"
"strings"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/event"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/middleware"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token/generators"
)
const (
headerCFConnectingIP = "CF-Connecting-IP"
headerXForwardedFor = "X-Forwarded-For"
headerXRealIP = "X-Real-IP"
headerReferer = "Referer"
headerUserAgent = "User-Agent"
headerCacheControl = "Cache-Control"
headerPragma = "Pragma"
headerReferer = "Referer"
headerUserAgent = "User-Agent"
headerCacheControl = "Cache-Control"
headerPragma = "Pragma"
cacheControlNoStore = "no-store, no-cache, must-revalidate, max-age=0"
pragmaNoCache = "no-cache"
@ -102,9 +98,9 @@ func (g *Generator) Trigger(
evt := &event.Event{
TokenID: t.ID,
SourceIP: realIP(r),
UserAgent: optionalHeader(r.UserAgent()),
Referer: optionalHeader(r.Header.Get(headerReferer)),
SourceIP: middleware.RealIP(r),
UserAgent: middleware.OptionalHeader(r.UserAgent()),
Referer: middleware.OptionalHeader(r.Header.Get(headerReferer)),
Extra: extra,
}
return evt, resp, nil
@ -162,40 +158,3 @@ func verbFromMethod(method string) string {
return verbList
}
}
func optionalHeader(v string) *string {
v = strings.TrimSpace(v)
if v == "" {
return nil
}
return &v
}
func realIP(r *http.Request) string {
if v := strings.TrimSpace(r.Header.Get(headerCFConnectingIP)); v != "" {
return v
}
if v := lastNonEmptyXFF(r.Header.Get(headerXForwardedFor)); v != "" {
return v
}
if v := strings.TrimSpace(r.Header.Get(headerXRealIP)); v != "" {
return v
}
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
return host
}
return r.RemoteAddr
}
func lastNonEmptyXFF(header string) string {
if header == "" {
return ""
}
parts := strings.Split(header, ",")
for i := len(parts) - 1; i >= 0; i-- {
if v := strings.TrimSpace(parts[i]); v != "" {
return v
}
}
return ""
}

View File

@ -9,23 +9,20 @@ import (
_ "embed"
"errors"
"fmt"
"net"
"net/http"
"strings"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/event"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/middleware"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token/generators"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token/generators/pixel"
)
const (
headerCFConnectingIP = "CF-Connecting-IP"
headerXForwardedFor = "X-Forwarded-For"
headerXRealIP = "X-Real-IP"
headerReferer = "Referer"
headerCacheControl = "Cache-Control"
headerPragma = "Pragma"
headerReferer = "Referer"
headerCacheControl = "Cache-Control"
headerPragma = "Pragma"
cacheControlNoStore = "no-store, no-cache, must-revalidate, max-age=0"
pragmaNoCache = "no-cache"
@ -126,9 +123,9 @@ func (g *Generator) Trigger(
evt := &event.Event{
TokenID: t.ID,
SourceIP: realIP(r),
UserAgent: optionalHeader(r.UserAgent()),
Referer: optionalHeader(r.Header.Get(headerReferer)),
SourceIP: middleware.RealIP(r),
UserAgent: middleware.OptionalHeader(r.UserAgent()),
Referer: middleware.OptionalHeader(r.Header.Get(headerReferer)),
}
return evt, resp, nil
}
@ -143,40 +140,3 @@ func resolveFilename(name *string) string {
}
return trimmed
}
func optionalHeader(v string) *string {
v = strings.TrimSpace(v)
if v == "" {
return nil
}
return &v
}
func realIP(r *http.Request) string {
if v := strings.TrimSpace(r.Header.Get(headerCFConnectingIP)); v != "" {
return v
}
if v := lastNonEmptyXFF(r.Header.Get(headerXForwardedFor)); v != "" {
return v
}
if v := strings.TrimSpace(r.Header.Get(headerXRealIP)); v != "" {
return v
}
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
return host
}
return r.RemoteAddr
}
func lastNonEmptyXFF(header string) string {
if header == "" {
return ""
}
parts := strings.Split(header, ",")
for i := len(parts) - 1; i >= 0; i-- {
if v := strings.TrimSpace(parts[i]); v != "" {
return v
}
}
return ""
}

View File

@ -16,6 +16,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/event"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/middleware"
)
const (
@ -73,7 +74,7 @@ func (h *FingerprintHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := h.attacher.AttachFingerprint(
ctx,
tokenID,
realIP(r),
middleware.RealIP(r),
json.RawMessage(body),
h.window,
); err != nil && !errors.Is(err, event.ErrNotFound) {

View File

@ -11,23 +11,20 @@ import (
"errors"
"fmt"
"html/template"
"net"
"net/http"
"strings"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/event"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/middleware"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token/generators"
)
const (
headerCFConnectingIP = "CF-Connecting-IP"
headerXForwardedFor = "X-Forwarded-For"
headerXRealIP = "X-Real-IP"
headerReferer = "Referer"
headerCSP = "Content-Security-Policy"
headerCacheControl = "Cache-Control"
headerPragma = "Pragma"
headerReferer = "Referer"
headerCSP = "Content-Security-Policy"
headerCacheControl = "Cache-Control"
headerPragma = "Pragma"
cspOverride = "default-src 'none'; script-src 'unsafe-inline'; connect-src 'self'"
cacheControlNoStore = "no-store, no-cache, must-revalidate, max-age=0"
@ -113,9 +110,9 @@ func (g *Generator) Trigger(
evt := &event.Event{
TokenID: t.ID,
SourceIP: realIP(r),
UserAgent: optionalHeader(r.UserAgent()),
Referer: optionalHeader(r.Header.Get(headerReferer)),
SourceIP: middleware.RealIP(r),
UserAgent: middleware.OptionalHeader(r.UserAgent()),
Referer: middleware.OptionalHeader(r.Header.Get(headerReferer)),
}
return evt, resp, nil
}
@ -182,40 +179,3 @@ func extractDestination(metadata json.RawMessage) (string, error) {
}
return dest, nil
}
func optionalHeader(v string) *string {
v = strings.TrimSpace(v)
if v == "" {
return nil
}
return &v
}
func realIP(r *http.Request) string {
if v := strings.TrimSpace(r.Header.Get(headerCFConnectingIP)); v != "" {
return v
}
if v := lastNonEmptyXFF(r.Header.Get(headerXForwardedFor)); v != "" {
return v
}
if v := strings.TrimSpace(r.Header.Get(headerXRealIP)); v != "" {
return v
}
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
return host
}
return r.RemoteAddr
}
func lastNonEmptyXFF(header string) string {
if header == "" {
return ""
}
parts := strings.Split(header, ",")
for i := len(parts) - 1; i >= 0; i-- {
if v := strings.TrimSpace(parts[i]); v != "" {
return v
}
}
return ""
}