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"; /** pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy