Rewrites the internal/api comment blocks to rule 32: task, milestone and rule ids gone, the stories behind them restated as the constraint they left, stale references corrected (the stream secret is wired from config, the timezone handler already refreshes the scheduler, the Android app is the native client, the design docs cited are not in the repo). Also rewraps comment lines over 80 columns left by the earlier batches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
3.6 KiB
Go
97 lines
3.6 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/auth"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/netsettings"
|
|
)
|
|
|
|
type networkSettingsResp struct {
|
|
TrustedProxyHops int `json:"trusted_proxy_hops"`
|
|
MaxHops int `json:"max_hops"`
|
|
// DetectedClientIP is what the CURRENT setting resolves this very request
|
|
// to. It's the difference between a number an admin has to reason
|
|
// about and one they can verify: set the value, reload, and check the
|
|
// address matches the machine you're sitting at.
|
|
DetectedClientIP string `json:"detected_client_ip"`
|
|
// ForwardedChain is the raw X-Forwarded-For as received, so an operator
|
|
// whose detected address looks wrong can see how many hops actually
|
|
// arrived and count them rather than guess.
|
|
ForwardedChain string `json:"forwarded_chain"`
|
|
RemoteAddr string `json:"remote_addr"`
|
|
// PublicURL is where users reach Minstrel; reset emails link to it and
|
|
// are not sent while it is empty.
|
|
PublicURL string `json:"public_url"`
|
|
}
|
|
|
|
// Both fields are optional so the proxy card and the public-address card can
|
|
// each save their own value without overwriting the other's.
|
|
type updateNetworkSettingsReq struct {
|
|
TrustedProxyHops *int `json:"trusted_proxy_hops"`
|
|
PublicURL *string `json:"public_url"`
|
|
}
|
|
|
|
func (h *handlers) handleGetNetworkSettings(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, http.StatusOK, h.networkSettingsPayload(r))
|
|
}
|
|
|
|
func (h *handlers) handleUpdateNetworkSettings(w http.ResponseWriter, r *http.Request) {
|
|
var req updateNetworkSettingsReq
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeErr(w, apierror.BadRequest("invalid_body", "malformed JSON"))
|
|
return
|
|
}
|
|
if req.TrustedProxyHops == nil && req.PublicURL == nil {
|
|
writeErr(w, apierror.BadRequest("invalid_body", "nothing to update"))
|
|
return
|
|
}
|
|
// Validate everything before writing anything, so a bad public URL can't
|
|
// leave the hops half-saved.
|
|
if req.TrustedProxyHops != nil && (*req.TrustedProxyHops < 0 || *req.TrustedProxyHops > netsettings.MaxTrustedProxyHops) {
|
|
writeErr(w, apierror.BadRequest("invalid_hops", netsettings.ErrHopsOutOfRange.Error()))
|
|
return
|
|
}
|
|
if req.PublicURL != nil {
|
|
if _, err := netsettings.NormalizePublicURL(*req.PublicURL); err != nil {
|
|
writeErr(w, apierror.BadRequest("invalid_public_url", err.Error()))
|
|
return
|
|
}
|
|
}
|
|
if req.TrustedProxyHops != nil {
|
|
if err := h.netSettings.SetHops(r.Context(), *req.TrustedProxyHops); err != nil {
|
|
if errors.Is(err, netsettings.ErrHopsOutOfRange) {
|
|
writeErr(w, apierror.BadRequest("invalid_hops", err.Error()))
|
|
return
|
|
}
|
|
writeErrWithLog(w, h.logger, "admin network: update failed", apierror.Internal(err))
|
|
return
|
|
}
|
|
}
|
|
if req.PublicURL != nil {
|
|
if err := h.netSettings.SetPublicURL(r.Context(), *req.PublicURL); err != nil {
|
|
writeErrWithLog(w, h.logger, "admin network: public URL update failed", apierror.Internal(err))
|
|
return
|
|
}
|
|
}
|
|
// Echo the payload recomputed under the NEW value, so the card can show
|
|
// immediately what the change did to this request's own address rather
|
|
// than making an admin reload to find out.
|
|
writeJSON(w, http.StatusOK, h.networkSettingsPayload(r))
|
|
}
|
|
|
|
func (h *handlers) networkSettingsPayload(r *http.Request) networkSettingsResp {
|
|
hops := h.netSettings.Hops()
|
|
return networkSettingsResp{
|
|
TrustedProxyHops: hops,
|
|
MaxHops: netsettings.MaxTrustedProxyHops,
|
|
DetectedClientIP: auth.ClientIP(r, hops),
|
|
ForwardedChain: r.Header.Get("X-Forwarded-For"),
|
|
RemoteAddr: r.RemoteAddr,
|
|
PublicURL: h.netSettings.PublicURL(),
|
|
}
|
|
}
|