From 9ce243702ff06b061cf21b44ba59b0ce0660033f Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 22 Jul 2025 16:55:00 +0100 Subject: [PATCH 1/7] feat: allow prefixing coder_session_token cookie --- coderd/apikey.go | 2 +- coderd/coderd_test.go | 2 +- coderd/coderdtest/coderdtest.go | 2 +- coderd/coderdtest/oidctest/idp.go | 2 +- coderd/httpapi/cookie.go | 2 +- coderd/httpmw/apikey.go | 6 +++--- coderd/httpmw/apikey_test.go | 4 ++-- coderd/httpmw/csrf.go | 8 ++++---- coderd/httpmw/csrf_test.go | 8 ++++---- coderd/httpmw/rfc6750_extended_test.go | 8 ++++---- coderd/httpmw/rfc6750_test.go | 2 +- coderd/httpmw/workspaceagent.go | 2 +- coderd/userauth.go | 4 ++-- coderd/userauth_test.go | 2 +- coderd/users_test.go | 4 ++-- coderd/workspaceapps/apptest/setup.go | 2 +- codersdk/agentsdk/agentsdk.go | 4 ++-- codersdk/client.go | 23 ++++++++++++++++++++++- codersdk/provisionerdaemons.go | 4 ++-- codersdk/workspaceagents.go | 4 ++-- codersdk/workspacesdk/workspacesdk.go | 2 +- 21 files changed, 59 insertions(+), 38 deletions(-) diff --git a/coderd/apikey.go b/coderd/apikey.go index 895be440ef930..c3893acf350a5 100644 --- a/coderd/apikey.go +++ b/coderd/apikey.go @@ -418,7 +418,7 @@ func (api *API) createAPIKey(ctx context.Context, params apikey.CreateParams) (* }) return api.DeploymentValues.HTTPCookies.Apply(&http.Cookie{ - Name: codersdk.SessionTokenCookie, + Name: codersdk.GetSessionTokenCookie(), Value: sessionToken, Path: "/", HttpOnly: true, diff --git a/coderd/coderd_test.go b/coderd/coderd_test.go index c94462814999e..ce2e6a3915840 100644 --- a/coderd/coderd_test.go +++ b/coderd/coderd_test.go @@ -373,7 +373,7 @@ func TestCSRFExempt(t *testing.T) { u := client.URL.JoinPath(fmt.Sprintf("/@%s/%s.%s/apps/%s", owner.Username, wrk.Workspace.Name, agentSlug, appSlug)).String() req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, nil) req.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenCookie, + Name: codersdk.GetSessionTokenCookie(), Value: client.SessionToken(), Path: "/", Domain: client.URL.String(), diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 7085068e97ff4..fc0ff80916ea1 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -1333,7 +1333,7 @@ func RequestExternalAuthCallback(t testing.TB, providerID string, client *coders Value: state, }) req.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenCookie, + Name: codersdk.GetSessionTokenCookie(), Value: client.SessionToken(), }) for _, opt := range opts { diff --git a/coderd/coderdtest/oidctest/idp.go b/coderd/coderdtest/oidctest/idp.go index c7f7d35937198..bee9c1d57fbaf 100644 --- a/coderd/coderdtest/oidctest/idp.go +++ b/coderd/coderdtest/oidctest/idp.go @@ -624,7 +624,7 @@ func (f *FakeIDP) LoginWithClient(t testing.TB, client *codersdk.Client, idToken var user *codersdk.Client cookies := cli.Jar.Cookies(client.URL) for _, cookie := range cookies { - if cookie.Name == codersdk.SessionTokenCookie { + if cookie.Name == codersdk.GetSessionTokenCookie() { user = codersdk.New(client.URL) user.SetSessionToken(cookie.Value) } diff --git a/coderd/httpapi/cookie.go b/coderd/httpapi/cookie.go index 526dfb8207fe7..0a3e7ae0f86bf 100644 --- a/coderd/httpapi/cookie.go +++ b/coderd/httpapi/cookie.go @@ -20,7 +20,7 @@ func StripCoderCookies(header string) string { continue } name, _, _ := strings.Cut(part, "=") - if name == codersdk.SessionTokenCookie || + if name == codersdk.GetSessionTokenCookie() || name == codersdk.OAuth2StateCookie || name == codersdk.OAuth2RedirectCookie || name == codersdk.PathAppSessionTokenCookie || diff --git a/coderd/httpmw/apikey.go b/coderd/httpmw/apikey.go index 8fb68579a91e5..be02571151411 100644 --- a/coderd/httpmw/apikey.go +++ b/coderd/httpmw/apikey.go @@ -159,7 +159,7 @@ func APIKeyFromRequest(ctx context.Context, db database.Store, sessionTokenFunc if token == "" { return nil, codersdk.Response{ Message: SignedOutErrorMessage, - Detail: fmt.Sprintf("Cookie %q or query parameter must be provided.", codersdk.SessionTokenCookie), + Detail: fmt.Sprintf("Cookie %q or query parameter must be provided.", codersdk.GetSessionTokenCookie()), }, false } @@ -711,12 +711,12 @@ func APITokenFromRequest(r *http.Request) string { // Prioritize existing Coder custom authentication methods first // to maintain backward compatibility and existing behavior - cookie, err := r.Cookie(codersdk.SessionTokenCookie) + cookie, err := r.Cookie(codersdk.GetSessionTokenCookie()) if err == nil && cookie.Value != "" { return cookie.Value } - urlValue := r.URL.Query().Get(codersdk.SessionTokenCookie) + urlValue := r.URL.Query().Get(codersdk.GetSessionTokenCookie()) if urlValue != "" { return urlValue } diff --git a/coderd/httpmw/apikey_test.go b/coderd/httpmw/apikey_test.go index 85f36959476b3..fb506ea4eef13 100644 --- a/coderd/httpmw/apikey_test.go +++ b/coderd/httpmw/apikey_test.go @@ -320,7 +320,7 @@ func TestAPIKey(t *testing.T) { rw = httptest.NewRecorder() ) r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenCookie, + Name: codersdk.GetSessionTokenCookie(), Value: token, }) @@ -357,7 +357,7 @@ func TestAPIKey(t *testing.T) { rw = httptest.NewRecorder() ) q := r.URL.Query() - q.Add(codersdk.SessionTokenCookie, token) + q.Add(codersdk.GetSessionTokenCookie(), token) r.URL.RawQuery = q.Encode() httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{ diff --git a/coderd/httpmw/csrf.go b/coderd/httpmw/csrf.go index 7196517119641..c8de67275a7e8 100644 --- a/coderd/httpmw/csrf.go +++ b/coderd/httpmw/csrf.go @@ -21,7 +21,7 @@ func CSRF(cookieCfg codersdk.HTTPCookieConfig) func(next http.Handler) http.Hand mw := nosurf.New(next) mw.SetBaseCookie(*cookieCfg.Apply(&http.Cookie{Path: "/", HttpOnly: true})) mw.SetFailureHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - sessCookie, err := r.Cookie(codersdk.SessionTokenCookie) + sessCookie, err := r.Cookie(codersdk.GetSessionTokenCookie()) if err == nil && r.Header.Get(codersdk.SessionTokenHeader) != "" && r.Header.Get(codersdk.SessionTokenHeader) != sessCookie.Value { @@ -32,7 +32,7 @@ func CSRF(cookieCfg codersdk.HTTPCookieConfig) func(next http.Handler) http.Hand fmt.Sprintf("CSRF error encountered. Authentication via %q cookie and %q header detected, but the values do not match. "+ "To resolve this issue ensure the values used in both match, or only use one of the authentication methods. "+ "You can also try clearing your cookies if this error persists.", - codersdk.SessionTokenCookie, codersdk.SessionTokenHeader), + codersdk.GetSessionTokenCookie(), codersdk.SessionTokenHeader), http.StatusBadRequest) return } @@ -70,7 +70,7 @@ func CSRF(cookieCfg codersdk.HTTPCookieConfig) func(next http.Handler) http.Hand // CSRF only affects requests that automatically attach credentials via a cookie. // If no cookie is present, then there is no risk of CSRF. //nolint:govet - sessCookie, err := r.Cookie(codersdk.SessionTokenCookie) + sessCookie, err := r.Cookie(codersdk.GetSessionTokenCookie()) if xerrors.Is(err, http.ErrNoCookie) { return true } @@ -82,7 +82,7 @@ func CSRF(cookieCfg codersdk.HTTPCookieConfig) func(next http.Handler) http.Hand return true } - if token := r.URL.Query().Get(codersdk.SessionTokenCookie); token == sessCookie.Value { + if token := r.URL.Query().Get(codersdk.GetSessionTokenCookie()); token == sessCookie.Value { // If the auth is set in a url param and matches the cookie, it // is the same as just using the url param. return true diff --git a/coderd/httpmw/csrf_test.go b/coderd/httpmw/csrf_test.go index 62e8150fb099f..8f000f23fed1a 100644 --- a/coderd/httpmw/csrf_test.go +++ b/coderd/httpmw/csrf_test.go @@ -63,7 +63,7 @@ func TestCSRFExemptList(t *testing.T) { r, err := http.NewRequestWithContext(context.Background(), http.MethodPost, c.URL, nil) require.NoError(t, err) - r.AddCookie(&http.Cookie{Name: codersdk.SessionTokenCookie, Value: "test"}) + r.AddCookie(&http.Cookie{Name: codersdk.GetSessionTokenCookie(), Value: "test"}) exempt := csrfmw.IsExempt(r) require.Equal(t, c.Exempt, exempt) }) @@ -96,7 +96,7 @@ func TestCSRFError(t *testing.T) { req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, urlPath, nil) require.NoError(t, err) - req.AddCookie(&http.Cookie{Name: codersdk.SessionTokenCookie, Value: "session_token_value"}) + req.AddCookie(&http.Cookie{Name: codersdk.GetSessionTokenCookie(), Value: "session_token_value"}) req.AddCookie(&http.Cookie{Name: nosurf.CookieName, Value: csrfCookieValue}) req.Header.Add(nosurf.HeaderName, csrfHeaderValue) @@ -113,7 +113,7 @@ func TestCSRFError(t *testing.T) { req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, urlPath, nil) require.NoError(t, err) - req.AddCookie(&http.Cookie{Name: codersdk.SessionTokenCookie, Value: "session_token_value"}) + req.AddCookie(&http.Cookie{Name: codersdk.GetSessionTokenCookie(), Value: "session_token_value"}) req.AddCookie(&http.Cookie{Name: nosurf.CookieName, Value: csrfCookieValue}) rec := httptest.NewRecorder() @@ -132,7 +132,7 @@ func TestCSRFError(t *testing.T) { req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, urlPath, nil) require.NoError(t, err) - req.AddCookie(&http.Cookie{Name: codersdk.SessionTokenCookie, Value: "session_token_value"}) + req.AddCookie(&http.Cookie{Name: codersdk.GetSessionTokenCookie(), Value: "session_token_value"}) req.AddCookie(&http.Cookie{Name: nosurf.CookieName, Value: csrfCookieValue}) req.Header.Add(codersdk.SessionTokenHeader, "mismatched_value") diff --git a/coderd/httpmw/rfc6750_extended_test.go b/coderd/httpmw/rfc6750_extended_test.go index 3cd6ca312a068..c0da65929e4a3 100644 --- a/coderd/httpmw/rfc6750_extended_test.go +++ b/coderd/httpmw/rfc6750_extended_test.go @@ -262,7 +262,7 @@ func TestOAuth2BearerTokenPrecedence(t *testing.T) { req := httptest.NewRequest("GET", "/test", nil) // Set both cookie and Bearer header - cookie should take precedence req.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenCookie, + Name: codersdk.GetSessionTokenCookie(), Value: validToken, }) req.Header.Set("Authorization", "Bearer invalid-token") @@ -279,7 +279,7 @@ func TestOAuth2BearerTokenPrecedence(t *testing.T) { // Set both query parameter and Bearer header - query should take precedence u, _ := url.Parse("/test") q := u.Query() - q.Set(codersdk.SessionTokenCookie, validToken) + q.Set(codersdk.GetSessionTokenCookie(), validToken) u.RawQuery = q.Encode() req := httptest.NewRequest("GET", u.String(), nil) @@ -329,13 +329,13 @@ func TestOAuth2BearerTokenPrecedence(t *testing.T) { u, _ := url.Parse("/test") q := u.Query() q.Set("access_token", validToken) - q.Set(codersdk.SessionTokenCookie, validToken) + q.Set(codersdk.GetSessionTokenCookie(), validToken) u.RawQuery = q.Encode() req := httptest.NewRequest("GET", u.String(), nil) req.Header.Set("Authorization", "Bearer "+validToken) req.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenCookie, + Name: codersdk.GetSessionTokenCookie(), Value: validToken, }) rec := httptest.NewRecorder() diff --git a/coderd/httpmw/rfc6750_test.go b/coderd/httpmw/rfc6750_test.go index 03b7d2d8c8360..d88815cad4b80 100644 --- a/coderd/httpmw/rfc6750_test.go +++ b/coderd/httpmw/rfc6750_test.go @@ -204,7 +204,7 @@ func TestAPITokenFromRequest(t *testing.T) { name: "CookiePriorityOverBearer", setupReq: func(req *http.Request) { req.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenCookie, + Name: codersdk.GetSessionTokenCookie(), Value: cookieToken, }) req.Header.Set("Authorization", "Bearer "+token) diff --git a/coderd/httpmw/workspaceagent.go b/coderd/httpmw/workspaceagent.go index 0ee231b2f5a12..a7fadf265fdea 100644 --- a/coderd/httpmw/workspaceagent.go +++ b/coderd/httpmw/workspaceagent.go @@ -78,7 +78,7 @@ func ExtractWorkspaceAgentAndLatestBuild(opts ExtractWorkspaceAgentAndLatestBuil tokenValue := APITokenFromRequest(r) if tokenValue == "" { optionalWrite(http.StatusUnauthorized, codersdk.Response{ - Message: fmt.Sprintf("Cookie %q must be provided.", codersdk.SessionTokenCookie), + Message: fmt.Sprintf("Cookie %q must be provided.", codersdk.GetSessionTokenCookie()), }) return } diff --git a/coderd/userauth.go b/coderd/userauth.go index 91472996737aa..b159763456583 100644 --- a/coderd/userauth.go +++ b/coderd/userauth.go @@ -702,7 +702,7 @@ func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) { cookie := &http.Cookie{ // MaxAge < 0 means to delete the cookie now. MaxAge: -1, - Name: codersdk.SessionTokenCookie, + Name: codersdk.GetSessionTokenCookie(), Path: "/", } http.SetCookie(rw, cookie) @@ -1914,7 +1914,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C ) } cookies = append(cookies, api.DeploymentValues.HTTPCookies.Apply(&http.Cookie{ - Name: codersdk.SessionTokenCookie, + Name: codersdk.GetSessionTokenCookie(), Path: "/", MaxAge: -1, HttpOnly: true, diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go index 4c9412fda3fb7..69183a565a92e 100644 --- a/coderd/userauth_test.go +++ b/coderd/userauth_test.go @@ -2476,7 +2476,7 @@ func i64ptr(i int64) *int64 { func authCookieValue(cookies []*http.Cookie) string { for _, cookie := range cookies { - if cookie.Name == codersdk.SessionTokenCookie { + if cookie.Name == codersdk.GetSessionTokenCookie() { return cookie.Value } } diff --git a/coderd/users_test.go b/coderd/users_test.go index 9d695f37c9906..94f2d6f2c6e2e 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -618,8 +618,8 @@ func TestPostLogout(t *testing.T) { var found bool for _, cookie := range cookies { - if cookie.Name == codersdk.SessionTokenCookie { - require.Equal(t, codersdk.SessionTokenCookie, cookie.Name, "Cookie should be the auth cookie") + if cookie.Name == codersdk.GetSessionTokenCookie() { + require.Equal(t, codersdk.GetSessionTokenCookie(), cookie.Name, "Cookie should be the auth cookie") require.Equal(t, -1, cookie.MaxAge, "Cookie should be set to delete") found = true } diff --git a/coderd/workspaceapps/apptest/setup.go b/coderd/workspaceapps/apptest/setup.go index 9d1df9e7fe09d..b1e5caab3c95a 100644 --- a/coderd/workspaceapps/apptest/setup.go +++ b/coderd/workspaceapps/apptest/setup.go @@ -261,7 +261,7 @@ func appServer(t *testing.T, headers http.Header, isHTTPS bool) uint16 { server := httptest.NewUnstartedServer( http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - _, err := r.Cookie(codersdk.SessionTokenCookie) + _, err := r.Cookie(codersdk.GetSessionTokenCookie()) assert.ErrorIs(t, err, http.ErrNoCookie) w.Header().Set("X-Forwarded-For", r.Header.Get("X-Forwarded-For")) w.Header().Set("X-Got-Host", r.Host) diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index 5bd0030456757..e62abb15a15c1 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -262,7 +262,7 @@ func (c *Client) connectRPCVersion(ctx context.Context, version *apiversion.APIV return nil, xerrors.Errorf("create cookie jar: %w", err) } jar.SetCookies(rpcURL, []*http.Cookie{{ - Name: codersdk.SessionTokenCookie, + Name: codersdk.GetSessionTokenCookie(), Value: c.SDK.SessionToken(), }}) httpClient := &http.Client{ @@ -705,7 +705,7 @@ func (c *Client) WaitForReinit(ctx context.Context) (*ReinitializationEvent, err return nil, xerrors.Errorf("create cookie jar: %w", err) } jar.SetCookies(rpcURL, []*http.Cookie{{ - Name: codersdk.SessionTokenCookie, + Name: codersdk.GetSessionTokenCookie(), Value: c.SDK.SessionToken(), }}) httpClient := &http.Client{ diff --git a/codersdk/client.go b/codersdk/client.go index 2097225ff489c..de12645a7b5b9 100644 --- a/codersdk/client.go +++ b/codersdk/client.go @@ -12,6 +12,7 @@ import ( "net/http" "net/http/httputil" "net/url" + "os" "strings" "sync" @@ -20,6 +21,7 @@ import ( "go.opentelemetry.io/otel/semconv/v1.14.0/httpconv" "golang.org/x/xerrors" + "github.com/coder/coder/v2/buildinfo" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/websocket" @@ -30,7 +32,10 @@ import ( // shouldn't be likely to conflict with any user-application set cookies. // Be sure to strip additional cookies in httpapi.StripCoderCookies! const ( - // SessionTokenCookie represents the name of the cookie or query parameter the API key is stored in. + // SessionTokenCookie represents the name of the cookie or query parameter in + // which the API key is stored. + // DEVELOPER NOTE: Please avoid referencing this value directly and use + // GetSessionTokenCookie() instead. SessionTokenCookie = "coder_session_token" // SessionTokenHeader is the custom header to use for authentication. SessionTokenHeader = "Coder-Session-Token" @@ -94,6 +99,22 @@ const ( EntitlementsWarningHeader = "X-Coder-Entitlements-Warning" ) +// GetSessionTokenCookie returns the name of the session token cookie. +// In almost all production cases, this will just be SessionTokenCookie. +// However, when developing inside a Coder workspace and accessing the UI +// proxied through a Coder deployment, we need to prefix the cookie name +// to avoid conflicting with the "parent" deployment. The prefix is controlled +// by the CODER_DEV_SESSION_TOKEN_COOKIE_PREFIX environment variable, and only +// applies in development builds. +func GetSessionTokenCookie() string { + if buildinfo.IsDev() { + if pfx, found := os.LookupEnv("CODER_DEV_SESSION_TOKEN_COOKIE_PREFIX"); found && pfx != "" { + return pfx + "_" + SessionTokenCookie + } + } + return SessionTokenCookie +} + // loggableMimeTypes is a list of MIME types that are safe to log // the output of. This is useful for debugging or testing. var loggableMimeTypes = map[string]struct{}{ diff --git a/codersdk/provisionerdaemons.go b/codersdk/provisionerdaemons.go index 5fbda371b8f3f..ebface3656fdc 100644 --- a/codersdk/provisionerdaemons.go +++ b/codersdk/provisionerdaemons.go @@ -215,7 +215,7 @@ func (c *Client) provisionerJobLogsAfter(ctx context.Context, path string, after return nil, nil, xerrors.Errorf("create cookie jar: %w", err) } jar.SetCookies(followURL, []*http.Cookie{{ - Name: SessionTokenCookie, + Name: GetSessionTokenCookie(), Value: c.SessionToken(), }}) httpClient := &http.Client{ @@ -302,7 +302,7 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione return nil, xerrors.Errorf("create cookie jar: %w", err) } jar.SetCookies(serverURL, []*http.Cookie{{ - Name: SessionTokenCookie, + Name: GetSessionTokenCookie(), Value: c.SessionToken(), }}) httpClient.Jar = jar diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 1eb37bb07c989..49163ec340d5a 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -545,7 +545,7 @@ func (c *Client) WatchWorkspaceAgentContainers(ctx context.Context, agentID uuid } jar.SetCookies(reqURL, []*http.Cookie{{ - Name: SessionTokenCookie, + Name: GetSessionTokenCookie(), Value: c.SessionToken(), }}) @@ -630,7 +630,7 @@ func (c *Client) WorkspaceAgentLogsAfter(ctx context.Context, agentID uuid.UUID, return nil, nil, xerrors.Errorf("create cookie jar: %w", err) } jar.SetCookies(reqURL, []*http.Cookie{{ - Name: SessionTokenCookie, + Name: GetSessionTokenCookie(), Value: c.SessionToken(), }}) httpClient := &http.Client{ diff --git a/codersdk/workspacesdk/workspacesdk.go b/codersdk/workspacesdk/workspacesdk.go index 9f587cf5267a8..4a1ba108a4a7b 100644 --- a/codersdk/workspacesdk/workspacesdk.go +++ b/codersdk/workspacesdk/workspacesdk.go @@ -380,7 +380,7 @@ func (c *Client) AgentReconnectingPTY(ctx context.Context, opts WorkspaceAgentRe return nil, xerrors.Errorf("create cookie jar: %w", err) } jar.SetCookies(serverURL, []*http.Cookie{{ - Name: codersdk.SessionTokenCookie, + Name: codersdk.GetSessionTokenCookie(), Value: c.client.SessionToken(), }}) httpClient = &http.Client{ From 5497686b11d507e8abf3cbf5c50de172467d1582 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 22 Jul 2025 16:55:14 +0100 Subject: [PATCH 2/7] chore: add dev.coder.com to vite allowedHosts --- site/vite.config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/vite.config.mts b/site/vite.config.mts index d386499e50ed0..e6a30aa71744e 100644 --- a/site/vite.config.mts +++ b/site/vite.config.mts @@ -116,7 +116,7 @@ export default defineConfig({ secure: process.env.NODE_ENV === "production", }, }, - allowedHosts: [".coder"], + allowedHosts: [".coder", ".dev.coder.com"], }, resolve: { alias: { From 5868b0b5736823a48189e807be79c3c5f04d4154 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 22 Jul 2025 17:57:20 +0100 Subject: [PATCH 3/7] chore: set CODER_DEV_SESSION_TOKEN_COOKIE_PREFIX in develop.sh --- scripts/develop.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/develop.sh b/scripts/develop.sh index c9d36d19db660..3500ba5354110 100755 --- a/scripts/develop.sh +++ b/scripts/develop.sh @@ -14,6 +14,7 @@ source "${SCRIPT_DIR}/lib.sh" set -euo pipefail CODER_DEV_ACCESS_URL="${CODER_DEV_ACCESS_URL:-http://127.0.0.1:3000}" +CODER_DEV_SESSION_TOKEN_COOKIE_PREFIX="${CODER_DEV_SESSION_TOKEN_COOKIE_PREFIX:-dev}" debug=0 DEFAULT_PASSWORD="SomeSecurePassword!" password="${CODER_DEV_ADMIN_PASSWORD:-${DEFAULT_PASSWORD}}" @@ -150,7 +151,7 @@ fatal() { trap 'fatal "Script encountered an error"' ERR cdroot - DEBUG_DELVE="${debug}" start_cmd API "" "${CODER_DEV_SHIM}" server --http-address 0.0.0.0:3000 --swagger-enable --access-url "${CODER_DEV_ACCESS_URL}" --dangerous-allow-cors-requests=true --enable-terraform-debug-mode "$@" + DEBUG_DELVE="${debug}" CODER_DEV_SESSION_TOKEN_COOKIE_PREFIX="${CODER_DEV_SESSION_TOKEN_COOKIE_PREFIX}" start_cmd API "" "${CODER_DEV_SHIM}" server --http-address 0.0.0.0:3000 --swagger-enable --access-url "${CODER_DEV_ACCESS_URL}" --dangerous-allow-cors-requests=true --enable-terraform-debug-mode "$@" echo '== Waiting for Coder to become ready' # Start the timeout in the background so interrupting this script From 6c86d8c720f56dd60dd2be7f2835626c7ed2b4df Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 22 Jul 2025 22:23:05 +0100 Subject: [PATCH 4/7] Revert "feat: allow prefixing coder_session_token cookie" This reverts commit 9ce243702ff06b061cf21b44ba59b0ce0660033f. --- coderd/apikey.go | 2 +- coderd/coderd_test.go | 2 +- coderd/coderdtest/coderdtest.go | 2 +- coderd/coderdtest/oidctest/idp.go | 2 +- coderd/httpapi/cookie.go | 2 +- coderd/httpmw/apikey.go | 6 +++--- coderd/httpmw/apikey_test.go | 4 ++-- coderd/httpmw/csrf.go | 8 ++++---- coderd/httpmw/csrf_test.go | 8 ++++---- coderd/httpmw/rfc6750_extended_test.go | 8 ++++---- coderd/httpmw/rfc6750_test.go | 2 +- coderd/httpmw/workspaceagent.go | 2 +- coderd/userauth.go | 4 ++-- coderd/userauth_test.go | 2 +- coderd/users_test.go | 4 ++-- coderd/workspaceapps/apptest/setup.go | 2 +- codersdk/agentsdk/agentsdk.go | 4 ++-- codersdk/client.go | 23 +---------------------- codersdk/provisionerdaemons.go | 4 ++-- codersdk/workspaceagents.go | 4 ++-- codersdk/workspacesdk/workspacesdk.go | 2 +- 21 files changed, 38 insertions(+), 59 deletions(-) diff --git a/coderd/apikey.go b/coderd/apikey.go index c3893acf350a5..895be440ef930 100644 --- a/coderd/apikey.go +++ b/coderd/apikey.go @@ -418,7 +418,7 @@ func (api *API) createAPIKey(ctx context.Context, params apikey.CreateParams) (* }) return api.DeploymentValues.HTTPCookies.Apply(&http.Cookie{ - Name: codersdk.GetSessionTokenCookie(), + Name: codersdk.SessionTokenCookie, Value: sessionToken, Path: "/", HttpOnly: true, diff --git a/coderd/coderd_test.go b/coderd/coderd_test.go index ce2e6a3915840..c94462814999e 100644 --- a/coderd/coderd_test.go +++ b/coderd/coderd_test.go @@ -373,7 +373,7 @@ func TestCSRFExempt(t *testing.T) { u := client.URL.JoinPath(fmt.Sprintf("/@%s/%s.%s/apps/%s", owner.Username, wrk.Workspace.Name, agentSlug, appSlug)).String() req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, nil) req.AddCookie(&http.Cookie{ - Name: codersdk.GetSessionTokenCookie(), + Name: codersdk.SessionTokenCookie, Value: client.SessionToken(), Path: "/", Domain: client.URL.String(), diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index fc0ff80916ea1..7085068e97ff4 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -1333,7 +1333,7 @@ func RequestExternalAuthCallback(t testing.TB, providerID string, client *coders Value: state, }) req.AddCookie(&http.Cookie{ - Name: codersdk.GetSessionTokenCookie(), + Name: codersdk.SessionTokenCookie, Value: client.SessionToken(), }) for _, opt := range opts { diff --git a/coderd/coderdtest/oidctest/idp.go b/coderd/coderdtest/oidctest/idp.go index bee9c1d57fbaf..c7f7d35937198 100644 --- a/coderd/coderdtest/oidctest/idp.go +++ b/coderd/coderdtest/oidctest/idp.go @@ -624,7 +624,7 @@ func (f *FakeIDP) LoginWithClient(t testing.TB, client *codersdk.Client, idToken var user *codersdk.Client cookies := cli.Jar.Cookies(client.URL) for _, cookie := range cookies { - if cookie.Name == codersdk.GetSessionTokenCookie() { + if cookie.Name == codersdk.SessionTokenCookie { user = codersdk.New(client.URL) user.SetSessionToken(cookie.Value) } diff --git a/coderd/httpapi/cookie.go b/coderd/httpapi/cookie.go index 0a3e7ae0f86bf..526dfb8207fe7 100644 --- a/coderd/httpapi/cookie.go +++ b/coderd/httpapi/cookie.go @@ -20,7 +20,7 @@ func StripCoderCookies(header string) string { continue } name, _, _ := strings.Cut(part, "=") - if name == codersdk.GetSessionTokenCookie() || + if name == codersdk.SessionTokenCookie || name == codersdk.OAuth2StateCookie || name == codersdk.OAuth2RedirectCookie || name == codersdk.PathAppSessionTokenCookie || diff --git a/coderd/httpmw/apikey.go b/coderd/httpmw/apikey.go index be02571151411..8fb68579a91e5 100644 --- a/coderd/httpmw/apikey.go +++ b/coderd/httpmw/apikey.go @@ -159,7 +159,7 @@ func APIKeyFromRequest(ctx context.Context, db database.Store, sessionTokenFunc if token == "" { return nil, codersdk.Response{ Message: SignedOutErrorMessage, - Detail: fmt.Sprintf("Cookie %q or query parameter must be provided.", codersdk.GetSessionTokenCookie()), + Detail: fmt.Sprintf("Cookie %q or query parameter must be provided.", codersdk.SessionTokenCookie), }, false } @@ -711,12 +711,12 @@ func APITokenFromRequest(r *http.Request) string { // Prioritize existing Coder custom authentication methods first // to maintain backward compatibility and existing behavior - cookie, err := r.Cookie(codersdk.GetSessionTokenCookie()) + cookie, err := r.Cookie(codersdk.SessionTokenCookie) if err == nil && cookie.Value != "" { return cookie.Value } - urlValue := r.URL.Query().Get(codersdk.GetSessionTokenCookie()) + urlValue := r.URL.Query().Get(codersdk.SessionTokenCookie) if urlValue != "" { return urlValue } diff --git a/coderd/httpmw/apikey_test.go b/coderd/httpmw/apikey_test.go index fb506ea4eef13..85f36959476b3 100644 --- a/coderd/httpmw/apikey_test.go +++ b/coderd/httpmw/apikey_test.go @@ -320,7 +320,7 @@ func TestAPIKey(t *testing.T) { rw = httptest.NewRecorder() ) r.AddCookie(&http.Cookie{ - Name: codersdk.GetSessionTokenCookie(), + Name: codersdk.SessionTokenCookie, Value: token, }) @@ -357,7 +357,7 @@ func TestAPIKey(t *testing.T) { rw = httptest.NewRecorder() ) q := r.URL.Query() - q.Add(codersdk.GetSessionTokenCookie(), token) + q.Add(codersdk.SessionTokenCookie, token) r.URL.RawQuery = q.Encode() httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{ diff --git a/coderd/httpmw/csrf.go b/coderd/httpmw/csrf.go index c8de67275a7e8..7196517119641 100644 --- a/coderd/httpmw/csrf.go +++ b/coderd/httpmw/csrf.go @@ -21,7 +21,7 @@ func CSRF(cookieCfg codersdk.HTTPCookieConfig) func(next http.Handler) http.Hand mw := nosurf.New(next) mw.SetBaseCookie(*cookieCfg.Apply(&http.Cookie{Path: "/", HttpOnly: true})) mw.SetFailureHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - sessCookie, err := r.Cookie(codersdk.GetSessionTokenCookie()) + sessCookie, err := r.Cookie(codersdk.SessionTokenCookie) if err == nil && r.Header.Get(codersdk.SessionTokenHeader) != "" && r.Header.Get(codersdk.SessionTokenHeader) != sessCookie.Value { @@ -32,7 +32,7 @@ func CSRF(cookieCfg codersdk.HTTPCookieConfig) func(next http.Handler) http.Hand fmt.Sprintf("CSRF error encountered. Authentication via %q cookie and %q header detected, but the values do not match. "+ "To resolve this issue ensure the values used in both match, or only use one of the authentication methods. "+ "You can also try clearing your cookies if this error persists.", - codersdk.GetSessionTokenCookie(), codersdk.SessionTokenHeader), + codersdk.SessionTokenCookie, codersdk.SessionTokenHeader), http.StatusBadRequest) return } @@ -70,7 +70,7 @@ func CSRF(cookieCfg codersdk.HTTPCookieConfig) func(next http.Handler) http.Hand // CSRF only affects requests that automatically attach credentials via a cookie. // If no cookie is present, then there is no risk of CSRF. //nolint:govet - sessCookie, err := r.Cookie(codersdk.GetSessionTokenCookie()) + sessCookie, err := r.Cookie(codersdk.SessionTokenCookie) if xerrors.Is(err, http.ErrNoCookie) { return true } @@ -82,7 +82,7 @@ func CSRF(cookieCfg codersdk.HTTPCookieConfig) func(next http.Handler) http.Hand return true } - if token := r.URL.Query().Get(codersdk.GetSessionTokenCookie()); token == sessCookie.Value { + if token := r.URL.Query().Get(codersdk.SessionTokenCookie); token == sessCookie.Value { // If the auth is set in a url param and matches the cookie, it // is the same as just using the url param. return true diff --git a/coderd/httpmw/csrf_test.go b/coderd/httpmw/csrf_test.go index 8f000f23fed1a..62e8150fb099f 100644 --- a/coderd/httpmw/csrf_test.go +++ b/coderd/httpmw/csrf_test.go @@ -63,7 +63,7 @@ func TestCSRFExemptList(t *testing.T) { r, err := http.NewRequestWithContext(context.Background(), http.MethodPost, c.URL, nil) require.NoError(t, err) - r.AddCookie(&http.Cookie{Name: codersdk.GetSessionTokenCookie(), Value: "test"}) + r.AddCookie(&http.Cookie{Name: codersdk.SessionTokenCookie, Value: "test"}) exempt := csrfmw.IsExempt(r) require.Equal(t, c.Exempt, exempt) }) @@ -96,7 +96,7 @@ func TestCSRFError(t *testing.T) { req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, urlPath, nil) require.NoError(t, err) - req.AddCookie(&http.Cookie{Name: codersdk.GetSessionTokenCookie(), Value: "session_token_value"}) + req.AddCookie(&http.Cookie{Name: codersdk.SessionTokenCookie, Value: "session_token_value"}) req.AddCookie(&http.Cookie{Name: nosurf.CookieName, Value: csrfCookieValue}) req.Header.Add(nosurf.HeaderName, csrfHeaderValue) @@ -113,7 +113,7 @@ func TestCSRFError(t *testing.T) { req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, urlPath, nil) require.NoError(t, err) - req.AddCookie(&http.Cookie{Name: codersdk.GetSessionTokenCookie(), Value: "session_token_value"}) + req.AddCookie(&http.Cookie{Name: codersdk.SessionTokenCookie, Value: "session_token_value"}) req.AddCookie(&http.Cookie{Name: nosurf.CookieName, Value: csrfCookieValue}) rec := httptest.NewRecorder() @@ -132,7 +132,7 @@ func TestCSRFError(t *testing.T) { req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, urlPath, nil) require.NoError(t, err) - req.AddCookie(&http.Cookie{Name: codersdk.GetSessionTokenCookie(), Value: "session_token_value"}) + req.AddCookie(&http.Cookie{Name: codersdk.SessionTokenCookie, Value: "session_token_value"}) req.AddCookie(&http.Cookie{Name: nosurf.CookieName, Value: csrfCookieValue}) req.Header.Add(codersdk.SessionTokenHeader, "mismatched_value") diff --git a/coderd/httpmw/rfc6750_extended_test.go b/coderd/httpmw/rfc6750_extended_test.go index c0da65929e4a3..3cd6ca312a068 100644 --- a/coderd/httpmw/rfc6750_extended_test.go +++ b/coderd/httpmw/rfc6750_extended_test.go @@ -262,7 +262,7 @@ func TestOAuth2BearerTokenPrecedence(t *testing.T) { req := httptest.NewRequest("GET", "/test", nil) // Set both cookie and Bearer header - cookie should take precedence req.AddCookie(&http.Cookie{ - Name: codersdk.GetSessionTokenCookie(), + Name: codersdk.SessionTokenCookie, Value: validToken, }) req.Header.Set("Authorization", "Bearer invalid-token") @@ -279,7 +279,7 @@ func TestOAuth2BearerTokenPrecedence(t *testing.T) { // Set both query parameter and Bearer header - query should take precedence u, _ := url.Parse("/test") q := u.Query() - q.Set(codersdk.GetSessionTokenCookie(), validToken) + q.Set(codersdk.SessionTokenCookie, validToken) u.RawQuery = q.Encode() req := httptest.NewRequest("GET", u.String(), nil) @@ -329,13 +329,13 @@ func TestOAuth2BearerTokenPrecedence(t *testing.T) { u, _ := url.Parse("/test") q := u.Query() q.Set("access_token", validToken) - q.Set(codersdk.GetSessionTokenCookie(), validToken) + q.Set(codersdk.SessionTokenCookie, validToken) u.RawQuery = q.Encode() req := httptest.NewRequest("GET", u.String(), nil) req.Header.Set("Authorization", "Bearer "+validToken) req.AddCookie(&http.Cookie{ - Name: codersdk.GetSessionTokenCookie(), + Name: codersdk.SessionTokenCookie, Value: validToken, }) rec := httptest.NewRecorder() diff --git a/coderd/httpmw/rfc6750_test.go b/coderd/httpmw/rfc6750_test.go index d88815cad4b80..03b7d2d8c8360 100644 --- a/coderd/httpmw/rfc6750_test.go +++ b/coderd/httpmw/rfc6750_test.go @@ -204,7 +204,7 @@ func TestAPITokenFromRequest(t *testing.T) { name: "CookiePriorityOverBearer", setupReq: func(req *http.Request) { req.AddCookie(&http.Cookie{ - Name: codersdk.GetSessionTokenCookie(), + Name: codersdk.SessionTokenCookie, Value: cookieToken, }) req.Header.Set("Authorization", "Bearer "+token) diff --git a/coderd/httpmw/workspaceagent.go b/coderd/httpmw/workspaceagent.go index a7fadf265fdea..0ee231b2f5a12 100644 --- a/coderd/httpmw/workspaceagent.go +++ b/coderd/httpmw/workspaceagent.go @@ -78,7 +78,7 @@ func ExtractWorkspaceAgentAndLatestBuild(opts ExtractWorkspaceAgentAndLatestBuil tokenValue := APITokenFromRequest(r) if tokenValue == "" { optionalWrite(http.StatusUnauthorized, codersdk.Response{ - Message: fmt.Sprintf("Cookie %q must be provided.", codersdk.GetSessionTokenCookie()), + Message: fmt.Sprintf("Cookie %q must be provided.", codersdk.SessionTokenCookie), }) return } diff --git a/coderd/userauth.go b/coderd/userauth.go index b159763456583..91472996737aa 100644 --- a/coderd/userauth.go +++ b/coderd/userauth.go @@ -702,7 +702,7 @@ func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) { cookie := &http.Cookie{ // MaxAge < 0 means to delete the cookie now. MaxAge: -1, - Name: codersdk.GetSessionTokenCookie(), + Name: codersdk.SessionTokenCookie, Path: "/", } http.SetCookie(rw, cookie) @@ -1914,7 +1914,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C ) } cookies = append(cookies, api.DeploymentValues.HTTPCookies.Apply(&http.Cookie{ - Name: codersdk.GetSessionTokenCookie(), + Name: codersdk.SessionTokenCookie, Path: "/", MaxAge: -1, HttpOnly: true, diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go index 69183a565a92e..4c9412fda3fb7 100644 --- a/coderd/userauth_test.go +++ b/coderd/userauth_test.go @@ -2476,7 +2476,7 @@ func i64ptr(i int64) *int64 { func authCookieValue(cookies []*http.Cookie) string { for _, cookie := range cookies { - if cookie.Name == codersdk.GetSessionTokenCookie() { + if cookie.Name == codersdk.SessionTokenCookie { return cookie.Value } } diff --git a/coderd/users_test.go b/coderd/users_test.go index 94f2d6f2c6e2e..9d695f37c9906 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -618,8 +618,8 @@ func TestPostLogout(t *testing.T) { var found bool for _, cookie := range cookies { - if cookie.Name == codersdk.GetSessionTokenCookie() { - require.Equal(t, codersdk.GetSessionTokenCookie(), cookie.Name, "Cookie should be the auth cookie") + if cookie.Name == codersdk.SessionTokenCookie { + require.Equal(t, codersdk.SessionTokenCookie, cookie.Name, "Cookie should be the auth cookie") require.Equal(t, -1, cookie.MaxAge, "Cookie should be set to delete") found = true } diff --git a/coderd/workspaceapps/apptest/setup.go b/coderd/workspaceapps/apptest/setup.go index b1e5caab3c95a..9d1df9e7fe09d 100644 --- a/coderd/workspaceapps/apptest/setup.go +++ b/coderd/workspaceapps/apptest/setup.go @@ -261,7 +261,7 @@ func appServer(t *testing.T, headers http.Header, isHTTPS bool) uint16 { server := httptest.NewUnstartedServer( http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - _, err := r.Cookie(codersdk.GetSessionTokenCookie()) + _, err := r.Cookie(codersdk.SessionTokenCookie) assert.ErrorIs(t, err, http.ErrNoCookie) w.Header().Set("X-Forwarded-For", r.Header.Get("X-Forwarded-For")) w.Header().Set("X-Got-Host", r.Host) diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index e62abb15a15c1..5bd0030456757 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -262,7 +262,7 @@ func (c *Client) connectRPCVersion(ctx context.Context, version *apiversion.APIV return nil, xerrors.Errorf("create cookie jar: %w", err) } jar.SetCookies(rpcURL, []*http.Cookie{{ - Name: codersdk.GetSessionTokenCookie(), + Name: codersdk.SessionTokenCookie, Value: c.SDK.SessionToken(), }}) httpClient := &http.Client{ @@ -705,7 +705,7 @@ func (c *Client) WaitForReinit(ctx context.Context) (*ReinitializationEvent, err return nil, xerrors.Errorf("create cookie jar: %w", err) } jar.SetCookies(rpcURL, []*http.Cookie{{ - Name: codersdk.GetSessionTokenCookie(), + Name: codersdk.SessionTokenCookie, Value: c.SDK.SessionToken(), }}) httpClient := &http.Client{ diff --git a/codersdk/client.go b/codersdk/client.go index de12645a7b5b9..2097225ff489c 100644 --- a/codersdk/client.go +++ b/codersdk/client.go @@ -12,7 +12,6 @@ import ( "net/http" "net/http/httputil" "net/url" - "os" "strings" "sync" @@ -21,7 +20,6 @@ import ( "go.opentelemetry.io/otel/semconv/v1.14.0/httpconv" "golang.org/x/xerrors" - "github.com/coder/coder/v2/buildinfo" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/websocket" @@ -32,10 +30,7 @@ import ( // shouldn't be likely to conflict with any user-application set cookies. // Be sure to strip additional cookies in httpapi.StripCoderCookies! const ( - // SessionTokenCookie represents the name of the cookie or query parameter in - // which the API key is stored. - // DEVELOPER NOTE: Please avoid referencing this value directly and use - // GetSessionTokenCookie() instead. + // SessionTokenCookie represents the name of the cookie or query parameter the API key is stored in. SessionTokenCookie = "coder_session_token" // SessionTokenHeader is the custom header to use for authentication. SessionTokenHeader = "Coder-Session-Token" @@ -99,22 +94,6 @@ const ( EntitlementsWarningHeader = "X-Coder-Entitlements-Warning" ) -// GetSessionTokenCookie returns the name of the session token cookie. -// In almost all production cases, this will just be SessionTokenCookie. -// However, when developing inside a Coder workspace and accessing the UI -// proxied through a Coder deployment, we need to prefix the cookie name -// to avoid conflicting with the "parent" deployment. The prefix is controlled -// by the CODER_DEV_SESSION_TOKEN_COOKIE_PREFIX environment variable, and only -// applies in development builds. -func GetSessionTokenCookie() string { - if buildinfo.IsDev() { - if pfx, found := os.LookupEnv("CODER_DEV_SESSION_TOKEN_COOKIE_PREFIX"); found && pfx != "" { - return pfx + "_" + SessionTokenCookie - } - } - return SessionTokenCookie -} - // loggableMimeTypes is a list of MIME types that are safe to log // the output of. This is useful for debugging or testing. var loggableMimeTypes = map[string]struct{}{ diff --git a/codersdk/provisionerdaemons.go b/codersdk/provisionerdaemons.go index ebface3656fdc..5fbda371b8f3f 100644 --- a/codersdk/provisionerdaemons.go +++ b/codersdk/provisionerdaemons.go @@ -215,7 +215,7 @@ func (c *Client) provisionerJobLogsAfter(ctx context.Context, path string, after return nil, nil, xerrors.Errorf("create cookie jar: %w", err) } jar.SetCookies(followURL, []*http.Cookie{{ - Name: GetSessionTokenCookie(), + Name: SessionTokenCookie, Value: c.SessionToken(), }}) httpClient := &http.Client{ @@ -302,7 +302,7 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione return nil, xerrors.Errorf("create cookie jar: %w", err) } jar.SetCookies(serverURL, []*http.Cookie{{ - Name: GetSessionTokenCookie(), + Name: SessionTokenCookie, Value: c.SessionToken(), }}) httpClient.Jar = jar diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 49163ec340d5a..1eb37bb07c989 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -545,7 +545,7 @@ func (c *Client) WatchWorkspaceAgentContainers(ctx context.Context, agentID uuid } jar.SetCookies(reqURL, []*http.Cookie{{ - Name: GetSessionTokenCookie(), + Name: SessionTokenCookie, Value: c.SessionToken(), }}) @@ -630,7 +630,7 @@ func (c *Client) WorkspaceAgentLogsAfter(ctx context.Context, agentID uuid.UUID, return nil, nil, xerrors.Errorf("create cookie jar: %w", err) } jar.SetCookies(reqURL, []*http.Cookie{{ - Name: GetSessionTokenCookie(), + Name: SessionTokenCookie, Value: c.SessionToken(), }}) httpClient := &http.Client{ diff --git a/codersdk/workspacesdk/workspacesdk.go b/codersdk/workspacesdk/workspacesdk.go index 4a1ba108a4a7b..9f587cf5267a8 100644 --- a/codersdk/workspacesdk/workspacesdk.go +++ b/codersdk/workspacesdk/workspacesdk.go @@ -380,7 +380,7 @@ func (c *Client) AgentReconnectingPTY(ctx context.Context, opts WorkspaceAgentRe return nil, xerrors.Errorf("create cookie jar: %w", err) } jar.SetCookies(serverURL, []*http.Cookie{{ - Name: codersdk.GetSessionTokenCookie(), + Name: codersdk.SessionTokenCookie, Value: c.client.SessionToken(), }}) httpClient = &http.Client{ From a2dcf6117dd3c427af11375d775a461a714fd048 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 22 Jul 2025 22:58:43 +0100 Subject: [PATCH 5/7] chore: use ldflags to override codersdk.SessionTokenCookie at build --- codersdk/client.go | 6 ++++-- scripts/build_go.sh | 8 ++++++++ scripts/coder-dev.sh | 10 ++++++++-- scripts/develop.sh | 10 +++++++--- site/src/api/typesGenerated.ts | 3 --- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/codersdk/client.go b/codersdk/client.go index 2097225ff489c..105c8437f841b 100644 --- a/codersdk/client.go +++ b/codersdk/client.go @@ -29,9 +29,11 @@ import ( // These cookies are Coder-specific. If a new one is added or changed, the name // shouldn't be likely to conflict with any user-application set cookies. // Be sure to strip additional cookies in httpapi.StripCoderCookies! +// SessionTokenCookie represents the name of the cookie or query parameter the API key is stored in. +// NOTE: This is declared as a var so that we can override it in `develop.sh` if required. +var SessionTokenCookie = "coder_session_token" + const ( - // SessionTokenCookie represents the name of the cookie or query parameter the API key is stored in. - SessionTokenCookie = "coder_session_token" // SessionTokenHeader is the custom header to use for authentication. SessionTokenHeader = "Coder-Session-Token" // OAuth2StateCookie is the name of the cookie that stores the oauth2 state. diff --git a/scripts/build_go.sh b/scripts/build_go.sh index b3b074b183f91..e291d5fc29189 100755 --- a/scripts/build_go.sh +++ b/scripts/build_go.sh @@ -49,6 +49,7 @@ boringcrypto=${CODER_BUILD_BORINGCRYPTO:-0} dylib=0 windows_resources="${CODER_WINDOWS_RESOURCES:-0}" debug=0 +develop_in_coder="${DEVELOP_IN_CODER:-0}" bin_ident="com.coder.cli" @@ -149,6 +150,13 @@ if [[ "$debug" == 0 ]]; then ldflags+=(-s -w) fi +if [[ "$develop_in_coder" == 1 ]]; then + echo "INFO : Overriding codersdk.SessionTokenCookie as we are developing inside a Coder workspace." + ldflags+=( + -X "'github.com/coder/coder/v2/codersdk.SessionTokenCookie=dev_coder_session_token'" + ) +fi + # We use ts_omit_aws here because on Linux it prevents Tailscale from importing # github.com/aws/aws-sdk-go-v2/aws, which adds 7 MB to the binary. TS_EXTRA_SMALL="ts_omit_aws,ts_omit_bird,ts_omit_tap,ts_omit_kube" diff --git a/scripts/coder-dev.sh b/scripts/coder-dev.sh index f475a124f2c05..51c198166942b 100755 --- a/scripts/coder-dev.sh +++ b/scripts/coder-dev.sh @@ -10,6 +10,8 @@ source "${SCRIPT_DIR}/lib.sh" GOOS="$(go env GOOS)" GOARCH="$(go env GOARCH)" +CODER_AGENT_URL="${CODER_AGENT_URL:-}" +DEVELOP_IN_CODER="${DEVELOP_IN_CODER:-0}" DEBUG_DELVE="${DEBUG_DELVE:-0}" BINARY_TYPE=coder-slim if [[ ${1:-} == server ]]; then @@ -35,6 +37,10 @@ CODER_DEV_DIR="$(realpath ./.coderv2)" CODER_DELVE_DEBUG_BIN=$(realpath "./build/coder_debug_${GOOS}_${GOARCH}") popd +if [ -n "${CODER_AGENT_URL}" ]; then + DEVELOP_IN_CODER=1 +fi + case $BINARY_TYPE in coder-slim) # Ensure the coder slim binary is always up-to-date with local @@ -42,9 +48,9 @@ coder-slim) # NOTE: we send all output of `make` to /dev/null so that we do not break # scripts that read the output of this command. if [[ -t 1 ]]; then - make -j "${RELATIVE_BINARY_PATH}" + DEVELOP_IN_CODER="${DEVELOP_IN_CODER}" make -j "${RELATIVE_BINARY_PATH}" else - make -j "${RELATIVE_BINARY_PATH}" >/dev/null 2>&1 + DEVELOP_IN_CODER="${DEVELOP_IN_CODER}" make -j "${RELATIVE_BINARY_PATH}" >/dev/null 2>&1 fi ;; coder) diff --git a/scripts/develop.sh b/scripts/develop.sh index 3500ba5354110..a83d2e5cbd57f 100755 --- a/scripts/develop.sh +++ b/scripts/develop.sh @@ -14,7 +14,7 @@ source "${SCRIPT_DIR}/lib.sh" set -euo pipefail CODER_DEV_ACCESS_URL="${CODER_DEV_ACCESS_URL:-http://127.0.0.1:3000}" -CODER_DEV_SESSION_TOKEN_COOKIE_PREFIX="${CODER_DEV_SESSION_TOKEN_COOKIE_PREFIX:-dev}" +DEVELOP_IN_CODER="${DEVELOP_IN_CODER:-0}" debug=0 DEFAULT_PASSWORD="SomeSecurePassword!" password="${CODER_DEV_ADMIN_PASSWORD:-${DEFAULT_PASSWORD}}" @@ -67,6 +67,10 @@ if [ "${CODER_BUILD_AGPL:-0}" -gt "0" ] && [ "${multi_org}" -gt "0" ]; then echo '== ERROR: cannot use both multi-organizations and APGL build.' && exit 1 fi +if [ -n "${CODER_AGENT_URL}" ]; then + DEVELOP_IN_CODER=1 +fi + # Preflight checks: ensure we have our required dependencies, and make sure nothing is listening on port 3000 or 8080 dependencies curl git go make pnpm curl --fail http://127.0.0.1:3000 >/dev/null 2>&1 && echo '== ERROR: something is listening on port 3000. Kill it and re-run this script.' && exit 1 @@ -76,7 +80,7 @@ curl --fail http://127.0.0.1:8080 >/dev/null 2>&1 && echo '== ERROR: something i # node_modules if necessary. GOOS="$(go env GOOS)" GOARCH="$(go env GOARCH)" -make -j "build/coder_${GOOS}_${GOARCH}" +DEVELOP_IN_CODER="${DEVELOP_IN_CODER}" make -j "build/coder_${GOOS}_${GOARCH}" # Use the coder dev shim so we don't overwrite the user's existing Coder config. CODER_DEV_SHIM="${PROJECT_ROOT}/scripts/coder-dev.sh" @@ -151,7 +155,7 @@ fatal() { trap 'fatal "Script encountered an error"' ERR cdroot - DEBUG_DELVE="${debug}" CODER_DEV_SESSION_TOKEN_COOKIE_PREFIX="${CODER_DEV_SESSION_TOKEN_COOKIE_PREFIX}" start_cmd API "" "${CODER_DEV_SHIM}" server --http-address 0.0.0.0:3000 --swagger-enable --access-url "${CODER_DEV_ACCESS_URL}" --dangerous-allow-cors-requests=true --enable-terraform-debug-mode "$@" + DEBUG_DELVE="${debug}" DEVELOP_IN_CODER="${DEVELOP_IN_CODER}" start_cmd API "" "${CODER_DEV_SHIM}" server --http-address 0.0.0.0:3000 --swagger-enable --access-url "${CODER_DEV_ACCESS_URL}" --dangerous-allow-cors-requests=true --enable-terraform-debug-mode "$@" echo '== Waiting for Coder to become ready' # Start the timeout in the background so interrupting this script diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 379cd21e03d4e..421cf0872a6b9 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -2700,9 +2700,6 @@ export interface SessionLifetime { readonly max_admin_token_lifetime?: number; } -// From codersdk/client.go -export const SessionTokenCookie = "coder_session_token"; - // From codersdk/client.go export const SessionTokenHeader = "Coder-Session-Token"; From cb6b8b30be872d02c957d324021c8c5b0b48d78c Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 23 Jul 2025 11:17:13 +0100 Subject: [PATCH 6/7] re-add missing autogenerated export manually --- site/src/api/api.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 9a46c40217091..9d8d040aab668 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -107,6 +107,9 @@ const getMissingParameters = ( return missingParameters; }; +// From codersdk/client.go +export const SessionTokenCookie = "coder_session_token"; + /** * @param agentId * @returns {OneWayWebSocket} A OneWayWebSocket that emits Server-Sent Events. From f4e5a14457431de135874d212f1f2e00e72a0b0c Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 23 Jul 2025 12:21:30 +0100 Subject: [PATCH 7/7] make knip shut up --- site/src/api/api.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 9d8d040aab668..cd70bfaf00600 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -107,7 +107,11 @@ const getMissingParameters = ( return missingParameters; }; -// From codersdk/client.go +/** + * Originally from codersdk/client.go. + * The below declaration is required to stop Knip from complaining. + * @public + */ export const SessionTokenCookie = "coder_session_token"; /**