e499ce884d
- Go backend: config, db (PostgreSQL migrations), auth (argon2id + sessions), middleware (cookie sessions, RequireAuth), chi router, cmd/server - React frontend: Vite + React 18 + TS + Tailwind + Gruvbox palette + terminal CSS - Docker: compose.yml (Postgres, Valkey, minio, LiveKit, coturn, Caddy), livekit.yaml, Caddyfile, Dockerfile (multi-stage Go+Node+Alpine) - .env.example, .gitignore, README.md
162 lines
3.9 KiB
Go
162 lines
3.9 KiB
Go
package auth
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"git.dustin.coffee/hobokenchicken/dumpsterChat/internal/config"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type Handler struct {
|
|
db *sql.DB
|
|
cfg *config.Config
|
|
sessions *SessionStore
|
|
}
|
|
|
|
func NewHandler(db *sql.DB, cfg *config.Config) *Handler {
|
|
return &Handler{
|
|
db: db,
|
|
cfg: cfg,
|
|
sessions: NewSessionStore(db, cfg),
|
|
}
|
|
}
|
|
|
|
func (h *Handler) RegisterRoutes(r chi.Router) {
|
|
r.Post("/register", h.Register)
|
|
r.Post("/login/password", h.Login)
|
|
r.Post("/logout", h.Logout)
|
|
r.Get("/me", h.Me)
|
|
}
|
|
|
|
type registerRequest struct {
|
|
Email string `json:"email"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
type loginRequest struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
func (h *Handler) Register(w http.ResponseWriter, r *http.Request) {
|
|
var req registerRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if req.Email == "" || req.Username == "" || req.Password == "" {
|
|
http.Error(w, `{"error":"missing fields"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
hash, err := HashPassword(req.Password)
|
|
if err != nil {
|
|
http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var userID string
|
|
err = h.db.QueryRowContext(r.Context(), `
|
|
INSERT INTO users (username, email, password_hash)
|
|
VALUES ($1, $2, $3)
|
|
RETURNING id
|
|
`, req.Username, req.Email, hash).Scan(&userID)
|
|
if err != nil {
|
|
http.Error(w, `{"error":"user exists"}`, http.StatusConflict)
|
|
return
|
|
}
|
|
|
|
token, err := h.sessions.Create(r.Context(), userID)
|
|
if err != nil {
|
|
http.Error(w, `{"error":"session error"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: h.cfg.Session.CookieName,
|
|
Value: token,
|
|
Path: "/",
|
|
HttpOnly: true,
|
|
Secure: true,
|
|
SameSite: http.SameSiteStrictMode,
|
|
MaxAge: int(h.cfg.Session.Duration.Seconds()),
|
|
})
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{"id": userID})
|
|
}
|
|
|
|
func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
|
|
var req loginRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var userID, hash string
|
|
err := h.db.QueryRowContext(r.Context(), `
|
|
SELECT id, password_hash FROM users WHERE email = $1
|
|
`, req.Email).Scan(&userID, &hash)
|
|
if err != nil {
|
|
http.Error(w, `{"error":"invalid credentials"}`, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
ok, err := VerifyPassword(req.Password, hash)
|
|
if err != nil || !ok {
|
|
http.Error(w, `{"error":"invalid credentials"}`, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
token, err := h.sessions.Create(r.Context(), userID)
|
|
if err != nil {
|
|
http.Error(w, `{"error":"session error"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: h.cfg.Session.CookieName,
|
|
Value: token,
|
|
Path: "/",
|
|
HttpOnly: true,
|
|
Secure: true,
|
|
SameSite: http.SameSiteStrictMode,
|
|
MaxAge: int(h.cfg.Session.Duration.Seconds()),
|
|
})
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{"id": userID})
|
|
}
|
|
|
|
func (h *Handler) Logout(w http.ResponseWriter, r *http.Request) {
|
|
cookie, err := r.Cookie(h.cfg.Session.CookieName)
|
|
if err == nil {
|
|
h.sessions.Delete(r.Context(), cookie.Value)
|
|
}
|
|
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: h.cfg.Session.CookieName,
|
|
Value: "",
|
|
Path: "/",
|
|
MaxAge: -1,
|
|
HttpOnly: true,
|
|
})
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *Handler) Me(w http.ResponseWriter, r *http.Request) {
|
|
// Placeholder; requires session middleware integration
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{"id": ""})
|
|
}
|
|
|
|
var _ = uuid.New()
|
|
var _ = errors.New("placeholder")
|