diff --git a/api/auth.go b/api/auth.go new file mode 100644 index 0000000..377da54 --- /dev/null +++ b/api/auth.go @@ -0,0 +1,99 @@ +package api + +import ( + "context" + "net/http" + + "github.com/google/uuid" + "golang.org/x/crypto/bcrypt" +) + +func Login(w http.ResponseWriter, r *http.Request) { + err := r.ParseMultipartForm(64 << 10) + if err != nil { + http.Error(w, "Unable to parse form", http.StatusBadRequest) + return + } + + username := r.FormValue("name") + password := r.FormValue("password") + if username == "" || password == "" { + http.Error(w, "Username and password cannot be empty", http.StatusBadRequest) + return + } + + user, err := dbGetUserByName(username) + if err != nil { + http.Error(w, "Invalid username or password", http.StatusUnauthorized) + return + } + + err = validatePassword(user.Password, password) + if err != nil { + http.Error(w, "Invalid username or password", http.StatusUnauthorized) + return + } + + sessionToken := CreateSession(username) + + http.SetCookie(w, &http.Cookie{ + Name: "session_token", + Value: sessionToken, + Path: "/", + HttpOnly: true, + Secure: false, + }) + + w.Write([]byte("Login successful")) +} + +var sessionStore = make(map[string]string) + +func CreateSession(username string) string { + sessionToken := uuid.New().String() + sessionStore[sessionToken] = username + return sessionToken +} + +func ValidateSession(sessionToken string) (string, bool) { + username, exists := sessionStore[sessionToken] + return username, exists +} + +type contextKey string + +const usernameKey contextKey = "username" + +func SessionAuthMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + cookie, err := r.Cookie("session_token") + if err != nil { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + sessionToken := cookie.Value + username, valid := ValidateSession(sessionToken) + if !valid { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // Add username to request context + ctx := context.WithValue(r.Context(), usernameKey, username) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +func (u *UserPayload) Render(w http.ResponseWriter, r *http.Request) error { + return nil +} + +func hashPassword(password string) (string, error) { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + return string(hashedPassword), err +} + +func validatePassword(hashedPassword, password string) error { + return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) +} diff --git a/api/user.go b/api/user.go index c7fe832..47f57e2 100644 --- a/api/user.go +++ b/api/user.go @@ -7,7 +7,6 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/render" "github.com/google/uuid" - "golang.org/x/crypto/bcrypt" ) func UserCtx(next http.Handler) http.Handler { @@ -94,96 +93,6 @@ func NewUser(w http.ResponseWriter, r *http.Request) { render.Render(w, r, NewUserPayloadResponse(&newUser)) } -func Login(w http.ResponseWriter, r *http.Request) { - err := r.ParseMultipartForm(64 << 10) - if err != nil { - http.Error(w, "Unable to parse form", http.StatusBadRequest) - return - } - - username := r.FormValue("name") - password := r.FormValue("password") - if username == "" || password == "" { - http.Error(w, "Username and password cannot be empty", http.StatusBadRequest) - return - } - - user, err := dbGetUserByName(username) - if err != nil { - http.Error(w, "Invalid username or password", http.StatusUnauthorized) - return - } - - err = validatePassword(user.Password, password) - if err != nil { - http.Error(w, "Invalid username or password", http.StatusUnauthorized) - return - } - - sessionToken := CreateSession(username) - - http.SetCookie(w, &http.Cookie{ - Name: "session_token", - Value: sessionToken, - Path: "/", - HttpOnly: true, - Secure: false, - }) - - w.Write([]byte("Login successful")) -} - -var sessionStore = make(map[string]string) - -func CreateSession(username string) string { - sessionToken := uuid.New().String() - sessionStore[sessionToken] = username - return sessionToken -} - -func ValidateSession(sessionToken string) (string, bool) { - username, exists := sessionStore[sessionToken] - return username, exists -} - -type contextKey string - -const usernameKey contextKey = "username" - -func SessionAuthMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie("session_token") - if err != nil { - http.Error(w, "Unauthorized", http.StatusUnauthorized) - return - } - - sessionToken := cookie.Value - username, valid := ValidateSession(sessionToken) - if !valid { - http.Error(w, "Unauthorized", http.StatusUnauthorized) - return - } - - // Add username to request context - ctx := context.WithValue(r.Context(), usernameKey, username) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -func (u *UserPayload) Render(w http.ResponseWriter, r *http.Request) error { - return nil -} - -func hashPassword(password string) (string, error) { - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - return string(hashedPassword), err -} - -func validatePassword(hashedPassword, password string) error { - return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) -} - type userKey struct{} type User struct {