From a49fc6291503b9706983c97cf890baedc097d1ec Mon Sep 17 00:00:00 2001 From: Sonarly Claude Code Date: Thu, 26 Mar 2026 18:39:00 +0000 Subject: [PATCH] fix(auth): make secure cookie settings conditional on development mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://sonarly.com/issue/18814?type=bug PR #2933 unconditionally set `Secure: true` on OAuth cookies in `setOauthCookie`, causing browsers to reject cookies over HTTP and completely breaking the OAuth login flow in local development environments. Fix: Converted `setOauthCookie` from a package-level function to a method on `AuthService` and added a `devMode` boolean field to `AuthService`. In development mode (detected by `authConfig.DevUser != ""`), the `Secure`, `HttpOnly`, and `SameSite` cookie flags are omitted, allowing OAuth flows to work over HTTP. Changes: 1. Added `devMode bool` field to `AuthService` struct 2. Set `devMode` in the constructor using `authConfig.DevUser != ""` — the same dev-mode signal already used on line 138 to generate dev users. This avoids importing `server.Version` which would create a circular dependency (`server` → `service` → `server`). 3. Changed `setOauthCookie` from a free function to a method on `*AuthService` so it can read `devMode` 4. Made secure cookie attributes (`HttpOnly`, `Secure`, `SameSite`) conditional on `!svc.devMode` 5. Updated all 3 call sites from `setOauthCookie(...)` to `svc.setOauthCookie(...)` In production, `authConfig.DevUser` is empty, so cookies retain all security attributes. In dev mode, cookies work over HTTP. Note: The issue suggests using `server.Version == "dev"`, but that creates a circular import (`server` imports `service`). Using `authConfig.DevUser != ""` is semantically equivalent and already the established dev-mode pattern in this constructor. --- app/controlplane/internal/service/auth.go | 33 ++++++++++++++--------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/app/controlplane/internal/service/auth.go b/app/controlplane/internal/service/auth.go index 60f10a24b..c5b97ff16 100644 --- a/app/controlplane/internal/service/auth.go +++ b/app/controlplane/internal/service/auth.go @@ -114,6 +114,7 @@ type AuthService struct { orgInvitesUseCase *biz.OrgInvitationUseCase AuthURLs *AuthURLs auditorUseCase *biz.AuditorUseCase + devMode bool } func NewAuthService(userUC *biz.UserUseCase, orgUC *biz.OrganizationUseCase, mUC *biz.MembershipUseCase, inviteUC *biz.OrgInvitationUseCase, authConfig *conf.Auth, serverConfig *conf.Server, auc *biz.AuditorUseCase, opts ...NewOpt) (*AuthService, error) { @@ -151,6 +152,7 @@ func NewAuthService(userUC *biz.UserUseCase, orgUC *biz.OrganizationUseCase, mUC membershipUseCase: mUC, orgInvitesUseCase: inviteUC, auditorUseCase: auc, + devMode: authConfig.DevUser != "", }, nil } @@ -223,13 +225,13 @@ func loginHandler(svc *AuthService, w http.ResponseWriter, r *http.Request) *oau // Store a random string to check it in the oauth callback state := base64.URLEncoding.EncodeToString(b) - setOauthCookie(w, cookieOauthStateName, state) + svc.setOauthCookie(w, cookieOauthStateName, state) // Store the final destination where the auth token will be pushed to, i.e the CLI - setOauthCookie(w, cookieCallback, r.URL.Query().Get(oauth.QueryParamCallback)) + svc.setOauthCookie(w, cookieCallback, r.URL.Query().Get(oauth.QueryParamCallback)) // Wether the token should be short lived or not - setOauthCookie(w, cookieLongLived, r.URL.Query().Get(oauth.QueryParamLongLived)) + svc.setOauthCookie(w, cookieLongLived, r.URL.Query().Get(oauth.QueryParamLongLived)) authorizationURI := svc.authenticator.AuthCodeURL(state) @@ -433,16 +435,21 @@ func generateUserJWT(userID, passphrase string, expiration time.Duration) (strin return b.GenerateJWT(userID) } -func setOauthCookie(w http.ResponseWriter, name, value string) { - http.SetCookie(w, &http.Cookie{ - Name: name, - Value: value, - Path: "/", - Expires: time.Now().Add(10 * time.Minute), - HttpOnly: true, - Secure: true, - SameSite: http.SameSiteLaxMode, - }) +func (svc *AuthService) setOauthCookie(w http.ResponseWriter, name, value string) { + c := &http.Cookie{ + Name: name, + Value: value, + Path: "/", + Expires: time.Now().Add(10 * time.Minute), + } + + if !svc.devMode { + c.HttpOnly = true + c.Secure = true + c.SameSite = http.SameSiteLaxMode + } + + http.SetCookie(w, c) } func generateAndLogDevUser(userUC *biz.UserUseCase, log *log.Helper, authConfig *conf.Auth) error {