[{"data":1,"prerenderedAt":1200},["ShallowReactive",2],{"blog-pt-email-link-is-invalid-or-has-expired":3,"blog-index-pt":673},{"id":4,"title":5,"body":6,"category":628,"cover":629,"coverAlt":630,"description":631,"draft":632,"extension":633,"faq":634,"image":653,"keywords":654,"meta":661,"navigation":662,"ogTitle":663,"path":664,"published":665,"seo":666,"stem":667,"tldr":668,"updated":665,"__hash__":672},"blog_pt\u002Fblog\u002Femail-link-is-invalid-or-has-expired.md","Supabase \"Email link is invalid or has expired\" num link novo",{"type":7,"value":8,"toc":615},"minimark",[9,13,24,27,30,38,43,46,61,85,92,96,99,114,117,123,126,142,146,149,161,178,190,194,197,206,209,215,222,226,233,240,246,257,321,329,333,336,342,348,381,387,393,404,408,411,421,427,430,436,442,455,458,464,471,490,495,498,504,510,514,517,526,540,549,569,573,604],[10,11,12],"p",{},"Um usuário novo se cadastra, abre o e-mail de confirmação um minuto depois e\naperta o botão. O seu app abre na página inicial, ainda sem login, e a barra de\nendereços termina assim:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","#error=access_denied&error_code=otp_expired&error_description=Email+link+is+invalid+or+has+expired\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26],{},"Você se cadastra com o seu próprio endereço para conferir. O link funciona de\nprimeira, e em todas as tentativas depois.",[10,28,29],{},"O Supabase diz \"Email link is invalid or has expired\" quando o token de um link\nde e-mail já foi usado, foi substituído ou passou da sua hora. Num link que\nchegou há um minuto, o motivo comum é que alguma coisa o usou antes do seu\nusuário: um filtro de e-mail que abre os links para verificá-los. O seu app\nfeito no Lovable, Bolt, v0, Cursor, Claude Code ou Replit está funcionando do\njeito que foi construído, e o e-mail que ele envia pode ser mudado para que um\nfiltro não tenha mais nada para gastar.",[10,31,32,33,37],{},"Esta é a parte que o conselho comum erra: ",[34,35,36],"strong",{},"\"peça para tentar de novo\" e \"faça\no link durar mais\" falham os dois diante de um filtro."," Um segundo link passa\npelo mesmo filtro e acaba do mesmo jeito, por mais tempo que fosse durar.",[39,40,42],"h2",{"id":41},"o-que-significa-email-link-is-invalid-or-has-expired-no-supabase","O que significa \"Email link is invalid or has expired\" no Supabase?",[10,44,45],{},"Significa que o Supabase Auth, a parte do Supabase que faz o login das pessoas,\nnão encontrou por trás do link um token que estivesse disposto a aceitar.",[10,47,48,49,56,57,60],{},"A mensagem está escrita no próprio código do servidor do Auth. Na\n",[50,51,55],"a",{"href":52,"rel":53},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fauth\u002Fblob\u002Fmaster\u002Finternal\u002Fapi\u002Fverify.go",[54],"nofollow","função que verifica um link de e-mail",",\nela é devolvida em dois pontos: quando nenhum token pendente bate com o do\nlink, e quando o token que bate foi enviado há mais tempo do que o seu projeto\npermite. As duas respostas são HTTP 403 com o código ",[21,58,59],{},"otp_expired",", então a\nmensagem sozinha não diz qual das duas aconteceu.",[10,62,63,64,67,68,71,72,75,76,79,80,84],{},"O seu usuário nunca vê o 403. O link do e-mail aponta para o endereço do seu\npróprio projeto Supabase, que verifica o token e depois manda o navegador para\no seu app. Quando a verificação falha, o Supabase coloca três coisas na parte\ndo endereço do seu app que vem depois do ",[21,65,66],{},"#",": ",[21,69,70],{},"error=access_denied",",\n",[21,73,74],{},"error_code=otp_expired"," e a própria mensagem. Se o seu app foi configurado com\no fluxo PKCE, elas também aparecem na parte depois do ",[21,77,78],{},"?",". Enquanto nenhuma\npágina do seu app as ler, a pessoa vê o que o seu app mostra a um visitante sem\nlogin, sem uma palavra sobre o motivo. Uma página de redefinição de senha que\nmostra o formulário mesmo assim falha ao salvar com \"Auth session missing!\",\nporque o link não fez o login de ninguém, e\n",[50,81,83],{"href":82},"\u002Fblog\u002Fauth-session-missing","essa mensagem tem um artigo próprio",".",[10,86,87,88,84],{},"Pense no link como um ingresso que deixa uma pessoa entrar uma única vez. Quem\no entrega primeiro passa, e o ingresso é furado. \"Email link is invalid or has\nexpired\" é a pessoa na porta segurando um ingresso que já tem um furo, ou um\ncujo horário já passou. Se os seus usuários entram e mais tarde são\ndesconectados, é outro erro, com\n",[50,89,91],{"href":90},"\u002Fblog\u002Fsupabase-users-logged-out","uma página própria",[39,93,95],{"id":94},"por-que-um-link-enviado-há-um-minuto-já-foi-usado","Por que um link enviado há um minuto já foi usado",[10,97,98],{},"Porque alguma coisa o abriu antes do seu usuário, e abrir um link de e-mail do\nSupabase é o mesmo que usá-lo.",[10,100,101,102,107,108,113],{},"O\n",[50,103,106],{"href":104,"rel":105},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fauth-email-templates",[54],"guia de modelos de e-mail","\ndo Supabase coloca isso entre as limitações: alguns provedores de e-mail têm\ndetecção de spam ou outros recursos de segurança que abrem antecipadamente os\nlinks dos e-mails recebidos. O exemplo que ele dá é o Safe Links do Microsoft\nDefender for Office 365. A\n",[50,109,112],{"href":110,"rel":111},"https:\u002F\u002Fsupabase.com\u002Fkb\u002Ftroubleshooting\u002Fotp-verification-failures-token-has-expired-or-otp_expired-errors",[54],"página do Supabase para resolver este erro","\nchama essa abertura antecipada de motivo mais comum para um token parecer\nexpirado ou inválido antes mesmo de o usuário conseguir usá-lo.",[10,115,116],{},"O link padrão não tem nenhum passo entre abrir e usar. O endereço dele é o\nendpoint de verificação do seu projeto, que confere o token e o gasta na\nprimeira visita, seja de quem for. Então o filtro entrega o ingresso enquanto a\nmensagem chega, e ele passa. O seu usuário clica um minuto depois e é barrado\nna porta.",[118,119],"diagram",{"alt":120,"caption":121,"src":122},"Um e-mail com um ingresso dentro. Dele saem dois caminhos para a mesma porta. O primeiro, de uma lupa que representa o filtro de e-mail, chega logo à porta e recebe um visto. O segundo, da pessoa, chega depois e recebe um X vermelho, porque o ingresso agora tem um furo.","A primeira visita usa o link. Um filtro de e-mail que o abre para verificá-lo chega antes da pessoa.","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fwho-used-it-first-1600x720.png",[10,124,125],{},"Daí saem duas coisas.",[127,128,129,136],"ul",{},[130,131,132,135],"li",{},[34,133,134],{},"Você não vai ver isso acontecer."," Acontece com pessoas cujo e-mail passa\npor um filtro desse tipo, e a sua própria caixa pode nunca ser uma delas. Os\nseus cadastros de teste continuam funcionando enquanto os seus usuários\nescrevem para você.",[130,137,138,141],{},[34,139,140],{},"A conta normalmente já está confirmada."," Numa confirmação de cadastro, a\nvisita do filtro conta: o Supabase confirma o endereço de quem abrir o link.\nSe um filtro o abriu, a conta está confirmada quando o seu usuário vê o erro,\ne ele pode entrar com a senha que escolheu.",[39,143,145],{"id":144},"as-outras-três-causas","As outras três causas",[10,147,148],{},"Cada uma produz a mesma mensagem, e cada uma pede uma coisa diferente de você.",[10,150,151,154,155,160],{},[34,152,153],{},"O link já foi usado."," O seu usuário o apertou uma vez, talvez no celular, e\nagora aperta o mesmo e-mail no notebook. Quando um link funciona, o Supabase\napaga todos os links que ainda estavam pendentes para aquela conta. O\n",[50,156,159],{"href":157,"rel":158},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fauth\u002Fblob\u002Fmaster\u002Finternal\u002Fmodels\u002Fuser.go",[54],"código de contas","\ndele faz a mesma limpeza depois de uma confirmação e depois de um link de\nlogin, então um segundo clique não encontra nada. O ingresso foi furado na\nprimeira visita.",[10,162,163,166,167,172,173,177],{},[34,164,165],{},"Um e-mail mais recente o substituiu."," Apertar \"reenviar\" cria um link novo,\ne o Supabase apaga o anterior do mesmo tipo antes de salvá-lo. Isso acontece no\n",[50,168,171],{"href":169,"rel":170},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fauth\u002Fblob\u002Fmaster\u002Finternal\u002Fmodels\u002Fone_time_token.go",[54],"código que cria o token",".\nO primeiro e-mail continua na caixa parecendo normal, e o link dele falha. Só o\ne-mail mais recente funciona. O Supabase também só deixa um endereço pedir esse\ntipo de e-mail uma vez a cada 60 segundos, o que\n",[50,174,176],{"href":175},"\u002Fblog\u002Fsupabase-email-rate-limit-exceeded","o limite de e-mails"," explica por\ncompleto.",[10,179,180,183,184,189],{},[34,181,182],{},"Expirou de verdade."," O\n",[50,185,188],{"href":186,"rel":187},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fauth-email-passwordless",[54],"guia de login sem senha","\ndo Supabase diz que os magic links expiram por padrão depois de 1 hora. A mesma\npágina diz que a configuração Email OTP Expiration também rege a validade dos\nmagic links e dos outros links de e-mail, incluindo os de confirmação,\nrecuperação de senha, troca de e-mail e convite. Quem se cadastra à noite e\nabre o e-mail na manhã seguinte encontra esta mensagem, faça o que fizer o seu\nserviço de e-mail.",[39,191,193],{"id":192},"o-que-acontece-quando-o-link-é-aberto-em-outro-aparelho","O que acontece quando o link é aberto em outro aparelho?",[10,195,196],{},"Se o seu app usa o fluxo PKCE, o outro aparelho falha primeiro com uma mensagem\ndiferente, e esta vem depois.",[10,198,199,200,205],{},"O PKCE divide o ingresso em dois. O\n",[50,201,204],{"href":202,"rel":203},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fsessions\u002Fpkce-flow",[54],"guia de PKCE"," do\nSupabase explica que o code verifier é criado e guardado localmente quando o\nfluxo do Auth começa, e que por isso a troca do código precisa ser iniciada no\nmesmo navegador e no mesmo aparelho onde o fluxo começou. O e-mail leva uma\nmetade. O navegador em que o seu usuário pediu o link fica com o canhoto.",[10,207,208],{},"Aberto num celular, o link passa mesmo assim pela verificação do Supabase, que\no gasta. Depois o seu app procura o canhoto no celular e não encontra nenhum. O\ncliente JavaScript do Supabase avisa isso com as próprias palavras:",[14,210,213],{"className":211,"code":212,"language":19},[17],"PKCE code verifier not found in storage. This can happen if the auth flow was initiated in a different browser or device, or if the storage was cleared.\n",[21,214,212],{"__ignoreMap":23},[10,216,217,218,221],{},"Aí a pessoa volta ao notebook e aperta o mesmo e-mail lá, e o Supabase responde\n\"Email link is invalid or has expired\", porque o celular já fez o ingresso ser\nfurado. Se o endereço em que o seu app abre depois de um link que funciona\ncontém ",[21,219,220],{},"?code=",", o seu app usa PKCE e este caso vale para você.",[39,223,225],{"id":224},"como-descubro-qual-delas-foi","Como descubro qual delas foi?",[10,227,228,229,232],{},"Pergunte ao seu próprio banco de dados. Cada conta do seu projeto é uma linha\nnuma tabela chamada ",[21,230,231],{},"auth.users",", e essa linha registra quando cada tipo de\ne-mail foi enviado e quando o endereço foi confirmado.",[10,234,235,236,239],{},"Abra o ",[34,237,238],{},"SQL Editor"," no painel do Supabase e rode isto com o endereço que a\npessoa passou:",[14,241,244],{"className":242,"code":243,"language":19},[17],"select email,\n       confirmation_sent_at,\n       email_confirmed_at,\n       recovery_sent_at\nfrom auth.users\nwhere lower(email) = lower('person@example.com');\n",[21,245,243],{"__ignoreMap":23},[10,247,248,249,252,253,256],{},"A consulta lê uma linha e não muda nada. ",[21,250,251],{},"confirmation_sent_at"," é quando saiu a\núltima confirmação de cadastro. ",[21,254,255],{},"recovery_sent_at"," é quando saiu o último magic\nlink ou a última redefinição de senha para uma conta que já existia. São os\nmomentos a partir dos quais o Supabase mede a hora, na mesma função que devolve\no erro. Compare com o horário do e-mail que a pessoa apertou.",[258,259,260,273],"table",{},[261,262,263],"thead",{},[264,265,266,270],"tr",{},[267,268,269],"th",{},"O que você vê",[267,271,272],{},"O que significa",[274,275,276,289,300,313],"tbody",{},[264,277,278,286],{},[279,280,281,282,285],"td",{},"Um link de confirmação falhou no primeiro clique, e ",[21,283,284],{},"email_confirmed_at"," está preenchido",[279,287,288],{},"Alguma coisa abriu o link antes da pessoa. A conta está confirmada, então ela pode entrar com a senha dela",[264,290,291,297],{},[279,292,293,294,296],{},"Um link de confirmação falhou, ",[21,295,284],{}," está vazio, e ela apertou mais de uma hora depois do envio",[279,298,299],{},"Expirou. Envie uma confirmação nova",[264,301,302,310],{},[279,303,304,306,307,309],{},[21,305,251],{}," ou ",[21,308,255],{}," é posterior ao e-mail que ela apertou",[279,311,312],{},"Um e-mail mais recente o substituiu. Só o link do mais novo funciona",[264,314,315,318],{},[279,316,317],{},"Um magic link do e-mail mais recente falhou no primeiro clique, dentro da hora",[279,319,320],{},"Alguma coisa o abriu primeiro: um filtro de e-mail, ou a própria pessoa em outro aparelho",[10,322,323,324,328],{},"Se a linha mostra o endereço confirmado e a pessoa ainda não consegue entrar\ncom senha, ",[50,325,327],{"href":326},"\u002Fblog\u002Fsupabase-invalid-login","Invalid login credentials"," é a\npróxima mensagem para ler.",[39,330,332],{"id":331},"o-que-não-mudar-enquanto-você-corrige","O que não mudar enquanto você corrige",[10,334,335],{},"A validade, a chave Confirm email e quem pode confirmar um usuário. Cada um dos\natalhos óbvios faz a mensagem parar e abre alguma coisa.",[10,337,338,341],{},[34,339,340],{},"Deixe a validade em uma hora."," Um filtro abre o link enquanto a mensagem\nchega, então um link que dura uma semana também é usado primeiro por ele. O guia\nde login sem senha do Supabase desaconselha fortemente mais de 86.400 segundos,\nou seja, um dia. Um link de login faz o login de quem o abrir. Quanto mais tempo\nele funciona, mais tempo um e-mail encaminhado, uma caixa compartilhada ou uma\nmensagem antiga no arquivo de alguém são um caminho para dentro daquela conta.",[10,343,344,347],{},[34,345,346],{},"Deixe Confirm email ligado."," Se você desligar, os cadastros deixam de enviar\nqualquer link e este erro para. Também significa que qualquer pessoa pode criar\numa conta com um endereço que nunca controlou.",[10,349,350,353,354,357,358,363,364,367,368,371,372,376,377,84],{},[34,351,352],{},"Não confirme usuários pelo navegador."," Peça a um assistente para \"só marcar\ncomo confirmados\" e ele pode recorrer às funções de administração do Supabase,\nque conseguem definir ",[21,355,356],{},"email_confirm"," numa conta. A\n",[50,359,362],{"href":360,"rel":361},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Freference\u002Fjavascript\u002Fadmin-api",[54],"referência de administração","\ndo Supabase diz que qualquer método em ",[21,365,366],{},"supabase.auth.admin"," exige uma chave\n",[21,369,370],{},"secret"," e que essa chave nunca deve ser exposta no navegador. Uma chave secreta\nno seu frontend lê e grava todas as tabelas do seu banco, digam o que disserem\nas suas regras, para qualquer pessoa que abra o código do seu site. É a chave\nque ",[50,373,375],{"href":374},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","este artigo sobre chaves de API","\nmanda você rotacionar. Se você não tem certeza de que nenhuma escapou para lá, a\nnossa varredura gratuita lê o código que o seu app no ar envia a cada visitante\ne aponta as chaves secretas que encontra ali. Leva cerca de 20 segundos e não\npede conta: ",[50,378,380],{"href":379},"\u002Fsecurity-scanner","escaneie o seu app",[10,382,383,386],{},[34,384,385],{},"Confira qualquer endereço que a sua página de confirmação repassa."," A\nsegunda solução sugerida pelo Supabase é uma página sua com um botão, alcançada\npor um link neste formato:",[14,388,391],{"className":389,"code":390,"language":19},[17],"\u003Ca href=\"{{ .SiteURL }}\u002Fconfirm-signup?confirmation_url={{ .ConfirmationURL }}\">\n",[21,392,390],{"__ignoreMap":23},[10,394,395,396,399,400,403],{},"O botão leva então para onde ",[21,397,398],{},"confirmation_url"," disser. Feita sem conferência,\nessa página manda qualquer pessoa que siga um link adulterado para qualquer\nendereço, com o nome do seu app. Ou você confere se o endereço começa com o do\nseu próprio projeto, ",[21,401,402],{},"https:\u002F\u002F\u003Cproject-ref>.supabase.co\u002Fauth\u002Fv1\u002Fverify",", ou usa\na versão com o token da próxima seção, que não faz nenhum endereço passar pela\npágina.",[39,405,407],{"id":406},"como-enviar-um-link-que-um-filtro-de-e-mail-não-consiga-gastar","Como enviar um link que um filtro de e-mail não consiga gastar",[10,409,410],{},"Faça com que abrir o endereço não faça nada, de modo que só entre uma pessoa\nque digita um código ou aperta um botão. O Supabase documenta as duas opções, e\nvocê as muda nos modelos de e-mail, em Authentication no seu painel. Confirm\nsignup e Magic Link são os dois para editar.",[10,412,413,416,417,420],{},[34,414,415],{},"Envie um código."," Coloque ",[21,418,419],{},"{{ .Token }}"," no modelo. O guia do Supabase o\ndescreve como uma senha de uso único de 6 dígitos que pode ser usada no lugar\ndo link. A sua página pede o endereço de e-mail e o código, e então chama:",[14,422,425],{"className":423,"code":424,"language":19},[17],"const { error } = await supabase.auth.verifyOtp({ email, token, type: 'email' })\n",[21,426,424],{"__ignoreMap":23},[10,428,429],{},"Um filtro consegue abrir um e-mail. Não consegue digitar um código na sua\npágina.",[10,431,432,435],{},[34,433,434],{},"Faça um link para uma página com um botão."," Aponte o link para uma página\nsua e leve o hash do token, para que nenhum endereço precise passar:",[14,437,440],{"className":438,"code":439,"language":19},[17],"\u003Ca href=\"{{ .SiteURL }}\u002Fconfirm?token_hash={{ .TokenHash }}&type=email\">Confirm your email\u003C\u002Fa>\n",[21,441,439],{"__ignoreMap":23},[10,443,444,447,448,71,451,84],{},[21,445,446],{},"{{ .SiteURL }}"," é a Site URL da página URL Configuration do seu projeto. Se\nela ainda diz ",[21,449,450],{},"http:\u002F\u002Flocalhost:3000",[50,452,454],{"href":453},"\u002Fblog\u002Fsupabase-auth-redirects-to-localhost","corrija isso primeiro",[10,456,457],{},"A página mostra um botão, e só o botão faz o trabalho:",[14,459,462],{"className":460,"code":461,"language":19},[17],"const params = new URLSearchParams(window.location.search)\n\nasync function onConfirmClick() {\n  const { error } = await supabase.auth.verifyOtp({\n    token_hash: params.get('token_hash'),\n    type: 'email',\n  })\n  if (error?.code === 'otp_expired') showSendNewLink()\n}\n",[21,463,461],{"__ignoreMap":23},[10,465,466,467,470],{},"O tipo ",[21,468,469],{},"email"," serve para os dois modelos: a verificação do Supabase procura o\nhash tanto entre as confirmações de cadastro quanto entre os links de login.",[10,472,473,183,480,485,486,489],{},[34,474,475,476,479],{},"A página não pode chamar ",[21,477,478],{},"verifyOtp"," ao carregar.",[50,481,484],{"href":482,"rel":483},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fauth\u002Fpasswords",[54],"guia de senhas"," do Supabase\nmonta o link de confirmação dele como\n",[21,487,488],{},"{{ .SiteURL }}\u002Fauth\u002Fconfirm?token_hash={{ .TokenHash }}&type=email&next={{ .RedirectTo }}","\ne o verifica no momento em que esse endereço é aberto. Isso é de novo o link\npadrão num endereço novo, e um filtro o abre com a mesma facilidade.",[118,491],{"alt":492,"caption":493,"src":494},"Dois e-mails lado a lado. À esquerda, o link vai direto para a porta: a lupa o abre primeiro e recebe um visto, e a pessoa atrás recebe um X vermelho. À direita, o link abre uma página com um botão: a lupa abre a página e nada acontece, o ingresso continua inteiro, e a pessoa aperta o botão e recebe o visto.","Com o link padrão, abrir já faz o login. Com um botão ou um código digitado, só a ação da pessoa faz.","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fopening-is-not-using-1600x760.png",[10,496,497],{},"O que quer que você mude, algumas pessoas ainda vão dar com um link antigo,\nentão dê a elas uma saída. A página onde os seus links chegam pode ler o erro e\noferecer um e-mail novo:",[14,499,502],{"className":500,"code":501,"language":19},[17],"const hash = new URLSearchParams(window.location.hash.slice(1))\nif (hash.get('error_code') === 'otp_expired') showSendNewLink()\n",[21,503,501],{"__ignoreMap":23},[10,505,506,507,509],{},"Num cadastro, acrescente uma linha dizendo que a pessoa talvez já esteja\nconfirmada e pode tentar entrar. Se um builder escreve as suas páginas de\nlogin, peça a versão com código ou com botão dos dois modelos, uma página de\nchegada que leia ",[21,508,59],{}," e ofereça um link novo, e nenhuma chave de\nadministração em lugar nenhum do app.",[39,511,513],{"id":512},"saber-dos-problemas-antes-que-os-seus-usuários-escrevam","Saber dos problemas antes que os seus usuários escrevam",[10,515,516],{},"Este erro chega aos seus usuários e nunca a você, e outras falhas também chegam\nprimeiro a eles: o app que cai de madrugada, ou um prompt posterior que coloca\numa chave no código do seu app.",[10,518,519],{},[34,520,521,525],{},[50,522,524],{"href":523},"\u002Fwebsite-monitoring","Reeve Monitor"," continua verificando o seu app no ar de fora:",[127,527,528,531,534,537],{},[130,529,530],{},"se ele está no ar, a cada 60 segundos, para separar \"o link não funciona\" de \"o site caiu\"",[130,532,533],{},"as nove verificações a cada hora, em até três apps, incluindo chaves secretas no código que o seu app envia aos visitantes",[130,535,536],{},"uma mensagem quando um resultado muda, para que uma chave que sai num deploy posterior chegue até você sem você precisar ir olhar",[130,538,539],{},"um relatório mensal, em linguagem simples, do que ele viu",[10,541,542],{},[34,543,544,548],{},[50,545,547],{"href":546},"\u002Fsupabase-backups","Reeve Care"," guarda uma cópia do seu banco de dados\nSupabase, contas incluídas:",[127,550,551,554,557,560,563,566],{},[130,552,553],{},"uma cópia criptografada em uma programação, no armazenamento próprio da Reeve na UE, fora da sua conta do Supabase",[130,555,556],{},"o schema auth onde vivem as contas dos seus usuários, para que elas voltem com todo o resto",[130,558,559],{},"cada cópia verificada antes de contar, contando as linhas de cada tabela",[130,561,562],{},"seus arquivos enviados também, assim que você conectar uma credencial do Storage",[130,564,565],{},"uma restauração com um clique, com uma cópia de segurança do estado atual feita antes",[130,567,568],{},"tudo o que o Monitor faz",[39,570,572],{"id":571},"o-que-fazer-agora","O que fazer agora",[574,575,576],"key-takeaways",{},[127,577,578,587,590,595,601],{},[130,579,580,581,583,584,586],{},"Leia a linha da pessoa em ",[21,582,231],{},". Um ",[21,585,284],{}," preenchido numa confirmação que falhou significa que um filtro abriu o link e que ela pode entrar.",[130,588,589],{},"Diga às pessoas para usar o e-mail mais recente e, se o seu app usa PKCE, para abri-lo no aparelho em que o pediram.",[130,591,592,593,84],{},"Mude os modelos Confirm signup e Magic Link para enviar um código, ou para abrir uma página onde um botão chama ",[21,594,478],{},[130,596,597,598,600],{},"Faça a página onde os seus links chegam ler ",[21,599,74],{}," e oferecer um link novo.",[130,602,603],{},"Deixe a validade em uma hora, deixe Confirm email ligado e mantenha a chave secreta fora do navegador.",[10,605,606,607,609,610,614],{},"Se os e-mails nem chegam, ",[50,608,176],{"href":175},"\né a primeira coisa a descartar. O\n",[50,611,613],{"href":612},"\u002Fchecklist","checklist de segurança de 10 minutos"," cobre o que costuma ficar\naberto num app recém-lançado.",{"title":23,"searchDepth":616,"depth":616,"links":617},3,[618,620,621,622,623,624,625,626,627],{"id":41,"depth":619,"text":42},2,{"id":94,"depth":619,"text":95},{"id":144,"depth":619,"text":145},{"id":192,"depth":619,"text":193},{"id":224,"depth":619,"text":225},{"id":331,"depth":619,"text":332},{"id":406,"depth":619,"text":407},{"id":512,"depth":619,"text":513},{"id":571,"depth":619,"text":572},"Noções de segurança","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fcover-1200x630.png","Um envelope aberto com um ingresso saindo pela metade, e o ingresso já tem um furo picotado.","Num link enviado há um minuto, \"Email link is invalid or has expired\" no Supabase costuma significar que um filtro de e-mail o usou antes. Como resolver.",false,"md",[635,638,641,644,647,650],{"q":636,"a":637},"Por que um magic link do Supabase diz que expirou logo depois de ser enviado?","Normalmente porque alguma coisa o abriu antes da pessoa. Alguns serviços de e-mail abrem os links de uma mensagem recebida para verificá-los, e o Supabase cita o Safe Links do Microsoft Defender for Office 365 como exemplo. Abrir um link de e-mail do Supabase é usá-lo, então quando a pessoa clica um minuto depois o token já não existe e o Supabase responde \"Email link is invalid or has expired\".",{"q":639,"a":640},"A conta está confirmada mesmo o link dizendo que expirou?","Se um filtro de e-mail abriu primeiro um link de confirmação de cadastro, sim. O Supabase confirma o endereço para quem abrir o link, então a visita do filtro o confirmou. Veja email_confirmed_at em auth.users: se estiver preenchido, a pessoa pode entrar com a senha que escolheu.",{"q":642,"a":643},"Quanto tempo vale um link de e-mail do Supabase?","Uma hora por padrão. Quem controla é a configuração Email OTP Expiration, e essa mesma configuração vale para magic links, confirmações de cadastro, redefinições de senha, trocas de e-mail e convites. O Supabase desaconselha fortemente qualquer valor acima de 86.400 segundos, ou seja, um dia.",{"q":645,"a":646},"Devo fazer os links durarem mais?","Não. Um filtro de e-mail abre o link quando a mensagem chega, então uma validade maior não o impede de usá-lo primeiro. Ela só mantém funcionando por mais tempo cada link não usado em cada caixa de entrada, e um link faz o login de quem o abrir, inclusive de alguém para quem o e-mail foi encaminhado.",{"q":648,"a":649},"Por que o link funciona no meu computador e falha no meu celular?","Se o seu app usa o fluxo PKCE, o navegador em que a pessoa pediu o link guarda metade do que o login precisa, então abrir o e-mail em outro aparelho falha com \"PKCE code verifier not found in storage\". Essa tentativa também gasta o link, e é por isso que clicar de novo no primeiro aparelho depois diz \"Email link is invalid or has expired\".",{"q":651,"a":652},"O que significa error_code=otp_expired no endereço do meu app?","É a forma como o Supabase Auth avisa que um link de e-mail falhou. Ele manda a pessoa para o seu app com error=access_denied, error_code=otp_expired e a mensagem na parte do endereço que vem depois do sinal #. O seu app pode ler error_code ali e mostrar uma página que ofereça enviar um link novo.","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fcard-800x500.png",[655,656,657,658,659,660,74],"email link is invalid or has expired supabase","email link is invalid or has expired","otp_expired supabase","supabase magic link expirado na hora","link de confirmação supabase não funciona","outlook safe links magic link",{},true,"\"Email link is invalid or has expired\" no Supabase","\u002Fblog\u002Femail-link-is-invalid-or-has-expired","2026-11-01",{"title":5,"description":631},"blog\u002Femail-link-is-invalid-or-has-expired",[669,670,671],"O Supabase mostra \"Email link is invalid or has expired\" quando o token de um link de login ou de confirmação já foi usado, foi substituído por um e-mail mais recente ou tem mais de uma hora. O código de erro é otp_expired.","Num link que a pessoa acabou de receber, a causa comum é um filtro de e-mail que abriu o link para verificá-lo e o gastou ao fazer isso. A própria página de solução de problemas do Supabase chama isso de motivo mais comum.","Aumentar a validade não muda nada. Mude o e-mail para que abrir o endereço não faça nada: envie um código para digitar, ou um link para uma página onde um botão faz o login da pessoa.","GSXs_bYsWj7kPfvdQyU2nZZmoZK2v1gr9DSqJT_p68Q",[674,680,686,692,698,703,709,715,720,721,727,734,740,745,751,757,763,769,775,781,787,792,797,803,809,815,821,827,833,839,845,851,857,863,869,875,881,887,893,899,905,911,917,923,929,935,940,946,952,958,964,970,976,982,988,994,1000,1006,1012,1018,1024,1030,1036,1042,1048,1054,1060,1066,1072,1078,1084,1090,1096,1102,1108,1114,1120,1126,1132,1138,1144,1150,1156,1162,1167,1173,1179,1185,1190,1196],{"path":675,"title":676,"description":677,"published":678,"category":628,"image":679,"draft":632},"\u002Fblog\u002Fmax-client-connections-reached","Supabase \"max client connections reached\", explicado","Supabase \"max client connections reached\": o pooler não tem lugar para mais um cliente. A porta da sua string de conexão define o limite.","2026-11-09","\u002Fblog\u002Fmax-client-connections-reached\u002Fcard-800x500.png",{"path":681,"title":682,"description":683,"published":684,"category":628,"image":685,"draft":632},"\u002Fblog\u002Funsupported-provider-provider-is-not-enabled","Erro do Supabase: \"Unsupported provider: provider is not enabled\"","\"Unsupported provider: provider is not enabled\": o Google está desativado no seu projeto Supabase. Como ativar e corrigir o redirect_uri_mismatch.","2026-11-08","\u002Fblog\u002Funsupported-provider-provider-is-not-enabled\u002Fcard-800x500.png",{"path":687,"title":688,"description":689,"published":690,"category":628,"image":691,"draft":632},"\u002Fblog\u002Fviolates-foreign-key-constraint","Erro do Supabase \"violates foreign key constraint\" ao inserir","Supabase \"insert or update on table violates foreign key constraint\": sua linha aponta para uma linha que não existe. Qual, por quê, e como corrigir.","2026-11-07","\u002Fblog\u002Fviolates-foreign-key-constraint\u002Fcard-800x500.png",{"path":693,"title":694,"description":695,"published":696,"category":628,"image":697,"draft":632},"\u002Fblog\u002Fnull-value-violates-not-null-constraint","Erro do Supabase \"null value in column violates not-null constraint\"","Supabase \"null value in column violates not-null constraint\": uma coluna obrigatória chegou vazia. De onde veio o null, e como corrigir.","2026-11-06","\u002Fblog\u002Fnull-value-violates-not-null-constraint\u002Fcard-800x500.png",{"path":82,"title":699,"description":700,"published":701,"category":628,"image":702,"draft":632},"Supabase \"Auth session missing!\": para onde foi a sessão","\"Auth session missing!\" é o supabase-js sem achar nenhuma sessão logada e sem chegar a perguntar ao Supabase. Para onde foi a sessão, e a correção.","2026-11-05","\u002Fblog\u002Fauth-session-missing\u002Fcard-800x500.png",{"path":704,"title":705,"description":706,"published":707,"category":628,"image":708,"draft":632},"\u002Fblog\u002Finvalid-claim-missing-sub-claim","Supabase \"invalid claim: missing sub claim\": ninguém está logado","\"invalid claim: missing sub claim\": o Supabase Auth recebeu um token autêntico sem nenhum usuário, a chave do projeto no lugar de uma sessão.","2026-11-04","\u002Fblog\u002Finvalid-claim-missing-sub-claim\u002Fcard-800x500.png",{"path":710,"title":711,"description":712,"published":713,"category":628,"image":714,"draft":632},"\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function","\"Failed to send a request to the Edge Function\": a causa real","\"Failed to send a request to the Edge Function\": o navegador não recebeu uma resposta legível. O console dele diz por quê e se a sua função rodou.","2026-11-03","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Fcard-800x500.png",{"path":453,"title":716,"description":717,"published":718,"category":628,"image":719,"draft":632},"Por que o Supabase redireciona links de e-mail para localhost:3000","Se o Supabase redireciona para localhost:3000, a Site URL nunca foi trocada ou o seu endereço falta em Redirect URLs. A solução, e o curinga a evitar.","2026-11-02","\u002Fblog\u002Fsupabase-auth-redirects-to-localhost\u002Fcard-800x500.png",{"path":664,"title":5,"description":631,"published":665,"category":628,"image":653,"draft":632},{"path":722,"title":723,"description":724,"published":725,"category":628,"image":726,"draft":632},"\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout","Supabase \"canceling statement due to statement timeout\", explicado","Supabase \"canceling statement due to statement timeout\": uma consulta passou do limite do seu papel, 3 segundos para um visitante. Veja antes a política RLS.","2026-10-31","\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout\u002Fcard-800x500.png",{"path":728,"title":729,"description":730,"published":731,"category":732,"image":733,"draft":632},"\u002Fblog\u002Fdatabase-error-deleting-user","\"Database error deleting user\" no Supabase, e o que vai junto","\"Database error deleting user\" no Supabase: seu banco recusou a exclusão. O motivo está nos seus logs, e a correção decide o que mais é apagado.","2026-10-30","Backups","\u002Fblog\u002Fdatabase-error-deleting-user\u002Fcard-800x500.png",{"path":735,"title":736,"description":737,"published":738,"category":628,"image":739,"draft":632},"\u002Fblog\u002Fstripe-webhook-no-signatures-found","\"No signatures found matching the expected signature\" no Supabase","O webhook do Stripe numa Edge Function do Supabase diz \"No signatures found matching the expected signature\"? São duas causas. Continue verificando.","2026-10-29","\u002Fblog\u002Fstripe-webhook-no-signatures-found\u002Fcard-800x500.png",{"path":326,"title":741,"description":742,"published":743,"category":628,"image":744,"draft":632},"\"Invalid login credentials\" no Supabase: as três causas","O Supabase diz \"Invalid login credentials\" para senha errada, e-mail desconhecido e conta Google. Como saber qual é, e por que é vago.","2026-10-28","\u002Fblog\u002Fsupabase-invalid-login\u002Fcard-800x500.png",{"path":746,"title":747,"description":748,"published":749,"category":628,"image":750,"draft":632},"\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code","\"Edge Function returned a non-2xx status code\": o erro real","\"Edge Function returned a non-2xx status code\" quer dizer que sua função do Supabase respondeu com um erro. Veja onde ler o erro real.","2026-10-27","\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code\u002Fcard-800x500.png",{"path":752,"title":753,"description":754,"published":755,"category":628,"image":756,"draft":632},"\u002Fblog\u002Fcertificate-not-trusted","Certificado não confiável: três causas além do vencimento","Certificado não confiável mesmo com a data certa? Ele cita outro endereço, assinou a si mesmo ou falta um elo na cadeia. Veja como saber qual.","2026-10-26","\u002Fblog\u002Fcertificate-not-trusted\u002Fcard-800x500.png",{"path":758,"title":759,"description":760,"published":761,"category":628,"image":762,"draft":632},"\u002Fblog\u002Finvalid-input-syntax-for-type-uuid","Supabase \"invalid input syntax for type uuid\": leia as aspas","Supabase \"invalid input syntax for type uuid\": seu app enviou algo que não é um id. Leia as aspas, segure a consulta, mantenha a coluna.","2026-10-25","\u002Fblog\u002Finvalid-input-syntax-for-type-uuid\u002Fcard-800x500.png",{"path":764,"title":765,"description":766,"published":767,"category":628,"image":768,"draft":632},"\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint","Supabase \"duplicate key value violates unique constraint\", explicado","Supabase \"duplicate key value violates unique constraint\": o valor já está em uso. Ache a coluna, escolha uma correção e tire o valor da tela.","2026-10-24","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fcard-800x500.png",{"path":770,"title":771,"description":772,"published":773,"category":628,"image":774,"draft":632},"\u002Fblog\u002Fsupabase-relation-does-not-exist","Supabase \"relation does not exist\" e a tabela está lá","Supabase \"relation does not exist\" ou PGRST205: nenhuma tabela tem esse nome nesse schema. Quatro verificações, e o que expor um schema abre.","2026-10-23","\u002Fblog\u002Fsupabase-relation-does-not-exist\u002Fcard-800x500.png",{"path":776,"title":777,"description":778,"published":779,"category":628,"image":780,"draft":632},"\u002Fblog\u002Fsupabase-function-not-found","Supabase PGRST202: \"Could not find the function in the schema cache\"","Supabase \"Could not find the function in the schema cache\" mostra os nomes de argumentos que seu app enviou. Compare-os com a função.","2026-10-22","\u002Fblog\u002Fsupabase-function-not-found\u002Fcard-800x500.png",{"path":782,"title":783,"description":784,"published":785,"category":628,"image":786,"draft":632},"\u002Fblog\u002Fsupabase-schema-cache-column-error","Supabase PGRST204: \"Could not find the column in the schema cache\"","Supabase \"Could not find the column in the schema cache\" costuma ser um nome que não bate. Quatro verificações e a linha que recarrega.","2026-10-21","\u002Fblog\u002Fsupabase-schema-cache-column-error\u002Fcard-800x500.png",{"path":175,"title":788,"description":789,"published":790,"category":628,"image":791,"draft":632},"Supabase \"Email Rate Limit Exceeded\": dois limites, um só código","Supabase \"Email Rate Limit Exceeded\" limita cada email que seu projeto envia: 2 por hora no remetente embutido. O que conta e como aumentar.","2026-10-20","\u002Fblog\u002Fsupabase-email-rate-limit-exceeded\u002Fcard-800x500.png",{"path":90,"title":793,"description":794,"published":795,"category":628,"image":796,"draft":632},"Supabase \"Invalid Refresh Token: Refresh Token Not Found\": 3 causas","Supabase \"Invalid Refresh Token: Refresh Token Not Found\" desconecta seus usuários. Três causas, e como o log do Auth separa cada uma.","2026-10-19","\u002Fblog\u002Fsupabase-users-logged-out\u002Fcard-800x500.png",{"path":798,"title":799,"description":800,"published":801,"category":628,"image":802,"draft":632},"\u002Fblog\u002Fpermission-denied-for-schema-public","Supabase \"permission denied for schema public\": três causas","Supabase \"permission denied for schema public\" é um grant que falta no schema. O que o removeu, e a linha que o devolve.","2026-10-18","\u002Fblog\u002Fpermission-denied-for-schema-public\u002Fcard-800x500.png",{"path":804,"title":805,"description":806,"published":807,"category":628,"image":808,"draft":632},"\u002Fblog\u002Fsupabase-bucket-not-found","Supabase \"Bucket not found\": as três coisas que isso significa","Supabase \"Bucket not found\" é uma de três coisas: um link público para um bucket privado, um nome que não bate exatamente ou o projeto errado.","2026-10-17","\u002Fblog\u002Fsupabase-bucket-not-found\u002Fcard-800x500.png",{"path":810,"title":811,"description":812,"published":813,"category":628,"image":814,"draft":632},"\u002Fblog\u002Fsupabase-invalid-jwt","Supabase invalid JWT: qual chave parou de funcionar?","Um erro Supabase invalid JWT é o PGRST301: um token assinado não passou na verificação. Num projeto novo isso é a sessão, não a chave de API.","2026-10-16","\u002Fblog\u002Fsupabase-invalid-jwt\u002Fcard-800x500.png",{"path":816,"title":817,"description":818,"published":819,"category":628,"image":820,"draft":632},"\u002Fblog\u002Fsupabase-single-row-error","Supabase \"multiple (or no) rows returned\" quase sempre é nenhuma","O Supabase devolve PGRST116, \"JSON object requested, multiple (or no) rows returned\", quando a consulta não achou nada. Linha escondida parece ausente.","2026-10-15","\u002Fblog\u002Fsupabase-single-row-error\u002Fcard-800x500.png",{"path":822,"title":823,"description":824,"published":825,"category":628,"image":826,"draft":632},"\u002Fblog\u002Fdatabase-error-saving-new-user","\"Database error saving new user\" no Supabase: onde olhar","\"Database error saving new user\" quer dizer que algo preso ao seu cadastro no Supabase falhou e tudo foi desfeito. O erro real está nos seus logs.","2026-10-14","\u002Fblog\u002Fdatabase-error-saving-new-user\u002Fcard-800x500.png",{"path":828,"title":829,"description":830,"published":831,"category":628,"image":832,"draft":632},"\u002Fblog\u002Fwhat-is-supabase","O que é Supabase? O banco de dados que seu builder escolheu","O que é Supabase: o banco de dados, o login e o armazenamento de arquivos do seu app. O Supabase cuida dos servidores; as regras das suas linhas são suas.","2026-10-13","\u002Fblog\u002Fwhat-is-supabase\u002Fcard-800x500.png",{"path":834,"title":835,"description":836,"published":837,"category":628,"image":838,"draft":632},"\u002Fblog\u002Faws-access-key-exposed-in-frontend","Uma chave AWS exposta no seu frontend não é como uma chave OpenAI","Uma chave de acesso da AWS exposta no seu frontend lê e grava no seu armazenamento. A solução costuma ser uma presigned URL, não uma chave nova.","2026-10-12","\u002Fblog\u002Faws-access-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":840,"title":841,"description":842,"published":843,"category":628,"image":844,"draft":632},"\u002Fblog\u002Fstorage-upload-violates-row-level-security","\"Row-level security policy for table objects\" ao enviar","\"New row violates row-level security policy for table objects\" quer dizer que o seu envio não tem regra insert. Deixar o bucket público não cria nenhuma.","2026-10-11","\u002Fblog\u002Fstorage-upload-violates-row-level-security\u002Fcard-800x500.png",{"path":846,"title":847,"description":848,"published":849,"category":628,"image":850,"draft":632},"\u002Fblog\u002Fbase44-security-scan","Scan de segurança do Base44: o que só ele enxerga","O scan de segurança do Base44 verifica sete tipos de problema por dentro. Esta é a metade que ele lê e mais ninguém, e a metade que ele nunca olha.","2026-10-10","\u002Fblog\u002Fbase44-security-scan\u002Fcard-800x500.png",{"path":852,"title":853,"description":854,"published":855,"category":628,"image":856,"draft":632},"\u002Fblog\u002Fapi-key-leaked-what-to-do","Sua chave de API vazou. A ordem em que agir","Uma chave de API vazou e você não sabe o que fazer primeiro. Nem toda chave no seu frontend é um vazamento, e a ordem importa mais que a pressa.","2026-10-09","\u002Fblog\u002Fapi-key-leaked-what-to-do\u002Fcard-800x500.png",{"path":858,"title":859,"description":860,"published":861,"category":628,"image":862,"draft":632},"\u002Fblog\u002Finfinite-recursion-in-policy-for-relation","\"Infinite recursion detected in policy\" sem desligar o RLS","\"Infinite recursion detected in policy for relation\" significa que a sua policy perguntou à tabela que ela protege. Veja como quebrar o círculo.","2026-10-08","\u002Fblog\u002Finfinite-recursion-in-policy-for-relation\u002Fcard-800x500.png",{"path":864,"title":865,"description":866,"published":867,"category":628,"image":868,"draft":632},"\u002Fblog\u002Fno-api-key-found-in-request","\"No API key found in request\" no Supabase, e a correção errada","\"No API key found in request\" significa que sua requisição chegou ao Supabase sem chave. Quase toda resposta aponta para as regras do seu banco.","2026-10-07","\u002Fblog\u002Fno-api-key-found-in-request\u002Fcard-800x500.png",{"path":870,"title":871,"description":872,"published":873,"category":628,"image":874,"draft":632},"\u002Fblog\u002Fis-base44-safe","O Base44 é seguro? O que vimos em 5.442 apps Base44","O Base44 é seguro? Rodamos nove verificações em 5.442 apps Base44 no ar. Quase todas tiraram B por motivos que o dono nunca escolheu.","2026-10-06","\u002Fblog\u002Fis-base44-safe\u002Fcard-800x500.png",{"path":876,"title":877,"description":878,"published":879,"category":628,"image":880,"draft":632},"\u002Fblog\u002Fmove-a-secret-to-an-edge-function","Esconder uma chave de API: mova para uma Edge Function do Supabase","Esconder uma chave de API é tirá-la do navegador, e uma Edge Function do Supabase é o menor lugar para guardá-la. Dois passos em volta pesam mais.","2026-10-05","\u002Fblog\u002Fmove-a-secret-to-an-edge-function\u002Fcard-800x500.png",{"path":882,"title":883,"description":884,"published":885,"category":628,"image":886,"draft":632},"\u002Fblog\u002Fenv-file-exposed-on-your-server","Seu arquivo .env está exposto? Os doze caminhos para testar","Seu arquivo .env está exposto no seu próprio servidor web? Doze endereços dizem isso em um minuto, e um acerto significa que tudo nele já é público.","2026-10-04","\u002Fblog\u002Fenv-file-exposed-on-your-server\u002Fcard-800x500.png",{"path":888,"title":889,"description":890,"published":891,"category":628,"image":892,"draft":632},"\u002Fblog\u002Fis-v0-safe","Segurança do v0: os 1.790 aplicativos v0 escaneados tiraram A","A segurança do v0 medida em 1.790 aplicativos v0 no ar: todos tiraram A. Só 17 citavam um banco de dados, e é sobretudo isso que esse A mede.","2026-10-03","\u002Fblog\u002Fis-v0-safe\u002Fcard-800x500.png",{"path":894,"title":895,"description":896,"published":897,"category":628,"image":898,"draft":632},"\u002Fblog\u002Fis-bolt-safe","O Bolt é seguro? O que vimos em 1.123 aplicativos Bolt","O Bolt é seguro? Passamos 1.123 aplicativos Bolt no ar por nove verificações. A hospedagem saiu limpa. Os achados: chaves e tabelas abertas nos aplicativos.","2026-10-02","\u002Fblog\u002Fis-bolt-safe\u002Fcard-800x500.png",{"path":900,"title":901,"description":902,"published":903,"category":628,"image":904,"draft":632},"\u002Fblog\u002Fis-supabase-down-or-your-app","O Supabase caiu ou o problema é o seu app? Como saber","O Supabase caiu ou o problema é o seu app? Um projeto pausado, um banco cheio e um erro seu parecem iguais. Quatro verificações separam os casos.","2026-10-01","\u002Fblog\u002Fis-supabase-down-or-your-app\u002Fcard-800x500.png",{"path":906,"title":907,"description":908,"published":909,"category":628,"image":910,"draft":632},"\u002Fblog\u002Fdoes-supabase-encrypt-my-data","O Supabase criptografa meus dados? Sim. Veja o que isso impede","O Supabase criptografa os dados? Sim: AES-256 em repouso, TLS em trânsito, SOC 2 e ISO 27001. O que cada um cobre, e o vazamento que nenhum deles impede.","2026-09-30","\u002Fblog\u002Fdoes-supabase-encrypt-my-data\u002Fcard-800x500.png",{"path":912,"title":913,"description":914,"published":915,"category":628,"image":916,"draft":632},"\u002Fblog\u002Fsupabase-free-plan-limits","O Supabase é gratuito? Sim, com estes limites e sem backups","Sim: o plano gratuito do Supabase vai até 500 MB de banco e 5 GB de egress, sem nenhum backup. O que para em cada limite e o que muda no Pro de $25.","2026-09-29","\u002Fblog\u002Fsupabase-free-plan-limits\u002Fcard-800x500.png",{"path":918,"title":919,"description":920,"published":921,"category":732,"image":922,"draft":632},"\u002Fblog\u002Ftest-your-supabase-backup","Teste o backup do seu Supabase antes do dia em que precisar","Como testar o backup do seu Supabase: restaurar num projeto de teste, contar as linhas, entrar e procurar a linha que falta num arquivo cortado.","2026-09-28","\u002Fblog\u002Ftest-your-supabase-backup\u002Fcard-800x500.png",{"path":924,"title":925,"description":926,"published":927,"category":732,"image":928,"draft":632},"\u002Fblog\u002Fsupabase-backup-github-action","Backup grátis do Supabase com uma GitHub Action, e a pegadinha","Um backup do Supabase com GitHub Action é grátis e serve para muitos apps. O workflow, a connection string certa e o egress de cada execução.","2026-09-27","\u002Fblog\u002Fsupabase-backup-github-action\u002Fcard-800x500.png",{"path":930,"title":931,"description":932,"published":933,"category":732,"image":934,"draft":632},"\u002Fblog\u002Fdownload-your-supabase-backup","Por que você não consegue baixar seu backup do Supabase","Você não consegue baixar seu backup do Supabase num projeto atual, porque a cópia diária é um snapshot físico. Como conferir e como ter uma cópia só sua.","2026-09-26","\u002Fblog\u002Fdownload-your-supabase-backup\u002Fcard-800x500.png",{"path":936,"title":937,"description":938,"published":933,"category":628,"image":939,"draft":632},"\u002Fblog\u002Fsupabase-permission-denied-for-table","Supabase \"permission denied for table\": o grant que falta","A partir de 30 de outubro, uma tabela nova no Supabase responde \"permission denied for table\" até receber um grant. O e-mail mostra só metade.","\u002Fblog\u002Fsupabase-permission-denied-for-table\u002Fcard-800x500.png",{"path":941,"title":942,"description":943,"published":944,"category":628,"image":945,"draft":632},"\u002Fblog\u002Fopen-api-endpoint-exposed","Um endpoint de API aberto é um problema? Leia o JSON","Seu scan apontou um endpoint de API aberto. Se isso importa depende do que voltou, e a maioria dos que encontramos era da própria plataforma.","2026-09-25","\u002Fblog\u002Fopen-api-endpoint-exposed\u002Fcard-800x500.png",{"path":947,"title":948,"description":949,"published":950,"category":628,"image":951,"draft":632},"\u002Fblog\u002Flovable-security-scan","Escaneamento de segurança do Lovable: o que ele não prova","Escaneamento de segurança do Lovable: o que o Quick scan e o Deep scan checam, quando cada um roda, e a única coisa que nenhum escaneamento por dentro prova.","2026-09-24","\u002Fblog\u002Flovable-security-scan\u002Fcard-800x500.png",{"path":953,"title":954,"description":955,"published":956,"category":732,"image":957,"draft":632},"\u002Fblog\u002Fsupabase-backup-auth-users","Por que seu dump do Supabase não tem usuários dentro","Rode supabase db dump sozinho e você recebe a forma do seu banco e nenhuma das linhas dele, sem o schema auth onde seus usuários moram.","2026-09-23","\u002Fblog\u002Fsupabase-backup-auth-users\u002Fcard-800x500.png",{"path":959,"title":960,"description":961,"published":962,"category":628,"image":963,"draft":632},"\u002Fblog\u002Fdomain-and-certificate-expiry","Domínio expirado, site fora do ar: o que acontece agora","Seu domínio expirou e seu site está fora do ar. O relógio que começa a correr, por que o certificado é o problema menor, e como conferir os dois.","2026-09-22","\u002Fblog\u002Fdomain-and-certificate-expiry\u002Fcard-800x500.png",{"path":965,"title":966,"description":967,"published":968,"category":628,"image":969,"draft":632},"\u002Fblog\u002Fis-lovable-safe","O Lovable é seguro? O que vimos em 18.554 aplicativos Lovable","O Lovable é seguro? Passamos 18.554 aplicativos Lovable no ar por nove verificações. A plataforma foi a mais limpa de cinco. Os achados estavam nos aplicativos.","2026-09-21","\u002Fblog\u002Fis-lovable-safe\u002Fcard-800x500.png",{"path":971,"title":972,"description":973,"published":974,"category":628,"image":975,"draft":632},"\u002Fblog\u002Fvibe-coded-app-security-checklist","O checklist de segurança do vibe coding, em nove verificações","Um checklist de segurança do vibe coding com nove itens, cada um verificável no seu app de fora por qualquer pessoa, e cada um com um teste de uma linha.","2026-09-20","\u002Fblog\u002Fvibe-coded-app-security-checklist\u002Fcard-800x500.png",{"path":977,"title":978,"description":979,"published":980,"category":628,"image":981,"draft":632},"\u002Fblog\u002Fstripe-secret-key-in-frontend","Uma chave secreta da Stripe no frontend pode mover dinheiro","Uma chave secreta da Stripe exposta no seu frontend pode reembolsar, cobrar e ler cada ficha de cliente que você tem. Sua chave pk_live_ é que deve estar aí.","2026-09-19","\u002Fblog\u002Fstripe-secret-key-in-frontend\u002Fcard-800x500.png",{"path":983,"title":984,"description":985,"published":986,"category":628,"image":987,"draft":632},"\u002Fblog\u002Fvite-and-next-public-env-vars","Variáveis de ambiente do Vite expostas: o prefixo diz publique isto","Variáveis de ambiente do Vite expostas no seu app: fizeram o que o prefixo pediu. VITE_ e NEXT_PUBLIC_ significam publique isto, e a IA nunca soube o custo.","2026-09-18","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcard-800x500.png",{"path":989,"title":990,"description":991,"published":992,"category":628,"image":993,"draft":632},"\u002Fblog\u002Fbase44-source-maps","Segurança no Base44: o que um scan aponta e o que é seu para corrigir","Segurança no Base44 em 5.438 apps escaneados: três achados em quase todos são da plataforma, o source map é o selo do Base44, e a sua parte é uma lista curta.","2026-09-17","\u002Fblog\u002Fbase44-source-maps\u002Fcard-800x500.png",{"path":995,"title":996,"description":997,"published":998,"category":628,"image":999,"draft":632},"\u002Fblog\u002Fis-cursor-ai-safe","O Cursor AI é seguro? O editor, o código e o app que você publicou","O Cursor AI é seguro? Três perguntas em uma busca: o que o Cursor guarda, o que o código que ele escreve erra, e se o app que você publicou está aberto.","2026-09-16","\u002Fblog\u002Fis-cursor-ai-safe\u002Fcard-800x500.png",{"path":1001,"title":1002,"description":1003,"published":1004,"category":628,"image":1005,"draft":632},"\u002Fblog\u002Fis-replit-safe","O Replit é seguro? O que vimos em 3.042 aplicativos Replit no ar","O Replit é seguro? Passamos 3.042 aplicativos Replit no ar por nove verificações externas. Os achados estavam no aplicativo publicado, não no host.","2026-09-15","\u002Fblog\u002Fis-replit-safe\u002Fcard-800x500.png",{"path":1007,"title":1008,"description":1009,"published":1010,"category":732,"image":1011,"draft":632},"\u002Fblog\u002Fsupabase-storage-backup","Supabase storage backup: sua cópia do banco não tem nenhum arquivo","Um Supabase storage backup é uma tarefa à parte. Os backups do banco guardam a lista dos seus arquivos e nenhum deles, então cada upload fica quebrado.","2026-09-14","\u002Fblog\u002Fsupabase-storage-backup\u002Fcard-800x500.png",{"path":1013,"title":1014,"description":1015,"published":1016,"category":732,"image":1017,"draft":632},"\u002Fblog\u002Fsupabase-point-in-time-recovery","Point-in-time recovery do Supabase: preço e limites","O point-in-time recovery do Supabase rebobina seu banco para qualquer segundo da última semana. Custa $100 por mês além do Pro e cobre só o banco.","2026-09-13","\u002Fblog\u002Fsupabase-point-in-time-recovery\u002Fcard-800x500.png",{"path":1019,"title":1020,"description":1021,"published":1022,"category":732,"image":1023,"draft":632},"\u002Fblog\u002Fsupabase-project-paused-recover","Projeto Supabase pausado? Seus dados continuam lá","O Supabase pausou seu projeto depois de uma semana sem atividade. Nada foi apagado, Restaurar fica ao lado do nome e você tem um ano antes disso mudar.","2026-09-12","\u002Fblog\u002Fsupabase-project-paused-recover\u002Fcard-800x500.png",{"path":1025,"title":1026,"description":1027,"published":1028,"category":628,"image":1029,"draft":632},"\u002Fblog\u002Fsafest-ai-app-builder","Qual builder de apps com IA é mais seguro? 30.998 apps testados","Qual builder de apps com IA é mais seguro? Escaneamos 30.998 apps no ar do Lovable, Base44, Replit, v0 e Bolt. O builder não é o que decide sua nota.","2026-09-11","\u002Fblog\u002Fsafest-ai-app-builder\u002Fcard-800x500.png",{"path":1031,"title":1032,"description":1033,"published":1034,"category":628,"image":1035,"draft":632},"\u002Fblog\u002Fenable-rls-on-every-supabase-table","Ative Row Level Security em toda tabela do Supabase e comprove","Ativar Row Level Security no Supabase sem política fecha a tabela por completo. Uma política sem a chave ligada não faz nada. Aqui está o SQL, e o teste.","2026-09-10","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fcard-800x500.png",{"path":1037,"title":1038,"description":1039,"published":1040,"category":628,"image":1041,"draft":632},"\u002Fblog\u002Fsupabase-rls-disabled-in-public","Supabase \"RLS disabled in public\": o que o aviso não vê","O Supabase marca \"RLS disabled in public\" como erro. Ele não diz nada sobre a política de leitura que deixa a sua tabela igualmente aberta.","2026-09-09","\u002Fblog\u002Fsupabase-rls-disabled-in-public\u002Fcard-800x500.png",{"path":1043,"title":1044,"description":1045,"published":1046,"category":628,"image":1047,"draft":632},"\u002Fblog\u002Frotate-supabase-service-role-key","Como rotacionar uma chave service_role do Supabase vazada","O Supabase manda fechar o vazamento primeiro. Outros guias mandam rotacionar agora. O que vale depende de para onde a sua chave service_role vazou.","2026-09-08","\u002Fblog\u002Frotate-supabase-service-role-key\u002Fcard-800x500.png",{"path":1049,"title":1050,"description":1051,"published":1052,"category":628,"image":1053,"draft":632},"\u002Fblog\u002Fvibe-coding-security-scanners-compared","Scanners de segurança para vibe coding comparados, o nosso incluído","O melhor scanner de segurança para vibe coding depende de três perguntas que nenhuma lista de recursos responde. Oito ferramentas comparadas, com preços.","2026-09-07","\u002Fblog\u002Fvibe-coding-security-scanners-compared\u002Fcard-800x500.png",{"path":1055,"title":1056,"description":1057,"published":1058,"category":628,"image":1059,"draft":632},"\u002Fblog\u002Fsupabase-security-checker","Supabase security checker: faça você mesmo as cinco verificações","Um Supabase security checker lê o seu app publicado em vez das configurações do projeto. Aqui estão as cinco verificações e como fazer cada uma sozinho.","2026-09-06","\u002Fblog\u002Fsupabase-security-checker\u002Fcard-800x500.png",{"path":1061,"title":1062,"description":1063,"published":1064,"category":628,"image":1065,"draft":632},"\u002Fblog\u002Fvibe-coding-security-scanner","Scanner de segurança para vibe coding: o que ele não vê","Um scanner de segurança para vibe coding lê seu app no ar, de fora. O que isso cobre, as quatro coisas que ele não consegue ver e como ler o resultado.","2026-09-05","\u002Fblog\u002Fvibe-coding-security-scanner\u002Fcard-800x500.png",{"path":1067,"title":1068,"description":1069,"published":1070,"category":628,"image":1071,"draft":632},"\u002Fblog\u002Fmissing-security-headers","Cabeçalhos de segurança faltando: quando isso importa","Cabeçalhos de segurança faltando é o achado que nosso scanner mais imprime. Contra o que eles protegem e quando são a linha menos urgente do relatório.","2026-09-04","\u002Fblog\u002Fmissing-security-headers\u002Fcard-800x500.png",{"path":1073,"title":1074,"description":1075,"published":1076,"category":628,"image":1077,"draft":632},"\u002Fblog\u002Fopenai-api-key-exposed-in-frontend","Sua chave de API da OpenAI está exposta no frontend. Troque-a.","Uma chave de API da OpenAI exposta no frontend não pode ser presa a um domínio. Troque-a hoje, mova a chamada para o seu servidor e limite o gasto.","2026-09-03","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1079,"title":1080,"description":1081,"published":1082,"category":628,"image":1083,"draft":632},"\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps","Chave de API exposta no frontend: o que 30.998 apps entregam","Uma chave de API exposta no seu frontend costuma ser uma chave do Google Maps. Escaneamos 30.998 apps vibe-coded e contamos quais segredos vazam de verdade.","2026-09-02","\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps\u002Fcard-800x500.png",{"path":1085,"title":1086,"description":1087,"published":1088,"category":732,"image":1089,"draft":632},"\u002Fblog\u002Fsupabase-backup-tools-compared","Ferramentas de backup do Supabase comparadas, a nossa incluída","Quatro tipos de ferramenta de backup do Supabase, o que cada uma copia de verdade e quando uma GitHub Action gratuita ganha de pagar, nós inclusive.","2026-09-01","\u002Fblog\u002Fsupabase-backup-tools-compared\u002Fcard-800x500.png",{"path":1091,"title":1092,"description":1093,"published":1094,"category":628,"image":1095,"draft":632},"\u002Fblog\u002Freplit-secrets-explained","Como usar secrets no Replit, e o que ainda assim é publicado","Como usar secrets no Replit: adicionar um, ler de volta e resolver as duas causas do undefined. E as chaves que a ferramenta Secrets não consegue proteger.","2026-08-31","\u002Fblog\u002Freplit-secrets-explained\u002Fcard-800x500.png",{"path":1097,"title":1098,"description":1099,"published":1100,"category":732,"image":1101,"draft":632},"\u002Fblog\u002Fback-up-supabase-free-tier","Backups do plano gratuito do Supabase: como fazer sem terminal","No plano gratuito do Supabase não existe backup nenhum: a cópia é você que faz. Como fazer uma pelo painel e o que a rota do CSV deixa para trás.","2026-08-30","\u002Fblog\u002Fback-up-supabase-free-tier\u002Fcard-800x500.png",{"path":1103,"title":1104,"description":1105,"published":1106,"category":628,"image":1107,"draft":632},"\u002Fblog\u002Fis-supabase-secure","O Supabase é seguro? É. Seu projeto é outra pergunta","O Supabase é seguro? A plataforma é auditada, criptografada e passa por pentests. A documentação deles diz onde isso termina e começam suas configurações.","2026-08-29","\u002Fblog\u002Fis-supabase-secure\u002Fcard-800x500.png",{"path":1109,"title":1110,"description":1111,"published":1112,"category":628,"image":1113,"draft":632},"\u002Fblog\u002Fwhere-to-find-supabase-api-keys","Onde ficam as chaves de API do Supabase: anon, service_role e URL","A URL do projeto, a chave anon e a chave service_role ficam todas numa página do painel. Aqui está onde ela fica, e qual dos quatro valores vai no seu app.","2026-08-28","\u002Fblog\u002Fwhere-to-find-supabase-api-keys\u002Fcard-800x500.png",{"path":1115,"title":1116,"description":1117,"published":1118,"category":628,"image":1119,"draft":632},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","New row violates row-level security policy no Supabase. E agora?","\"New row violates row-level security policy\" significa que o Supabase recusou uma escrita. O conserto rápido reabre a tabela para qualquer um.","2026-08-27","\u002Fblog\u002Fnew-row-violates-row-level-security-policy\u002Fcard-800x500.png",{"path":1121,"title":1122,"description":1123,"published":1124,"category":628,"image":1125,"draft":632},"\u002Fblog\u002Fcors-wildcard-security-risk","Um curinga CORS é um risco de segurança? Quase nunca.","Um curinga CORS é um risco de segurança? Em geral é o padrão do seu builder e não entrega nada que o seu servidor já não desse a qualquer um que pedisse.","2026-08-26","\u002Fblog\u002Fcors-wildcard-security-risk\u002Fcard-800x500.png",{"path":1127,"title":1128,"description":1129,"published":1130,"category":732,"image":1131,"draft":632},"\u002Fblog\u002Fsupabase-branching-is-not-a-backup","Branching do Supabase não é backup. Ele só anda para a frente.","Por que o branching do Supabase não é backup: um branch começa sem os seus dados e um merge só leva o esquema. Para que ele serve e o que usar no lugar.","2026-08-25","\u002Fblog\u002Fsupabase-branching-is-not-a-backup\u002Fcard-800x500.png",{"path":1133,"title":1134,"description":1135,"published":1136,"category":732,"image":1137,"draft":632},"\u002Fblog\u002Fhow-to-restore-a-supabase-backup","Como restaurar um backup do Supabase, e o que quebra depois","Como restaurar um backup do Supabase pelo painel ou por um arquivo de dump, o que a restauração substitui e por que o seu app pode continuar quebrado no fim.","2026-08-24","\u002Fblog\u002Fhow-to-restore-a-supabase-backup\u002Fcard-800x500.png",{"path":1139,"title":1140,"description":1141,"published":1142,"category":628,"image":1143,"draft":632},"\u002Fblog\u002Fsupabase-storage-bucket-public","Seu bucket do Supabase Storage é público. Isso é um problema?","Um bucket público do Supabase Storage significa que quem tem a URL de um arquivo pode abri-lo. Não significa que alguém possa listar o que está lá dentro.","2026-08-23","\u002Fblog\u002Fsupabase-storage-bucket-public\u002Fcard-800x500.png",{"path":1145,"title":1146,"description":1147,"published":1148,"category":628,"image":1149,"draft":632},"\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend","Uma chave de API do Google no frontend é um problema?","Uma chave de API do Google exposta no seu frontend é a chave que nosso scanner mais encontra, e quase sempre está tudo bem. Um ajuste gratuito decide isso.","2026-08-22","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1151,"title":1152,"description":1153,"published":1154,"category":732,"image":1155,"draft":632},"\u002Fblog\u002Fai-agent-deleted-my-database","Um agente de IA apagou meus dados do Supabase. O que eu recupero?","Um agente de IA apagou dados do seu banco. O que você recupera já estava decidido antes de ele rodar, e os próximos minutos decidem o resto.","2026-08-21","\u002Fblog\u002Fai-agent-deleted-my-database\u002Fcard-800x500.png",{"path":1157,"title":1158,"description":1159,"published":1160,"category":628,"image":1161,"draft":632},"\u002Fblog\u002Fcan-anyone-read-your-supabase-database","Qualquer um consegue ler seu banco Supabase? Testamos 3.680 apps","Qualquer um consegue ler seu banco de dados Supabase sem fazer login? Escaneamos 30.998 apps no ar criadas com builders de IA e medimos quantas dizem que sim.","2026-08-18","\u002Fblog\u002Fcan-anyone-read-your-supabase-database\u002Fcard-800x500.png",{"path":1163,"title":1164,"description":1165,"published":1160,"category":628,"image":1166,"draft":632},"\u002Fblog\u002Fsource-maps-exposed-in-production","Source maps expostos: seu app está publicando o código original","Um source map exposto deixa qualquer um ler o código original do seu app, comentários incluídos. A verificação de 30 segundos e o que importa de verdade.","\u002Fblog\u002Fsource-maps-exposed-in-production\u002Fcard-800x500.png",{"path":1168,"title":1169,"description":1170,"published":1171,"category":628,"image":1172,"draft":632},"\u002Fblog\u002Fsupabase-new-api-keys","Supabase publishable key: é a mesma coisa que a anon key?","O Supabase trocou anon e service_role por chaves publicáveis e secretas. Qual delas pertence ao seu app e qual nunca deve estar lá?","2026-08-12","\u002Fblog\u002Fsupabase-new-api-keys\u002Fcard-800x500.png",{"path":1174,"title":1175,"description":1176,"published":1177,"category":732,"image":1178,"draft":632},"\u002Fblog\u002Fdoes-supabase-back-up-my-database","O Supabase faz backup do meu banco de dados? Depende do plano.","O Supabase faz backup do seu banco de dados? Todo dia nos planos pagos, e nada no gratuito. Como descobrir o que você tem e o que essa cópia não cobre.","2026-08-11","\u002Fblog\u002Fdoes-supabase-back-up-my-database\u002Fcard-800x500.png",{"path":1180,"title":1181,"description":1182,"published":1183,"category":628,"image":1184,"draft":632},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","O Supabase Row Level Security está ligado. A tabela continua pública.","Ligar o Supabase Row Level Security não protege uma tabela. Quem protege são as políticas, e a que consertou o seu app pode deixar qualquer um entrar.","2026-08-10","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public\u002Fcard-800x500.png",{"path":1186,"title":1187,"description":1188,"published":1183,"category":732,"image":1189,"draft":632},"\u002Fblog\u002Fversion-history-is-not-a-backup","O histórico de versões não é backup. Ele não desfaz um delete.","Lovable e Bolt guardam um histórico de versões do seu código. Seu banco de dados é um serviço à parte, e voltar atrás não traz os seus dados de volta.","\u002Fblog\u002Fversion-history-is-not-a-backup\u002Fcard-800x500.png",{"path":1191,"title":1192,"description":1193,"published":1194,"category":732,"image":1195,"draft":632},"\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database","Três formas de fazer backup de um banco Supabase, e o que falta","O painel, o pg_dump e um serviço gerenciado. O que cada um salva de verdade, o que deixa de fora sem avisar e qual sobrevive a perder a conta.","2026-08-09","\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database\u002Fcard-800x500.png",{"path":374,"title":1197,"description":1198,"published":1194,"category":628,"image":1199,"draft":632},"Quais chaves de API são seguras no navegador e quais não são","Sua chave anon do Supabase foi feita para ser pública. Sua chave service_role não, e ela ignora todas as regras que você criar. Como diferenciar as duas.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fcard-800x500.png",1794213648830]