Prompt · Login Sancruza
Prompt focado APENAS em autenticação: Login Sancruza canônico em modo SSO, sessão emitida pelo Sancruza Core, gate por slug, página /sem-acesso e remoção de login local. Não mexe em design nem em dados.
Quando usar
Use em lote em todos os sistemas pra resolver autenticação de uma vez. Depois aplique prompt-design e prompt-blindagem separadamente.
- Confirme com o time que o domínio corporativo Sancruza é @sancruza.com.br.
- Abra o projeto no Lovable e salve a versão atual.
- Cole o prompt abaixo no chat.
- Aprove migrations quando solicitado (trigger de domínio, renomes _legacy_).
- Confirme manualmente: login com email fora de @sancruza.com.br é rejeitado.
Prompt oficial
Versão 4.0 · SSO do Sancruza Core · Sem senha local · Branding Sancruza. Não toca em design nem em schema de negócio.
sancruza-core / prompt-login / v4.0
Aplique APENAS o módulo de AUTENTICAÇÃO do padrão Sancruza (Sancruza Core) — prompt-login v4.0 · SSO.
ESCOPO DESTE PROMPT_LOGIN (LEIA ANTES)
- SÓ mexe em: login (SSO), logout, gate de rota protegida, página /sem-acesso, hook useSancruzaSession.
- NÃO mexe em: design system, cores, tipografia, layout interno das telas, RLS de tabelas de dados, schema de negócio.
- NÃO desabilita Lovable Cloud. Mantém os dados deste sistema intactos.
- Se a UI interna estiver feia, IGNORE — existe um prompt-design separado pra isso.
MUDANÇA CRÍTICA NA v4.0 (em relação à v3.x)
- ACABOU o login local. NÃO existe mais supabase.auth.signInWithPassword, nem senha, nem "esqueci minha senha", nem /reset-password neste sistema.
- A identidade é 100% do Sancruza Core. Este sistema NÃO guarda senha de ninguém.
- Se o sistema veio da v3.x com login local: REMOVA o formulário de email+senha, o /reset-password e o trigger local de domínio. Não apague dados de negócio.
- Motivo: com login local, o usuário criado no Sancruza Core não existe no banco deste sistema e recebe "e-mail ou senha incorretos". Isso quebrou 3 sistemas em produção. Não repita.
CONTEXTO
- Sancruza é uma imobiliária. Domínio corporativo único: @sancruza.com.br. Não existem usuários externos.
- Sancruza Core (https://coresancruza.lovable.app) é o ÚNICO provedor de identidade: só ele tem senha, convite, reset e permissão.
- Cada sistema mantém seu próprio Lovable Cloud pros DADOS, mas a SESSÃO vem do Sancruza Core via SSO.
================================================================
0. PRÉ-REQUISITOS (me confirme antes de começar)
================================================================
a) SLUG deste sistema no Sancruza Core (ex.: "blog-sancruza"). Se não souber, peça pro humano abrir o Sancruza Core → Sistemas e copiar. Se não está cadastrado, PARE e peça cadastro primeiro.
b) NOME LEGÍVEL (ex.: "Blog Sancruza") e CATEGORIA (operacao | comercial | marketing | inteligencia | administrativo | publico | outros).
c) O humano precisa cadastrar, no Sancruza Core → Sistemas → este sistema → "Origens permitidas", TODAS as origens deste projeto (produção + preview), ex.:
https://<seu-app>.lovable.app
https://id-preview--<uuid>.lovable.app
d) Stack: existe src/router.tsx + src/routes/__root.tsx → "tanstack". Caso contrário, "vite-spa". Reporte qual detectou.
e) SECRET obrigatório neste projeto: SC_CORE_SHARED_SECRET (peça pro humano; é o mesmo valor configurado no Sancruza Core). Ele é usado SÓ no servidor, nunca no browser.
f) Confirme ao humano: "Este prompt mexe SÓ em autenticação. Design e dados ficam como estão."
================================================================
1. COMO FUNCIONA O SSO (handshake em 3 passos)
================================================================
1) Usuário sem sessão abre qualquer rota protegida deste sistema.
→ redirecione o BROWSER pra:
https://coresancruza.lovable.app/sso/start?system=<SLUG>&return=<URL_CALLBACK_ENCODADA>
onde URL_CALLBACK = ${window.location.origin}/sso/callback
2) O Sancruza Core autentica (ou já tem sessão), confere se o usuário tem acesso ao slug
e devolve o browser pra: <URL_CALLBACK>?token=<jwt>&sso_token=<jwt>
(os dois parâmetros carregam o MESMO token — leia "token", com fallback "sso_token").
3) Seu SERVIDOR (nunca o browser) troca esse token por perfil + sessão:
POST https://coresancruza.lovable.app/api/public/sso/verify
Headers: Content-Type: application/json
x-sc-shared-secret: <SC_CORE_SHARED_SECRET>
Body: { "token": "<jwt>", "expected_system_slug": "<SLUG>" }
Resposta 200:
{
"ok": true,
"user": { "id","email","name","photo_url","department","position","role","is_admin",
"cargo","departamento","equipe","team","is_team_manager","creci","genero" },
// CARGO: "cargo" vem do Diretório do Core (ex.: "Coordenadora de Treinamentos").
// Use-o para regras locais de permissão elevada, ALÉM de role/is_admin.
// Cargo NUNCA concede acesso ao sistema (isso é do Core via role/can_access);
// ele só amplia o que a pessoa pode fazer dentro do sistema.
"session": { "handle": "<opaco>", "expires_at": "<iso>" }
}
Erros (401/409): invalid_token, token_expired, token_already_used, claims_mismatch, unauthorized…
REGRAS DURAS
- O token é SINGLE-USE e vale poucos minutos. Verifique UMA vez, no servidor, e guarde a sessão local.
- NUNCA chame /api/public/sso/verify do browser: o SC_CORE_SHARED_SECRET vazaria.
- NÃO use Supabase Edge Function pra isso. Use rota de servidor do próprio framework
([TANSTACK] src/routes/api/sso.callback.ts com createFileRoute + server.handlers · [VITE-SPA] a rota de servidor equivalente do projeto).
- Guarde a sessão em cookie HttpOnly + Secure + SameSite=Lax (nome sugerido: sc_session), com o session.handle e expires_at.
- Falha na verificação = redirecione pra /sem-acesso com o motivo. NUNCA libere por dúvida (fail-closed).
================================================================
2. TELA DE LOGIN — botão único, sem formulário
================================================================
REGRA ZERO: o componente LoginSC é mantido no Sancruza Core e servido como TSX puro. Baixe literalmente:
mkdir -p src/components
curl -sSf https://coresancruza.lovable.app/api/public/login-sc.tsx \
-o src/components/login-sc.tsx
Logo oficial Sancruza (use a URL direto no src, não baixe como asset):
https://project--31b09c74-1b33-4cdf-b3df-15ea0c40d2aa.lovable.app/__l5e/assets-v1/9530e63d-1f2f-4b74-acfa-28fe5f3d63bb/sancruza-logo.png
Na página /auth, use o modo SSO do componente — sem campos de email/senha:
import { LoginSC } from "@/components/login-sc";
const startSso = () => {
const ret = encodeURIComponent(window.location.origin + "/sso/callback");
window.location.href =
"https://coresancruza.lovable.app/sso/start?system=<SLUG>&return=" + ret;
};
<LoginSC
logoUrl="https://project--31b09c74-1b33-4cdf-b3df-15ea0c40d2aa.lovable.app/__l5e/assets-v1/9530e63d-1f2f-4b74-acfa-28fe5f3d63bb/sancruza-logo.png"
systemName="<NOME LEGÍVEL>"
systemDescription="<UMA FRASE CURTA E PERMANENTE — sem métricas, sem números>"
category="<operacao | comercial | marketing | inteligencia | administrativo | publico | outros>"
mode="sso"
onSso={startSso}
/>
Se o componente baixado ainda não expuser mode="sso", renderize-o e substitua APENAS o bloco do formulário por um botão único
"Entrar com a conta Sancruza" em VERMELHO SANCRUZA #E30613 chamando startSso(). Não mexa em mais nada do arquivo.
PROIBIDO: campo de senha, "criar conta", magic link, botão Google, "acesso administrativo", qualquer fallback de login local,
editar o resto do login-sc.tsx, trocar a paleta (azul navy #0A1B3D + vermelho #E30613 são contrato).
Opcional (recomendado): se a rota protegida for aberta sem sessão, chame startSso() direto, sem passar pela /auth.
================================================================
3. AUTH LOCAL — desligar de vez
================================================================
- Rode configure_auth: disable_signup=true, external_anonymous_users_enabled=false, auto_confirm_email=false, password_hibp_enabled=true.
- Se houver provider social ativo (Google legado), DESATIVE via configure_social_auth disable_providers: ["google"].
- REMOVA: página /reset-password, chamadas a signInWithPassword / resetPasswordForEmail / signInWithOAuth / signInWithOtp.
- REMOVA telas locais de gestão de usuários (criar, listar, alterar papel). Tudo isso é no Sancruza Core.
- Tabelas locais tipo user_profiles, app_users, permissions: NÃO apague dados. Renomeie pra _legacy_<nome>.
- RLS local NÃO depende do Sancruza Core: reescreva policies pra depender só das tabelas do próprio sistema.
================================================================
4. PAPÉIS CANÔNICOS — sem mapeamento, sem tradução
================================================================
REGRA INEGOCIÁVEL: Sancruza Core é a fonte única de papéis. Os 11 papéis oficiais são:
admin, diretor, gestor, corretor, colaborador, marketing,
administrativo, financeiro, juridico, vistoriador, visualizador
- Use esses nomes LITERAIS. PROIBIDO criar papel próprio, traduzir, abreviar ou mapear N pra 1.
- Grave o role LITERAL devolvido pelo /api/public/sso/verify na sessão local — sem if/else.
- is_admin === true significa SUPERADMIN DO CORE: tem acesso a TUDO neste sistema, independentemente de qualquer regra local.
Sempre trate is_admin antes de checar role. Não exigir cadastro local pra ele.
- Papel novo no futuro = ALTER TYPE ADD VALUE no enum local. Nunca nome novo.
================================================================
5. SESSÃO LOCAL + HOOK useSancruzaSession
================================================================
- Após o verify, crie a sessão local (cookie HttpOnly) e uma rota de servidor GET /api/me que lê o cookie
e devolve { authenticated, user: { id,email,name,photo_url,role,is_admin }, expires_at }.
- Crie src/hooks/use-sancruza-session.ts: useQuery(['sancruza-session'], GET /api/me), staleTime 60_000.
Retorna { isLoading, authenticated, user, role, isAdmin }.
- USE em TODA decisão de UI (esconder botão, bloquear aba, ação destrutiva).
- NUNCA crie endpoint local de "promover a admin" ou "criar usuário".
REGRAS ANTI-RACE (causa de "cai em /sem-acesso mesmo logado"):
(a) Enquanto isLoading=true o gate mostra skeleton — NUNCA decide.
(b) authenticated vem do /api/me, não de heurística de cookie no browser.
(c) authenticated só vira false depois que a resposta chega; undefined = ainda carregando.
================================================================
6. GATE DE ROTAS — SancruzaSystemGate
================================================================
Crie src/components/sancruza-system-gate.tsx com props { slug, systemName, children }:
- Consulta useSancruzaSession().
- isLoading → skeleton neutro. NUNCA renderiza conteúdo protegido antes de validar.
- !authenticated → startSso() (ou navega pra /auth).
- authenticated mas sem permissão → /sem-acesso?slug=<SLUG>.
- Ordem: isLoading → skeleton; depois !authenticated → SSO; depois sem permissão → /sem-acesso; senão children.
[TANSTACK] Envolva TODAS as rotas protegidas com <SancruzaSystemGate slug="<SLUG>" systemName="<NOME LEGÍVEL>">.
[VITE-SPA] Envolva o <Routes> protegido em src/App.tsx. /auth, /sso/callback e /sem-acesso ficam FORA do gate.
================================================================
7. PÁGINA /sem-acesso
================================================================
[TANSTACK] src/routes/sem-acesso.tsx · [VITE-SPA] src/pages/SemAcesso.tsx + rota /sem-acesso.
- Fundo idêntico ao painel esquerdo do Login Sancruza (azul navy #0A1B3D com aurora sutil).
- Card central: ícone de cadeado, título "Você não tem acesso a este sistema", texto
"Sua conta @sancruza.com.br não tem permissão para acessar <NOME LEGÍVEL>. Procure o administrador no Sancruza Core para liberar.".
- Botões: "Voltar ao Sancruza Core" (https://coresancruza.lovable.app) e "Sair".
- Mostra o email logado em fonte pequena pra facilitar o pedido ao admin.
================================================================
8. LOGOUT
================================================================
- Rota de servidor que apaga o cookie sc_session e, se possível, avisa o Core:
POST https://coresancruza.lovable.app/api/public/sso/logout com x-sc-shared-secret e { "session_handle": "<handle>" }.
- No client: queryClient.cancelQueries() + queryClient.clear() antes de navegar (evita 401 storm).
- Depois: navigate("/auth", replace).
- No header autenticado, link discreto "← Sancruza Core".
================================================================
9. ENTREGÁVEL FINAL
================================================================
Reporte, em ordem:
1) Stack detectado. Sistema-alvo: <slug> · <nome> · <categoria>.
2) Confirmação: "Lovable Cloud NÃO foi tocado em schema/dados de negócio. Design NÃO foi alterado."
3) Screenshot da tela de login: painel esquerdo AZUL NAVY (#0A1B3D) com aurora e logo IMAGEM,
painel direito claro, BOTÃO ÚNICO "Entrar com a conta Sancruza" em #E30613, SEM campo de senha e SEM Google.
4) md5sum src/components/login-sc.tsx (prova que baixou e não recriou).
5) Teste manual: abrir rota protegida deslogado → vai pro Sancruza Core → volta logado no sistema.
6) Teste manual: usuário sem permissão pro slug → cai em /sem-acesso.
7) Teste manual: superadmin do Core (is_admin=true) entra sem cadastro local.
8) Lista de tabelas renomeadas pra _legacy_ e telas de gestão removidas.
9) Checklist:
(a) login-sc.tsx baixado via curl (md5 confere).
(b) Nenhum signInWithPassword / resetPasswordForEmail / OAuth / OTP no código.
(c) /reset-password removido.
(d) /sso/callback é rota de SERVIDOR e chama /api/public/sso/verify com o shared secret.
(e) SC_CORE_SHARED_SECRET só no servidor, nunca em import.meta.env / VITE_.
(f) Cookie de sessão HttpOnly + Secure + SameSite=Lax.
(g) role gravado LITERAL; is_admin tratado como superadmin global.
(h) Gate com skeleton, sem race de redirect.
(i) /sem-acesso existe com visual Sancruza.
(j) Falha de verificação = fail-closed.
(k) Logout limpa cache, apaga cookie e avisa o Core.
(l) Origens deste projeto cadastradas no Sancruza Core (produção + preview).
Se algum item falhar, PARE e reporte. NÃO entregue parcial.