diff --git a/config/settings.py b/config/settings.py index 8be9bc3..d405c36 100644 --- a/config/settings.py +++ b/config/settings.py @@ -92,7 +92,7 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ - "rest_framework.authentication.SessionAuthentication", + "portal_api.authentication.PidSessionAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", diff --git a/portal_api/authentication.py b/portal_api/authentication.py new file mode 100644 index 0000000..d7ddec8 --- /dev/null +++ b/portal_api/authentication.py @@ -0,0 +1,19 @@ +from rest_framework.authentication import SessionAuthentication +from rest_framework.request import Request + + +class PidSessionAuthentication(SessionAuthentication): + """`SessionAuthentication` padrão do DRF não define `authenticate_header()` + (devolve `None`) — sem um cabeçalho `WWW-Authenticate` pra anunciar, o DRF + rebaixa `NotAuthenticated` de 401 pra 403 (`rest_framework.views.exception_handler`), + já que semanticamente 401 exige indicar como o cliente deveria se autenticar. + + O frontend (`pidApiRequest`, `api.js`) só redireciona pro login em 401 — com o + padrão do DRF, qualquer request sem sessão ativa (cookie expirado, nunca logado) + voltava 403, e a página ficava travada mostrando o shell vazio/sem usuário em vez + de voltar pro login. Devolver um valor não-vazio aqui mantém o status em 401, + sem acionar nenhum prompt nativo do navegador (só "Basic"/"Digest" fazem isso). + """ + + def authenticate_header(self, request: Request) -> str: + return "Session" diff --git a/static/css/layout.css b/static/css/layout.css index f01fb45..d0ea216 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -26,6 +26,20 @@ transition: opacity 420ms ease, transform 420ms cubic-bezier(0.16, 1, 0.3, 1); } +/* Antes de access.js aplicar a visibilidade real (união de permissões, vinda + de /api/me/, assíncrona), todo item de menu gated por permissão começa + escondido — "fail closed". Sem isso, o menu inteiro (todas as seções/ + aplicações) ficava visível por um instante em qualquer carregamento de + página — inclusive pra quem não tem acesso a todas —, já que o HTML de + cada shell já vem com todos os itens no DOM e só é filtrado depois que o + fetch de /api/me/ resolve. `pidApplyAccessVisibility` (access.js) só marca + `.sidebar` como `.is-ready` depois de já ter escondido cada item que o + usuário não pode ver. */ +.sidebar:not(.is-ready) [data-section], +.sidebar:not(.is-ready) [data-app] { + display: none !important; +} + .sidebar__brand { position: relative; display: flex; diff --git a/static/js/access.js b/static/js/access.js index 566a87b..e81acfb 100644 --- a/static/js/access.js +++ b/static/js/access.js @@ -45,6 +45,12 @@ function pidApplyAccessVisibility(me) { document.querySelectorAll("[data-restricted-badge]").forEach((el) => { el.hidden = !isDiretoria; }); + + // Só revela o menu (ver regra `.sidebar:not(.is-ready)` em layout.css) depois + // que todo item já teve sua visibilidade real decidida acima — evita mostrar + // seções/aplicações que o usuário não deveria ver, ainda que por um instante. + const sidebarEl = document.querySelector(".sidebar"); + if (sidebarEl) sidebarEl.classList.add("is-ready"); } async function pidInitAccess() {