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
2 changes: 1 addition & 1 deletion apps/cli-go/internal/utils/access_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

var (
AccessTokenPattern = regexp.MustCompile(`^sbp_(oauth_)?[a-f0-9]{40}$`)
AccessTokenPattern = regexp.MustCompile(`^sbp_(oauth_|v0_)?[a-f0-9]{40}$`)
Comment thread
7ttp marked this conversation as resolved.
ErrInvalidToken = errors.New("Invalid access token format. Must be like `sbp_0102...1920`.")
ErrMissingToken = errors.Errorf("Access token not provided. Supply an access token by running %s or setting the SUPABASE_ACCESS_TOKEN environment variable.", Aqua("supabase login"))
ErrNotLoggedIn = errors.New("You were not logged in, nothing to do.")
Expand Down
17 changes: 17 additions & 0 deletions apps/cli-go/internal/utils/access_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ func TestLoadToken(t *testing.T) {
assert.Equal(t, token, loaded)
})

t.Run("loads v0 token from env var", func(t *testing.T) {
v0Token := "sbp_v0_" + token[len("sbp_"):]
t.Setenv("SUPABASE_ACCESS_TOKEN", v0Token)
fsys := afero.NewMemMapFs()
loaded, err := LoadAccessTokenFS(fsys)
assert.NoError(t, err)
assert.Equal(t, v0Token, loaded)
})

t.Run("throws error on unknown version prefix", func(t *testing.T) {
t.Setenv("SUPABASE_ACCESS_TOKEN", "sbp_v1_"+token[len("sbp_"):])
fsys := afero.NewMemMapFs()
loaded, err := LoadAccessTokenFS(fsys)
assert.ErrorIs(t, err, ErrInvalidToken)
assert.Empty(t, loaded)
})

