CRM переехала на shotam.tvorymo.com: allowlist origin (CRM_ORIGINS) для postMessage и frame-ancestors, стили CRM от корня
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
- Деплой: Coolify на сервере storage — env `COOLIFY_URL`, `COOLIFY_KEY` (через профиль «Storage coolify»).
|
||||
Деплоить через Coolify v4 REST API (скилл `deploying-to-coolify-via-api`), fire-and-forget.
|
||||
- Домен страницы: `https://test-crm.tes.gd` (HTTPS обязателен; DNS и домен на том же сервере).
|
||||
- CRM: `https://test.units.tvorymo.com`. Supabase: `https://yjxbkcqsquwbsyoonfkn.supabase.co`.
|
||||
- CRM: `https://shotam.tvorymo.com`, `https://units.tvorymo.com` (env `CRM_ORIGINS`). CSS/JS CRM — от корня: `/css/crm.css`, `/css/tvorymo-ui.css`, `/js/pagebridge.js`. Supabase: `https://yjxbkcqsquwbsyoonfkn.supabase.co`.
|
||||
- Publishable-ключ Supabase пришлёт Лана (секретарь владельца) напрямую — запиши его сюда, в раздел ниже (ключ публичный по дизайну).
|
||||
|
||||
## Supabase publishable key
|
||||
|
||||
@@ -5,8 +5,13 @@
|
||||
Supabase `GET /auth/v1/user` (+ `exp`, `aud = authenticated`) и показывает имя/email/id.
|
||||
|
||||
- Node 22, без зависимостей: `server.js` + `public/index.html`.
|
||||
- Заголовок `Content-Security-Policy: frame-ancestors *`, без `X-Frame-Options`.
|
||||
- Env: `SUPABASE_URL`, `SUPABASE_PUBLISHABLE_KEY`, `PORT` (по умолчанию 3000).
|
||||
- Разрешённые origin CRM — env `CRM_ORIGINS` (через запятую; по умолчанию
|
||||
`https://shotam.tvorymo.com,https://units.tvorymo.com`). Используются в
|
||||
`Content-Security-Policy: frame-ancestors …` (без `X-Frame-Options`) и как allowlist для `postMessage`:
|
||||
сообщения от других origin игнорируются (пишется в журнал).
|
||||
- Стили CRM берутся с `https://shotam.tvorymo.com/css/tvorymo-ui.css` и `/css/crm.css` (от корня;
|
||||
старые пути `/tvorymo-crm/css/...` дают 404).
|
||||
- Env: `SUPABASE_URL`, `SUPABASE_PUBLISHABLE_KEY`, `CRM_ORIGINS`, `PORT` (по умолчанию 3000).
|
||||
- Деплой: Coolify (Dockerfile), домен `https://test-crm.tes.gd`.
|
||||
|
||||
Подробности — `SPEC.md`.
|
||||
|
||||
6
SPEC.md
6
SPEC.md
@@ -12,13 +12,13 @@
|
||||
## Адреса
|
||||
|
||||
- Страница: `https://test-crm.tes.gd` (HTTPS обязателен).
|
||||
- CRM: `https://test.units.tvorymo.com`.
|
||||
- CRM: `https://shotam.tvorymo.com` и `https://units.tvorymo.com` (с 2026-10-16; `test.units.tvorymo.com` больше не используется — 403). Та же база Supabase.
|
||||
- Supabase: `https://yjxbkcqsquwbsyoonfkn.supabase.co`.
|
||||
|
||||
## Протокол (со стороны страницы)
|
||||
|
||||
1. После загрузки: `window.parent.postMessage({ type: "crm:ready", version: 1 }, "*")`.
|
||||
2. Принимать `crm:auth` `{ access_token, expires_at, user: { id, email, name } }` от любого origin (решение владельца для теста). Origin отправителя показывать на странице.
|
||||
2. Принимать `crm:auth` `{ access_token, expires_at, user: { id, email, name } }` только от разрешённых origin CRM (env `CRM_ORIGINS`, решение владельца 2026-10-16). Origin отправителя показывать на странице.
|
||||
3. `access_token` отправить на сервер страницы; сервер проверяет его через `GET /auth/v1/user` (заголовки `apikey: <publishable key>`, `Authorization: Bearer <token>`) и сверяет `exp`, `aud = "authenticated"`.
|
||||
4. Имя и email брать из ответа Supabase, не из `user` в сообщении (имя из `user_metadata`; если пусто, показать «имя не задано в Supabase» и email).
|
||||
5. Кнопка «Обновить токен»: отправляет `{ type: "crm:refresh" }`, CRM отвечает новым `crm:auth`; страница показывает, что токен сменился (время `expires_at`, без самого токена).
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
## Требования
|
||||
|
||||
- Заголовок `Content-Security-Policy: frame-ancestors *` (встраивание откуда угодно, для теста), без `X-Frame-Options`.
|
||||
- Заголовок `Content-Security-Policy: frame-ancestors <origin CRM>` (только разрешённые origin CRM), без `X-Frame-Options`.
|
||||
- Токен не логировать ни в браузере, ни на сервере. Сессию держать в памяти.
|
||||
- Publishable-ключ Supabase агент получает от Ланы и записывает в свой CLAUDE.md (ключ публичный по дизайну).
|
||||
- Без открытого токена в ответе страницы: показываем только имя, email, id, origin, `expires_at`.
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<!-- Стили CRM Tvorymo: сначала дизайн-система, затем стили самой CRM (так требует шапка crm.css). -->
|
||||
<link rel="stylesheet" href="https://test.units.tvorymo.com/tvorymo-crm/css/tvorymo-ui.css">
|
||||
<link rel="stylesheet" href="https://test.units.tvorymo.com/tvorymo-crm/css/crm.css">
|
||||
<link rel="stylesheet" href="https://shotam.tvorymo.com/css/tvorymo-ui.css">
|
||||
<link rel="stylesheet" href="https://shotam.tvorymo.com/css/crm.css">
|
||||
</head>
|
||||
<body class="side-off">
|
||||
<main id="main">
|
||||
@@ -151,6 +151,7 @@
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
const CRM_ORIGINS = __CRM_ORIGINS__;
|
||||
(() => {
|
||||
// Сессия только в памяти, токен не выводится и не логируется.
|
||||
let session = null; // { token, expiresAt }
|
||||
@@ -230,7 +231,10 @@
|
||||
window.addEventListener("message", (ev) => {
|
||||
const data = ev.data;
|
||||
if (!data || typeof data !== "object") return;
|
||||
// Для теста принимаем от любого origin (решение владельца).
|
||||
if (!CRM_ORIGINS.includes(ev.origin)) {
|
||||
if (data.type === "crm:auth" || data.type === "crm:theme") log(data.type + " от " + ev.origin + " — origin не разрешён, игнорирую");
|
||||
return;
|
||||
}
|
||||
if (data.type === "crm:auth") onAuth(data, ev);
|
||||
else if (data.type === "crm:theme") onTheme(data);
|
||||
});
|
||||
|
||||
@@ -7,11 +7,15 @@ const path = require("path");
|
||||
const PORT = Number(process.env.PORT || 3000);
|
||||
const SUPABASE_URL = (process.env.SUPABASE_URL || "").replace(/\/$/, "");
|
||||
const SUPABASE_KEY = process.env.SUPABASE_PUBLISHABLE_KEY || "";
|
||||
// Разрешённые origin CRM: встраивание (frame-ancestors) и приём postMessage.
|
||||
const CRM_ORIGINS = (process.env.CRM_ORIGINS || "https://shotam.tvorymo.com,https://units.tvorymo.com")
|
||||
.split(",").map((s) => s.trim().replace(/\/$/, "")).filter(Boolean);
|
||||
|
||||
const INDEX = fs.readFileSync(path.join(__dirname, "public", "index.html"));
|
||||
const INDEX = fs.readFileSync(path.join(__dirname, "public", "index.html"), "utf8")
|
||||
.replace("__CRM_ORIGINS__", JSON.stringify(CRM_ORIGINS).replace(/</g, "\\u003c"));
|
||||
|
||||
const BASE_HEADERS = {
|
||||
"Content-Security-Policy": "frame-ancestors *",
|
||||
"Content-Security-Policy": `frame-ancestors ${CRM_ORIGINS.join(" ")}`,
|
||||
"Cache-Control": "no-store",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user