Skip to content

Add presigned URL APIs#9

Merged
steven-passynkov merged 7 commits intomainfrom
feature/presigned-urls
Apr 14, 2026
Merged

Add presigned URL APIs#9
steven-passynkov merged 7 commits intomainfrom
feature/presigned-urls

Conversation

@steven-passynkov
Copy link
Copy Markdown
Contributor

@steven-passynkov steven-passynkov commented Apr 14, 2026

Summary

  • add sandbox client and service APIs for creating and deleting presigned URLs
  • export the new presigned URL schemas and types from the public package entrypoints
  • cover the new behavior with tests and document the workflow in the README

Summary by CodeRabbit

  • New Features

    • Presigned URLs: generate and revoke temporary, publicly accessible sandbox URLs with configurable expiration.
  • Documentation

    • README: added Presigned URLs usage and examples.
  • Examples

    • SSH example now demonstrates rotating an SSH credential after validation.
  • Tests

    • Added/updated tests for presigned URL create/delete and updated SSH request/validation/regeneration flows.
  • Breaking Changes

    • Sandbox SSH flows now operate on specific credential IDs (validate/regenerate/delete target explicit credentials).

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5304ff95-6631-45e8-959a-bfc96755b647

📥 Commits

Reviewing files that changed from the base of the PR and between 76d391b and f83b031.

📒 Files selected for processing (1)
  • src/services/ssh.ts

📝 Walkthrough

Walkthrough

Adds presigned-URL support for sandboxes (models, service methods, client wrappers, tests, and README) and changes SSH service methods to operate on specific credential IDs (endpoints, signatures, tests, and an example).

Changes

Cohort / File(s) Summary
Presigned URL models
src/models/sandbox.ts
Added createPresignedUrlParamsSchema and presignedUrlSchema plus CreatePresignedUrlParams and PresignedUrl types.
Model re-exports
src/models/index.ts, src/index.ts
Re-exported new presigned URL schemas and types (createPresignedUrlParamsSchema, presignedUrlSchema, CreatePresignedUrlParams, PresignedUrl).
Sandboxes service
src/services/sandboxes.ts
Added createPresignedUrl(sandbox, params, options) (validates, POSTs /v1/sandbox/{id}/presigned-url, normalizes response) and deletePresignedUrl(sandbox, id, options) (trim/encode id, DELETE /v1/sandbox/{id}/presigned-url/{id}).
Sandbox client wrapper
src/client/sandbox.ts
Added Sandbox.createPresignedUrl(port, expiresIn?, options?) and Sandbox.deletePresignedUrl(id, options?) delegating to service; updated type imports to include PresignedUrl.
SSH service API changes
src/services/ssh.ts
SSH methods now operate on explicit credential id: deleteAccess(sandbox, id, ...) → DELETE /v1/sandbox/{sandboxId}/ssh/{id}, validateAccess(sandbox, id, password, ...) → POST /v1/sandbox/{sandboxId}/ssh/{id}/validate with { password }, regenerateAccess(sandbox, id, ...) → POST /v1/sandbox/{sandboxId}/ssh/{id}/regen.
Tests (client & service)
tests/client/client-sandbox.test.ts, tests/services/sandboxes-client.test.ts, tests/services/ssh.test.ts
Updated mocks and assertions for new presigned URL flows and SSH path/signature changes; added tests for create/delete presigned URL and payload normalization (expiresInexpires_in).
Example
examples/ssh.ts
Now regenerates SSH access after validation and logs rotated SSH command.
Docs
README.md
Inserted "Presigned URLs" section documenting new sandbox APIs and usage examples.

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Developer
  participant SandboxClient as Sandbox instance
  participant SandboxesSvc as SandboxesClient
  participant Transport as HTTP Transport
  participant API as Leap0 API

  rect rgba(0,128,0,0.5)
    Dev->>SandboxClient: sandbox.createPresignedUrl(port, expiresIn)
    SandboxClient->>SandboxesSvc: createPresignedUrl(sandboxRef, {port, expiresIn})
    SandboxesSvc->>Transport: POST /v1/sandbox/{id}/presigned-url with {port, expires_in}
    Transport->>API: HTTP request
    API-->>Transport: 200 JSON { id, url, token, ... }
    Transport-->>SandboxesSvc: response JSON
    SandboxesSvc->>SandboxesSvc: validate/normalize via presignedUrlSchema
    SandboxesSvc-->>SandboxClient: PresignedUrl
    SandboxClient-->>Dev: Promise<PresignedUrl>
  end
