Open
Description
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
Labels
No labels