package lkgateway import ( "crypto/sha256" "crypto/subtle" "encoding/hex" "net/http" "os" ) // advanced_gate.go — пароль на «перспективные модули» (QUIK МНП, Обмен Fancy). // Эти модули относятся к будущим профилям («автомат/полуавтомат» со своим ЛК) и // пока не в проде. Чтобы оператор их случайно не трогал, они скрыты и // открываются паролем. Простая cookie-схема: значение cookie = токен пароля, // подделать без пароля нельзя. Пароль берётся из BJ_ADVANCED_PASSWORD, иначе — // значение по умолчанию. const defaultAdvancedPassword = "bj-modules-2026" const advancedCookie = "bj_adv" func effectiveAdvancedPassword() string { if p := os.Getenv("BJ_ADVANCED_PASSWORD"); p != "" { return p } return defaultAdvancedPassword } // advancedToken — неподделываемый токен для cookie: hex(sha256(пароль)). func advancedToken() string { sum := sha256.Sum256([]byte("bj-adv:" + effectiveAdvancedPassword())) return hex.EncodeToString(sum[:]) } // advancedUnlocked — открыт ли доступ к перспективным модулям в этом запросе. func advancedUnlocked(r *http.Request) bool { // Модуль QUIK МНП (обмен Fancy) есть в лицензии — разделы открыты без пароля: пароль нужен // только там, где модули не куплены. Иначе кнопки разделов видны, а настройки пустые. if globalRC != nil && moduleAllowed(globalRC, "quik-mnp") { return true } if !devMode(globalRC) { // пароль на модули — инструмент разработчиков return false } c, err := r.Cookie(advancedCookie) if err != nil { return false } want := advancedToken() return subtle.ConstantTimeCompare([]byte(c.Value), []byte(want)) == 1 } // requireAdvanced — защита ручек сохранения перспективных модулей: если доступ // не открыт, отвечает 403. true — можно продолжать. // requireDepo — настройки обмена с DEPO: открыты, если модуль «QUIK: неторговые поручения» // входит в лицензию (мастер настройки, шаг 3), иначе — как перспективный модуль, по паролю. func requireDepo(w http.ResponseWriter, r *http.Request) bool { if globalRC != nil && moduleAllowed(globalRC, "quik-mnp") { return true } return requireAdvanced(w, r) } func requireAdvanced(w http.ResponseWriter, r *http.Request) bool { if advancedUnlocked(r) { return true } http.Error(w, "Раздел «Перспективные модули» закрыт. Откройте его паролем в Настройке.", http.StatusForbidden) return false } // unlockAdvanced — POST /admin/setup/advanced-unlock: проверяет пароль и ставит // cookie доступа. func (h *setupHandlers) unlockAdvanced(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method", http.StatusMethodNotAllowed) return } if r.FormValue("password") != effectiveAdvancedPassword() { setupFlash(w, r, "Перспективные модули: неверный пароль") return } http.SetCookie(w, &http.Cookie{ Name: advancedCookie, Value: advancedToken(), Path: "/admin", HttpOnly: true, SameSite: http.SameSiteLaxMode, MaxAge: 30 * 24 * 3600, }) setupFlash(w, r, "Перспективные модули открыты (QUIK, Обмен Fancy). Разделы появились в меню слева.") } // lockAdvanced — POST /admin/setup/advanced-lock: снова прячет модули. func (h *setupHandlers) lockAdvanced(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method", http.StatusMethodNotAllowed) return } http.SetCookie(w, &http.Cookie{ Name: advancedCookie, Value: "", Path: "/admin", HttpOnly: true, MaxAge: -1, }) setupFlash(w, r, "Перспективные модули снова скрыты") }