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 _, c := range []struct { key, obj, act string }{ {"room:list", "room", "list"}, {"room:publish", "room", "publish"}, {"room:subscribe", "room", "subscribe"}, {"room:watch", "room", "watch"}, {"room:stop", "room", "stop"}, {"room:chat", "room", "chat"}, {"user:list", "user", "list"}, {"user:manage", "user", "manage"}, {"config:read", "config", "read"}, {"srs:streams", "srs", "streams"}, } { ok, _ := s.auth.Check(claims.Username, claims.Role, c.obj, c.act) perms[c.key] = 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 "" }