Skip to content

Commit 92117e0

Browse files
chore: fix shell, port forward
1 parent ff59586 commit 92117e0

3 files changed

Lines changed: 38 additions & 25 deletions

File tree

cmd/sandbox/shell.go

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"golang.org/x/term"
2525

2626
"github.com/NodeOps-app/createos-cli/internal/api"
27-
"github.com/NodeOps-app/createos-cli/internal/config"
2827
"github.com/NodeOps-app/createos-cli/internal/terminal"
2928
)
3029

@@ -259,12 +258,12 @@ func runShellPTY(c *cli.Context, id, ref string) error {
259258
if ctrlURL == "" {
260259
ctrlURL = api.DefaultSandboxBaseURL
261260
}
262-
token, err := loadAPIToken()
261+
authHeader, token, err := sandboxAuth(c)
263262
if err != nil {
264263
return err
265264
}
266265

267-
conn, err := dialControlUpgrade(c.Context, ctrlURL, token, "/v1/sandboxes/"+id+"/shell")
266+
conn, err := dialControlUpgrade(c.Context, ctrlURL, authHeader, token, "/v1/sandboxes/"+id+"/shell")
268267
if err != nil {
269268
return err
270269
}
@@ -353,7 +352,7 @@ func writeFrame(w io.Writer, mu *sync.Mutex, typ byte, payload []byte) error {
353352
// Upgrade handshake, and returns the raw connection on a 101 reply.
354353
// Used by the keyless PTY path — same wire shape as the tunnel bridge
355354
// but with a different target path.
356-
func dialControlUpgrade(ctx context.Context, ctrlURL, token, path string) (net.Conn, error) {
355+
func dialControlUpgrade(ctx context.Context, ctrlURL, authHeader, token, path string) (net.Conn, error) {
357356
u, err := url.Parse(ctrlURL)
358357
if err != nil {
359358
return nil, fmt.Errorf("bad sandbox URL %q: %w", ctrlURL, err)
@@ -383,7 +382,7 @@ func dialControlUpgrade(ctx context.Context, ctrlURL, token, path string) (net.C
383382

384383
req := "POST " + path + " HTTP/1.1\r\n" +
385384
"Host: " + u.Host + "\r\n" +
386-
"X-Api-Key: " + token + "\r\n" +
385+
authHeader + ": " + token + "\r\n" +
387386
"Connection: Upgrade\r\n" +
388387
"Upgrade: tcp-tunnel\r\n" +
389388
"Content-Length: 0\r\n\r\n"
@@ -458,7 +457,7 @@ func startTunnelBridge(parent context.Context, c *cli.Context, sandboxID string,
458457
if ctrlURL == "" {
459458
ctrlURL = api.DefaultSandboxBaseURL
460459
}
461-
token, err := loadAPIToken()
460+
authHeader, token, err := sandboxAuth(c)
462461
if err != nil {
463462
return nil, err
464463
}
@@ -479,23 +478,25 @@ func startTunnelBridge(parent context.Context, c *cli.Context, sandboxID string,
479478
if err != nil {
480479
return
481480
}
482-
go bridgeOne(ctx, ctrlURL, token, sandboxID, remotePort, conn)
481+
go bridgeOne(ctx, ctrlURL, authHeader, token, sandboxID, remotePort, conn)
483482
}
484483
}()
485484
return b, nil
486485
}
487486

488-
// loadAPIToken pulls the user's token the same way the root Before
489-
// hook does — OAuth session if present, else the static api-key file.
490-
// We re-read because the Resty client doesn't expose the raw value.
491-
func loadAPIToken() (string, error) {
492-
if config.HasOAuthSession() {
493-
sess, err := config.LoadOAuthSession()
494-
if err == nil && sess != nil && sess.AccessToken != "" {
495-
return sess.AccessToken, nil
496-
}
487+
// sandboxAuth returns the auth header name and current token from the
488+
// sandbox client the root Before hook already built and stored in
489+
// metadata — the single source of truth for credentials. The raw
490+
// HTTP-Upgrade paths reuse it so they authenticate exactly like every
491+
// other sandbox call: the right header for the auth type (X-Access-Token
492+
// for OAuth, X-Api-Key for an api key) and the already-refreshed token.
493+
func sandboxAuth(c *cli.Context) (header, token string, err error) {
494+
sc, ok := c.App.Metadata[api.SandboxClientKey].(*api.SandboxClient)
495+
if !ok {
496+
return "", "", fmt.Errorf("you're not signed in — run 'createos login' to get started")
497497
}
498-
return config.LoadToken()
498+
header, token = sc.AuthHeader()
499+
return header, token, nil
499500
}
500501

501502
// bridgeOne handles a single accepted local connection: it opens an
@@ -504,9 +505,9 @@ func loadAPIToken() (string, error) {
504505
// not just one — because SSH negotiation interleaves writes from both
505506
// peers, and closing early on a transient half-drain truncates the
506507
// handshake mid-flight and looks like an auth failure.
507-
func bridgeOne(ctx context.Context, ctrlURL, token, id string, port int, local net.Conn) {
508+
func bridgeOne(ctx context.Context, ctrlURL, authHeader, token, id string, port int, local net.Conn) {
508509
defer func() { _ = local.Close() }() //nolint:errcheck
509-
remote, err := dialControlTunnel(ctx, ctrlURL, token, id, port)
510+
remote, err := dialControlTunnel(ctx, ctrlURL, authHeader, token, id, port)
510511
if err != nil {
511512
return
512513
}
@@ -542,7 +543,7 @@ func bridgeOne(ctx context.Context, ctrlURL, token, id string, port int, local n
542543
// protocol by hand: POST `/v1/sandboxes/:id/tunnel/:port` with the
543544
// Upgrade headers, watch for 101 Switching Protocols, then return a
544545
// net.Conn that carries only tunnel bytes from then on.
545-
func dialControlTunnel(ctx context.Context, ctrlURL, token, id string, port int) (net.Conn, error) {
546+
func dialControlTunnel(ctx context.Context, ctrlURL, authHeader, token, id string, port int) (net.Conn, error) {
546547
u, err := url.Parse(ctrlURL)
547548
if err != nil {
548549
return nil, fmt.Errorf("bad sandbox URL %q: %w", ctrlURL, err)
@@ -574,9 +575,9 @@ func dialControlTunnel(ctx context.Context, ctrlURL, token, id string, port int)
574575
}
575576

576577
req := fmt.Sprintf("POST /v1/sandboxes/%s/tunnel/%d HTTP/1.1\r\n"+
577-
"Host: %s\r\nX-Api-Key: %s\r\n"+
578+
"Host: %s\r\n%s: %s\r\n"+
578579
"Connection: Upgrade\r\nUpgrade: tcp-tunnel\r\nContent-Length: 0\r\n\r\n",
579-
id, port, u.Host, token)
580+
id, port, u.Host, authHeader, token)
580581
if _, err = conn.Write([]byte(req)); err != nil {
581582
_ = conn.Close() //nolint:errcheck
582583
return nil, err

cmd/sandbox/tunnel.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func runTunnel(c *cli.Context) error {
164164
if ctrlURL == "" {
165165
ctrlURL = api.DefaultSandboxBaseURL
166166
}
167-
token, err := loadAPIToken()
167+
authHeader, token, err := sandboxAuth(c)
168168
if err != nil {
169169
return err
170170
}
@@ -190,6 +190,6 @@ func runTunnel(c *cli.Context) error {
190190
// Closed by signal handler or local error → done.
191191
return nil
192192
}
193-
go bridgeOne(c.Context, ctrlURL, token, id, remote, conn)
193+
go bridgeOne(c.Context, ctrlURL, authHeader, token, id, remote, conn)
194194
}
195195
}

internal/api/sandbox_client.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ const DefaultSandboxBaseURL = "https://fc-spawn.bhautik.in"
1616
// is not accepted on user-facing routes).
1717
type SandboxClient struct {
1818
Client *resty.Client
19+
// authHeader is the header name this client sends its credential
20+
// under (X-Api-Key for an api key, X-Access-Token for an OAuth JWT).
21+
authHeader string
22+
}
23+
24+
// AuthHeader returns the header name and the current token this client
25+
// authenticates with. The hand-rolled HTTP-Upgrade streaming paths
26+
// (sandbox shell PTY, tunnel, sync) reuse this so they send the same
27+
// header and the same (refresh-rotated) token as every other sandbox
28+
// call, instead of re-deriving credentials and hardcoding X-Api-Key.
29+
func (c *SandboxClient) AuthHeader() (header, token string) {
30+
return c.authHeader, c.Client.Header.Get(c.authHeader)
1931
}
2032

2133
// NewSandboxClient builds a SandboxClient for API-key auth. Empty url
@@ -56,7 +68,7 @@ func newSandboxClient(authHeader, token, sandboxURL string, debug bool, refreshe
5668
})
5769
}
5870
installAuthRefresh(client, authHeader, refresher)
59-
return SandboxClient{Client: client}
71+
return SandboxClient{Client: client, authHeader: authHeader}
6072
}
6173

6274
// SandboxClientKey is the cli.Context metadata key for the sandbox client.

0 commit comments

Comments
 (0)