package api import ( "crypto/subtle" "errors" "net/http" "os" "time" "github.com/mrleo1nid/pzmanager/internal/auth" ) // sessionTTL — сколько живёт сессия без активности. const sessionTTL = 12 * time.Hour type credentials struct { Login string `json:"login"` Password string `json:"password"` // Token нужен только при первичной настройке. Token string `json:"token,omitempty"` } // handleSession сообщает фронтенду, вошёл ли пользователь и нужна ли // первоначальная настройка (когда учёток ещё нет). func (s *Server) handleSession(w http.ResponseWriter, r *http.Request) { login, ok := s.users.SessionFromRequest(r) writeJSON(w, http.StatusOK, map[string]any{ "authenticated": ok, "login": login, "setup_needed": s.users.IsEmpty(), }) } // handleSetup создаёт первого администратора. Доступен, только пока учёток нет. func (s *Server) handleSetup(w http.ResponseWriter, r *http.Request) { if !s.users.IsEmpty() { writeError(w, http.StatusConflict, "панель уже настроена") return } var creds credentials if !decodeJSON(w, r, &creds) { return } // Токен печатается в журнал менеджера при старте: без него первым // администратором стал бы любой, кто успел открыть страницу. if s.setupToken == "" || subtle.ConstantTimeCompare([]byte(creds.Token), []byte(s.setupToken)) != 1 { writeError(w, http.StatusForbidden, "неверный код первичной настройки") return } if err := s.users.AddUser(creds.Login, creds.Password); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } id, err := s.users.Login(creds.Login, creds.Password) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } // Администратор создан — код больше не нужен и не должен лежать на диске. if s.setupTokenFile != "" { os.Remove(s.setupTokenFile) } s.setSessionCookie(w, id, sessionTTL) writeJSON(w, http.StatusOK, map[string]any{"authenticated": true, "login": creds.Login}) } func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) { var creds credentials if !decodeJSON(w, r, &creds) { return } id, err := s.users.Login(creds.Login, creds.Password) if err != nil { if errors.Is(err, auth.ErrBadCredentials) { writeError(w, http.StatusUnauthorized, err.Error()) return } writeError(w, http.StatusInternalServerError, err.Error()) return } s.setSessionCookie(w, id, sessionTTL) writeJSON(w, http.StatusOK, map[string]any{"authenticated": true, "login": creds.Login}) } func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) { if c, err := r.Cookie(auth.SessionCookie); err == nil { s.users.Logout(c.Value) } s.clearSessionCookie(w) writeJSON(w, http.StatusOK, map[string]any{"authenticated": false}) } func (s *Server) handleListUsers(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{"users": s.users.Logins()}) } func (s *Server) handleCreateUser(w http.ResponseWriter, r *http.Request) { var creds credentials if !decodeJSON(w, r, &creds) { return } if err := s.users.AddUser(creds.Login, creds.Password); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } writeJSON(w, http.StatusCreated, map[string]any{"login": creds.Login}) } func (s *Server) handleDeleteUser(w http.ResponseWriter, r *http.Request) { login := r.PathValue("login") current, _ := s.users.SessionFromRequest(r) if login == current { writeError(w, http.StatusBadRequest, "нельзя удалить учётную запись, под которой вы вошли") return } if err := s.users.DeleteUser(login); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"deleted": login}) } func (s *Server) handleChangePassword(w http.ResponseWriter, r *http.Request) { var body struct { Password string `json:"password"` } if !decodeJSON(w, r, &body) { return } if err := s.users.SetPassword(r.PathValue("login"), body.Password); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"ok": true}) }