You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
O Ruscker possui contas locais criadas por administradores, login por usuário/senha e controle de acesso por app (access-groups / access-users), mas não possui segundo fator.
Queremos permitir que apenas aplicações sensíveis exijam autenticação reforçada. Usuários que nunca acessarem um app protegido não devem ser obrigados a configurar MFA.
Implementar step-up MFA por app, inicialmente com TOTP compatível com Google Authenticator, Microsoft Authenticator, Authy, 1Password e equivalentes.
O Ruscker não envia o código: o aplicativo autenticador gera localmente um código temporário a partir de um segredo cadastrado por QR Code. E-mail/SMS e links de convite ficam fora deste MVP.
O fator pertence ao usuário, não ao app. O usuário cadastra TOTP uma única vez; cada app decide se exige MFA e qual a idade máxima aceita para a última comprovação.
Configuração por app
Adicionar campos opcionais ao Spec, preservando compatibilidade:
require-mfa: truemfa-validity-days: 7
require-mfa: Option<bool>, padrão false.
mfa-validity-days: Option<u16>, usado somente quando require-mfa=true.
Proposta de semântica: padrão efetivo de 7 dias e máximo validado de 30 dias.
Valor 0: comprovação válida somente na sessão de login atual, sem dispositivo lembrado entre logins.
No formulário de apps:
checkbox “Exigir 2FA”;
ao marcar, mostrar “Solicitar novamente após N dias”;
ajuda explicando que usuários sem TOTP serão guiados pelo cadastro no primeiro acesso;
campos disponíveis no YAML, import/export, histórico de versão e quatro locales.
Fluxo do usuário
App sem MFA
Comportamento atual, sem qualquer prompt adicional.
App com MFA — usuário anônimo
Redirecionar para o login normal, preservando next.
Após usuário/senha, continuar para a decisão de step-up do app.
App com MFA — usuário ainda sem TOTP
Redirecionar para uma página autenticada de cadastro, por exemplo /account/security/mfa/setup?next=....
Solicitar novamente a senha atual antes de vincular o fator.
Gerar um segredo TOTP com CSPRNG e mostrar QR Code + chave manual.
O próprio usuário escaneia o QR Code no dispositivo dele. O administrador nunca recebe nem visualiza o segredo.
Exigir um primeiro código válido para confirmar o cadastro.
Gerar códigos de recuperação de uso único e mostrá-los uma única vez.
Retornar ao app originalmente solicitado.
App com MFA — usuário já cadastrado
Se existir uma comprovação recente válida para aquele navegador/dispositivo e ela estiver dentro de mfa-validity-days, abrir o app diretamente.
Caso contrário, solicitar um código TOTP e, após sucesso, retornar ao app.
Uma comprovação TOTP pode satisfazer todos os apps: cada app compara a idade de mfa_verified_at com sua própria janela. Exemplo: uma comprovação de 3 dias abre um app com janela de 7 dias, mas não um app com janela de 1 dia.
Enforcement
Aplicar o guard no proxy antes de spawn, seleção de réplica ou encaminhamento ao container.
Proteger acesso direto por URL, não apenas o clique no card.
Preservar next com segurança: somente paths locais e normalizados, incluindo server.context-path.
O MVP cobre apps interativos em /app/*.
APIs em /api/* não recebem redirects HTML: se a política for configurada futuramente para APIs, responder 401/403 de forma explícita. Definir suporte a API e external links fora deste MVP.
Usuários Admin autenticados por conta também cumprem MFA. A sessão de token break-glass pode fazer bypass para evitar lockout, mas deve gerar log/auditoria destacada.
Persistência e HA
Não guardar apenas um timestamp global no usuário: isso faria qualquer navegador aproveitar a validação feita em outro dispositivo.
vínculo com usuário, dispositivo/navegador, mfa_verified_at, expiração e versão do fator;
implementação SQLite e Postgres para manter o comportamento em HA;
validade efetiva limitada pelo menor prazo entre o grant e a política do app;
revogar grants em reset/troca de senha, reset do MFA, exclusão/desativação do usuário e “sair de todos os dispositivos”;
um logout comum continua removendo a sessão primária; a decisão de manter ou remover o dispositivo lembrado deve ficar explícita na UI (“Esquecer este dispositivo”).
A sessão primária atual dura 24h, portanto janelas de vários dias não podem depender somente de AdminSessionStore/SessionInfo.
Armazenamento do fator
Segredo TOTP criptografado em repouso com RUSCKER_MASTER_KEY; nunca em claro no DB, logs, audit ou HTML depois do enrollment.
Se não houver master key, cadastro e uso de apps com require-mfa=true falham fechados, com diagnóstico operacional claro.
Códigos de recuperação armazenados como hashes e consumidos uma única vez.
Administrador vê somente o estado “2FA configurado” e pode executar um reset auditado após verificar a identidade; nunca vê QR Code/segredo.
Segurança do desafio
Código TOTP com janela curta e tolerância de relógio limitada.
Impedir replay do mesmo contador TOTP após sucesso.
Limite estrito de tentativas por usuário/desafio e backoff; resposta sem enumeração de contas.
Operações de setup, reset e recuperação protegidas contra CSRF e session fixation.
Não registrar códigos, segredos, recovery codes ou tokens de dispositivo.
Auditoria: mfa.enroll, mfa.verify, mfa.recovery_used, mfa.reset, mfa.trusted_device.revoke e mfa.break_glass_bypass.
UX administrativo
O administrador continua criando o usuário e a senha inicial como hoje.
Não há cadastro prévio de Google Authenticator pelo administrador.
O usuário é conduzido ao enrollment somente quando tenta acessar o primeiro app protegido.
Na tela de usuários, exibir estado do fator e ação de reset com confirmação forte e auditoria.
Critérios de aceite
Usuário que acessa somente apps sem MFA nunca vê setup ou desafio.
Acesso direto a um app protegido exige login e depois TOTP quando necessário.
Usuário sem fator cadastra TOTP por QR Code e retorna ao app original.
O mesmo fator funciona para todos os apps protegidos.
Cada app respeita sua própria janela de validade.
A comprovação é vinculada ao navegador/dispositivo, não global ao usuário.
Um request bloqueado não inicia nem acorda container.
Funciona em root e sob server.context-path.
Funciona em SQLite e Postgres/HA.
Reset de senha/MFA e exclusão do usuário revogam grants.
Break-glass bypass é explícito e auditado.
Segredos/códigos não aparecem em logs, audit, export YAML ou respostas posteriores ao enrollment.
Testes cobrem expiração, replay, tentativa excessiva, recovery code, revogação, acesso direto, múltiplos apps e HA.
Formulário, mensagens e documentação possuem paridade pt/en/es/fr.
Fatiamento sugerido
Não implementar este épico inteiro em um PR:
Schema + form + validação — campos opcionais, YAML, import/export e i18n.
Enrollment TOTP + recovery — armazenamento criptografado, página de segurança e reset administrativo.
Desafio + grants de dispositivo — verificação, cookie opaco, expiração e revogação SQLite/Postgres.
Guard por app no proxy — next, base-path, bloqueio antes do spawn e break-glass auditado.
Hardening + testes/Docs — rate limit, replay, CSRF, HA e documentação operacional.
Contexto
O Ruscker possui contas locais criadas por administradores, login por usuário/senha e controle de acesso por app (
access-groups/access-users), mas não possui segundo fator.Queremos permitir que apenas aplicações sensíveis exijam autenticação reforçada. Usuários que nunca acessarem um app protegido não devem ser obrigados a configurar MFA.
Relacionado:
Decisão de produto
Implementar step-up MFA por app, inicialmente com TOTP compatível com Google Authenticator, Microsoft Authenticator, Authy, 1Password e equivalentes.
O Ruscker não envia o código: o aplicativo autenticador gera localmente um código temporário a partir de um segredo cadastrado por QR Code. E-mail/SMS e links de convite ficam fora deste MVP.
O fator pertence ao usuário, não ao app. O usuário cadastra TOTP uma única vez; cada app decide se exige MFA e qual a idade máxima aceita para a última comprovação.
Configuração por app
Adicionar campos opcionais ao
Spec, preservando compatibilidade:require-mfa:Option<bool>, padrãofalse.mfa-validity-days:Option<u16>, usado somente quandorequire-mfa=true.0: comprovação válida somente na sessão de login atual, sem dispositivo lembrado entre logins.No formulário de apps:
Fluxo do usuário
App sem MFA
Comportamento atual, sem qualquer prompt adicional.
App com MFA — usuário anônimo
next.App com MFA — usuário ainda sem TOTP
/account/security/mfa/setup?next=....App com MFA — usuário já cadastrado
mfa-validity-days, abrir o app diretamente.mfa_verified_atcom sua própria janela. Exemplo: uma comprovação de 3 dias abre um app com janela de 7 dias, mas não um app com janela de 1 dia.Enforcement
nextcom segurança: somente paths locais e normalizados, incluindoserver.context-path./app/*./api/*não recebem redirects HTML: se a política for configurada futuramente para APIs, responder401/403de forma explícita. Definir suporte a API e external links fora deste MVP.Persistência e HA
Não guardar apenas um timestamp global no usuário: isso faria qualquer navegador aproveitar a validação feita em outro dispositivo.
Criar um grant de dispositivo confiável:
HttpOnly,Secure,SameSite=Strict;mfa_verified_at, expiração e versão do fator;A sessão primária atual dura 24h, portanto janelas de vários dias não podem depender somente de
AdminSessionStore/SessionInfo.Armazenamento do fator
RUSCKER_MASTER_KEY; nunca em claro no DB, logs, audit ou HTML depois do enrollment.require-mfa=truefalham fechados, com diagnóstico operacional claro.Segurança do desafio
mfa.enroll,mfa.verify,mfa.recovery_used,mfa.reset,mfa.trusted_device.revokeemfa.break_glass_bypass.UX administrativo
Critérios de aceite
server.context-path.Fatiamento sugerido
Não implementar este épico inteiro em um PR:
next, base-path, bloqueio antes do spawn e break-glass auditado.Fora de escopo
amr/acr/auth_timedo IdP como evidência de step-up.