Skip to content

fix(portal): reload to the portal login when its session expires - #29

Open
rodrigopg wants to merge 2 commits into
mainfrom
fix/portal-session-expired
Open

rodrigopg wants to merge 2 commits into
mainfrom
fix/portal-session-expired

Conversation

@rodrigopg

Copy link
Copy Markdown

An expired portal session answers /api/* with 401, which the app read as its own token gate and drew the old AccessGate (Pair this device). A 401 on a page the portal signed in now reloads, and the portal serves its own login on the same URL. Remote-PC 401s are ignored.

🤖 Generated with Claude Code

rodrigopg and others added 2 commits November 6, 2026 08:23
An expired portal session answers /api/* with 401, which the app read as its
own token gate and drew the old AccessGate. A 401 on a page the portal signed
in now reloads, and the portal serves its own login on the same URL.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@gianlukamoraes gianlukamoraes left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revisão automática — PR #29

Resumo: a correção faz o que promete. Quando a sessão do portal expira, a próxima chamada local que recebe 401 recarrega a página, e o portal mostra o próprio login em vez do antigo "Pair this device". O 401 de um PC remoto fica de fora. O teste do arquivo passa e o typecheck também. Fica um ponto não bloqueante: se o próprio herdr responder 401 enquanto o portal ainda está válido, a página entra num ciclo de recarga.

  • 🟡 Pode entrar em ciclo de recarga. O código parte do princípio de que todo 401 local, atrás do portal, vem do portal. Mas o servidor do herdr também devolve 401 por conta própria (token trocado, dispositivo não pareado). Nesse caso a página recarrega, o portal responde de novo que está autenticado, o mesmo 401 volta e a página recarrega outra vez, sem parar. Correção barata: antes de recarregar, perguntar de novo ao /api/portal/me e só recarregar se ele não disser mais authenticated: true.
  • ⚪ Sem CI neste PR (nenhum check rodou) e o gate do pr-monitor ainda não tem matriz para este repo. Rodei manualmente o teste do arquivo alterado e o tsc.
Detalhe técnico

🟡 Ciclo de recarga quando o 401 é do próprio herdr (src/lib/portal.ts:40, src/lib/api.ts:134)

  • O portalSeen é estado de módulo e volta a true em cada carga da página, assim que fetchPortalSession recebe {authenticated:true} (portal.ts:63). O reset para false dentro de reloadIfPortalExpired só protege a carga atual.
  • O herdr gera 401 sozinho: server/index.ts:1260 (unauthorizedJson(access.reason | "token_required")), server/auth.ts:90, além de /api/auth e /api/devices/pair (auth.ts:282, devices.ts:225), que também passam por errorFrom com URL local.
  • Cenário: o portal está válido, mas o herdr atrás dele recusa (token rotacionado, devices.gated ligado, identidade do proxy mudou). O /api/portal/me responde 200 com authenticated, portalSeen vira true, o poll de fetchMachines recebe 401 e a página recarrega. Na carga seguinte acontece o mesmo, para sempre. Antes do PR o usuário via o AccessGate, o que pelo menos dava diagnóstico.
  • Correção sugerida: em reloadIfPortalExpired, para status 401 com portalSeen, refazer fetchPortalSession() e recarregar só se portal === false. Se o portal ainda autentica, o 401 é do herdr e segue o fluxo atual (setLocked). Como a função passa a ser assíncrona, ela é chamada com void em errorFrom. Alternativa: distinguir pelo corpo, já que o 401 do herdr traz {error:{code:"unauthorized"|"invalid_token"|"invalid_code"}}, se o 401 do portal tiver outro formato (não verificado, o portal é outro repo).

Menores

  • Entre o 401 e o reload(), o load() do App.tsx:339 ainda chama setLocked(true), então o AccessGate pode piscar por um frame antes da recarga. Cosmético.
  • O teste não cobre a exclusão de /api/machines/ em api.ts:134. Typo no nome do teste: "for for".

Verificado limpo

  • machinePath(LOCAL_MACHINE, …) gera /api/<path> (shared/machines.ts:96), então as rotas locais entram e as remotas (/api/machines/<id>/) ficam de fora, como o comentário diz.
  • Todas as respostas não-ok de getJson/sendJson passam por errorFrom, inclusive o poll de fetchMachines, que é o que dispara o gate hoje.
  • Antes de o portal responder (ou sem portal) portalSeen é false, então nada muda para quem usa sem portal.
  • bun test src/lib/portal.test.ts: 10 pass / 0 fail; tsc --noEmit: ok (worktree na head 8bb4a1d).

Modo revisor: não faço merge. Do lado do código está aceitável para merge (o 🟡 pode ir agora ou num follow-up); falta o merge humano.

@gianlukamoraes gianlukamoraes added the needs-owner Reviewed by pr-monitor; waiting for a human to decide or merge label Nov 7, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-owner Reviewed by pr-monitor; waiting for a human to decide or merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants