Пароль домена не попадает в страницу; домен в мастере — открыт, крупно, группы; справочник DEPO без переводов; 1С — бэк-офис
- Безопасность: в мастере подпись к паролю выводила сам сохранённый пароль служебной записи домена — теперь только «задан/введён» (тест: пароль не попадает в HTML). Список сессий в настройках больше не содержит токены (по ним можно было войти под чужим именем) — короткий ID. - Домен: после подключения и «Доверять» блок остаётся открытым, страница возвращается к нему; карточка «Домен подключён» и «Найден домен» — крупно; поиск сотрудников и групп; группе — доступ с ролью (ADGroups), список групп с «убрать». - «Обмен с DEPO»: справочник FTV_M2M_HANDBOOK считается и когда переводов ещё нет (на свежей установке писало «не создана»). - Модуль 1С: onec-back «1С: база бэк-офиса (тест)» вместо мидл-офиса (в лицензиях не выдавался). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -15,6 +15,7 @@ package lkgateway
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
@@ -479,20 +480,45 @@ func (s *sessionStore) DropUser(login string) {
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *sessionStore) Active() []*Session {
|
||||
// SessionView — сессия для списка в настройках: без токена (по нему можно войти под чужим именем), вместо него — ID.
|
||||
type SessionView struct {
|
||||
ID, Login, IP, UA string
|
||||
Created, Seen time.Time
|
||||
}
|
||||
|
||||
// sessionID — короткий отпечаток токена: им список ссылается на сессию, сам токен в страницу не попадает.
|
||||
func sessionID(token string) string {
|
||||
sum := sha256.Sum256([]byte("bj-session-id:" + token))
|
||||
return hex.EncodeToString(sum[:9])
|
||||
}
|
||||
|
||||
func (s *sessionStore) Active() []SessionView {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
out := make([]*Session, 0, len(s.list))
|
||||
for _, x := range s.list {
|
||||
out := make([]SessionView, 0, len(s.list))
|
||||
for t, x := range s.list {
|
||||
if time.Since(x.Created) < sessionMaxLife {
|
||||
cp := *x
|
||||
out = append(out, &cp)
|
||||
out = append(out, SessionView{ID: sessionID(t), Login: x.Login, IP: x.IP, UA: x.UA, Created: x.Created, Seen: x.Seen})
|
||||
}
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Seen.After(out[j].Seen) })
|
||||
return out
|
||||
}
|
||||
|
||||
// DropID — завершить сессию по её ID из списка.
|
||||
func (s *sessionStore) DropID(id string) bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for t := range s.list {
|
||||
if id != "" && sessionID(t) == id {
|
||||
delete(s.list, t)
|
||||
s.save()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ---------- защита от перебора ----------
|
||||
|
||||
var loginFails = struct {
|
||||
@@ -910,12 +936,12 @@ func moduleForPath(p string) string {
|
||||
case p == "/admin/setup/lk", has("/admin/setup/lk-check"), has("/admin/setup/lk-integration"), has("/admin/setup/lk-pull"):
|
||||
return "esia-finance" // интеграция с платформой ЕСИА Финанс — отдельный модуль лицензии
|
||||
case has("/admin/setup/onec"):
|
||||
return "onec-middle"
|
||||
return "onec-back"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var moduleTitles = map[string]string{"esia-finance": "ЕСИА Финанс", "quik-mnp": "QUIK: неторговые поручения и обмен с DEPO", "quik-export": "Экспорт биржевой информации QUIK", "web-cabinet": "Личный кабинет клиента", "npr": "Риск-монитор НПР / УДС", "onec-middle": "1С: база мидл-офиса"}
|
||||
var moduleTitles = map[string]string{"esia-finance": "ЕСИА Финанс", "quik-mnp": "QUIK: неторговые поручения и обмен с DEPO", "quik-export": "Экспорт биржевой информации QUIK", "web-cabinet": "Личный кабинет клиента", "npr": "Риск-монитор НПР / УДС", "onec-back": "1С: база бэк-офиса"}
|
||||
|
||||
// moduleTitle — признак лицензии словами (в мастере и на странице лицензии — не коды вроде quik-mnp).
|
||||
func moduleTitle(f string) string {
|
||||
@@ -1186,7 +1212,7 @@ type UsersView struct {
|
||||
Roles []roleDef // доступные при текущей лицензии
|
||||
Hidden []struct{ Title, Missing string } // роли, недоступные без модулей
|
||||
Modules []struct{ Key, Title string } // подключённые модули
|
||||
Sessions []*Session
|
||||
Sessions []SessionView
|
||||
Open bool
|
||||
Me *User
|
||||
ADRecheck ADRecheckStatus
|
||||
@@ -1373,7 +1399,7 @@ func (h *setupHandlers) dropSession(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
globalSessions.Drop(r.FormValue("token"))
|
||||
globalSessions.DropID(r.FormValue("id"))
|
||||
h.usersFlash(w, r, "Сессия завершена.")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user