Kodekanox Studio: plataforma onde criadores organizam prompts, personagens e cenas para produzir anime com IA — com segurança e cobrança que sustentam um negócio real.
01 · Contexto e problema
Kodekanox Studio é uma plataforma onde criadores organizam prompts, personagens e cenas para produzir conteúdo estilo anime com IA — a Studio automatiza o trabalho pesado para o criador focar só em criar.
É um SaaS pago de verdade: Next.js 16 com Turbopack e App Router (Server Components + Server Actions por padrão), Supabase como backend (Postgres, Auth, Storage), Stripe para cobrança, Resend para e-mail transacional, Upstash Redis para rate-limit e reCAPTCHA contra bots.
O desafio central: vender acesso por assinatura a uma ferramenta de IA mantendo os fornecedores de IA invisíveis na UI (marca neutra), com segurança em profundidade — e garantindo que só um pagamento verificado, nunca um redirect do navegador, libere acesso.
Restrições
02 · Decisões técnicas
Acesso liberado só pelo webhook do Stripe
Problema
O redirect do navegador depois do checkout é falsificável; não pode ser a fonte de verdade para liberar acesso a um produto pago.
Opções
Liberar acesso no redirect do checkout · polling do estado da sessão · webhook assinado do Stripe como única fonte
Decisão ✓
O webhook do Stripe (checkout.session.completed e afins) é a ÚNICA fonte que libera acesso, com upsert idempotente por owner_id; studio_subscriptions é read-only para o usuário — só o service-role escreve status/plano.
Trade-off
Existe um pequeno delay entre pagar e ver o acesso ativo (espera o webhook chegar), mas é a única forma de ninguém se autoconceder uma assinatura manipulando o cliente.
Segurança em profundidade para arquivos privados
Problema
A plataforma guarda e serve arquivos privados de criadores; um bypass de RLS ou um bucket mal configurado expõe conteúdo pago de outra pessoa.
Opções
Buckets públicos com URLs ofuscadas · RLS só no Postgres · RLS em tabelas e Storage + URLs assinadas + verificação de posse antes do service-role
Decisão ✓
RLS em tabelas e em Storage, buckets privados, URLs assinadas de 60s, upload em 3 passos (assinatura → PUT direto ao Storage → confirmação com verificação de magic bytes), e o service-role só age depois de verificar a posse do recurso.
Trade-off
Mais passos no fluxo de upload (três idas em vez de uma), em troca de nenhuma URL válida por mais de 60s e nenhum bypass de RLS como caminho padrão.
Um proxy único como muralha (idioma, subdomínio, MFA)
Problema
Precisava de um único ponto que resolvesse idioma, protegesse rotas (/creator, /admin), separasse subdomínios (app vs admin) e exigisse MFA do admin — sem duplicar essa lógica em cada rota.
Opções
Checagem por página · middleware clássico · src/proxy.ts como muralha única (Next.js 16)
Decisão ✓
src/proxy.ts concentra tudo: resolve idioma por cookie/geo, protege /creator e /admin, separa kodekanox.com de admin.kodekanox.com e exige MFA no admin. É intencional que viva em src/ — na raiz simplesmente não roda, e todos esses gates somem em silêncio.
Trade-off
Vira um ponto único de falha se quebrar, então ficou documentado no AGENTS.md como algo que nunca se mexe sem ler antes os docs locais do Next.js 16 (a versão trouxe breaking changes).
Arquitetura
03 · Upload de arquivo privado: assinatura, PUT direto e confirmação por magic bytes
Guardar e servir arquivos de usuário num produto pago não pode confiar na extensão ou no mimetype que o cliente declara — e não pode passar o peso do arquivo pelo servidor da aplicação sem necessidade.
O fluxo ficou em 3 passos: o cliente pede uma URL assinada (o servidor verifica posse e cota antes de assinar), o cliente sobe o arquivo direto pro Storage (o app-server nunca vê os bytes), e só depois o cliente confirma — momento em que o servidor verifica a assinatura real do arquivo (magic bytes) antes de marcá-lo válido.
Isso se soma a uma cota de storage única em GB por plano (não por tipo de arquivo) — decisão direta: um vídeo pesa como 4 imagens, então cobrar por tipo deixaria o custo correr na frente da receita do plano.
Resultado: nenhum bucket público, nenhuma URL assinada sobrevive além de 60s, e nenhum arquivo é confiável só pela extensão que carrega.
04 · Segurança e processo antes de builds sensíveis
Revisão de segurança formal + teste de intrusão A/B antes de mergear features sensíveis (pagamento, acesso) — um hábito interno antes de tocar essas áreas, não uma auditoria externa pontual.
Fluxo de git disciplinado: branch por feature, commitlint com scope-enum, pre-push bloqueando push direto a main, e merges em ordem quando há dependência entre PRs.
Marca neutra por decisão de produto: nenhuma menção a fornecedores de IA terceiros na UI — o stack de ferramentas é segredo do negócio, não só estética.
Regra dura de engenharia, nascida de um bug real: valores runtime nunca são exportados de módulos "use server" (só funções async) — um .map is not a function em produção foi o que fez essa regra virar permanente.
Demo interativa
Em breve: execute o código e veja-o rodando ao vivo, sem instalar nada.
Editor ao vivo (DartPad) — em breve
04 · Resultados · antes / depois
05 · Retrospectiva
Definiria desde o dia 1 que valores runtime nunca saem de módulos "use server": o bug real que virou essa regra custou um tempo de debug que dava pra evitar.
Trocaria o login por navegação dura mais cedo: a corrida de cookies com redirect() só apareceu testando o fluxo completo, do checkout até o primeiro acesso — não em isolamento.
Manteria o webhook do Stripe como única fonte de acesso desde a primeira versão: é tentador confiar no redirect do checkout porque 'funciona no happy path', mas é exatamente o caminho que alguém malicioso tentaria falsificar primeiro.