Loading
sequenceDiagram
  participant Dev as Developer
  participant SandboxClient as Sandbox instance
  participant SandboxesSvc as SandboxesClient
  participant Transport as HTTP Transport
  participant API as Leap0 API

  rect rgba(0,0,255,0.5)
    Dev->>SandboxClient: sandbox.deletePresignedUrl(presignedId)
    SandboxClient->>SandboxesSvc: deletePresignedUrl(sandboxRef, presignedId)
    SandboxesSvc->>SandboxesSvc: trim + encode id
    SandboxesSvc->>Transport: DELETE /v1/sandbox/{id}/presigned-url/{encodedId}
    Transport->>API: HTTP DELETE
    API-->>Transport: 204 No Content
    Transport-->>SandboxesSvc: success
    SandboxesSvc-->>SandboxClient: void
    SandboxClient-->>Dev: Promise<void>
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Add presigned URL APIs #9: Adds presigned-URL APIs (create/delete), schemas, client/service methods and tests — directly related.
  • Init fixes #3: Refactors sandbox models and client/service surfaces that overlap with these presigned-URL and SSH changes.

Poem

🐇 I hopped through schemas, tokens, and a dotted road,
I carved a short-lived link that bears a tiny code.
I trimmed and encoded IDs with a delicate paw,
I rotated SSH keys and logged the cheerful awe.
The sandbox door swings open now—hooray, I applaud!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding presigned URL APIs to the codebase. It is concise, clear, and directly reflects the primary objective evident across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/presigned-urls

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/client/client-sandbox.test.ts (1)

200-200: Consider strengthening the type assertion for createPresignedUrl.

Line 200 only checks Promise<{ url: string }>; asserting against Promise<PresignedUrl> would better guard the full return contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/client/client-sandbox.test.ts` at line 200, The test currently asserts
that Sandbox.createPresignedUrl returns Promise<{ url: string }>; update the
type assertion to check for Promise<PresignedUrl> instead so the full
PresignedUrl contract is validated. Locate the assertion using
ReturnType<Sandbox["createPresignedUrl"]> in tests/client/client-sandbox.test.ts
and replace the expected type with Promise<PresignedUrl>, ensuring the
PresignedUrl type is imported or available in the test scope.
tests/services/sandboxes-client.test.ts (1)

145-148: Fix mismatched assertion type key in test payload cast.

Line 145 casts to { expires_in_minutes: number } but the asserted payload uses expires_in. Aligning this keeps the test contract clear.

🧪 Suggested cleanup
-  assert.deepEqual(jsonOf(calls[0]!) as { port: number; expires_in_minutes: number }, {
+  assert.deepEqual(jsonOf(calls[0]!) as { port: number; expires_in: number }, {
     port: 8080,
     expires_in: 900,
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/services/sandboxes-client.test.ts` around lines 145 - 148, The test
casts jsonOf(calls[0]!) to the wrong shape: it currently uses { port: number;
expires_in_minutes: number } but the asserted object uses expires_in; update the
cast to match the asserted payload (e.g., { port: number; expires_in: number })
or change the asserted key to expires_in_minutes so the type and runtime
assertion align—locate the usage of jsonOf(calls[0]!) in
tests/services/sandboxes-client.test.ts and make the cast and expected key
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 135-144: The example for createPresignedUrl(8080, 15) is ambiguous
about the expiresIn unit; update the README example and surrounding text to
explicitly state the unit (e.g., "expiresIn: 15 minutes" or "expiresIn: 15
seconds") and, if possible, name the parameter in the call (e.g., show
createPresignedUrl(port, { expiresInMinutes: 15 }) or document that the second
numeric argument to createPresignedUrl is in minutes) so readers know exactly
what the 15 represents.

In `@src/services/sandboxes.ts`:
- Around line 292-299: The presigned URL deletion uses trimmedID directly in the
path which can break for IDs with reserved chars; before interpolating,
URL-encode the ID (e.g., via encodeURIComponent) and use that encoded value in
the call to this.transport.request inside withErrorPrefix for the
`/v1/sandbox/${sandboxIdOf(sandbox)}/presigned-url/...` path so that trimmedID
is safe for use in the request URL.

---

Nitpick comments:
In `@tests/client/client-sandbox.test.ts`:
- Line 200: The test currently asserts that Sandbox.createPresignedUrl returns
Promise<{ url: string }>; update the type assertion to check for
Promise<PresignedUrl> instead so the full PresignedUrl contract is validated.
Locate the assertion using ReturnType<Sandbox["createPresignedUrl"]> in
tests/client/client-sandbox.test.ts and replace the expected type with
Promise<PresignedUrl>, ensuring the PresignedUrl type is imported or available
in the test scope.

In `@tests/services/sandboxes-client.test.ts`:
- Around line 145-148: The test casts jsonOf(calls[0]!) to the wrong shape: it
currently uses { port: number; expires_in_minutes: number } but the asserted
object uses expires_in; update the cast to match the asserted payload (e.g., {
port: number; expires_in: number }) or change the asserted key to
expires_in_minutes so the type and runtime assertion align—locate the usage of
jsonOf(calls[0]!) in tests/services/sandboxes-client.test.ts and make the cast
and expected key consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 488b7c59-23c5-43b1-9c1f-89199e87bc6e

📥 Commits

Reviewing files that changed from the base of the PR and between 16065ba and 94bd9be.

