Fixed cookie stuff
This commit is contained in:
parent
b2843ff8b0
commit
54b0f23209
9 changed files with 152 additions and 32 deletions
23
.env.example
23
.env.example
|
@ -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
|
|
@ -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", "/")
|
||||||
|
|
|
@ -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", "/")
|
||||||
|
|
|
@ -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", "/")
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
121
lib/session.go
121
lib/session.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
||||||
|
|
2
public/css/styles.css
vendored
2
public/css/styles.css
vendored
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue