Fixed cookie stuff

This commit is contained in:
Atridad Lahiji 2024-06-28 00:35:58 -06:00
parent b2843ff8b0
commit 54b0f23209
No known key found for this signature in database
9 changed files with 152 additions and 32 deletions

View file

@ -1,14 +1,15 @@
# This file is an example of the .env file that should be created in the root of the project # This file is used to store environment variables for the application
# This file should not be committed to the repository # Database Connection Information
# The actual .env file should contain the following variables
# The database credentials
POSTGRES_HOST=localhost POSTGRES_HOST=localhost
POSTGRES_PORT=5432 POSTGRES_PORT=5432
POSTGRES_PASSWORD=postgres POSTGRES_PASSWORD=password
POSTGRES_USER=postgres POSTGRES_USER=atridadlahiji
POSTGRES_DB=db POSTGRES_DB=pollo
# The secret key used to sign the JWT tokens
AUTH_SECRET=super-duper-secret # Security
# If you want to run the app in development mode, set this to true ENCRYPTION_KEY="super-secret"
SIGNING_KEY="super-secret"
AUTH_SECRET="super-secret"
# Feature Flags
DEVMODE=true DEVMODE=true

View file

@ -27,7 +27,12 @@ func RegisterUserHandler(c echo.Context) error {
} }
// Set the session cookie using the helper function // Set the session cookie using the helper function
lib.SetSessionCookie(c.Response().Writer, "session_id", sessionID) lib.SetSessionCookie(c.Response().Writer, "session", lib.SessionData{
SessionID: sessionID,
UserID: user.ID,
Email: user.Email,
Roles: []string{"user"},
})
// Redirect or respond with a success status code // Redirect or respond with a success status code
c.Response().Header().Set("HX-Redirect", "/") c.Response().Header().Set("HX-Redirect", "/")

View file

@ -25,7 +25,12 @@ func SignInUserHandler(c echo.Context) error {
} }
// Set the session cookie with the generated session ID // Set the session cookie with the generated session ID
lib.SetSessionCookie(c.Response().Writer, "session_id", sessionID) lib.SetSessionCookie(c.Response().Writer, "session", lib.SessionData{
SessionID: sessionID,
UserID: user.ID,
Email: user.Email,
Roles: []string{"user"},
})
// Proceed with login success logic // Proceed with login success logic
c.Response().Header().Set("HX-Redirect", "/") c.Response().Header().Set("HX-Redirect", "/")

View file

