Files
Poll-system/app/internal/handlers/login.go

367 lines
11 KiB
Go
Raw Normal View History

2025-08-26 14:13:09 -06:00
package handlers
import (
"context"
"log"
"net/http"
"time"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
"github.com/patel-mann/poll-system/app/internal/models"
"github.com/patel-mann/poll-system/app/internal/utils"
)
var jwtKey = []byte("your-secret-key") //TODO: Move to env/config
// Helper function to get redirect URL based on role
func getDefaultRedirectURL(role int) string {
switch role {
case 1: // Admin
return "/dashboard"
case 2: // Volunteer
return "/volunteer/dashboard"
case 3: // Volunteer
return "/volunteer/dashboard"
default:
return "/" // Fallback to login page
}
}
// Helper function to render error pages with consistent data
func renderLoginError(w http.ResponseWriter, errorMsg string) {
utils.Render(w, "login.html", map[string]interface{}{
"Error": errorMsg,
"Title": "Login",
"IsAuthenticated": false,
})
}
func renderRegisterError(w http.ResponseWriter, errorMsg string) {
utils.Render(w, "register.html", map[string]interface{}{
"Error": errorMsg,
"Title": "Register",
"IsAuthenticated": false,
})
}
// Helper function to create and sign JWT token
func createJWTToken(userID, role int) (string, time.Time, error) {
expirationTime := time.Now().Add(12 * time.Hour)
claims := &models.Claims{
UserID: userID,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(jwtKey)
return tokenString, expirationTime, err
}
// Helper function to set session cookie
func setSessionCookie(w http.ResponseWriter, tokenString string, expirationTime time.Time) {
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: tokenString,
Path: "/",
HttpOnly: true,
Secure: false, // Set to true in production with HTTPS
SameSite: http.SameSiteStrictMode,
Expires: expirationTime,
})
}
// Helper function to clear session cookie
func clearSessionCookie(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
Secure: false, // Set to true in production with HTTPS
SameSite: http.SameSiteStrictMode,
})
}
// func LoginPage(w http.ResponseWriter, r *http.Request) {
// utils.Render(w, "login.html", map[string]interface{}{
// "Title": "Login",
// "IsAuthenticated": false,
// })
// }
func LoginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
email := r.FormValue("email")
password := r.FormValue("password")
// Input validation
if email == "" || password == "" {
renderLoginError(w, "Email and password are required")
return
}
// Get user from database
var storedHash string
var userID int
var role int
err := models.DB.QueryRow(`
SELECT user_id, password, role_id
FROM "users"
WHERE email = $1
`, email).Scan(&userID, &storedHash, &role)
if err != nil {
log.Printf("Login failed for email %s: %v", email, err)
renderLoginError(w, "Invalid email or password")
return
}
// Verify password
err = bcrypt.CompareHashAndPassword([]byte(storedHash), []byte(password))
if err != nil {
log.Printf("Password verification failed for user ID %d", userID)
renderLoginError(w, "Invalid email or password")
return
}
// Create JWT token
tokenString, expirationTime, err := createJWTToken(userID, role)
if err != nil {
log.Printf("JWT token creation failed for user ID %d: %v", userID, err)
http.Error(w, "Could not log in", http.StatusInternalServerError)
return
}
// Set session cookie
setSessionCookie(w, tokenString, expirationTime)
// Redirect based on user role
redirectURL := getDefaultRedirectURL(role)
log.Printf("User %d (role %d) logged in successfully, redirecting to %s", userID, role, redirectURL)
http.Redirect(w, r, redirectURL, http.StatusSeeOther)
}
func RegisterHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
utils.Render(w, "register.html", map[string]interface{}{
"Title": "Register",
"IsAuthenticated": false,
})
return
}
firstName := r.FormValue("first_name")
lastName := r.FormValue("last_name")
email := r.FormValue("email")
phone := r.FormValue("phone")
role := r.FormValue("role")
password := r.FormValue("password")
// Input validation
if firstName == "" || lastName == "" || email == "" || password == "" || role == "" {
renderRegisterError(w, "All fields are required")
return
}
// Hash password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
log.Printf("Password hashing failed: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
// Insert user into database
_, err = models.DB.Exec(`
INSERT INTO "users" (first_name, last_name, email, phone, password, role_id)
VALUES ($1, $2, $3, $4, $5, $6)
`, firstName, lastName, email, phone, string(hashedPassword), role)
if err != nil {
log.Printf("User registration failed for email %s: %v", email, err)
renderRegisterError(w, "Could not create account. Email might already be in use.")
return
}
log.Printf("User registered successfully: %s %s (%s)", firstName, lastName, email)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
clearSessionCookie(w)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// // Admin Dashboard Handler
// func AdminDashboardHandler(w http.ResponseWriter, r *http.Request) {
// role := r.Context().Value("user_role").(int)
// userID := r.Context().Value("user_id").(int)
// // TODO: Fetch real data from database
// dashboardData := map[string]interface{}{
// "UserID": userID,
// "TotalUsers": 100, // Example: get from database
// "TotalVolunteers": 50, // Example: get from database
// "TotalAddresses": 200, // Example: get from database
// "RecentActivity": []string{"User logged in", "New volunteer registered"}, // Example
// }
// data := createTemplateData("Admin Dashboard", "dashboard", role, true, dashboardData)
// utils.Render(w, "dashboard/dashboard.html", data)
// }
// // Volunteer Management Handler
// func VolunteerHandler(w http.ResponseWriter, r *http.Request) {
// role := r.Context().Value("user_role").(int)
// // TODO: Fetch real volunteer data from database
// volunteerData := map[string]interface{}{
// "Volunteers": []map[string]interface{}{
// {"ID": 1, "Name": "John Doe", "Email": "john@example.com", "Status": "Active"},
// {"ID": 2, "Name": "Jane Smith", "Email": "jane@example.com", "Status": "Active"},
// }, // Example: get from database
// }
// data := createTemplateData("Volunteers", "volunteer", role, true, volunteerData)
// utils.Render(w, "volunteers/volunteers.html", data)
// }
// // Address Management Handler
// func AddressHandler(w http.ResponseWriter, r *http.Request) {
// role := r.Context().Value("user_role").(int)
// // TODO: Fetch real address data from database
// addressData := map[string]interface{}{
// "Addresses": []map[string]interface{}{
// {"ID": 1, "Street": "123 Main St", "City": "Calgary", "Status": "Validated"},
// {"ID": 2, "Street": "456 Oak Ave", "City": "Calgary", "Status": "Pending"},
// }, // Example: get from database
// }
// data := createTemplateData("Addresses", "address", role, true, addressData)
// utils.Render(w, "addresses/addresses.html", data)
// }
// // Reports Handler
// func ReportHandler(w http.ResponseWriter, r *http.Request) {
// role := r.Context().Value("user_role").(int)
// // TODO: Fetch real report data from database
// reportData := map[string]interface{}{
// "Reports": []map[string]interface{}{
// {"ID": 1, "Name": "Weekly Summary", "Date": "2025-08-25", "Status": "Complete"},
// {"ID": 2, "Name": "Monthly Analytics", "Date": "2025-08-01", "Status": "Pending"},
// }, // Example: get from database
// }
// data := createTemplateData("Reports", "report", role, true, reportData)
// utils.Render(w, "reports/reports.html", data)
// }
// // Profile Handler (works for both admin and volunteer)
// func ProfileHandler(w http.ResponseWriter, r *http.Request) {
// role := r.Context().Value("user_role").(int)
// userID := r.Context().Value("user_id").(int)
// // Fetch real user data from database
// var firstName, lastName, email, phone string
// err := models.DB.QueryRow(`
// SELECT first_name, last_name, email, phone
// FROM "users"
// WHERE user_id = $1
// `, userID).Scan(&firstName, &lastName, &email, &phone)
// profileData := map[string]interface{}{
// "UserID": userID,
// }
// if err != nil {
// log.Printf("Error fetching user profile for ID %d: %v", userID, err)
// profileData["Error"] = "Could not load profile data"
// } else {
// profileData["FirstName"] = firstName
// profileData["LastName"] = lastName
// profileData["Email"] = email
// profileData["Phone"] = phone
// }
// data := createTemplateData("Profile", "profile", role, true, profileData)
// utils.Render(w, "profile/profile.html", data)
// }
// // Volunteer Dashboard Handler
// func VolunteerDashboardHandler(w http.ResponseWriter, r *http.Request) {
// role := r.Context().Value("user_role").(int)
// userID := r.Context().Value("user_id").(int)
// // TODO: Fetch volunteer-specific data from database
// dashboardData := map[string]interface{}{
// "UserID": userID,
// "AssignedTasks": 5, // Example: get from database
// "CompletedTasks": 12, // Example: get from database
// "UpcomingEvents": []string{"Community Meeting - Aug 30", "Training Session - Sep 5"}, // Example
// }
// data := createTemplateData("Volunteer Dashboard", "dashboard", role, true, dashboardData)
// utils.Render(w, "volunteer/dashboard.html", data)
// }
// // Schedule Handler for Volunteers
// func ScheduleHandler(w http.ResponseWriter, r *http.Request) {
// role := r.Context().Value("user_role").(int)
// userID := r.Context().Value("user_id").(int)
// // TODO: Fetch schedule data from database
// scheduleData := map[string]interface{}{
// "UserID": userID,
// "Schedule": []map[string]interface{}{
// {"Date": "2025-08-26", "Time": "10:00 AM", "Task": "Door-to-door survey", "Location": "Downtown"},
// {"Date": "2025-08-28", "Time": "2:00 PM", "Task": "Data entry", "Location": "Office"},
// }, // Example: get from database
// }
// data := createTemplateData("My Schedule", "schedual", role, true, scheduleData)
// utils.Render(w, "volunteer/schedule.html", data)
// }
// Enhanced middleware to check JWT auth and add user context
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session")
if err != nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
claims := &models.Claims{}
token, err := jwt.ParseWithClaims(cookie.Value, claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil || !token.Valid {
log.Printf("Invalid token: %v", err)
clearSessionCookie(w) // Clear invalid cookie
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Add user info to context
ctx := context.WithValue(r.Context(), "user_id", claims.UserID)
ctx = context.WithValue(ctx, "user_role", claims.Role)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
}
}