package console import ( "context" "net" "net/http" "sort" "strings" "github.com/carlosframework/platform/internal/geo" "github.com/carlosframework/platform/internal/provision" ) // regionReq is what a create door hands claimApp: never a resolved region. // The guess is resolved INSIDE claimApp, after it knows whether this is an // adoption (spec §5.5). ip is request-local and only set for a person. The // timezone and the address go into the guess and nowhere else: never // logged, stored, audited or echoed (spec §6.5). type regionReq struct { explicit string // a person's choice, record vocabulary hasExplicit bool tz string ip net.IP // request-local, person requests only } // validateExplicitRegion checks a person's explicit region as the instance // door checks one (spec §8.1): ValidRegion; armed and hosting unless the // gate is dormant; not provably without the secrets key (R9). The home's // name normalizes to "". A refusal lists exactly the codes this same check // would accept now (codex plan-R4 2). func (s *Server) validateExplicitRegion(ctx context.Context, raw string) (string, *doorRefusal) { code := s.homeRegionIn(strings.TrimSpace(raw)) if code == "" { return "", nil } gate := s.readExplicitGate(ctx) switch gate.refusal(code) { case "": return code, nil case "unknown": return "", &doorRefusal{http.StatusBadRequest, msgRegionUnknown(code, s.explicitChoices(gate))} case "not-armed": return "", &doorRefusal{http.StatusBadRequest, msgRegionNotArmed(code, s.explicitChoices(gate))} } return "", &doorRefusal{http.StatusBadRequest, msgNoSecretsKey(code)} } // explicitGate is one read of what an explicit app region must pass. type explicitGate struct { caps map[string]placementCapability published bool cands []regionCandidate // nil when the edges read failed: R9 cannot refuse } func (s *Server) readExplicitGate(ctx context.Context) explicitGate { g := explicitGate{} g.caps, g.published = placements(ctx, s.cfg.Releases) if snap, err := s.liveEdges(ctx); err == nil { g.cands = s.regionCands(snap) } return g } // refusal is "" when an explicit, record-vocabulary code passes, else // which bar it fails: "unknown", "not-armed" or "no-key". func (g explicitGate) refusal(code string) string { switch { case code == "": return "" case !provision.ValidRegion(code): return "unknown" case g.published && !(g.caps[code].Reachable && g.caps[code].Hosting): return "not-armed" } if c, ok := findCandidate(g.cands, code); ok && c.NoKeyProven { return "no-key" } return "" } // explicitChoices is the home's name plus every published code that // refusal passes, sorted: never a code this door would refuse. func (s *Server) explicitChoices(g explicitGate) []string { set := map[string]bool{} if s.cfg.HomeRegion != "" { set[s.cfg.HomeRegion] = true } for code := range g.caps { if c := s.homeRegionIn(code); c != "" && g.refusal(c) == "" { set[c] = true } } out := make([]string, 0, len(set)) for c := range set { out = append(out, c) } sort.Strings(out) return out } // resolveNewAppRegion is spec §5.5 and §5.6 for a NEW row. A guess must // pass the same predicate that builds the C18 and C19 lists // (explicitGate.refusal), so no door ever stores a region another door // would refuse to list. func (s *Server) resolveNewAppRegion(ctx context.Context, rq regionReq, adopting bool) (region, source string) { if !s.appRegionsOn() { return "", "default" } if rq.hasExplicit { // Defence in depth for a future door that forgets to validate: a // code that is not a region never reaches the row. "" is the home. if rq.explicit != "" && !provision.ValidRegion(rq.explicit) { return "", "default" } return rq.explicit, "chosen" } if adopting { return "", "default" } g := s.readExplicitGate(ctx) if g.cands == nil { return "", "default" } guess, ok := guessRegion(g.cands, func(code string) bool { return g.refusal(code) == "" }, guessInput{TZ: rq.tz, IP: rq.ip}, s.cfg.GeoIP) if !ok { return "", "default" } return guess.Code, guess.Source } // placeOf names a record-vocabulary code: its AWS region's place, else the // code under its name. func (s *Server) placeOf(code string, cands []regionCandidate) string { c, _ := findCandidate(cands, code) return geo.PlaceName(c.AWSRegion, s.homeRegionOut(code)) } // personIP is the request's own address, for a person only (spec §6.2). // A service or app principal is a server; its address says nothing about // where a person is. func (s *Server) personIP(r *http.Request) net.IP { if p, ok := principalFrom(r.Context()); ok && p.Kind != PrincipalUser { return nil } return net.ParseIP(s.clientIP(r)) }