56 lines
1.5 KiB
Go
56 lines
1.5 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"halcyon/store"
|
|
)
|
|
|
|
// clientIP is the address a request really came from. Behind our own reverse
|
|
// proxy (a loopback or private peer) the proxy's X-Forwarded-For names it;
|
|
// otherwise the header is untrusted and ignored.
|
|
func clientIP(r *http.Request) string {
|
|
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
if err != nil {
|
|
host = r.RemoteAddr
|
|
}
|
|
if ip := net.ParseIP(host); ip != nil && (ip.IsLoopback() || ip.IsPrivate()) {
|
|
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
|
first := strings.TrimSpace(strings.Split(xff, ",")[0])
|
|
if net.ParseIP(first) != nil {
|
|
return first
|
|
}
|
|
}
|
|
}
|
|
return host
|
|
}
|
|
|
|
func sha256Hex(b []byte) string {
|
|
h := sha256.Sum256(b)
|
|
return hex.EncodeToString(h[:])
|
|
}
|
|
|
|
// audit records something a player did. It is best effort: a failure to write
|
|
// the log is reported but never fails the player's request.
|
|
func (s *Server) audit(r *http.Request, player int64, action string, ship int64, slot int, detail map[string]any) {
|
|
if detail == nil {
|
|
detail = map[string]any{}
|
|
}
|
|
b, _ := json.Marshal(detail)
|
|
err := s.st.AddAudit(context.WithoutCancel(r.Context()), store.AuditEntry{
|
|
At: time.Now().Unix(), PlayerID: player, Action: action, Ship: ship, Slot: slot,
|
|
Detail: string(b), IP: clientIP(r),
|
|
})
|
|
if err != nil {
|
|
log.Printf("audit: could not record %s for player %d: %v", action, player, err)
|
|
}
|
|
}
|