da12a9ec1c
- bj_exchange и «кран» Fancy убраны: данные модуля ЛК в PostgreSQL (схема lk) - трансферные таблицы FTV_M2M: создание при установке, события, справочник участников - сверка с DEPO: счёт, ФИО, дата рождения, ИИС по договору, квал-инвестор из опции DEPO - боевой ЛК ЕСИА Финанс при тестовом ИШ: журнал, отказ через DEPO, статусы в ЛК - онлайн-лицензирование в bj, план тестирования M2M для контрагента (интерактивный HTML) - модули QUIK МНП, экспорт QUIK, НПР/УДС; документы и тестовые материалы Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
87 lines
3.7 KiB
Go
87 lines
3.7 KiB
Go
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
|
|
}
|
|
c, err := r.Cookie(advancedCookie)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
want := advancedToken()
|
|
return subtle.ConstantTimeCompare([]byte(c.Value), []byte(want)) == 1
|
|
}
|
|
|
|
// requireAdvanced — защита ручек сохранения перспективных модулей: если доступ
|
|
// не открыт, отвечает 403. true — можно продолжать.
|
|
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, "Перспективные модули снова скрыты")
|
|
}
|