t.Run("throws error on invalid token", func(t *testing.T) {
t.Setenv("SUPABASE_ACCESS_TOKEN", "invalid")
// Setup in-memory fs
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/legacy/auth/legacy-access-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Effect } from "effect";
import { legacyAqua } from "../shared/legacy-colors.ts";
import { LegacyInvalidAccessTokenError } from "./legacy-errors.ts";

export const LEGACY_ACCESS_TOKEN_PATTERN = /^sbp_(oauth_)?[a-f0-9]{40}$/;
export const LEGACY_ACCESS_TOKEN_PATTERN = /^sbp_(oauth_|v0_)?[a-f0-9]{40}$/;
Comment thread
7ttp marked this conversation as resolved.

/**
* Message shown when no access token is available, passing `supabase login`
Expand Down
49 changes: 48 additions & 1 deletion apps/cli/src/legacy/auth/legacy-credentials.layer.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { join } from "node:path";

import { describe, expect, it } from "@effect/vitest";
import { BunServices } from "@effect/platform-bun";
import { Effect, FileSystem, Layer, Option, PlatformError, Redacted } from "effect";
import { Effect, Exit, FileSystem, Layer, Option, PlatformError, Redacted } from "effect";
import { afterEach, beforeEach, vi } from "vitest";

import {
Expand Down Expand Up @@ -164,6 +164,7 @@ afterEach(() => {

const VALID_TOKEN = "sbp_" + "a".repeat(40);
const VALID_OAUTH_TOKEN = "sbp_oauth_" + "b".repeat(40);
const VALID_V0_TOKEN = "sbp_v0_" + "c".repeat(40);
const encodeGoKeyringBase64 = (token: string) =>
`go-keyring-base64:${Buffer.from(token).toString("base64")}`;
const goWindowsKey = (account: string) => `Supabase CLI:${account}/Supabase CLI/${account}`;
Expand Down Expand Up @@ -197,6 +198,14 @@ describe("legacyCredentialsLayer.getAccessToken", () => {
}).pipe(Effect.provide(makeLayer({ env: { SUPABASE_ACCESS_TOKEN: VALID_TOKEN } })));
});

it.effect("returns a versioned-format (sbp_v0_) env token", () =>
Effect.gen(function* () {
const { getAccessToken } = yield* LegacyCredentials;
const token = yield* getAccessToken;
expectSomeToken(token, VALID_V0_TOKEN);
}).pipe(Effect.provide(makeLayer({ env: { SUPABASE_ACCESS_TOKEN: VALID_V0_TOKEN } }))),
);

it.effect("uses the keyring profile account when env is unset", () => {
passwords.set("Supabase CLI/supabase", VALID_TOKEN);
return Effect.gen(function* () {
Expand Down Expand Up @@ -304,6 +313,36 @@ describe("legacyCredentialsLayer.getAccessToken", () => {
}).pipe(Effect.provide(makeLayer()));
});

it.effect("rejects an unknown version prefix (sbp_v1_)", () =>
Effect.gen(function* () {
const { getAccessToken } = yield* LegacyCredentials;
const exit = yield* Effect.exit(getAccessToken);
expect(Exit.isFailure(exit)).toBe(true);
const errorOption = Exit.findErrorOption(exit);
expect(Option.isSome(errorOption)).toBe(true);
if (Option.isSome(errorOption)) {
expect(errorOption.value).toBeInstanceOf(LegacyInvalidAccessTokenError);
}
}).pipe(
Effect.provide(makeLayer({ env: { SUPABASE_ACCESS_TOKEN: "sbp_v1_" + "c".repeat(40) } })),
),
);

it.effect("rejects a versioned-format (sbp_v0_) token with a truncated payload", () =>
Effect.gen(function* () {
const { getAccessToken } = yield* LegacyCredentials;
const exit = yield* Effect.exit(getAccessToken);
expect(Exit.isFailure(exit)).toBe(true);
Comment thread
7ttp marked this conversation as resolved.
const errorOption = Exit.findErrorOption(exit);
expect(Option.isSome(errorOption)).toBe(true);
if (Option.isSome(errorOption)) {
expect(errorOption.value).toBeInstanceOf(LegacyInvalidAccessTokenError);
}
}).pipe(
Effect.provide(makeLayer({ env: { SUPABASE_ACCESS_TOKEN: "sbp_v0_" + "c".repeat(39) } })),
),
);

it.effect("falls back to the filesystem when keyring throws", () => {
throwOnGetPasswordAccounts.add("Supabase CLI/supabase");
throwOnGetPasswordAccounts.add("Supabase CLI/access-token");
Expand Down Expand Up @@ -338,6 +377,14 @@ describe("legacyCredentialsLayer.saveAccessToken", () => {
}).pipe(Effect.provide(makeLayer())),
);

it.effect("saves a versioned-format (sbp_v0_) token", () =>
Effect.gen(function* () {
const { saveAccessToken } = yield* LegacyCredentials;
yield* saveAccessToken(VALID_V0_TOKEN);
expect(passwords.get("Supabase CLI/supabase")).toBe(VALID_V0_TOKEN);
}).pipe(Effect.provide(makeLayer())),
);

it.effect("writes Windows credentials where Go keyring reads them", () =>
Effect.gen(function* () {
const { saveAccessToken } = yield* LegacyCredentials;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
| ---- | ------------------------------------------------------------------------------------------ |
| `0` | success — backup list printed to stdout |
| `1` | `LegacyPlatformAuthRequiredError` — no token in env/keyring/file |
| `1` | `LegacyInvalidAccessTokenError` — token violates `^sbp_(oauth_)?[a-f0-9]{40}$` |
| `1` | `LegacyInvalidAccessTokenError` — token violates `^sbp_(oauth_\|v0_)?[a-f0-9]{40}$` |
| `1` | `LegacyProjectNotLinkedError` — `--project-ref` unset, env/file empty, and stdin not a TTY |
| `1` | `LegacyInvalidProjectRefError` — resolved ref violates `^[a-z]{20}$` |
| `1` | `LegacyBackupListUnexpectedStatusError` — non-2xx response from the backups endpoint |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
| ---- | ------------------------------------------------------------------------------------------ |
| `0` | success — restore initiated |
| `1` | `LegacyPlatformAuthRequiredError` — no token in env/keyring/file |
| `1` | `LegacyInvalidAccessTokenError` — token violates `^sbp_(oauth_)?[a-f0-9]{40}$` |
| `1` | `LegacyInvalidAccessTokenError` — token violates `^sbp_(oauth_\|v0_)?[a-f0-9]{40}$` |
| `1` | `LegacyProjectNotLinkedError` — `--project-ref` unset, env/file empty, and stdin not a TTY |
| `1` | `LegacyInvalidProjectRefError` — resolved ref violates `^[a-z]{20}$` |
| `1` | `LegacyBackupRestoreUnexpectedStatusError` — non-201 response from the restore endpoint |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
| ---- | ------------------------------------------------------------------------------------------ |
| `0` | success — secrets printed to stdout |
| `1` | `LegacyPlatformAuthRequiredError` — no token in env/keyring/file |
| `1` | `LegacyInvalidAccessTokenError` — token violates `^sbp_(oauth_)?[a-f0-9]{40}$` |
| `1` | `LegacyInvalidAccessTokenError` — token violates `^sbp_(oauth_\|v0_)?[a-f0-9]{40}$` |
| `1` | `LegacyProjectNotLinkedError` — `--project-ref` unset, env/file empty, and stdin not a TTY |
| `1` | `LegacyInvalidProjectRefError` — resolved ref violates `^[a-z]{20}$` |
| `1` | `LegacySecretsListUnexpectedStatusError` — non-2xx response from the secrets endpoint |
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/legacy/commands/secrets/set/SIDE_EFFECTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
| ---- | -------------------------------------------------------------------------------------------- |
| `0` | success — secrets set on the linked project |
| `1` | `LegacyPlatformAuthRequiredError` — no token in env/keyring/file |
| `1` | `LegacyInvalidAccessTokenError` — token violates `^sbp_(oauth_)?[a-f0-9]{40}$` |
| `1` | `LegacyInvalidAccessTokenError` — token violates `^sbp_(oauth_\|v0_)?[a-f0-9]{40}$` |
| `1` | `LegacyProjectNotLinkedError` — `--project-ref` unset, env/file empty, and stdin not a TTY |
| `1` | `LegacyInvalidProjectRefError` — resolved ref violates `^[a-z]{20}$` |
| `1` | `LegacySecretsNoArgumentsError` — no positional pairs and no entries from env-file or config |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
| `0` | success — secrets unset from the linked project |
| `0` | empty-args path resolved to zero non-`SUPABASE_` secrets (stderr no-op, no DELETE call) |
| `1` | `LegacyPlatformAuthRequiredError` — no token in env/keyring/file |
| `1` | `LegacyInvalidAccessTokenError` — token violates `^sbp_(oauth_)?[a-f0-9]{40}$` |
| `1` | `LegacyInvalidAccessTokenError` — token violates `^sbp_(oauth_\|v0_)?[a-f0-9]{40}$` |
| `1` | `LegacyProjectNotLinkedError` — `--project-ref` unset, env/file empty, and stdin not a TTY |
| `1` | `LegacyInvalidProjectRefError` — resolved ref violates `^[a-z]{20}$` |
| `1` | `LegacySecretsListUnexpectedStatusError` — non-2xx response from GET (empty-args path) |
Expand Down
Loading