| 1 | package console |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "encoding/json" |
| 6 | "net/http" |
| 7 | "net/http/httptest" |
| 8 | "testing" |
| 9 | "time" |
| 10 | |
| 11 | "github.com/carlosframework/platform/internal/release" |
| 12 | ) |
| 13 | |
| 14 | func TestHostClaimRoutesArePersonOperatorFlagGatedAndInert(t *testing.T) { |
| 15 | st := openGatedStore(t) |
| 16 | rel := release.NewDirStore(t.TempDir()) |
| 17 | disabled, err := NewServer(Config{Origin: "https://console.example", Keymail: "https://keymail.example", Releases: rel}, st) |
| 18 | if err != nil { |
| 19 | t.Fatal(err) |
| 20 | } |
| 21 | path := "/api/cli/ops/hosts/app.example.com/resolve/inspect" |
| 22 | ghostStatus, ghostBody := serveHostClaim(t, disabled, "/unknown-host-claim-route", "") |
| 23 | if status, body := serveHostClaim(t, disabled, path, ""); status != ghostStatus || !bytes.Equal(body, ghostBody) { |
| 24 | t.Fatalf("flag-off route = %d %q, want unknown route %d %q", status, body, ghostStatus, ghostBody) |
| 25 | } |
| 26 | |
| 27 | token := mintHostClaimToken(t, st, "paul@keymail.dev") |
| 28 | enabled, err := NewServer(Config{Origin: "https://console.example", Keymail: "https://keymail.example", Releases: rel, HostClaim: true}, st) |
| 29 | if err != nil { |
| 30 | t.Fatal(err) |
| 31 | } |
| 32 | if status, _ := serveHostClaim(t, enabled, path, ""); status != http.StatusUnauthorized { |
| 33 | t.Fatalf("unsigned enabled status = %d", status) |
| 34 | } |
| 35 | if status, _ := serveHostClaim(t, enabled, path, token); status != http.StatusNotFound { |
| 36 | t.Fatalf("enabled member status = %d", status) |
| 37 | } |
| 38 | if err := st.SetOperator("paul@keymail.dev", true); err != nil { |
| 39 | t.Fatal(err) |
| 40 | } |
| 41 | status, body := serveHostClaim(t, enabled, path, token) |
| 42 | if status != http.StatusServiceUnavailable { |
| 43 | t.Fatalf("flag-on status = %d, body %s", status, body) |
| 44 | } |
| 45 | var out map[string]string |
| 46 | if err := json.Unmarshal(body, &out); err != nil { |
| 47 | t.Fatal(err) |
| 48 | } |
| 49 | if out["error"] != hostClaimUnavailable { |
| 50 | t.Fatalf("refusal = %q", out["error"]) |
| 51 | } |
| 52 | |
| 53 | status, body = serveHostClaim(t, enabled, "/api/cli/ops/hosts/app.example.com/resolve", token) |
| 54 | if status != http.StatusServiceUnavailable { |
| 55 | t.Fatalf("mutation route = %d, body %s", status, body) |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | func mintHostClaimToken(t *testing.T, st *Store, address string) string { |
| 60 | t.Helper() |
| 61 | now := time.Now() |
| 62 | userCode, deviceCode, err := st.NewCLIAuth("test@host", "192.0.2.1", now) |
| 63 | if err != nil { |
| 64 | t.Fatal(err) |
| 65 | } |
| 66 | if err := st.ApproveCLIAuth(userCode, address, now); err != nil { |
| 67 | t.Fatal(err) |
| 68 | } |
| 69 | status, token, _, err := st.PollCLIAuth(deviceCode, now) |
| 70 | if err != nil || status != CLIAuthApproved || token == "" { |
| 71 | t.Fatalf("mint token = %q %v", status, err) |
| 72 | } |
| 73 | return token |
| 74 | } |
| 75 | |
| 76 | func serveHostClaim(t *testing.T, srv *Server, path, bearer string) (int, []byte) { |
| 77 | t.Helper() |
| 78 | req := httptest.NewRequest(http.MethodPost, path, bytes.NewBufferString(`{}`)) |
| 79 | req.Header.Set("Content-Type", "application/json") |
| 80 | if bearer != "" { |
| 81 | req.Header.Set("Authorization", "Bearer "+bearer) |
| 82 | } |
| 83 | rec := httptest.NewRecorder() |
| 84 | srv.ServeHTTP(rec, req) |
| 85 | return rec.Code, rec.Body.Bytes() |
| 86 | } |
| 87 | |