added account

This commit is contained in:
2025-10-25 15:55:25 +02:00
parent 72c50549d7
commit 4db5084bc6
15 changed files with 1214 additions and 150 deletions

99
auth/auth.go Normal file
View File

@@ -0,0 +1,99 @@
package auth
import (
"crypto/rand"
"encoding/base64"
"errors"
"time"
"golang.org/x/crypto/bcrypt"
)
const (
// BCrypt cost - 12 is a good balance between security and performance
bcryptCost = 12
// Session token length in bytes (32 bytes = 256 bits)
sessionTokenLength = 32
// Session expiry duration (7 days)
sessionExpiry = 7 * 24 * time.Hour
)
var (
ErrInvalidCredentials = errors.New("invalid email or password")
ErrUserExists = errors.New("user already exists")
ErrInvalidSession = errors.New("invalid or expired session")
ErrWeakPassword = errors.New("password must be at least 8 characters")
ErrInvalidEmail = errors.New("invalid email format")
)
// HashPassword securely hashes a password using bcrypt
func HashPassword(password string) (string, error) {
if len(password) < 8 {
return "", ErrWeakPassword
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost)
if err != nil {
return "", err
}
return string(hash), nil
}
// CheckPassword verifies a password against a hash
func CheckPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
// GenerateSessionToken creates a cryptographically secure random token
func GenerateSessionToken() (string, error) {
bytes := make([]byte, sessionTokenLength)
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
// Use URL-safe base64 encoding
token := base64.URLEncoding.EncodeToString(bytes)
return token, nil
}
// GetSessionExpiry returns the expiry time for a new session
func GetSessionExpiry() time.Time {
return time.Now().Add(sessionExpiry)
}
// ValidateEmail performs basic email validation
func ValidateEmail(email string) bool {
if len(email) < 3 || len(email) > 254 {
return false
}
// Basic check for @ symbol and domain
atCount := 0
dotAfterAt := false
atPos := -1
for i, c := range email {
if c == '@' {
atCount++
atPos = i
}
if atPos > 0 && i > atPos && c == '.' {
dotAfterAt = true
}
}
return atCount == 1 && dotAfterAt && atPos > 0 && atPos < len(email)-2
}
// SanitizeInput removes dangerous characters for basic XSS prevention
// Note: Go templates auto-escape, but this adds an extra layer
func SanitizeInput(input string) string {
// Templates handle escaping, but we can enforce length limits
if len(input) > 1000 {
return input[:1000]
}
return input
}

70
auth/middleware.go Normal file
View File

@@ -0,0 +1,70 @@
package auth
import (
"context"
"mealprep/database"
"net/http"
)
type contextKey string
const (
userIDKey contextKey = "userID"
)
// RequireAuth is middleware that checks if user is authenticated
func RequireAuth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Get session cookie
cookie, err := r.Cookie("session_token")
if err != nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Validate session
session, err := database.GetSession(cookie.Value)
if err != nil {
// Invalid or expired session
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: "",
MaxAge: -1,
HttpOnly: true,
Path: "/",
})
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Add user ID to request context
ctx := context.WithValue(r.Context(), userIDKey, session.UserID)
next.ServeHTTP(w, r.WithContext(ctx))
}
}
// GetUserID retrieves the user ID from the request context
func GetUserID(r *http.Request) int {
userID, ok := r.Context().Value(userIDKey).(int)
if !ok {
return 0
}
return userID
}
// RedirectIfAuthenticated redirects to home if user is already logged in
func RedirectIfAuthenticated(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session_token")
if err == nil {
// Check if session is valid
_, err := database.GetSession(cookie.Value)
if err == nil {
// Valid session, redirect to home
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
}
next.ServeHTTP(w, r)
}
}