mirror of
https://github.com/R0m1k3/FlowReader.git
synced 2026-10-11 17:28:05 +02:00
feat(story-1.4): add session-based authentication with login/logout
This commit is contained in:
1 parent
e186a4dd87
commit
d67e169a43
8 files changed
+446
-13
No files matched your search
+106
-1
@@ -1,10 +1,10 @@
|
||||
// Package handler contains HTTP handlers for the REST API.
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/michael/flowreader/internal/service"
|
||||
)
|
||||
@@ -45,6 +45,111 @@ func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
respondJSON(w, http.StatusCreated, resp)
|
||||
}
|
||||
|
||||
// Login handles POST /api/v1/auth/login
|
||||
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.LoginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, "Invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
// Add request metadata
|
||||
req.UserAgent = r.UserAgent()
|
||||
req.IPAddress = getClientIP(r)
|
||||
|
||||
resp, err := h.authService.Login(req)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrInvalidCredentials) {
|
||||
respondError(w, http.StatusUnauthorized, "Invalid email or password")
|
||||
} else {
|
||||
respondError(w, http.StatusInternalServerError, "Login failed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Set session cookie
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "session_id",
|
||||
Value: resp.Token,
|
||||
Path: "/",
|
||||
Expires: resp.ExpiresAt,
|
||||
HttpOnly: true,
|
||||
Secure: r.TLS != nil, // Secure only if HTTPS
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
})
|
||||
|
||||
respondJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// Logout handles POST /api/v1/auth/logout
|
||||
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
cookie, err := r.Cookie("session_id")
|
||||
if err != nil {
|
||||
respondJSON(w, http.StatusOK, map[string]string{"message": "Already logged out"})
|
||||
return
|
||||
}
|
||||
|
||||
_ = h.authService.Logout(cookie.Value)
|
||||
|
||||
// Clear the cookie
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "session_id",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
MaxAge: -1,
|
||||
HttpOnly: true,
|
||||
Secure: r.TLS != nil,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
})
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]string{"message": "Logged out successfully"})
|
||||
}
|
||||
|
||||
// Me handles GET /api/v1/users/me
|
||||
func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request) {
|
||||
cookie, err := r.Cookie("session_id")
|
||||
if err != nil {
|
||||
respondError(w, http.StatusUnauthorized, "Not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.authService.GetUserByToken(cookie.Value)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "Failed to get user")
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
respondError(w, http.StatusUnauthorized, "Session expired")
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, service.UserInfo{
|
||||
ID: user.ID,
|
||||
Email: user.Email,
|
||||
IsAdmin: user.IsAdmin,
|
||||
})
|
||||
}
|
||||
|
||||
// getClientIP extracts the client IP from the request.
|
||||
func getClientIP(r *http.Request) string {
|
||||
// Check X-Forwarded-For header
|
||||
forwarded := r.Header.Get("X-Forwarded-For")
|
||||
if forwarded != "" {
|
||||
// Take the first IP in the chain
|
||||
parts := strings.Split(forwarded, ",")
|
||||
return strings.TrimSpace(parts[0])
|
||||
}
|
||||
|
||||
// Check X-Real-IP header
|
||||
realIP := r.Header.Get("X-Real-IP")
|
||||
if realIP != "" {
|
||||
return realIP
|
||||
}
|
||||
|
||||
// Fall back to RemoteAddr
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
// respondJSON writes a JSON response.
|
||||
func respondJSON(w http.ResponseWriter, status int, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
Reference in new issue
Block a user