Skip to content
Open
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
3 changes: 3 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ jobs:
timeout-minutes: 30

env:
NO_COLOR: 1
E2E_ABLY_API_KEY: ${{ secrets.E2E_ABLY_API_KEY }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ACCESS_TOKEN }}
TERMINAL_SERVER_SIGNING_SECRET: ${{ secrets.TERMINAL_SERVER_SIGNING_SECRET }}
E2E_TESTS: true

steps:
Expand Down Expand Up @@ -52,6 +54,7 @@ jobs:
echo "ABLY_API_KEY=${{ secrets.E2E_ABLY_API_KEY }}" > .env.test
echo "E2E_ABLY_API_KEY=${{ secrets.E2E_ABLY_API_KEY }}" >> .env.test
echo "E2E_ABLY_ACCESS_TOKEN=${{ secrets.E2E_ABLY_ACCESS_TOKEN }}" >> .env.test
echo "TERMINAL_SERVER_SIGNING_SECRET=${{ secrets.TERMINAL_SERVER_SIGNING_SECRET }}" >> .env.test

- name: Run All E2E CLI Tests
run: |
Expand Down
12 changes: 8 additions & 4 deletions .github/workflows/e2e-web-cli-parallel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
env:
NO_COLOR: 1
E2E_ABLY_API_KEY: ${{ secrets.E2E_ABLY_API_KEY }}
CI_BYPASS_SECRET: ${{ secrets.CI_BYPASS_SECRET }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ACCESS_TOKEN }}
TERMINAL_SERVER_SIGNING_SECRET: ${{ secrets.TERMINAL_SERVER_SIGNING_SECRET }}
E2E_TESTS: true
HEADLESS: true
TEST_GROUP: auth
Expand Down Expand Up @@ -115,9 +116,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
env:
NO_COLOR: 1
E2E_ABLY_API_KEY: ${{ secrets.E2E_ABLY_API_KEY }}
CI_BYPASS_SECRET: ${{ secrets.CI_BYPASS_SECRET }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ACCESS_TOKEN }}
TERMINAL_SERVER_SIGNING_SECRET: ${{ secrets.TERMINAL_SERVER_SIGNING_SECRET }}
E2E_TESTS: true
HEADLESS: true
TEST_GROUP: session
Expand Down Expand Up @@ -164,9 +166,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
env:
NO_COLOR: 1
E2E_ABLY_API_KEY: ${{ secrets.E2E_ABLY_API_KEY }}
CI_BYPASS_SECRET: ${{ secrets.CI_BYPASS_SECRET }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ACCESS_TOKEN }}
TERMINAL_SERVER_SIGNING_SECRET: ${{ secrets.TERMINAL_SERVER_SIGNING_SECRET }}
E2E_TESTS: true
HEADLESS: true
TEST_GROUP: ui
Expand Down Expand Up @@ -213,10 +216,11 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
env:
NO_COLOR: 1
E2E_ABLY_API_KEY: ${{ secrets.E2E_ABLY_API_KEY }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ACCESS_TOKEN }}
TERMINAL_SERVER_SIGNING_SECRET: ${{ secrets.TERMINAL_SERVER_SIGNING_SECRET }}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rate-limit test bypass silently enabled by signing secret

Medium Severity

The rate-limit test group now has TERMINAL_SERVER_SIGNING_SECRET set, and the bypass checks were changed from CI_BYPASS_SECRET to TERMINAL_SERVER_SIGNING_SECRET. Previously, CI_BYPASS_SECRET was intentionally excluded from the rate-limit test group (as noted in the workflow comments) so rate limiting would remain active. Now, since the signing secret doubles as the bypass flag, the test rate limiter is effectively disabled (1000 connections, 0 pause) and the global setup initial delay is skipped, undermining the rate-limit test's purpose.

Additional Locations (2)

Fix in Cursor Fix in Web

# Rate limit test should NOT use CI bypass
# CI_BYPASS_SECRET is intentionally not set
E2E_TESTS: true
HEADLESS: true
TEST_GROUP: rate-limit
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 30
env:
NO_COLOR: 1

steps:
- name: Checkout code
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4145,7 +4145,7 @@ DESCRIPTION
Commands for interacting with Cursors in Ably Spaces