@ -9,7 +9,7 @@ import (
func SignOutUserHandler(c echo.Context) error { func SignOutUserHandler(c echo.Context) error {
// Clear the session cookie // Clear the session cookie
lib.ClearSessionCookie(c.Response().Writer, "session_id") lib.ClearSessionCookie(c.Response().Writer, "session")
// Proceed with login success logic // Proceed with login success logic
c.Response().Header().Set("HX-Redirect", "/") c.Response().Header().Set("HX-Redirect", "/")

View file

@ -11,7 +11,7 @@ import (
) )
type User struct { type User struct {
ID int ID string
Email string Email string
Password string Password string
} }
@ -35,7 +35,8 @@ func GetUserByEmail(dbPool *pgxpool.Pool, email string) (*User, error) {
} }
var user User var user User
err := dbPool.QueryRow(context.Background(), "SELECT id, email, password FROM users WHERE email = $1", email).Scan(&user.ID, &user.Email, &user.Password) // Ensure the ID is being scanned as a string.
err := dbPool.QueryRow(context.Background(), "SELECT id::text, email, password FROM users WHERE email = $1", email).Scan(&user.ID, &user.Email, &user.Password)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -1,8 +1,14 @@
package lib package lib
import ( import (
"crypto/aes"
"crypto/cipher"
"crypto/rand" "crypto/rand"
"encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json"
"errors"
"fmt"
"log" "log"
"net/http" "net/http"
"os" "os"
@ -12,6 +18,13 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
type SessionData struct {
SessionID string `json:"sessionID"`
UserID string `json:"userId"`
Email string `json:"email"`
Roles []string `json:"roles"`
}
func InitSessionMiddleware() echo.MiddlewareFunc { func InitSessionMiddleware() echo.MiddlewareFunc {
authSecret := os.Getenv("AUTH_SECRET") authSecret := os.Getenv("AUTH_SECRET")
if authSecret == "" { if authSecret == "" {
@ -22,30 +35,108 @@ func InitSessionMiddleware() echo.MiddlewareFunc {
return session.Middleware(store) return session.Middleware(store)
} }
// SetSessionCookie sets a secure cookie with the session ID in the client's browser // Encrypt data using AES
func SetSessionCookie(w http.ResponseWriter, name, value string) { func encrypt(data []byte) (string, error) {
devMode := os.Getenv("DEVMODE") == "true" encryptionKey := []byte(os.Getenv("ENCRYPTION_KEY"))
secureFlag := !devMode fmt.Printf("Encryption Key Length: %d\n", len(encryptionKey))
println("Secure flag: ", secureFlag)
block, err := aes.NewCipher(encryptionKey)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, gcm.NonceSize())
_, err = rand.Read(nonce)
if err != nil {
return "", err
}
cipherText := gcm.Seal(nonce, nonce, data, nil)
return base64.StdEncoding.EncodeToString(cipherText), nil
}
// decrypt decrypts the data using AES-GCM.
func decrypt(encryptedString string) (string, error) {
encryptionKey := []byte(os.Getenv("ENCRYPTION_KEY"))
data, err := base64.StdEncoding.DecodeString(encryptedString)
if err != nil {
return "", err
}
block, err := aes.NewCipher(encryptionKey)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonceSize := gcm.NonceSize()
if len(data) < nonceSize {
return "", errors.New("ciphertext too short")
}
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}
// Adjusted SetSessionCookie to include more user info
func SetSessionCookie(w http.ResponseWriter, name string, sessionData SessionData) {
// Serialize session data
dataBytes, err := json.Marshal(sessionData)
if err != nil {
log.Fatal("Failed to serialize session data:", err)
}
// Encrypt serialized session data
encryptedData, err := encrypt(dataBytes)
if err != nil {
log.Fatal("Failed to encrypt session data:", err)
}
// Set cookie with encrypted data
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: name, Name: name,
Value: value, Value: encryptedData,
Path: "/", Path: "/",
HttpOnly: true, HttpOnly: true,
Secure: secureFlag, Secure: os.Getenv("DEVMODE") != "true",
SameSite: http.SameSiteStrictMode, SameSite: http.SameSiteStrictMode,
MaxAge: 3600, MaxAge: 3600,
}) })
} }
// GetSessionCookie retrieves the session cookie value by its name // Adjusted GetSessionCookie to return SessionData
func GetSessionCookie(r *http.Request, name string) (string, error) { func GetSessionCookie(r *http.Request, name string) (*SessionData, error) {
cookie, err := r.Cookie(name) cookie, err := r.Cookie(name)
if err != nil { if err != nil {
return "", err return nil, err
} }
return cookie.Value, nil
// Decrypt the cookie value
decryptedValue, err := decrypt(cookie.Value)
if err != nil {
return nil, err
}
// Deserialize session data
var sessionData SessionData
err = json.Unmarshal([]byte(decryptedValue), &sessionData)
if err != nil {
return nil, err
}
return &sessionData, nil
} }
// ClearSessionCookie clears the session cookie from the client's browser // ClearSessionCookie clears the session cookie from the client's browser
@ -57,13 +148,17 @@ func ClearSessionCookie(w http.ResponseWriter, name string) {
HttpOnly: true, HttpOnly: true,
Secure: os.Getenv("DEVMODE") != "true", Secure: os.Getenv("DEVMODE") != "true",
SameSite: http.SameSiteStrictMode, SameSite: http.SameSiteStrictMode,
MaxAge: -1, // Set MaxAge to -1 to delete the cookie immediately. MaxAge: -1, // This will delete the cookie
}) })
} }
// Checks if the user is signed in by checking the session cookie // Checks if the user is signed in by checking the session cookie
func IsSignedIn(c echo.Context) bool { func IsSignedIn(c echo.Context) bool {
_, err := GetSessionCookie(c.Request(), "session_id") _, err := GetSessionCookie(c.Request(), "session")
if err != nil {
// Log the error for debugging purposes
log.Printf("Error retrieving session cookie: %v", err)
}
return err == nil return err == nil
} }

View file

@ -8,11 +8,18 @@ import (
type DashboardProps struct { type DashboardProps struct {
IsLoggedIn bool IsLoggedIn bool
Email string
} }
func Dashboard(c echo.Context) error { func Dashboard(c echo.Context) error {
currentSession, error := lib.GetSessionCookie(c.Request(), "session")
if error != nil {
return c.Redirect(302, "/signin")
}
props := DashboardProps{ props := DashboardProps{
IsLoggedIn: lib.IsSignedIn(c), IsLoggedIn: lib.IsSignedIn(c),
Email: currentSession.Email,
} }
// Specify the partials used by this page // Specify the partials used by this page

View file

@ -11,8 +11,14 @@ Pollo // Dashboard
{{end}} {{end}}
{{define "main"}} {{define "main"}}
<div class="flex flex-col text-center items-center justify-center px-4 py-16 gap-4"> <div class="flex flex-col items-center justify-center gap-8">
SIGNED IN <h1 class="flex flex-row flex-wrap text-center justify-center items-center gap-1 text-4xl font-bold">
Hi, {{.Email}}!
</h1>
<label htmlFor="new-room-modal" class="btn btn-primary">
New Room
</label>
</div> </div>
{{end}} {{end}}

File diff suppressed because one or more lines are too long