O InfraHub é uma plataforma self-hostedque provisiona backend completo (Postgres + Auth + Storage + Realtime) por projeto. Você cria um projeto pelo painel, recebe credenciais, e a sua aplicação consome via SDK ou REST — igual Supabase, mas rodando na sua infra.
Conceitos
- Projeto: unidade de isolamento. Cada projeto tem banco Postgres dedicado, bucket MinIO próprio, JWT secret próprio.
- API key (
if_xxx...): identifica o projeto. Vai no headerx-infraforge-keyde toda request server-side. Pode ficar no app cliente (não é segredo de produção isolado — mas não exponha pra atacantes desnecessariamente). - JWT secret: assina os tokens dos end-users. Backend do InfraHub usa pra validar JWT em cada query. Nunca deve sair do InfraHub nem do servidor do app que precisar emitir tokens externos.
- RLS (Row-Level Security): políticas SQL que filtram linhas por usuário. InfraHub injeta
request.jwt.sub/role/emailem cada query do SDK, e suas policies referenciamauth.uid()/auth.role().
Fluxo típico
1. Usuário cria projeto no painel → recebe API key + JWT secret 2. App instala o SDK e configura com (URL + slug + apiKey) 3. End-user faz signUp / signIn → recebe JWT 4. SDK injeta JWT no Authorization de cada query 5. Backend valida JWT, aplica RLS, retorna só linhas que o user pode ver
1. Criar projeto
No painel, vai em Projetos → Novo projeto. Escolhe modo:
- managed_local: InfraHub spawna container Postgres+MinIO na própria VPS. Sem setup, mais rápido.
- external (BYODB): você fornece a connection URL do seu Postgres. Veja BYO Database antes.
- managed_remote: InfraHub spawna container numa VPS sua (cadastrada em Minhas VPSs) via SSH.
Após o provisionamento (~10-30s), você recebe API key(formato if_xxx...). Copia essa key.
2. Instalar SDK
Instalar: Node 18+
npm install @infraforge/sdkts
3. Inicializar cliente
import { createClient } from '@infraforge/sdk'
const client = createClient({
url: 'https://infraforge.seudominio.com',
projectSlug: 'meu-projeto',
apiKey: process.env.INFRAFORGE_API_KEY!,
})ts4. Primeiro signUp + query
// Cadastra usuário (default: pending de aprovação manual)
const { user } = await client.auth.signUp({
email: 'alice@example.com',
password: 'senha-forte-aqui',
})
// Após admin aprovar, faz login
await client.auth.signIn({ email: 'alice@example.com', password: 'senha-forte-aqui' })
// Cria tabela (use o painel SQL Editor pra rodar isso 1×)
// CREATE TABLE posts (id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
// title text, author_id uuid REFERENCES auth.users(id));
// ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
// CREATE POLICY "own posts" ON posts USING (author_id = auth.uid());
// Insert + query (RLS aplicado automático via JWT)
await client.db.query("INSERT INTO posts (title, author_id) VALUES ($1, auth.uid())", ['Olá!'])
const { data } = await client.db.query('SELECT * FROM posts')
console.log(data) // só os posts do usuário logadotsPróximo: as seções Autenticação, Database e Storage cobrem cada módulo em detalhe.
End-users do seu app são gerenciados pelo InfraHub: cadastro, login, OAuth social, MFA, reset de senha. JWT assinado com o jwtSecret do projeto vai em todo request — backend valida em cada chamada.
Cadastro e login com email/senha
// Cadastro — default vai pra "pending approval" (admin libera no painel)
const { user, pending } = await client.auth.signUp({
email: 'alice@example.com',
password: 'senha-forte',
})
// Login (após aprovação)
const { token, error } = await client.auth.signIn({
email: 'alice@example.com',
password: 'senha-forte',
})
// Logout — limpa sessão local
client.auth.signOut()
// Verificar usuário atual (faz request server-side)
const { user } = await client.auth.getUser()
// Sessão local (sem rede)
const session = client.auth.getSession()
if (session) console.log(session.user.email)tsModos de aprovação (configura no painel do projeto, aba Auth): manual_approval (default — admin aprova), email_verification (link no email confirma), auto_approve (libera direto, use com cuidado).
OAuth social (Google, GitHub)
Configura credenciais do provider no painel do projeto (aba Auth → Providers). SDK lida com o redirect + callback.
// Inicia o fluxo (browser redireciona pro provider)
client.auth.signInWithSocial({
provider: 'google', // ou 'github'
redirectTo: '/dashboard', // path relativo OU mesma origin (rejeita externos)
})
// Na sua página /callback, rode handleOAuthCallback
const result = client.auth.handleOAuthCallback()
if (result?.user) {
// Logado! result.token tem o JWT
} else if (result?.error) {
console.error('OAuth falhou:', result.error)
}tsSegurança: o SDK gera um state random salvo em sessionStorage antes do redirect, e valida no callback. Previne session fixation via link malicioso #token=....
MFA / TOTP no painel
MFA com TOTP (Google Authenticator, Authy, 1Password) está disponível pra contas do painel InfraHub (admin/tenant), não pros end-users do projeto. Ative em Conta → Segurança.
MFA pra end-users do projeto não está implementado ainda — está no roadmap. Se você precisa, abra uma issue.
Reset de senha
// User esqueceu — manda link por email (sempre retorna ok pra não vazar emails)
await client.auth.requestPasswordReset({ email: 'alice@example.com' })
// User clica no link → sua página /reset-password recebe ?token=...
// SDK não tem helper pronto — você posta direto:
await fetch(`${url}/api/auth/${slug}/reset-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, newPassword: 'nova-senha' }),
})tssetToken — quando você precisa
Útil quando o JWT chega de outro fluxo (callback OAuth externo, sistema legado, server-side handoff).
// Lança erro se JWT mal-formado, expirado, ou sem 'sub'.
// Não cria sessão "vazia" — UI nunca fica em estado pseudo-logada.
try {
client.auth.setToken(jwt)
} catch (err) {
console.error('JWT inválido:', err.message)
}Onde a sessão fica no browser
Por default, sessão (JWT) é persistida em localStorage. Pra apps mais sensíveis, escolha modo:
createClient({
url, projectSlug, apiKey,
session: 'localStorage', // (default) persiste entre tabs e refresh
// session: 'sessionStorage',// só vive enquanto a aba estiver aberta
// session: 'memory', // nada gravado; refresh = re-login
})memory é imune a XSS de leitura, mas pior UX. Use pra apps com dados muito sensíveis.
SDK envia SQL pro endpoint /api/query/[slug]. Backend valida JWT, abre transação, injeta request.jwt.sub/role/email, executa, devolve rows. Tudo passa por RLS.
Query parametrizada
// Sempre use $1, $2 — concatenação de string vira SQL injection
const { data, error } = await client.db.query<{ id: string; title: string }>(
'SELECT id, title FROM posts WHERE author_id = $1 ORDER BY created_at DESC LIMIT $2',
[userId, 10],
)
if (error) throw new Error(error)
console.log(data)tsRLS — funções helper disponíveis
O backend popula 3 functions no schema auth que suas policies usam:
auth.uid() -- UUID do user logado (request.jwt.sub) auth.role() -- role do JWT (default 'authenticated') auth.email() -- email do usersql
Exemplo de policies básicas:
-- Tabela com author_id CREATE TABLE posts ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), author_id uuid REFERENCES auth.users(id) NOT NULL DEFAULT auth.uid(), title text, body text, created_at timestamptz DEFAULT now() ); ALTER TABLE posts ENABLE ROW LEVEL SECURITY; -- Cada user vê só os próprios posts CREATE POLICY "select own" ON posts FOR SELECT USING (author_id = auth.uid()); -- Pode inserir posts seus CREATE POLICY "insert own" ON posts FOR INSERT WITH CHECK (author_id = auth.uid()); -- Pode editar/deletar só próprios CREATE POLICY "update own" ON posts FOR UPDATE USING (author_id = auth.uid()); CREATE POLICY "delete own" ON posts FOR DELETE USING (author_id = auth.uid());sql
Aplica via aba Migrations ou SQL Editor do projeto. Use a aba RLS pra editar policies via UI.
Transações
Cada call de client.db.query() já roda em transação implícita (BEGIN + sets de JWT context + COMMIT). Pra múltiplas operações atômicas, mande tudo num DO $$ BEGIN ... END $$ OU separe em statements numa única string com ;.
Bypass RLS (emergência)
Se você precisa ver/consertar dados como admin (ex: perdeu acesso a conta de user, precisa migrar dados manualmente), use o toggle Bypass RLS em Configurações do projeto. Vale por 1h.
Importante: o bypass aplica APENAS no painel (SQL Editor, Tables) e na CLI (admin). O SDK do app continua respeitando RLS — end-users não veem dados além das policies.
Limites
- Pool: até 20 conexões simultâneas por projeto
- Query timeout: 5 minutos (longo, mas pra evitar pendurar conexão)
- Tamanho da query SQL: max 50.000 caracteres
Cada projeto tem um bucket MinIO próprio com credenciais isoladas. SDK oferece tanto helpers (upload/download) quanto presigned URLs pra upload direto do browser sem passar bytes pelo InfraHub.
Upload + download (helpers de alto nível)
// Upload (arquivo do browser ou Buffer no Node)
const file = formInput.files[0]
const { error } = await client.storage.upload('avatars/user-42.png', file, {
contentType: 'image/png',
})
// Download → Blob
const { data, error: dlErr } = await client.storage.download('avatars/user-42.png')
const url = URL.createObjectURL(data!)tsPresigned URLs (upload direto do browser)
Pra arquivos grandes (vídeo, dump), evite proxar bytes pelo InfraHub. Pega URL presigned, browser faz PUT direto no MinIO.
// 1. Pede URL presigned (TTL default 1h, max 24h)
const { url, method } = await client.storage.getUploadUrl('videos/clip.mp4', {
expiresIn: 3600,
})
// 2. Browser faz PUT direto
await fetch(url, {
method,
body: file,
headers: { 'Content-Type': 'video/mp4' },
})
// Download URL similar:
const { url: dlUrl } = await client.storage.getDownloadUrl('videos/clip.mp4')
window.location.href = dlUrltsListar e deletar
// Lista objects de um prefix
const { objects } = await client.storage.list({ prefix: 'avatars/', limit: 100 })
objects.forEach((o) => console.log(o.key, o.size))
// Deleta
await client.storage.delete('avatars/user-42.png')tsBucket policies (público vs privado)
Por default, bucket é privado — só presigned URLs (com expiração) permitem acesso. Pra expor diretório como público (ex: avatares com URLs estáveis), use a aba Arquivos → Policy do projeto ou direto via SDK:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::meu-projeto/public/*"
}
]
}URLs públicas ficam em https://<slug>.<STORAGE_PUBLIC_DOMAIN>/<bucket>/<path>
Limites
- Tamanho de arquivo via SDK helper: ~25 MB recomendado (proxa pelo Next). Acima disso, use presigned URL.
- Presigned URL TTL: 60s a 24h
- Quota total de storage: por plano (ver Limites & Quotas)
Realtime usa Server-Sent Events (SSE), não WebSocket. Funciona via HTTPS direto (sem custom server), reconexão automática do EventSource. Backend anexa um trigger Postgres (infraforge_realtime) na tabela e usa pg_notify pra streamar mudanças.
1. Ativar realtime numa tabela
No painel do projeto, aba Realtime, encontra a tabela e clica Ativar. Cria o trigger AFTER INSERT/UPDATE/DELETE chamando realtime.notify_change().
2. Subscribe no SDK
// Inscreve em mudanças da tabela 'posts' (schema 'public')
const sub = client.realtime.subscribe('posts', (change) => {
console.log(change.op, change.new ?? change.old)
// change.op: 'INSERT' | 'UPDATE' | 'DELETE'
// change.new: row inserida/atualizada (null pra DELETE)
// change.old: row antes do UPDATE/DELETE (null pra INSERT)
})
// Filtra por operação:
client.realtime.subscribe('posts', cb, { event: 'INSERT' })
client.realtime.subscribe('posts', cb, { event: ['INSERT', 'UPDATE'] })
// Outro schema (default 'public'):
client.realtime.subscribe('orders', cb, { schema: 'analytics' })
// Cancelar
sub.unsubscribe()tsComo a auth do SSE funciona
Browser EventSource não permite custom headers, então credenciais não vão direto na URL (era um bug de segurança CWE-598). Em vez disso:
- SDK faz
POST /api/realtime/[slug]/sessioncom headers normais → recebesse_tokenefêmero (1h) - Abre
EventSourcecom?sse_token=... - Token tem
purpose:'sse'— se vazar em log de proxy, só dá pra subscribe a esse endpoint, nada mais.
SDKs server-side (Python, Go, Java) mandam headers direto, sem precisar do handshake.
Limites
- 16 canais por conexão SSE
- 20 conexões SSE simultâneas por projeto (cada uma consome 1 client do pool PG)
- ~8KB de payload por evento (pg_notify limite). Rows maiores chegam com
truncated:true— cliente busca viaclient.db.query() - sse_token expira em 1h. Reconexão automática reaproveita o mesmo token; após 1h, faz nova handshake
CLI oficial pra desenvolvedor: introspecção do schema pra gerar tipos TypeScript, executar SQL admin, rodar migrations versionadas em git.
Instalação e setup
npm install -g @infraforge/cli # Salva URL + API key globalmente em ~/.infraforge/config.json (chmod 600) infraforge login # Linka o diretório atual a um projeto (.infraforge/config.json no cwd) infraforge link meu-projeto
Override por env: INFRAFORGE_URL, INFRAFORGE_API_KEY, INFRAFORGE_PROJECT. Override por flag: --url, --api-key, --project.
Type generation (Database TypeScript types)
infraforge gen types -o src/database.ts
Lê o schema do projeto (tabelas, colunas com tipos PG, PKs, FKs, enums) e gera interface TypeScript no estilo Supabase:
import type { Database, Tables, TablesInsert } from './database'
type Post = Tables<'public', 'posts'>
type NewPost = TablesInsert<'public', 'posts'>
const newPost: NewPost = {
title: 'Hello',
// colunas com default são opcionais
}tsMapeia 30+ tipos PG (incluindo arrays _int4 e enums custom como union types).
Executar SQL admin
# Inline infraforge sql "SELECT count(*) FROM posts" # Arquivo infraforge sql -f schema.sql # Stdin echo "TRUNCATE old_data" | infraforge sql # JSON output infraforge sql "SELECT * FROM posts" --json
Modo admin: bypass RLS automático. Usa apenas a API key (sem JWT). Quem tem a API key tem acesso total ao projeto via CLI.
Migrations versionadas em git
# Cria infraforge/migrations/<timestamp>_<nome>.sql infraforge migrations new add_posts_table # Aplica migrations pendentes em ordem alfabética infraforge migrations up
Estado é mantido na tabela _infraforge_migrations do banco do projeto (id, applied_at). Re-rodar pula aplicadas.
⚠ Painel vs CLI: o painel usa um sistema separado (tabela migrations no meta-DB). Hoje os dois não se sincronizam. Migrations aplicadas via CLI não aparecem no painel e vice-versa. Item §6.4 do roadmap propõe unificar.
Se você já tem Postgres rodando (na sua VPS, RDS, Neon, Supabase, Hetzner Managed DB, etc.), pode conectar ao InfraHub em vez de deixar a plataforma spawnar um container novo. Vai em Meus bancos → Novo banco.
Pré-requisitos importantes:
- O servidor InfraHub precisa alcançar a porta do seu PG (geralmente 5432). Se firewall fechado, libere o IP de saída do InfraHub — está visível no card no topo de Meus bancos.
- Usuário PG da connection URL precisa de
CREATE SCHEMA/CREATE TABLE/CREATE FUNCTIONno banco destino. Crie um user dedicado. - O InfraHub cria schemas
auth(tabelaauth.users+ funções helper) erealtime. Se já tem com estrutura diferente, pode dar conflito.
Tutorial completo (firewall por sistema, criar user PG dedicado, diagnosticar conflitos com SQL pronto, soluções por cenário):
Após cadastrar o banco, ao criar projeto novo escolha modo external e selecione esse banco. Backups (pg_dump), migrations, SQL editor, RLS, type gen via CLI e Realtime passam a operar contra ele. Hospedagem da app continua na sua infra — InfraHub não hospeda apps ainda.
Existem 2 caminhos pra aplicar migrations no banco do projeto. Eles fazem a mesma coisa (executar SQL) mas usam tabelas de controle diferentes — importante saber o trade-off.
Painel — aba Migrations
Cola SQL no textarea, dá nome, click Aplicar:
- Grava em
migrations(tabela no meta-DB do InfraHub) - Histórico fica visível no painel
- Sem versionamento de arquivo, sem idempotência (re-rodar mesmo SQL falha 2× em CREATE TABLE)
- Sem “down” — só forward. Pra desfazer, escreve outra migration manual
- Bom pra: hotfix rápido, mudança one-off, exploração
CLI — `infraforge migrations`
# Cria infraforge/migrations/<timestamp>_<nome>.sql infraforge migrations new add_orders_table # Aplica pendentes em ordem alfabética (idempotente) infraforge migrations up
- Lê
.sqldeinfraforge/migrations/(cwd) - Estado em
_infraforge_migrationsno banco do projeto - Idempotente — re-rodar pula migrations já aplicadas
- Versionável em git → revisão de PR
- Bom pra: produção, time, fluxo CI/CD
⚠ Gap entre os dois
Painel e CLI usam tabelas de controle diferentese não se sincronizam. Se você aplicar migration via CLI, o painel mostra “0 migrations”. Vice-versa idem.
Item §6.4 do roadmap propõe unificar (ambos gravando em _infraforge_migrations no banco do projeto). Por enquanto, escolha um caminho e fique nele.
Boas práticas
- Use
CREATE TABLE IF NOT EXISTSeALTER TABLE ... ADD COLUMN IF NOT EXISTSpra garantir idempotência manual mesmo no painel - Prefira CLI pra qualquer projeto que vá pra produção
- Evite queries enormes numa migration só — divide em arquivos
- Backup antes de migration que toca dados (não só DDL)
- Teste em projeto staging antes (BYO DB com banco separado pra dev)
API key vs JWT — escopo de cada um
O InfraHub tem 2 tipos de credencial. Saber a diferença evita acidentes.
- API key (
if_xxx...): identifica o projeto. Acompanha toda request server-side via headerx-infraforge-key. Vai no app cliente também (browser SDK precisa). Não é segredo absoluto — mas trate como secreta-ish (não cole em commit público de exemplo). - JWT do end-user: assinado com
jwtSecretdo projeto, identifica QUAL user fez a request. Fica no headerAuthorization: Bearer <jwt>. Backend valida em cada call e injeta como contexto pra RLS. - JWT secret: assina os JWTs. Nunca expor em código cliente. Só fica no InfraHub e no backend do app que precisar emitir tokens externos.
RLS — primeira linha de defesa
Sempre habilite RLS em qualquer tabela com dados por usuário:
ALTER TABLE minha_tabela ENABLE ROW LEVEL SECURITY; -- Sem POLICY, ninguém vê nada (negação por default). -- Adicione policies USING / WITH CHECK explícitas: CREATE POLICY "select_own" ON minha_tabela FOR SELECT USING (owner_id = auth.uid());sql
⚠ Tabela sem RLS = tudo público pra qualquer user autenticado. Tem aba RLS no painel pra auditar quais tabelas estão protegidas.
Bypass RLS — só pelo painel/CLI
Toggle Bypass RLS em Configurações dura 1h e libera SQL Editor + Tables + CLI a ignorar policies. SDK do app continua respeitando RLS — end-users não veem dados além das policies, mesmo com bypass ligado.
OAuth — proteção de session fixation
O fluxo OAuth do SDK gera um state random salvo em sessionStorage antes de redirecionar pro provider. Backend roundtripa o state via state assinado HMAC (TTL 10min). No callback, SDK compara — se mismatch, rejeita. Bloqueia ataque de atacante forjar link #token=ATTACKER_JWT pra fixar sessão na vítima.
Sessão no browser — escolha do storage
JWT da sessão é persistido em localStorage por default. Pra apps com dados muito sensíveis, escolha:
createClient({
url, projectSlug, apiKey,
session: 'localStorage', // (default) persiste entre tabs
// session: 'sessionStorage',// só durante a aba aberta
// session: 'memory', // nada gravado, refresh = re-login
})localStorage: melhor UX, vulnerável a XSS de leitura no app clientesessionStorage: blast radius menor (limpa quando fecha aba)memory: imune a XSS de leitura, mas usuário precisa logar de novo a cada refresh
Realtime — token efêmero
SSE auth usa sse_token curto-vivo (1h, purpose:'sse') em vez de credencial de longa duração na URL. Se vazar em log, expira rápido e só dá pra subscribe a esse endpoint do projeto, nada mais.
Hardening que está NA SUA mão (no app cliente)
- Sanitizar input — XSS é bug do APP, não do framework
- CSP restrita (Content-Security-Policy)
- HTTPS obrigatório (HSTS — InfraHub já manda esse header)
- Rate limit no seu lado se relevante (InfraHub tem por IP global, mas você pode ter cota por feature)
- Não logue JWT em error tracker (Sentry, Datadog) — adicione filter
Quotas concretas estão na página Plano e uso → Planos (sempre atualizada). Resumo dos limites técnicos da plataforma (independente de plano):
| Recurso | Limite | Onde |
|---|---|---|
| Pool de conexões PG por projeto | 20 | MAX_CONNECTIONS_PER_PROJECT |
| Query timeout | 5 min | QUERY_TIMEOUT_MS |
| Tamanho de query SQL | 50.000 chars | /api/query schema |
| SQL admin (CLI/migrations) | 500.000 chars | /api/admin/sql |
| Conexões SSE simultâneas | ~20 / projeto | limitado pelo pool PG |
| Canais por conexão SSE | 16 | MAX_CHANNELS |
| Payload pg_notify (Realtime) | ~8 KB | Postgres limit |
| Presigned URL TTL | 60s a 24h | opção do upload-url/download-url |
| Backup retention (storage S3) | por plano | getBackupRetentionDays() |
| RLS bypass TTL | 60 min | auto-desliga |
| OAuth state HMAC TTL | 10 min | link OAuth válido por isso |
Limites por plano (projetos, BYO databases, storage GB, backup minutes/mês) são configuráveis e vivem em src/lib/plans.ts. Veja os valores reais na página de Planos.
| Linguagem | Package | Instalar | Features |
|---|---|---|---|
| TypeScript / JavaScript | @infraforge/sdk | npm install @infraforge/sdk | Auth, DB, Storage, Realtime, OAuth com state validation, multi storage backend |
| Python | infraforge | pip install infraforge | Auth, DB, Storage, Realtime via SSE (headers nativos) |
| Go | github.com/seu-org/infraforge-go | go get github.com/seu-org/infraforge-go | Auth, DB, Storage (Realtime planejado) |
| PHP | infraforge/sdk | composer require infraforge/sdk | Auth, DB, Storage |
| Ruby | infraforge | gem install infraforge | Auth, DB, Storage |
| Java | com.infraforge:sdk | Maven / Gradle | Auth, DB, Storage |
| C# / .NET | InfraForge.Sdk | dotnet add package InfraForge.Sdk | Auth, DB, Storage |
SDK TypeScript é o mais completo (Realtime com handshake de SSE token, OAuth state validation, multi storage backend). Outros SDKs cobrem o essencial (auth + DB + storage). Realtime nos SDKs server-side usa headers nativos — sem necessidade do handshake do browser.
Versões e changelogs vivem nos READMEs dos pacotes. Pra acompanhar bumps, seguir o repo do InfraHub.
Erro "connect ECONNREFUSED" ao cadastrar BYO Database▾
Firewall do seu Postgres está bloqueando o IP do InfraHub. Libere o IP de saída visível no card azul de Meus bancos.
Veja a seção BYO Database pra setups de firewall por sistema (UFW, pg_hba.conf, painéis cloud).
Erro "auth.users já existe" ao conectar BYO Database▾
Seu banco já tem schema auth de outro projeto/framework. Soluções:
- Renomear:
ALTER SCHEMA auth RENAME TO meu_auth - Criar banco separado pro InfraHub no mesmo PG (recomendado)
Veja BYO Database pra detalhes + SQL pronto pra diagnose.
RLS bloqueando query inesperadamente (0 rows quando deveria ter)▾
Default do PG: tabela com RLS ON e sem policy = bloqueia tudo (negação por default). Provável causa:
- Tabela tem
ENABLE ROW LEVEL SECURITYmas sem POLICY - POLICY existe mas a condição não bate (ex:
auth.uid()retornando NULL porque JWT não temsub) - User tem role diferente do esperado pela policy
Diagnose: ative bypass RLS (Configurações → 1h), rode a query — se voltar dados, é policy. Pra debug fino, no SQL editor:
-- Lista policies da tabela
SELECT * FROM pg_policies WHERE tablename = 'sua_tabela';
-- Confere que JWT context tá injetado
SELECT current_setting('request.jwt.sub', true), auth.uid();sql504 em query longa (export, backup, migration grande)▾
Backend do InfraHub aceita queries até 5 min. Se ainda bate timeout, provavelmente é o reverse proxy (Traefik/nginx) com timeout menor.
Adicione no docker-compose.yaml do InfraHub:
labels: - 'traefik.http.middlewares.app-timeout.buffering.responseTimeout=300s' - 'traefik.http.routers.app.middlewares=app-timeout'
EventSource fica reconectando infinitamente (Realtime)▾
Causas comuns:
sse_tokenexpirou (TTL 1h). SDK reconecta com mesmo token → 401 → loop. Workaround:sub.unsubscribe()+ novosubscribe()renova handshake.- Pool PG do projeto saturado (20 conexões). Fecha alguma SSE não usada ou aumenta o pool.
- Tabela do canal sem trigger realtime ativo. Confirma na aba Realtime do projeto.
CLI "JWT inválido ou expirado"▾
CLI usa só API key, não JWT. Esse erro vem se você chamou infraforge sql sem login (rode infraforge login primeiro) ou se a API key salva está errada/revogada.
Confere com: cat ~/.infraforge/config.json
Browser reclama que URL do storage tem hostname Docker interno▾
O projeto não migrou pro endpoint público ainda. Vai em Configuraçõesdo projeto → “Expor storage publicamente” → click. ~30s de downtime do MinIO. Próximas presigned URLs já vêm com o subdomínio correto.
Pré-requisito: env STORAGE_PUBLIC_DOMAIN setado no servidor.
Form de cadastro de banco vem auto-preenchido com email/senha▾
Já corrigido na versão atual (autoComplete=“off” + new-password nos campos sensíveis). Se ainda acontece, pode ser cache do browser — recarrega com Ctrl+Shift+R.