package lkgateway // lkusers_admin.go — учётные записи кабинета клиента: управление из настроек ядра и // первый администратор из веб-установщика кабинета. // // Пользователи кабинета живут только здесь, в lk.users: кабинет на хостинге своей // базы не имеет. Здесь заводят клиентов и сотрудников, меняют пароли, блокируют. // Письма никому не отправляются: почта хранится, но рассылка выключена — данные // пока тестовые, и случайное письмо реальному человеку недопустимо. import ( "context" "database/sql" "errors" "fmt" "net/http" "strconv" "strings" "time" ) // globalLKSessions — сессии кабинета: нужны, чтобы гасить их при смене пароля и блокировке. var globalLKSessions *lkSessions // lkHasAdmin — есть ли хотя бы один администратор кабинета. func lkHasAdmin(ctx context.Context, rc *RuntimeConfig) (bool, error) { db, err := lkDB(rc) if err != nil { return false, err } cctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() var n int if err := db.QueryRowContext(cctx, `SELECT count(*) FROM lk.users WHERE is_admin AND active`).Scan(&n); err != nil { return false, err } return n > 0, nil } // lkUserSetFlags — включить/выключить учётную запись, права сотрудника, подачу поручений. func lkUserSetFlags(ctx context.Context, rc *RuntimeConfig, id int64, column string, value bool) error { switch column { case "active", "is_admin", "can_transfer": default: return errors.New("неизвестный признак") } db, err := lkDB(rc) if err != nil { return err } cctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() res, err := db.ExecContext(cctx, `UPDATE lk.users SET `+column+`=$1 WHERE id=$2`, value, id) if err != nil { return err } if n, _ := res.RowsAffected(); n == 0 { return errors.New("учётная запись не найдена") } if globalLKSessions != nil { globalLKSessions.forget(id) if !value && column == "active" { globalLKSessions.dropUser(id) // заблокированного выбрасываем сразу } } return nil } // lkUserUpdate — контакты и договор. func lkUserUpdate(ctx context.Context, rc *RuntimeConfig, id int64, contract, fullName, email, phone string) error { if strings.TrimSpace(contract) == "" { return errors.New("номер договора обязателен") } db, err := lkDB(rc) if err != nil { return err } cctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() _, err = db.ExecContext(cctx, `UPDATE lk.users SET contract_number=$1, full_name=$2, email=$3, phone=$4 WHERE id=$5`, strings.TrimSpace(contract), strings.TrimSpace(fullName), strings.TrimSpace(email), strings.TrimSpace(phone), id) if err == nil && globalLKSessions != nil { globalLKSessions.forget(id) } return err } // lkUserDelete — удалить учётную запись. Поручения и журнал остаются: они привязаны к договору. func lkUserDelete(ctx context.Context, rc *RuntimeConfig, id int64) error { db, err := lkDB(rc) if err != nil { return err } cctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() if _, err := db.ExecContext(cctx, `DELETE FROM lk.users WHERE id=$1`, id); err != nil { return err } if globalLKSessions != nil { globalLKSessions.dropUser(id) } return nil } // lkUserByLogin — учётная запись по логину (для смены пароля из настроек). func lkUserIDByLogin(ctx context.Context, rc *RuntimeConfig, login string) (int64, error) { db, err := lkDB(rc) if err != nil { return 0, err } cctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() var id int64 if err := db.QueryRowContext(cctx, `SELECT id FROM lk.users WHERE login=$1`, login).Scan(&id); err != nil { if errors.Is(err, sql.ErrNoRows) { return 0, errors.New("учётная запись не найдена") } return 0, err } return id, nil } // lkUsersSave — POST /admin/setup/lk/users: заведение, пароль, признаки, удаление. func (h *setupHandlers) lkUsersSave(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method", http.StatusMethodNotAllowed) return } ctx := r.Context() id, _ := strconv.ParseInt(r.FormValue("id"), 10, 64) var err error msg := "Пользователи кабинета: сохранено" switch op := r.FormValue("op"); op { case "create": u := LKUser{ Login: strings.TrimSpace(r.FormValue("login")), Contract: strings.TrimSpace(r.FormValue("contract")), FullName: strings.TrimSpace(r.FormValue("full_name")), Email: strings.TrimSpace(r.FormValue("email")), Phone: strings.TrimSpace(r.FormValue("phone")), Active: true, IsAdmin: r.FormValue("is_admin") == "1", CanTransfer: r.FormValue("can_transfer") == "1", } if u.IsAdmin && u.Contract == "" { u.Contract = "ADMIN" // у сотрудника своего договора нет } err = lkCreateUser(ctx, h.rc, u, r.FormValue("password")) msg = fmt.Sprintf("Учётная запись «%s» создана. Пароль сообщите пользователю лично: письма кабинет не отправляет.", u.Login) case "password": login := strings.TrimSpace(r.FormValue("login")) if err = lkSetPassword(ctx, h.rc, login, r.FormValue("password")); err == nil { if uid, e := lkUserIDByLogin(ctx, h.rc, login); e == nil && globalLKSessions != nil { globalLKSessions.dropUser(uid) // старые сессии после смены пароля не живут } msg = fmt.Sprintf("Пароль «%s» изменён, его сессии завершены.", login) } case "update": err = lkUserUpdate(ctx, h.rc, id, r.FormValue("contract"), r.FormValue("full_name"), r.FormValue("email"), r.FormValue("phone")) case "block", "unblock": err = lkUserSetFlags(ctx, h.rc, id, "active", op == "unblock") msg = map[bool]string{true: "Учётная запись разблокирована", false: "Учётная запись заблокирована, сессии завершены"}[op == "unblock"] case "admin_on", "admin_off": err = lkUserSetFlags(ctx, h.rc, id, "is_admin", op == "admin_on") case "transfer_on", "transfer_off": err = lkUserSetFlags(ctx, h.rc, id, "can_transfer", op == "transfer_on") case "delete": err = lkUserDelete(ctx, h.rc, id) msg = "Учётная запись удалена" default: err = errors.New("неизвестное действие") } if err != nil { setupFlash(w, r, "Пользователи кабинета: "+err.Error()) return } setupFlash(w, r, msg) } // lkUsersForSetup — список для страницы настроек; ошибка базы — строкой, чтобы страница открылась. func lkUsersForSetup(rc *RuntimeConfig) ([]LKUser, string) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() list, err := lkListUsers(ctx, rc) if err != nil { return nil, err.Error() } return list, "" }