📒 Files selected for processing (8)
  • README.md
  • src/client/sandbox.ts
  • src/index.ts
  • src/models/index.ts
  • src/models/sandbox.ts
  • src/services/sandboxes.ts
  • tests/client/client-sandbox.test.ts
  • tests/services/sandboxes-client.test.ts

Comment thread README.md
Comment thread src/services/sandboxes.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/services/ssh.ts`:
- Around line 38-40: The SSH service currently interpolates raw credential IDs
into request paths (e.g., in deleteAccess) which breaks for IDs containing
characters like "/", "?", or "#"; update all places that splice id into the URL
(deleteAccess and the other methods that call this.transport.request with
`/v1/sandbox/${sandboxIdOf(sandbox)}/ssh/${id}`) to encode the id with
encodeURIComponent before building the path so the URL is safe; ensure every
occurrence (the calls around the code labeled by deleteAccess and the other two
request usages) uses the encoded id consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d20c321e-cb0c-4538-81fe-34868cb83a35

📥 Commits

Reviewing files that changed from the base of the PR and between 94bd9be and c188cc3.

📒 Files selected for processing (8)
  • README.md
  • examples/ssh.ts
  • src/models/sandbox.ts
  • src/services/sandboxes.ts
  • src/services/ssh.ts
  • tests/client/client-sandbox.test.ts
  • tests/services/sandboxes-client.test.ts
  • tests/services/ssh.test.ts
✅ Files skipped from review due to trivial changes (2)
  • README.md
  • tests/services/sandboxes-client.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/services/sandboxes.ts
  • tests/client/client-sandbox.test.ts

Comment thread src/services/ssh.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/services/ssh.ts (1)

39-41: Consider centralizing encoded SSH credential path building.

The same sandboxIdOf + encodeURIComponent + /ssh/{id} construction appears in three methods; a small private helper would reduce drift risk in future endpoint edits.

Refactor sketch
 export class SshClient {
   constructor(private readonly transport: Leap0Transport) {}

+  private credentialPath(sandbox: SandboxRef, id: string, suffix = ""): string {
+    const encodedId = encodeURIComponent(id);
+    return `/v1/sandbox/${sandboxIdOf(sandbox)}/ssh/${encodedId}${suffix}`;
+  }
+
   async deleteAccess(sandbox: SandboxRef, id: string, options: RequestOptions = {}): Promise<void> {
-    const encodedID = encodeURIComponent(id);
     await this.transport.request(
-      `/v1/sandbox/${sandboxIdOf(sandbox)}/ssh/${encodedID}`,
+      this.credentialPath(sandbox, id),
       { method: "DELETE" },
       options,
     );
   }
@@
   ): Promise<SshValidation> {
-    const encodedID = encodeURIComponent(id);
     const data = await this.transport.requestJson<SshValidation>(
-      `/v1/sandbox/${sandboxIdOf(sandbox)}/ssh/${encodedID}/validate`,
+      this.credentialPath(sandbox, id, "/validate"),
       { method: "POST", body: jsonBody({ password }) },
       options,
     );
@@
   async regenerateAccess(sandbox: SandboxRef, id: string, options: RequestOptions = {}): Promise<SshAccess> {
-    const encodedID = encodeURIComponent(id);
     const data = await this.transport.requestJson<SshAccess>(
-      `/v1/sandbox/${sandboxIdOf(sandbox)}/ssh/${encodedID}/regen`,
+      this.credentialPath(sandbox, id, "/regen"),
       { method: "POST" },
       options,
     );

Also applies to: 62-64, 80-82

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/ssh.ts` around lines 39 - 41, Create a single private helper
(e.g., sshEndpoint(sandbox, id) or buildSshPath) that encapsulates
sandboxIdOf(sandbox), encodeURIComponent(id) and returns the full path string
`/v1/sandbox/${sandboxId}/ssh/${encodedId}`, then replace the three places that
call
this.transport.request(`/v1/sandbox/${sandboxIdOf(sandbox)}/ssh/${encodeURIComponent(id)}`)
with calls to this new helper; keep using sandboxIdOf, encodeURIComponent and
this.transport.request but centralize path construction in the new function to
avoid duplication and drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/services/ssh.ts`:
- Around line 39-41: Create a single private helper (e.g., sshEndpoint(sandbox,
id) or buildSshPath) that encapsulates sandboxIdOf(sandbox),
encodeURIComponent(id) and returns the full path string
`/v1/sandbox/${sandboxId}/ssh/${encodedId}`, then replace the three places that
call
this.transport.request(`/v1/sandbox/${sandboxIdOf(sandbox)}/ssh/${encodeURIComponent(id)}`)
with calls to this new helper; keep using sandboxIdOf, encodeURIComponent and
this.transport.request but centralize path construction in the new function to
avoid duplication and drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2e14e9fc-c891-4004-830c-2e3f281b7544

📥 Commits

Reviewing files that changed from the base of the PR and between c188cc3 and ac109fb.

📒 Files selected for processing (2)
  • src/services/ssh.ts
  • tests/services/ssh.test.ts
✅ Files skipped from review due to trivial changes (1)
  • tests/services/ssh.test.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant