/ REMP integrace Interní dokument

05 — Identita: EGO-SSO (Ecoidentita) × REMP × CMS

Pojmenování: Systém se oficiálně jmenuje EGO-SSO (viz Implementace EGO-SSO pro klienty). V kódu a dokumentaci projektu bývá označován také jako Ecoidentita nebo ECO Identita — jedná se o tentýž systém. Implementace do Folio CMS je vedena v CMS-353, větev CMS-353_Ego_identita.

Problém

Tři systémy mají vlastní uživatelskou databázi:

  1. Folio CMSFolio::User (PostgreSQL), autentizace přes Devise + EGO-SSO callback flow
  2. REMP CRMcrm_users (MySQL), vlastní auth, API tokeny
  3. EGO-SSO (Ecoidentita) — SSO provider, master identita

Potřebujeme zajistit, aby čtenář měl jednu identitu napříč všemi systémy, bez duplikace a s konzistentním stavem.

SSO providery v Economia CMS

Typ uživateleSSO providerPoznámka
Čtenář (koncový zákazník)EGO-SSO (Ecoidentita)SSO redirect + userinfo flow, ucet.centrum.cz
Admin / redaktor (zaměstnanec)Google SSOGoogle Workspace Economia

Ke kolizi SSO providerů nedochází — každý typ uživatele má právě jednoho providera. Mailer Economia::DeviseMailer#omniauth_conflict existuje v kódu, ale v praxi se neuplatní.


Stávající implementace EGO-SSO (z větve CMS-353_Ego_identita)

Důležité: EGO-SSO není klasický OAuth2 Authorization Code flow. Je to proprietární SSO řešení provozované na doméně centrum.cz (produkce) / mailkafe.cz (dev).

Architektura

                    ┌──────────────────────┐
                    │    EGO-SSO           │
                    │  ucet.centrum.cz     │
                    │  master user: uuid   │
                    └──────────┬───────────┘

                    ┌──────────┴───────────┐
                    │                      │
                    ▼                      ▼
           ┌───────────────┐     ┌──────────────┐
           │   Folio CMS   │     │   REMP CRM   │
           │ Folio::User   │     │  crm_users   │
           │ id: 42        │     │  id: 99      │
           │ eco_id: uuid  │◄───►│  ext_id: uuid│
           │ remp_id: 99   │     │              │
           └───────────────┘     └──────────────┘

Klíčové komponenty v CMS

KomponentaSouborPopis
EgoIdentitaAuthenticatorapp/services/economia/ego_identita_authenticator.rbService: sign-in URL, sign-out URL, user-info volání
OmniauthCallbacksControllerapp/controllers/economia/folio/users/omniauth_callbacks_controller.rbZpracování EGO callback → OmniAuth hash → Devise sign-in
SessionsControllerapp/controllers/economia/folio/users/sessions_controller.rbSign-in redirect na EGO, sign-out s EGO kolečkem

Princip

  • EGO uuid je kanonický identifikátor (v kódu uid z userinfo response)
  • Folio ukládá eco_user_id (= EGO uuid) a remp_user_id (REMP CRM PK)
  • REMP CRM ukládá ext_id = EGO uuid
  • Při prvním přihlášení se uživatel vytvoří v obou systémech

Flow: Přihlášení čtenáře (produkce, aktualne.cz)

Rekonstruováno z implementace v CMS-353_Ego_identita:

1. Čtenář klikne "Přihlásit se"
   │  → SessionsController#egoidentita_sign_in
   │  → uloží referrer do session

2. Redirect na EGO-SSO authorize
   │  https://ucet.centrum.cz/auth/authorize
   │    ?client_id=<EGO_IDENTITA_CLIENT_ID>
   │    &redirect_uri=https://www.hn.cz/users/auth/egoidentita/callback
   │    &state=not_used_yet

3. Čtenář se přihlásí na ucet.centrum.cz

4. Řetězec redirectů (nastavuje cookies):
   │  a) ucet.centrum.cz → Set-Cookie: EGO_SESS_ID (domain=.centrum.cz)
   │  b) sso.centrum.cz  → Set-Cookie: EGO_APROFILE (domain=.centrum.cz)
   │  c) sso.hn.cz       → Set-Cookie: EGO_APROFILE (domain=.hn.cz) ← KLÍČOVÝ KROK
   │  d) www.hn.cz/users/auth/egoidentita/callback

5. OmniauthCallbacksController#egoidentita
   │  → EgoIdentitaAuthenticator.user_info(cookies)
   │  → POST https://userinfo.centrum.cz/userinfo
   │    {scope, profile: EGO_APROFILE, session: EGO_SESS_ID,
   │     client_id, client_secret}

6. UserInfo response:
   │  {uuid, email, firstname, lastname, avatar, subscriber, ego_id}

7. Build OmniAuth hash → bind_user_and_redirect
   │  → find_or_create Folio::User (eco_user_id: uuid)
   │  → Devise sign_in

8. (NOVÉ pro REMP) Background job: SyncUserToRempJob
   │  POST REMP CRM /api/v1/users/register
   │  {email, ext_id: uuid, first_name, last_name}

9. Session established, redirect na uloženou stránku

Toto je klíčový mechanismus. EGO-SSO propaguje cookies na cílovou doménu přes SSO redirect:

  1. EGO SSO server zná seznam registrovaných domén (hn.cz, aktualne.cz, zena.cz…)
  2. Po úspěšném přihlášení provede redirect přes sso.<domena> pro každou doménu
  3. Na sso.hn.cz (Cloudflare Worker nebo jiný edge endpoint) se nastaví cookie EGO_APROFILE s domain=.hn.cz
  4. Folio CMS na www.hn.cz pak cookie přečte v Rails controlleru

⚠️ Otevřená otázka pro HN.cz: Je sso.hn.cz endpoint nasazen? Na aktualne.cz je sso.aktualne.cz funkční. Pro HN.cz je potřeba ověřit s Economia DevOps, zda:

  • existuje CF Worker / endpoint na sso.hn.cz
  • je nakonfigurován pro propagaci EGO cookies
  • callback URL https://www.hn.cz/users/auth/egoidentita/callback je registrován v EGO-SSO

Flow: Odhlášení

1. Čtenář klikne "Odhlásit se"
   │  → SessionsController#destroy

2. Pokud has_egoidentita_authentication? && EGO cookies přítomny:
   │  → Redirect na EGO logout
   │  https://ucet.centrum.cz/logout
   │    ?url=https://www.hn.cz/users/sign_out

3. EGO SSO smaže cookies přes redirect kolečko
   │  (obdobně jako při přihlášení, přes sso.hn.cz)

4. Finální redirect na /users/sign_out
   │  → Devise sign_out
   │  → SessionsController#after_sign_out → redirect na původní stránku

Flow: Změna e-mailu

1. Čtenář změní e-mail → EGO-SSO je source of truth
2. Při dalším přihlášení userinfo vrátí nový e-mail
3. Folio::User aktualizuje e-mail z userinfo response
4. Background job: sync nový e-mail do REMP CRM

EGO-SSO je source of truth pro e-mail, jméno, příjmení. CMS se synchronizuje při každém přihlášení z userinfo response.


Datový model

Folio::User (rozšíření)

# Stávající pole (z Devise / OmniAuth)
# email, encrypted_password, first_name, last_name, ...

# Nová pole pro REMP integraci
add_column :folio_users, :eco_user_id, :string, index: { unique: true }
add_column :folio_users, :remp_user_id, :integer, index: { unique: true }
add_column :folio_users, :remp_synced_at, :datetime

REMP CRM User

-- V REMP CRM (MySQL)
-- Stávající tabulka crm_users
-- Klíčová pole:
-- id (auto-increment)
-- email (unique)
-- ext_id (VARCHAR, indexed) -- = eco_user_id

Mapování polí

Folio::UserEcoidentitaREMP CRM UserPoznámka
idInterní Folio PK
eco_user_iduuid / uid z userinfoext_idKanonický identifier
remp_user_ididPro přímé API volání
emailemailemailSync needed
first_namegiven_namefirst_nameSync needed
last_namefamily_namelast_nameSync needed

REMP SSO vs. EGO-SSO