EXAMPLES
$ ably spaces cursors set my-space --position '{"x": 100, "y": 200}' --data '{"color": "red"}'
$ ably spaces cursors set my-space --x 100 --y 200 --data '{"color": "red"}'

$ ably spaces cursors subscribe my-space

Expand Down
8 changes: 4 additions & 4 deletions examples/web-cli/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface CliWindow extends Window {
}

// Default WebSocket URL - use public endpoint for production, localhost for development
const DEFAULT_WEBSOCKET_URL = "wss://web-cli.ably.com";
const DEFAULT_WEBSOCKET_URL = "wss://web-cli-terminal.ably-dev.com";

// Get WebSocket URL from query parameters only
const getWebSocketUrl = () => {
Expand Down Expand Up @@ -139,7 +139,7 @@ function App() {
const [connectionStatus, setConnectionStatus] = useState<TermStatus>('disconnected');
const [displayMode, setDisplayMode] = useState<"fullscreen" | "drawer">(initialMode);
const [showAuthSettings, setShowAuthSettings] = useState(false);

// Initialize signed credentials
const initialCreds = getInitialCredentials();
const [signedConfig, setSignedConfig] = useState<string | undefined>(initialCreds.signedConfig);
Expand Down Expand Up @@ -277,8 +277,8 @@ function App() {

// Show auth screen if not authenticated
if (!isAuthenticated) {
return <AuthScreen
onAuthenticate={handleAuthenticate}
return <AuthScreen
onAuthenticate={handleAuthenticate}
rememberCredentials={rememberCredentials}
onRememberChange={setRememberCredentials}
/>;
Expand Down
86 changes: 50 additions & 36 deletions packages/react-web-cli/src/AblyCliTerminal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@
) => {
return render(
<AblyCliTerminal
websocketUrl="wss://web-cli.ably.com"
websocketUrl="wss://web-cli-terminal.ably-dev.com"
signedConfig={DEFAULT_SIGNED_CONFIG}
signature={DEFAULT_SIGNATURE}
onConnectionStatusChange={onConnectionStatusChangeMock}
Expand Down Expand Up @@ -773,7 +773,7 @@
expect(onConnectionStatusChangeMock).toHaveBeenCalledWith("disconnected");
});

test.skip("shows installation tip after 6 seconds during connection attempts", async () => {

Check warning on line 776 in packages/react-web-cli/src/AblyCliTerminal.test.tsx

View workflow job for this annotation

GitHub Actions / test

Disabled test - if you want to skip a test temporarily, use .todo() instead

Check warning on line 776 in packages/react-web-cli/src/AblyCliTerminal.test.tsx

View workflow job for this annotation

GitHub Actions / e2e-cli

Disabled test - if you want to skip a test temporarily, use .todo() instead
// SKIPPED: This test has timing issues with fake timers in CI environments
// The 6-second delay doesn't advance consistently with vi.advanceTimersByTime
vi.useFakeTimers();
Expand Down Expand Up @@ -829,7 +829,7 @@
});
});

test.skip("shows installation tip during reconnection after 6 seconds", async () => {

Check warning on line 832 in packages/react-web-cli/src/AblyCliTerminal.test.tsx

View workflow job for this annotation

GitHub Actions / test

Disabled test - if you want to skip a test temporarily, use .todo() instead

Check warning on line 832 in packages/react-web-cli/src/AblyCliTerminal.test.tsx

View workflow job for this annotation

GitHub Actions / e2e-cli

Disabled test - if you want to skip a test temporarily, use .todo() instead
// SKIPPED: This test has timing issues with fake timers in CI environments
// The 6-second delay doesn't advance consistently with vi.advanceTimersByTime
vi.useFakeTimers();
Expand Down Expand Up @@ -912,7 +912,7 @@
expect(onConnectionStatusChangeMock).toHaveBeenCalledWith("connecting");
});

test.skip("manual reconnect resets attempt counter after max attempts reached - skipped due to CI timing issues", async () => {

Check warning on line 915 in packages/react-web-cli/src/AblyCliTerminal.test.tsx

View workflow job for this annotation

GitHub Actions / test

Disabled test - if you want to skip a test temporarily, use .todo() instead

Check warning on line 915 in packages/react-web-cli/src/AblyCliTerminal.test.tsx

View workflow job for this annotation

GitHub Actions / e2e-cli

Disabled test - if you want to skip a test temporarily, use .todo() instead
// SKIPPED: This test has timing issues in CI environments
// Manual reconnect state transitions don't complete reliably with mocked timers
// Set up max attempts reached state
Expand Down Expand Up @@ -1000,11 +1000,11 @@
// Pre-populate sessionStorage with a sessionId and matching credential hash (domain-scoped)
const expectedHash = "hash-test-key:test-token"; // Based on our mock
globalThis.sessionStorage.setItem(
"ably.cli.sessionId.web-cli.ably.com",
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
"resume-123",
);
globalThis.sessionStorage.setItem(
"ably.cli.credentialHash.web-cli.ably.com",
"ably.cli.credentialHash.web-cli-terminal.ably-dev.com",
expectedHash,
);

Expand All @@ -1018,7 +1018,7 @@
await waitFor(() => {
expect(consoleLogSpy).toHaveBeenCalledWith(
"[AblyCLITerminal] Restored session with matching credentials for domain:",
"web-cli.ably.com",
"web-cli-terminal.ably-dev.com",
);
});

Expand Down Expand Up @@ -1079,7 +1079,7 @@
() =>
expect(
globalThis.sessionStorage.getItem(
"ably.cli.sessionId.web-cli.ably.com",
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
),
).toBe("new-session-456"),
{
Expand Down Expand Up @@ -1210,7 +1210,7 @@
);
});

test.skip("connection timeout triggers error after 30 seconds", async () => {

Check warning on line 1213 in packages/react-web-cli/src/AblyCliTerminal.test.tsx

View workflow job for this annotation

GitHub Actions / test

Disabled test - if you want to skip a test temporarily, use .todo() instead

Check warning on line 1213 in packages/react-web-cli/src/AblyCliTerminal.test.tsx

View workflow job for this annotation

GitHub Actions / e2e-cli

Disabled test - if you want to skip a test temporarily, use .todo() instead
// SKIPPED: This test has timing issues with fake timers
// The 30-second timeout doesn't trigger consistently with vi.advanceTimersByTime
vi.useFakeTimers();
Expand Down Expand Up @@ -1478,7 +1478,7 @@
render(
<AblyCliTerminal
ref={reference}
websocketUrl="wss://web-cli.ably.com"
websocketUrl="wss://web-cli-terminal.ably-dev.com"
signedConfig={DEFAULT_SIGNED_CONFIG}
signature={DEFAULT_SIGNATURE}
enableSplitScreen={true}
Expand Down Expand Up @@ -1613,7 +1613,7 @@
setItemMock.mockRestore();
});

test.skip("prompt detection correctly handles ANSI color codes", async () => {

Check warning on line 1616 in packages/react-web-cli/src/AblyCliTerminal.test.tsx

View workflow job for this annotation

GitHub Actions / test

Disabled test - if you want to skip a test temporarily, use .todo() instead

Check warning on line 1616 in packages/react-web-cli/src/AblyCliTerminal.test.tsx

View workflow job for this annotation

GitHub Actions / e2e-cli

Disabled test - if you want to skip a test temporarily, use .todo() instead
// SKIPPED: This test depends on React fiber internal structure
// React's internal structure is not stable across versions and breaks this test
// Create a mock component and socket
Expand Down Expand Up @@ -1651,7 +1651,7 @@
expect(instance.isSessionActive).toBe(true);
});

test.skip("onConnectionStatusChange only reports status for the primary terminal in split-screen mode", async () => {

Check warning on line 1654 in packages/react-web-cli/src/AblyCliTerminal.test.tsx

View workflow job for this annotation

GitHub Actions / test

Disabled test - if you want to skip a test temporarily, use .todo() instead

Check warning on line 1654 in packages/react-web-cli/src/AblyCliTerminal.test.tsx

View workflow job for this annotation

GitHub Actions / e2e-cli

Disabled test - if you want to skip a test temporarily, use .todo() instead
// SKIPPED: This test verifies implementation details that are subject to change
// Core functionality is covered by other unit and integration tests
// The environment is not stable enough for this internal implementation test
Expand Down Expand Up @@ -1810,7 +1810,7 @@
) => {
return render(
<AblyCliTerminal
websocketUrl="wss://web-cli.ably.com"
websocketUrl="wss://web-cli-terminal.ably-dev.com"
signedConfig={DEFAULT_SIGNED_CONFIG}
signature={DEFAULT_SIGNATURE}
onConnectionStatusChange={onConnectionStatusChangeMock}
Expand All @@ -1826,11 +1826,11 @@

// Pre-populate sessionStorage with a sessionId and credential hash (domain-scoped)
globalThis.sessionStorage.setItem(
"ably.cli.sessionId.web-cli.ably.com",
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
"old-session-123",
);
globalThis.sessionStorage.setItem(
"ably.cli.credentialHash.web-cli.ably.com",
"ably.cli.credentialHash.web-cli-terminal.ably-dev.com",
"old-hash-value",
);

Expand Down Expand Up @@ -1863,11 +1863,13 @@

// Verify storage was cleared due to credential mismatch
expect(
globalThis.sessionStorage.getItem("ably.cli.sessionId.web-cli.ably.com"),
globalThis.sessionStorage.getItem(
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
),
).toBeNull();
expect(
globalThis.sessionStorage.getItem(
"ably.cli.credentialHash.web-cli.ably.com",
"ably.cli.credentialHash.web-cli-terminal.ably-dev.com",
),
).toBeNull();
});
Expand All @@ -1876,11 +1878,11 @@
// First setup the stored session with matching hash
const expectedHash = "hash-test-key:test-token"; // Based on our mock
globalThis.sessionStorage.setItem(
"ably.cli.sessionId.web-cli.ably.com",
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
"session-456",
);
globalThis.sessionStorage.setItem(
"ably.cli.credentialHash.web-cli.ably.com",
"ably.cli.credentialHash.web-cli-terminal.ably-dev.com",
expectedHash,
);

Expand All @@ -1894,7 +1896,7 @@
await waitFor(() => {
expect(consoleLogSpy).toHaveBeenCalledWith(
"[AblyCLITerminal] Restored session with matching credentials for domain:",
"web-cli.ably.com",
"web-cli-terminal.ably-dev.com",
);
});

Expand Down Expand Up @@ -1925,11 +1927,13 @@

// Verify storage wasn't cleared
expect(
globalThis.sessionStorage.getItem("ably.cli.sessionId.web-cli.ably.com"),
globalThis.sessionStorage.getItem(
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
),
).toBe("session-456");
expect(
globalThis.sessionStorage.getItem(
"ably.cli.credentialHash.web-cli.ably.com",
"ably.cli.credentialHash.web-cli-terminal.ably-dev.com",
),
).toBe(expectedHash);

Expand Down Expand Up @@ -1965,23 +1969,25 @@

// Both sessionId and credential hash should be stored (domain-scoped)
expect(
globalThis.sessionStorage.getItem("ably.cli.sessionId.web-cli.ably.com"),
globalThis.sessionStorage.getItem(
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
),
).toBe("new-session-789");
expect(
globalThis.sessionStorage.getItem(
"ably.cli.credentialHash.web-cli.ably.com",
"ably.cli.credentialHash.web-cli-terminal.ably-dev.com",
),
).toBe("hash-test-key-123:test-token-456");
}, 10_000);

test("clears credential hash when session is purged due to server disconnect", async () => {
// Set up initial state with stored session and hash (domain-scoped)
globalThis.sessionStorage.setItem(
"ably.cli.sessionId.web-cli.ably.com",
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
"session-to-purge",
);
globalThis.sessionStorage.setItem(
"ably.cli.credentialHash.web-cli.ably.com",
"ably.cli.credentialHash.web-cli-terminal.ably-dev.com",
"hash-to-purge",
);

Expand Down Expand Up @@ -2010,11 +2016,13 @@

// Both sessionId and credential hash should be cleared (domain-scoped)
expect(
globalThis.sessionStorage.getItem("ably.cli.sessionId.web-cli.ably.com"),
globalThis.sessionStorage.getItem(
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
),
).toBeNull();
expect(
globalThis.sessionStorage.getItem(
"ably.cli.credentialHash.web-cli.ably.com",
"ably.cli.credentialHash.web-cli-terminal.ably-dev.com",
),
).toBeNull();
}, 10_000);
Expand Down Expand Up @@ -2048,11 +2056,13 @@

// Should store session and hash even without accessToken (anonymous mode)
expect(
globalThis.sessionStorage.getItem("ably.cli.sessionId.web-cli.ably.com"),
globalThis.sessionStorage.getItem(
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
),
).toBe("session-no-key");
expect(
globalThis.sessionStorage.getItem(
"ably.cli.credentialHash.web-cli.ably.com",
"ably.cli.credentialHash.web-cli-terminal.ably-dev.com",
),
).toBe("hash-test-key:"); // apiKey present, no accessToken
}, 10_000);
Expand Down Expand Up @@ -2080,11 +2090,13 @@
globalThis.sessionStorage.getItem("ably.cli.credentialHash"),
).toBeNull();
expect(
globalThis.sessionStorage.getItem("ably.cli.sessionId.web-cli.ably.com"),
globalThis.sessionStorage.getItem(
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
),
).toBeNull();
expect(
globalThis.sessionStorage.getItem(
"ably.cli.credentialHash.web-cli.ably.com",
"ably.cli.credentialHash.web-cli-terminal.ably-dev.com",
),
).toBeNull();
}, 10_000);
Expand Down Expand Up @@ -2125,7 +2137,7 @@
) => {
return render(
<AblyCliTerminal
websocketUrl="wss://web-cli.ably.com"
websocketUrl="wss://web-cli-terminal.ably-dev.com"
signedConfig={DEFAULT_SIGNED_CONFIG}
signature={DEFAULT_SIGNATURE}
onConnectionStatusChange={onConnectionStatusChangeMock}
Expand Down Expand Up @@ -2166,11 +2178,13 @@

// Verify credentials are stored for the ably domain
expect(
globalThis.sessionStorage.getItem("ably.cli.sessionId.web-cli.ably.com"),
globalThis.sessionStorage.getItem(
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
),
).toBe("session-for-ably");
expect(
globalThis.sessionStorage.getItem(
"ably.cli.credentialHash.web-cli.ably.com",
"ably.cli.credentialHash.web-cli-terminal.ably-dev.com",
),
).toBe("hash-secure-key-123:secure-token-456");

Expand Down Expand Up @@ -2212,11 +2226,11 @@
test("credentials are properly scoped per domain", async () => {
// Set up credentials for multiple domains
globalThis.sessionStorage.setItem(
"ably.cli.sessionId.web-cli.ably.com",
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
"ably-session-123",
);
globalThis.sessionStorage.setItem(
"ably.cli.credentialHash.web-cli.ably.com",
"ably.cli.credentialHash.web-cli-terminal.ably-dev.com",
"hash-test-key:test-token",
);

Expand All @@ -2229,9 +2243,9 @@
"hash-test-key:test-token",
);

// Render terminal connecting to web-cli.ably.com
// Render terminal connecting to web-cli-terminal.ably-dev.com
renderTerminal({
websocketUrl: "wss://web-cli.ably.com",
websocketUrl: "wss://web-cli-terminal.ably-dev.com",
resumeOnReload: true,
});

Expand Down Expand Up @@ -2368,7 +2382,7 @@
) => {
return render(
<AblyCliTerminal
websocketUrl="wss://web-cli.ably.com"
websocketUrl="wss://web-cli-terminal.ably-dev.com"
signedConfig={DEFAULT_SIGNED_CONFIG}
signature={DEFAULT_SIGNATURE}
onConnectionStatusChange={onConnectionStatusChangeMock}
Expand Down Expand Up @@ -2441,11 +2455,11 @@
// Pre-populate storage with existing session
const expectedHash = "hash-test-key:test-token";
globalThis.sessionStorage.setItem(
"ably.cli.sessionId.web-cli.ably.com",
"ably.cli.sessionId.web-cli-terminal.ably-dev.com",
"resumed-session-456",
);
globalThis.sessionStorage.setItem(
"ably.cli.credentialHash.web-cli.ably.com",
"ably.cli.credentialHash.web-cli-terminal.ably-dev.com",
expectedHash,
);

Expand Down
Loading
Loading