live-sfu-demo/internal/server/auth_handler.go

248 lines
7.0 KiB
Go

package server
import (
"encoding/json"
"net/http"
"time"
"gospeak-live-sfu-demo/internal/auth"
)
type authRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Role string `json:"role,omitempty"`
}
type authResponse struct {
Token string `json:"token"`
Username string `json:"username"`
Role string `json:"role"`
ExpiresAt int64 `json:"expires_at,omitempty"`
}
func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
if s.auth == nil {
http.Error(w, "auth not configured", http.StatusInternalServerError)
return
}
var req authRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "bad request: "+err.Error(), http.StatusBadRequest)
return
}
if req.Username == "" || req.Password == "" {
http.Error(w, "username and password required", http.StatusBadRequest)
return
}
token, user, err := s.auth.Login(req.Username, req.Password)
if err != nil {
http.Error(w, "login failed: "+err.Error(), http.StatusUnauthorized)
return
}
http.SetCookie(w, &http.Cookie{
Name: "token",
Value: token,
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
Expires: time.Now().Add(s.auth.JWT.TTL()),
})
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(authResponse{
Token: token,
Username: user.Username,
Role: user.Role,
ExpiresAt: time.Now().Add(s.auth.JWT.TTL()).Unix(),
})
}
func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
if s.auth == nil {
http.Error(w, "auth not configured", http.StatusInternalServerError)
return
}
if !s.cfg.AllowRegister {
http.Error(w, "registration disabled", http.StatusForbidden)
return
}
var req authRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "bad request: "+err.Error(), http.StatusBadRequest)
return
}
if req.Username == "" || req.Password == "" {
http.Error(w, "username and password required", http.StatusBadRequest)
return
}
role := req.Role
if role == "" {
role = auth.RoleViewer
}
if role == auth.RoleAdmin || role == auth.RolePublisher {
token := extractAuthToken(r)
if token != "" {
if claims, _, err := s.auth.VerifyToken(token); err == nil && claims.Role == auth.RoleAdmin {
} else {
role = auth.RoleViewer
}
} else {
role = auth.RoleViewer
}
}
token, user, err := s.auth.Register(req.Username, req.Password, role)
if err != nil {
http.Error(w, "register failed: "+err.Error(), http.StatusBadRequest)
return
}
http.SetCookie(w, &http.Cookie{
Name: "token",
Value: token,
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
Expires: time.Now().Add(s.auth.JWT.TTL()),
})
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(authResponse{
Token: token,
Username: user.Username,
Role: user.Role,
ExpiresAt: time.Now().Add(s.auth.JWT.TTL()).Unix(),
})
}
func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{
Name: "token",
Value: "",
Path: "/",
HttpOnly: true,
MaxAge: -1,
})
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "logged out"})
}
func (s *Server) handleMe(w http.ResponseWriter, r *http.Request) {
if s.auth == nil {
http.Error(w, "auth not configured", http.StatusInternalServerError)
return
}
token := extractAuthToken(r)
if token == "" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
claims, user, err := s.auth.VerifyToken(token)
if err != nil {
http.Error(w, "invalid token: "+err.Error(), http.StatusUnauthorized)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"username": user.Username,
"role": user.Role,
"expires_at": claims.ExpiresAt.Unix(),
"issued_at": claims.IssuedAt.Unix(),
})
}
func (s *Server) handleListUsers(w http.ResponseWriter, r *http.Request) {
if s.auth == nil {
http.Error(w, "auth not configured", http.StatusInternalServerError)
return
}
token := extractAuthToken(r)
claims, _, err := s.auth.VerifyToken(token)
if err != nil || claims.Role != auth.RoleAdmin {
http.Error(w, "forbidden: admin only", http.StatusForbidden)
return
}
users := s.auth.Store.List()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{"users": users})
}
func (s *Server) handleUpdateRole(w http.ResponseWriter, r *http.Request) {
if s.auth == nil {
http.Error(w, "auth not configured", http.StatusInternalServerError)
return
}
token := extractAuthToken(r)
claims, _, err := s.auth.VerifyToken(token)
if err != nil || claims.Role != auth.RoleAdmin {
http.Error(w, "forbidden: admin only", http.StatusForbidden)
return
}
var req struct {
Username string `json:"username"`
Role string `json:"role"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "bad request: "+err.Error(), http.StatusBadRequest)
return
}
if req.Username == "" || req.Role == "" {
http.Error(w, "username and role required", http.StatusBadRequest)
return
}
if err := s.auth.UpdateUserRole(req.Username, req.Role); err != nil {
http.Error(w, "update failed: "+err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "ok", "username": req.Username, "role": req.Role})
}
func (s *Server) handleAuthCheck(w http.ResponseWriter, r *http.Request) {
if s.auth == nil {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{"enabled": false})
return
}
token := extractAuthToken(r)
if token == "" {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{"enabled": true, "authenticated": false, "role": auth.RoleGuest})
return
}
claims, user, err := s.auth.VerifyToken(token)
if err != nil {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{"enabled": true, "authenticated": false, "error": err.Error()})
return
}
perms := map[string]bool{}
for _, act := range []string{"list", "publish", "subscribe", "watch", "stop"} {
ok, _ := s.auth.Check(claims.Username, claims.Role, "room", act)
perms["room:"+act] = ok
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"enabled": true,
"authenticated": true,
"username": user.Username,
"role": user.Role,
"permissions": perms,
})
}
func extractAuthToken(r *http.Request) string {
if h := r.Header.Get("Authorization"); h != "" {
if len(h) > 7 && (h[:7] == "Bearer " || h[:7] == "bearer ") {
return h[7:]
}
}
if c, err := r.Cookie("token"); err == nil && c.Value != "" {
return c.Value
}
if q := r.URL.Query().Get("token"); q != "" {
return q
}
if h := r.Header.Get("X-Token"); h != "" {
return h
}
return ""
}