Autentica homologação com Supabase real e resolve workspace no servidor
Duas lacunas impediam qualquer uso de homologação. Primeira: sem SSO do Elo Pro e sem o login local, não havia como entrar. Segunda, mais grave: nada no repositório gravava o cookie `elo_pro_workspace_id`, que era lido como origem do workspace ativo — mesmo com sessão válida a resposta era sempre "sem workspace". - `identity-provider.ts` define o contrato de identidade. Trocar o Supabase de homologação pelo SSO do Elo Pro passa a ser acrescentar uma implementação; nenhum outro arquivo conhece o mecanismo. - `/api/auth/staging/session` autentica com `signInWithPassword` no projeto Supabase de homologação. Há senha, emitida e validada pelo Supabase, e o Adaptador continua sem tabela de usuário, senha ou sessão própria. - O login local sem senha existe apenas em `ADAPTER_ENV=development`, e não por configuração: fora de development o provedor nem entra na lista. - `workspace.ts` resolve o workspace por associação real lida sob RLS. O cookie passa a ser preferência, não prova: aceitá-lo como autorização transformava um valor editável pelo cliente em credencial. - `/api/auth/workspace` grava a preferência só depois de confirmar a associação, e `/selecionar-workspace` existe para quem participa de mais de um workspace. - Rate limiting por usuário e bucket nas rotas do módulo, com Retry-After. Upload e processamento custam muito mais que leitura. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b972589949
commit
f5560cc4ac
15 files changed
+586
-55
No files matched your search
@@ -0,0 +1,71 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { Button, Card } from "./ui";
|
||||
|
||||
export type WorkspaceOption = {
|
||||
workspaceId: string;
|
||||
workspaceName: string;
|
||||
role: string;
|
||||
moduleEnabled: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Escolha do workspace ativo. A seleção grava apenas preferência: o servidor
|
||||
* revalida a associação em toda requisição.
|
||||
*/
|
||||
export function WorkspacePicker({ memberships, next }: { memberships: WorkspaceOption[]; next: string }) {
|
||||
const router = useRouter();
|
||||
const [pending, setPending] = useState<string | null>(null);
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<Card className="mt-6">
|
||||
<h2 className="text-lg font-semibold">Selecione o workspace</h2>
|
||||
<p className="mt-1 text-sm text-ink/80">
|
||||
Você participa de mais de um workspace. O Adaptador trabalha em um por vez.
|
||||
</p>
|
||||
<div className="mt-4 space-y-2">
|
||||
{memberships.map((membership) => (
|
||||
<Button
|
||||
key={membership.workspaceId}
|
||||
variant="secondary"
|
||||
className="w-full justify-between"
|
||||
loading={pending === membership.workspaceId}
|
||||
onClick={async () => {
|
||||
setPending(membership.workspaceId);
|
||||
setMessage(null);
|
||||
try {
|
||||
const response = await fetch("/api/auth/workspace", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ workspaceId: membership.workspaceId }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
setMessage("Não foi possível selecionar este workspace.");
|
||||
return;
|
||||
}
|
||||
router.push(next);
|
||||
router.refresh();
|
||||
} finally {
|
||||
setPending(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span>{membership.workspaceName}</span>
|
||||
<span className="text-xs font-normal text-ink/70">
|
||||
{membership.role}
|
||||
{membership.moduleEnabled ? "" : " · módulo desligado"}
|
||||
</span>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
{message ? (
|
||||
<p role="alert" className="mt-3 text-sm text-danger">
|
||||
{message}
|
||||
</p>
|
||||
) : null}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user