Эта страница переведена с английского. Читать страницу на английском
Безопасность в Zeyn объявляется рядом с кодом, который она защищает, и проверяется на сервере при каждом вызове. Значения по умолчанию строгие; ослабление любого из них заметно как отдельная строка в app.rs.
Разрешения
Каждое действие (action) и каждый экран (Screen) объявляют permission = "feature.resource.verb" или public. Без этого код не компилируется. Идентичность запроса несёт его разрешения:
Identity::user("ada").with_permissions(["stock.product.read", "stock.product.write"])Пункты меню, кнопки панели инструментов, действия строк и команды, которые пользователю нельзя выполнять, не попадают в HTML. Это лишь удобство; сервер проверяет разрешение заново при каждом вызове.
Вход в систему
Приложение не хранит пароли. Authenticator определяет, кто выполняет вызов, обычно по прокси единого входа (single sign on) перед приложением:
App::new(t!("app.name")).auth(
ForwardAuth::new("x-forwarded-user")
.groups("x-forwarded-groups")
.require_header("x-proxy-secret", secret.expose())
.role("stock-admins", ["stock.product.read", "stock.product.write"]),
)Используйте ForwardAuth только тогда, когда каждый запрос проходит через прокси: любой, кто может обратиться к приложению напрямую, способен отправить эти заголовки. require_header проверяет общий секрет, который добавляет прокси. DevAuth::superuser(name) даёт каждому запросу полный доступ для локальной разработки, и сервер выводит предупреждение, пока он включён. Релизная сборка отказывается запускаться с ним (App::check сообщает об этом); демонстрация, которая должна работать в релизе, может вызвать App::allow_dev_auth(). Приложения, созданные через zeyn new, включают его только в отладочных сборках.
Охранники (Guards)
Охранник выполняется до открытия любого экрана Feature и до вызова любой из его таблиц, форм или команд:
fn warehouse_open(ctx: &Ctx) -> GuardResult {
if ctx.identity().permissions.contains("stock.warehouse.read") { GuardResult::Allow } else { GuardResult::Deny(t!("stock.closed")) }
}
Feature::new("stock").guard(SignedIn).guard(warehouse_open)Встроенные guard-ы: SignedIn, TenantRequired и Licensed.
Перехватчики (Interceptors)
Перехватчик выполняется вокруг каждого вызова экрана, таблицы, формы, API и команды после проверок доступа. before может остановить вызов; after видит, чем он закончился. AuditLog(sink) встроенный перехватчик, который записывает, кто что сделал.
CSRF, CSP и заголовки
- Каждая страница устанавливает случайную cookie
zeyn_csrf(HttpOnly,SameSite=Strict) и помещает тот же токен в meta-тег. Каждый вызов из среды выполнения отправляет его в заголовкеx-zeyn-csrf, а сервер сравнивает оба значения за постоянное время. - Исключение составляют публичные экраны, открытые без сессии: они не устанавливают cookie и не несут токена, поэтому их можно кэшировать. Среда выполнения получает токен через
GET /_zeyn/csrfперед первым вызовом, и каждыйPOSTпо-прежнему проверяется так же (см. Публичные страницы). - Content Security Policy разрешает скрипты, стили и изображения только с собственного источника (origin) приложения. Встроенных скриптов нет; темы являются таблицами стилей, а не встроенными стилями.
App::cspоткрывает политику для указанных хостов, по одной директиве за раз. Заголовок строится один раз при запуске.
App::new("Shop").csp(|c| {
c.img_src(["https://images.example.com"])
.frame_src(["https://www.youtube-nocookie.com", "https://*.vimeo.com"])
.media_src(["https://media.example.com:8443"])
.media_blob()
.connect_src(["https://api.example.com"])
.font_src(["https://fonts.example.com"])
})Принимаются только https://host, https://host:port и https://*.host (подстановочный знак соответствует поддоменам, но не самому хосту). .media_blob() добавляет blob: в media-src для библиотек потоковой передачи. App::check отклоняет *, http:, голые схемы вроде data: или https:, ключевые слова в кавычках вроде 'unsafe-inline' или 'unsafe-eval', пути, имена пользователей и значения с пробелами, запятыми или точками с запятой; каждое сообщение об ошибке подсказывает, что написать вместо этого. Директивы script-src, object-src, base-uri и frame-ancestors изменить нельзя. Embed и Image читают те же списки, поэтому хост, который браузер заблокировал бы, никогда не отображается (см. Отображение).
- Ответы содержат
X-Content-Type-Options: nosniff,Referrer-Policy: same-originиX-Frame-Options: DENY. App::secure_cookies(true)помечает cookie какSecure; включайте это за HTTPS.
Экранирование
Пользовательские данные в HTML всегда проходят через render::escape. Компоненты экранируют каждое отображаемое значение, поэтому товар с названием <script> показывается как текст.
Лимиты
App::new(t!("app.name"))
.rate_limit(RateLimit::per_ip(600, Duration::from_secs(60)))
.write_limit(RateLimit::per_user(60, Duration::from_secs(60)))
.trusted_proxies(["10.0.0.0/8"])
.allow_only_ips(["10.0.0.0/8", "192.168.0.0/16"])
.deny_ips(["203.0.113.7"])rate_limitприменяется к каждому запросу, кроме статических ресурсов и чтения публичных медиаисточников (Feature::public_media);write_limitдобавляет более строгий лимит для вызовов, изменяющих данные: отправка форм, правка в таблицах, действия над строками, массовые действия и действия меню, команды, импорт, сохранённые представления таблиц, раскладки док-панелей и темы, вызовы API, вызовы MCP и ассистент. Отклонённые запросы получают429сRetry-After.X-Forwarded-Forпринимается на веру только отtrusted_proxies. Учитывается каждая строка заголовка по порядку, а берётся последний адрес, не являющийся доверенным прокси.- Раскрытия значений, вызовы островов (island) и источников данных тоже считаются записью, как и экспорт и печатные документы (
GET /_zeyn/grid/export,GET /_zeyn/pdf). - Размеры страниц, число строк, длина запросов и размеры загрузок ограничиваются на сервере независимо от того, что отправляет клиент.
Сессии
App::idle_timeout(d) завершает сеанс пользователя на странице после периода без ввода, предварительно показав диалог с предупреждением. Действия с пометкой reauth требуют недавнего входа (App::reauth_within).
Защита данных
Маскирование полей, указание причины перед открытием чувствительных записей, аудит на уровне полей и аудит чтения описаны в разделе Аудит, маскирование и задания.
API и агенты
Вызовы API требуют заголовка Authorization; одной cookie браузерной сессии никогда не хватает для вызова API или конечной точки MCP, поэтому CSRF-токен им не нужен. Агенты получают ровно те разрешения, что и пользователь, от имени которого они действуют. См. API и OpenAPI и MCP и ИИ-агенты.