REMP SSO modul (/Sso) poskytuje:

  • Google OAuth login pro admin uživatele REMP nástrojů
  • JWT session management
  • API token management (/api/auth/*)

REMP SSO NENÍ identity provider pro čtenáře. Čtenáři se autentizují přes EGO-SSO → Folio CMS → REMP CRM API.

REMP admin uživatelé (redaktoři, marketéři) se přihlašují:

  • Do Campaign admin → přes REMP SSO (Google OAuth)
  • Do Mailer admin → přes REMP SSO
  • Do Beam admin → přes REMP SSO

Doporučení: Pro budoucí zjednodušení zvážit napojení REMP SSO na EGO-SSO (jako další SSO provider), aby se admin uživatelé nemuseli přihlašovat zvlášť.


Cookies a session management

Skutečné cookies (z implementace CMS-353)

EGO-SSO používá dvě klíčové cookies:

CookieDoména (produkce)Popis
EGO_SESS_ID.centrum.czSession ID, nastavuje ucet.centrum.cz, httpOnly
EGO_APROFILE.centrum.cz a .hn.czProfilový token, propagován přes SSO redirect kolečko

Mechanismus cross-domain propagace

EGO-SSO řeší sdílení identity přes domény redirectovým řetězcem:

ucet.centrum.cz
  → Set-Cookie: EGO_SESS_ID (domain=.centrum.cz, httpOnly, secure)

sso.centrum.cz
  → Set-Cookie: EGO_APROFILE (domain=.centrum.cz)

sso.hn.cz                    ← Cloudflare Worker / edge endpoint
  → Set-Cookie: EGO_APROFILE (domain=.hn.cz)

www.hn.cz/users/auth/egoidentita/callback
  → Rails čte EGO_APROFILE + EGO_SESS_ID
  → POST na userinfo.centrum.cz/userinfo

sso.hn.cz je endpoint (pravděpodobně Cloudflare Worker), který přijme token z SSO redirect řetězce a nastaví EGO_APROFILE cookie na doméně .hn.cz. Bez tohoto endpointu by CMS na www.hn.cz cookie nepřečetl.

Přehled všech cookies na hn.cz

Doména: .hn.cz
├── EGO_APROFILE     → EGO-SSO profilový token (nastavuje sso.hn.cz)
├── _hn_session      → Folio CMS Rails session cookie (Devise)
└── remplib_*        → remplib.js tracking cookies (pro Campaign/Beam)

Doména: .centrum.cz (čitelná pouze userinfo endpointem)
├── EGO_SESS_ID      → EGO-SSO session (httpOnly, secure)
├── EGO_APROFILE     → EGO-SSO profilový token

Doména: remp.economia.cz (admin)
├── laravel_session  → REMP SSO admin session cookie
│                      JWT stav je v server-side session `jwt.user`, `jwt.token`

Prostředí

ProstředíAuth URLUserInfo URLCookie doména
Produkceucet.centrum.czuserinfo.centrum.cz.centrum.cz
Dev/Testucet.mailkafe.czuserinfo.mailkafe.cz.mailkafe.cz
Demoucet.mailkafe.czuserinfo.mailkafe.cz.ecodevel.cz

⚠️ Bezpečnostní poznámky

  • state parametr je aktuálně hardcoded jako "not_used_yet"CSRF ochrana není implementována
  • Cookies EGO_SESS_ID a EGO_APROFILE nastavuje SSO server, nikoli Rails aplikace

remplib.js na frontendu hn.cz potřebuje identifikovat uživatele pro Campaign targeting:

var rempConfig = {
  userId: "<%= current_user&.remp_user_id %>",
  userSubscribed: <%= user_has_active_subscription? %>,
  cookieDomain: ".hn.cz",
  campaign: {
    url: "https://campaign.remp.economia.cz"
  }
};

remplib.campaign.init(rempConfig);

Edge cases

1. Uživatel existuje v REMP CRM, ale ne ve Folio

Scénář: Legacy uživatel importovaný přímo do REMP CRM.

Řešení: Při prvním přihlášení přes Ecoidentita, Folio hledá uživatele v CRM dle ext_id (eco_user_id). Pokud nalezen, propojí. Pokud ne, vytvoří.

def sync_user_to_remp(folio_user)
  response = Remp::CrmClient.find_user_by_ext_id(folio_user.eco_user_id)
  if response.success?
    folio_user.update!(remp_user_id: response.body["user"]["id"])
  else
    result = Remp::CrmClient.register_user(
      email: folio_user.email,
      ext_id: folio_user.eco_user_id
    )
    folio_user.update!(remp_user_id: result.body["user"]["id"])
  end
end

2. E-mail conflict

Scénář: Uživatel v CRM má jiný e-mail než v Ecoidentita (legacy stav).

Řešení: Ecoidentita je source of truth. Při linku aktualizujeme e-mail v CRM. Log warning.

3. REMP CRM nedostupný při loginu

Scénář: CRM API timeout při user sync.

Řešení: Login proběhne (UX nesmí trpět). Sync se zopakuje přes retry mechanismus Sidekiq jobu. remp_user_id zůstane nil → entitlement check fallback na “locked”.

4. Anonymní uživatel

Scénář: Nepřihlášený čtenář.

Řešení: Žádný REMP user. remplib.js pracuje s anonymous segment. Paywall je vždy zamknutý. Campaign cílí dle anonymous pravidel.


Bezpečnost

AspektOpatření
API tokenyUloženy v Rails credentials / env vars, nikdy v kódu
Webhook signatureHMAC-SHA256 verifikace pro REMP → CMS webhooky
CORSremplib.js volání Campaign – CORS headers nakonfigurovat
PII dataPouze email, jméno. Žádné platební údaje do REMP CRM
GDPRUser deletion musí propagovat do REMP CRM (right to be forgotten)
Rate limitingREMP API volání rate-limitovat na straně CMS

GDPR: Právo na výmaz

Při požadavku na smazání uživatele (GDPR Art. 17):

  1. Folio CMS: Soft-delete Folio::User
  2. REMP CRM: DELETE /api/v1/users/{remp_user_id} (nebo anonymizace)
  3. REMP Mailer: POST /api/v1/users/deleteUserDeleteApiHandler
  4. Ecoidentita: Delete user (řeší Ecoidentita)
  5. Log deletion request pro audit trail