Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/cli/docs/go-cli-porting-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,8 @@ Legend:
| `vanity-subdomains check-availability` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts`](../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts) |
| `vanity-subdomains activate` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/activate/activate.command.ts`](../src/legacy/commands/vanity-subdomains/activate/activate.command.ts) |
| `vanity-subdomains delete` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/delete/delete.command.ts`](../src/legacy/commands/vanity-subdomains/delete/delete.command.ts) |
| `network-bans get` | `wrapped` | [`../src/legacy/commands/network-bans/get/get.command.ts`](../src/legacy/commands/network-bans/get/get.command.ts) |
| `network-bans remove` | `wrapped` | [`../src/legacy/commands/network-bans/remove/remove.command.ts`](../src/legacy/commands/network-bans/remove/remove.command.ts) |
| `network-bans get` | `ported` | [`../src/legacy/commands/network-bans/get/get.command.ts`](../src/legacy/commands/network-bans/get/get.command.ts) |
| `network-bans remove` | `ported` | [`../src/legacy/commands/network-bans/remove/remove.command.ts`](../src/legacy/commands/network-bans/remove/remove.command.ts) |
| `network-restrictions get` | `ported` | [`../src/legacy/commands/network-restrictions/get/get.command.ts`](../src/legacy/commands/network-restrictions/get/get.command.ts) |
| `network-restrictions update` | `ported` | [`../src/legacy/commands/network-restrictions/update/update.command.ts`](../src/legacy/commands/network-restrictions/update/update.command.ts) |
| `encryption get-root-key` | `wrapped` | [`../src/legacy/commands/encryption/get-root-key/get-root-key.command.ts`](../src/legacy/commands/encryption/get-root-key/get-root-key.command.ts) |
Expand Down
82 changes: 54 additions & 28 deletions apps/cli/src/legacy/commands/network-bans/get/SIDE_EFFECTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,83 @@

## Files Read

| Path | Format | When |
| -------------------------- | ------------------------- | ---------------------------------------------------------- |
| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable |
| Path | Format | When |
| -------------------------------------- | ------------------------- | ------------------------------------------------------------- |
| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable |
| `<workdir>/supabase/.temp/project-ref` | plain text (project ref) | when `--project-ref` flag and `PROJECT_ID` env are both unset |

## Files Written

| Path | Format | When |
| ---- | ------ | ---- |
| — | — | — |
| Path | Format | When |
| ------------------------------------------------ | ------ | ----------------------------------------------------------------------------- |
| `~/.supabase/<workdir-hash>/linked-project.json` | JSON | always (after ref resolution), via `Effect.ensuring` — on success and failure |
| `~/.supabase/telemetry.json` | JSON | always, via `Effect.ensuring` — on success and failure |

## API Routes

| Method | Path | Auth | Request body | Response (used fields) |
| ------ | --------------------------------- | ------------ | ------------ | ------------------------- |
| `GET` | `/v1/projects/{ref}/network-bans` | Bearer token | none | `{banned_ipv4_addresses}` |
| Method | Path | Auth | Request body | Response (used fields) |
| ------ | ------------------------------------------ | ------------ | ------------ | ------------------------- |
| `POST` | `/v1/projects/{ref}/network-bans/retrieve` | Bearer token | none | `{banned_ipv4_addresses}` |

The Management API exposes this read operation as `POST .../network-bans/retrieve` (not `GET`) — see `V1ListAllNetworkBans` in `packages/api/src/generated/contracts.ts`.

## Environment Variables

| Variable | Purpose | Required? |
| ----------------------- | ---------------------------------------------------- | ------------------------------------------------------- |
| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring → `~/.supabase/access-token`) |
| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) |
| Variable | Purpose | Required? |
| ----------------------- | ---------------------------------------------------- | -------------------------------------------------------- |
| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring → `~/.supabase/access-token`) |
| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) |
| `PROJECT_ID` | project ref fallback when `--project-ref` is unset | no (falls back to `supabase/.temp/project-ref` → prompt) |

## Exit Codes

| Code | Condition |
| ---- | ------------------------------------------------------- |
| `0` | success — network bans printed to stdout |
| `1` | authentication error — no valid token found |
| `1` | API error — non-2xx response from network bans endpoint |
| `1` | network / connection failure |
| Code | Condition |
| ---- | --------------------------------------------------------------------------------------- |
| `0` | success — network bans printed to stdout |
| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) |
| `1` | API non-2xx (`LegacyNetworkBansGetUnexpectedStatusError`) |
| `1` | transport failure (`LegacyNetworkBansGetNetworkError`) |
| `1` | `--output env` requested (`LegacyNetworkBansEnvNotSupportedError`) |

## Telemetry Events Fired

| Event | When | Notable properties / groups |
| ---------------------- | ------------------------------------------ | -------------------------------------------------------------------- |
| `cli_command_executed` | post-run, success or failure (via wrapper) | `exit_code`, `duration_ms`, `flags` (`--project-ref` → `<redacted>`) |

Matches `apps/cli-go/internal/bans/get/`. Go does not fire any custom telemetry event for this command.

## Output

### `--output-format text` (Go CLI compatible)
A `DB banned IPs:` heading is written to stderr unconditionally before any stdout output (mirrors Go's `fmt.Fprintln(os.Stderr, "DB banned IPs:")`). Exception: `--output-format json` / `--output-format stream-json` (with no Go `--output` flag set) emit a structured success event and skip the stderr heading to keep machine-readable output clean.

### `--output-format text` (default) — Go CLI compatible

Stderr heading followed by the banned IP array rendered as Go-compatible JSON (alphabetical key order, two-space indent, trailing newline).

Prints a list of banned IP addresses to stdout.
### Go `--output {json,pretty,yaml,toml}`

Byte-identical to the Go CLI's encoders.

- `json` and `pretty` — Go-compatible JSON of the IP array (`pretty` aliases to `json` per `apps/cli-go/internal/bans/get/get.go:21-23`).
- `yaml` — `stringifyYaml(ipArray)`.
- `toml` — `banned_ips = ["…", "…"]\n` (matches the Go struct tag `toml:"banned_ips"`).

### Go `--output env`

Fails with `LegacyNetworkBansEnvNotSupportedError`, matching Go's `utils.ErrEnvNotSupported`.

### `--output-format json`

Single JSON object emitted to stdout on success.
The full `V1ListAllNetworkBansOutput` response object (`{ banned_ipv4_addresses: string[] }`) emitted as the `success` event payload. Note: the Go `--output json` mode emits only the bare array — the TS-native `--output-format json` mode wraps it in the response object for consistency with other TS-native commands.

### `--output-format stream-json`

One `result` event on success.

```ndjson
{"type":"result","data":{...}}
```
One `result` event whose `data` is the full response object.

## Notes

- Requires `--project-ref` or a linked project (`.supabase/config.json`).
- The Go `--output` flag wins over the TS `--output-format` flag when both are provided.
- `linked-project.json` is written **after** the project ref is resolved, regardless of whether the subsequent API call succeeds (mirrors Go's `PersistentPostRun`).
- `telemetry.json` is written on every invocation, including failures.
- Network bans are temporary blocks on IPs with abusive traffic patterns (e.g. multiple failed auth attempts).
12 changes: 11 additions & 1 deletion apps/cli/src/legacy/commands/network-bans/get/get.command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Command, Flag } from "effect/unstable/cli";
import type * as CliCommand from "effect/unstable/cli/Command";

import { withJsonErrorHandling } from "../../../../shared/output/json-error-handling.ts";
import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts";
import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts";
import { legacyNetworkBansGet } from "./get.handler.ts";

const config = {
Expand All @@ -14,5 +18,11 @@ export type LegacyNetworkBansGetFlags = CliCommand.Command.Config.Infer<typeof c
export const legacyNetworkBansGetCommand = Command.make("get", config).pipe(
Command.withDescription("Get the current network bans."),
Command.withShortDescription("Get the current network bans"),
Command.withHandler((flags) => legacyNetworkBansGet(flags)),
Command.withHandler((flags) =>
legacyNetworkBansGet(flags).pipe(
withLegacyCommandInstrumentation({ flags }),
withJsonErrorHandling,
),
),
Command.provide(legacyManagementApiRuntimeLayer(["network-bans", "get"])),
);
83 changes: 78 additions & 5 deletions apps/cli/src/legacy/commands/network-bans/get/get.handler.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,85 @@
import { Effect, Option } from "effect";
import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts";

import { LegacyPlatformApi } from "../../../auth/legacy-platform-api.service.ts";
import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts";
import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts";
import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts";
import { LegacyOutputFlag } from "../../../../shared/legacy/global-flags.ts";
import { Output } from "../../../../shared/output/output.service.ts";
import { encodeGoJson, encodeYaml } from "../../../shared/legacy-go-output.encoders.ts";
import { mapLegacyHttpError } from "../../../shared/legacy-http-errors.ts";
import { encodeBannedIpsToml } from "../network-bans.encoders.ts";
import {
LegacyNetworkBansEnvNotSupportedError,
LegacyNetworkBansGetNetworkError,
LegacyNetworkBansGetUnexpectedStatusError,
} from "../network-bans.errors.ts";
import type { LegacyNetworkBansGetFlags } from "./get.command.ts";

const mapGetError = mapLegacyHttpError({
networkError: LegacyNetworkBansGetNetworkError,
statusError: LegacyNetworkBansGetUnexpectedStatusError,
networkMessage: (cause) => `failed to list network bans: ${cause}`,
statusMessage: (status, body) => `unexpected list bans status ${status}: ${body}`,
});

export const legacyNetworkBansGet = Effect.fn("legacy.network-bans.get")(function* (
flags: LegacyNetworkBansGetFlags,
) {
const proxy = yield* LegacyGoProxy;
const args: string[] = ["network-bans", "get"];
if (Option.isSome(flags.projectRef)) args.push("--project-ref", flags.projectRef.value);
yield* proxy.exec(args);
const output = yield* Output;
const legacyOutputFlag = yield* LegacyOutputFlag;
const api = yield* LegacyPlatformApi;
const resolver = yield* LegacyProjectRefResolver;
const linkedProjectCache = yield* LegacyLinkedProjectCache;
const telemetryState = yield* LegacyTelemetryState;

yield* Effect.gen(function* () {
const ref = yield* resolver.resolve(flags.projectRef);

yield* Effect.gen(function* () {
const fetching =
output.format === "text" ? yield* output.task("Fetching network bans...") : undefined;
const response = yield* api.v1.listAllNetworkBans({ ref }).pipe(
Effect.tapError(() => fetching?.fail() ?? Effect.void),
Effect.catch(mapGetError),
);
yield* fetching?.clear() ?? Effect.void;

const legacyOutput = Option.getOrUndefined(legacyOutputFlag);

// TS-native machine-readable modes skip the stderr heading for clean output.
// Go --output takes priority (CLAUDE.md item 6), so this only fires when the
// legacy flag is unset.
if (
legacyOutput === undefined &&
(output.format === "json" || output.format === "stream-json")
) {
yield* output.success("", response);
return;
}

// Go's `get.Run` prints `DB banned IPs:` to stderr unconditionally before
// the format switch (`apps/cli-go/internal/bans/get/get.go:19`), including
// for `--output env` (which then errors).
yield* output.raw("DB banned IPs:\n", "stderr");

if (legacyOutput === "env") {
return yield* new LegacyNetworkBansEnvNotSupportedError({
message: "--output env flag is not supported",
});
}
if (legacyOutput === "yaml") {
yield* output.raw(encodeYaml(response.banned_ipv4_addresses));
return;
}
if (legacyOutput === "toml") {
yield* output.raw(encodeBannedIpsToml(response.banned_ipv4_addresses));
return;
}

// Default and `--output {json,pretty}`. Go aliases `pretty` → `json` in
// `get.go:21-23` and falls through to `EncodeOutput(format, ips)`.
yield* output.raw(encodeGoJson(response.banned_ipv4_addresses));
}).pipe(Effect.ensuring(linkedProjectCache.cache(ref)));
}).pipe(Effect.ensuring(telemetryState.flush));
});
Loading
Loading