Skip to content

cookie not issued on prod. #603

Open
@kaleb110

Description

@kaleb110

it works greate locally but in prod cookie doesnt get issued over https:

here is my google handler function:

func GoogleCallbackHandler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Println("GoogleAuthHandler called.")
		//log all headers before the redirect
		for key, values := range w.Header() {
			for _, value := range values {
				log.Printf("Header: %s=%s", key, value)
			}
		}
		// Check for the "code" query parameter.
		code := r.URL.Query().Get("code")
		if code == "" {
			// User canceled the Google account selection.
			log.Println("Google authentication canceled by user (no code parameter).")

			// Redirect to a safe location or display an error.
			http.Redirect(w, r, "/", http.StatusFound) // Redirect to home page.

			return // Crucial: Stop further execution.
		}

		// Finalize the authentication process (only if "code" is present).
		user, err := gothic.CompleteUserAuth(w, r)
		if err != nil {
			http.Error(w, "Authentication failed", http.StatusUnauthorized)
			log.Println(err)
			return
		}

		// Directly perform FindOrCreateUser logic here
		ctx := r.Context()
		userEmail := user.Email
		userID := uuid.New().String()
		log.Println("id", userID)

		// Assuming db.DB is a *pgxpool.Pool

		// Check if user exists
		query := `SELECT id FROM "User" WHERE email = $1`
		err = db.DB.QueryRow(ctx, query, userEmail).Scan(&userID)
		if err == nil {
			// User exists, userID is already set
			log.Println("skipped...")
		} else if err.Error() == "no rows in result set" { // Use err.Error() for pgxpool
			// User does not exist, create new user
			query = `INSERT INTO "User" (id, "fullName", email, "avatarUrl") VALUES ($1, $2, $3, $4)` // Include id
			_, err = db.DB.Exec(ctx, query, userID, user.Name, user.Email, user.AvatarURL)            // use Exec since we already have the id
			if err != nil {
				http.Error(w, "Database error", http.StatusInternalServerError)
				log.Println(err)
				return
			}

			// Set User Preference
			query = `INSERT INTO "UserPreference" ("userId") VALUES ($1)` // Include id
			_, err = db.DB.Exec(ctx, query, userID)                       // use Exec since we already have the id
			if err != nil {
				http.Error(w, "Database error", http.StatusInternalServerError)
				log.Println(err)
				return
			}
		} else {
			http.Error(w, "Database error", http.StatusInternalServerError)
			log.Println(err)
			return
		}

		// Save user ID in the gothic session (as gothic requires).
		err = gothic.StoreInSession("user_id", userID, r, w)
		if err != nil {
			http.Error(w, "Failed to save session", http.StatusInternalServerError)
			log.Println(err)
			return
		}

		// Save user data in the custom session store.
		session, _ := utils.SessionStore.Get(r, "user_session")
		session.Values["user_id"] = userID
		session.Save(r, w)

		var redirectSecure string
		if utils.IsProd {
			redirectSecure = os.Getenv("REDIRECT_SECURE_PROD")
		} else {
			redirectSecure = os.Getenv("REDIRECT_SECURE")
		}

		// Redirect to home page
		if redirectSecure == "" {
			redirectSecure = "http://localhost:3000"
		}

		http.Redirect(w, r, redirectSecure, http.StatusFound)
	}
}

in main i initialze a session:

var SessionStore *sessions.CookieStore

func init() {
	// Initialize Zerolog with pretty console output
	logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Timestamp().Logger()
	log.SetOutput(logger)

	// Load environment variables
	if err := godotenv.Load(); err != nil {
		LogInfoWithPrettyError("init", "No .env file found, using system environment variables", err)
	}
	// Load environment variables (moved to main)
	isProduction := os.Getenv("ENV") == "production"
	LogInfoWithPrettyError("init", fmt.Sprintf("is production: %v", isProduction), nil)

	sessionSecret := os.Getenv("SESSION_SECRET")
	if sessionSecret == "" {
		LogWarnWithPrettyError("init", "SESSION_SECRET environment variable not set, using default (not recommended for production)", nil)
		sessionSecret = "default-session-secret"
	}
	LogDebugWithPrettyError("init", "Session key loaded", nil)

	var domain string
	if isProduction {
		domain = os.Getenv("SESSION_COOKIE_DOMAIN_PROD")
	} else {
		domain = os.Getenv("SESSION_COOKIE_DOMAIN")
	}
	LogDebugWithPrettyError("init", fmt.Sprintf("Session cookie domain: %s", domain), nil)

	SessionStore = sessions.NewCookieStore([]byte(sessionSecret))
	SessionStore.Options = &sessions.Options{
		HttpOnly: true,
		Secure:   isProduction,
		Path:     "/",
		MaxAge:   86400 * 30,
		Domain:   domain, // Ensure this is set in .env
		SameSite: http.SameSiteNoneMode,
	}
	LogDebugWithPrettyError("init", "Session store initialized", nil)

	gothic.Store = SessionStore // Tell gothic to use our session store
	LogDebugWithPrettyError("init", "Gothic store set", nil)

	clientID := os.Getenv("GOOGLE_CLIENT_ID")
	clientSecret := os.Getenv("GOOGLE_CLIENT_SECRET")

	var callbackURL string
	if isProduction {
		callbackURL = os.Getenv("GOOGLE_CALLBACK_URL_PROD")
	} else {
		callbackURL = os.Getenv("GOOGLE_CALLBACK_URL")
	}
	LogDebugWithPrettyError("init", fmt.Sprintf("Google Client ID: %s", clientID), nil)
	LogDebugWithPrettyError("init", "Google Client Secret loaded", nil)
	LogDebugWithPrettyError("init", fmt.Sprintf("Google Callback URL: %s", callbackURL), nil)

	if clientID == "" || clientSecret == "" || callbackURL == "" {
		LogWarnWithPrettyError("init", "Error: Google OAuth environment variables are not fully set in .env", nil)
	}

	goth.UseProviders(
		google.New(clientID, clientSecret, callbackURL, "email", "profile"),
	)
	LogDebugWithPrettyError("init", "Google OAuth provider initialized", nil)
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions