Pular para o conteúdo

Referência OpenAPI: Identidade (canverly-auth)

Esta página é gerada no build a partir de api/openapi.yaml no repositório canverly-auth. Não edite à mão: a fonte é a especificação.

  • Versão da especificação: 0.1.0
  • Total de operações: 45
  • Servidores declarados: https://auth.canverly.com/api/v1
  • Especificação publicada (YAML): /specs/auth-openapi.yaml

See [ADRs 0014, 0019, 0020] in camverly-platform. This API is reached via auth.camverly.com (browser) and /api/v1/auth/* through the gateway (service-to-service and SPAs).

Liveness

Autenticação: nenhuma declarada

Status Descrição
200 alive

Readiness — pings deps

Autenticação: nenhuma declarada

Status Descrição
200 ready
503 error

OIDC discovery

Autenticação: nenhuma declarada

Status Descrição
200 ok

JWKS (EdDSA)

Autenticação: nenhuma declarada

Status Descrição
200 ok

Public self-signup (account without organization)

Cria uma conta staff sem organização nenhuma, em status=pending_verification, e dispara um OTP de purpose=email_verify. A conta não opera (não recebe sessão, não alcança recurso de tenant) até confirmar o código em POST /auth/email/verify/confirm.

Anti-enumeração: a resposta é IDÊNTICA (mesmo 202, mesmo corpo) para e-mail novo e para e-mail que já tem conta, e o custo de CPU é o mesmo nos dois casos. Quando o e-mail já existe, o dono da caixa recebe um aviso — é o único canal pelo qual essa informação circula. Nunca devolve 409.

Rate-limit por IP e por e-mail (429).

POLÍTICA DE SENHA (onda 4 do IAM): comprimento mínimo 12 e a senha não pode constar de vazamentos públicos conhecidos (PASSWORD_BREACHED). A verificação usa a API de range do Have I Been Pwned por k-anonimato: só os 5 primeiros caracteres do SHA-1 saem do servidor — a senha nunca sai, nem inteira nem em hash completo. Nenhuma das duas recusas depende da existência da conta, então o contrato anti-enumeração acima continua intacto.

Quando o corpus de vazamentos está inalcançável, a senha é ACEITA com o degrau de comprimento aplicado (fail-open deliberado, logado e medido em auth_password_breach_checks_total{outcome="unavailable"}). Ver a justificativa em application.PasswordPolicy.Validate.

Autenticação: nenhuma declarada

Tipos de conteúdo: application/json (object)

Status Descrição
202 Aceito. Mesmo corpo para e-mail novo e já cadastrado.
400 INVALID_EMAIL, PASSWORD_TOO_WEAK (< 12 caracteres) ou PASSWORD_BREACHED (a senha escolhida consta de listas públicas de vazamento). Os mesmos códigos valem em POST /auth/password/reset/confirm, POST /v1/auth/sso/register e POST /auth/password/change.
429 error
501 error

Convites pendentes daquele site

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
site_id path sim —
Status Descrição
200 lista (somente convites daquele site)
404 error
501 INVITES_DISABLED, em duas posições distintas da escada: * repositório de CONVITES não cabeado → checado DEPOIS da autorização do site (um 501 antes do gate confirmaria que o site existe); sem acesso ao site a resposta continua 404; * fonte de AUTORIZAÇÃO (memberships) não cabeada → checado ANTES do gate, e seguro porque nesse estado TODO chamador recebe a mesma resposta: o serviço não reconhece membro nenhum, então ela não revela nada sobre aquele site em particular.

Convidar um e-mail para UM site (escopo de site)

O aceite concede site_membership naquele site e nunca org_membership — o convidado não alcança os outros sites da organização. O org_id é derivado no servidor a partir do vínculo do site; não existe campo de escopo no corpo.

Autoriza: site_owner do site, ou quem tem members:manage na organização dona daquele site. Sem acesso ao site → 404.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
site_id path sim —

Tipos de conteúdo: application/json (object)

Status Descrição
202 convite criado e e-mail despachado
400 error
404 error
501 error

Revogar convite pendente daquele site

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
site_id path sim —
id path sim —
Status Descrição
204 revogado
404 error
501 error

Convites pendentes da organização (org + sites dela)

Lista os convites não aceitos e não expirados da empresa, nos dois escopos (scope: org e scope: site) — quem administra membros da org precisa enxergar tudo o que foi prometido em nome dela.

Autoriza em DOIS degraus, com negativas diferentes de propósito: quem não é membro da org recebe 404 (um 403 confirmaria a existência da empresa a quem só chutou um ULID); quem é membro sem members:manage recebe 403.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
org_id path sim —
Status Descrição
200 lista de convites pendentes (org + sites da org)
400 error
401 error
403 error
404 error
500 error
501 DOIS códigos, em POSIÇÕES DIFERENTES da escada — a diferença é deliberada e é o que impede o 501 de virar oráculo de existência: * INVITES_DISABLED — o módulo de convites não está cabeado. Checado DEPOIS da autorização, porque neste estado EXISTE resposta de membro (a org é reconhecível) e um 501 antes do gate contaria a um estranho que a empresa existe; ele continua com 404. Falha FECHADA e explícita: uma lista vazia (200) faria a tela confundir “não há convite pendente” com “convites desligados”. * AUTHZ_UNAVAILABLE — a fonte de autorização (memberships) não está cabeada. Checado ANTES do gate, e isso é seguro porque a resposta não depende do org_id nem de quem chama: nesse estado o serviço não reconhece membro nenhum, então não há resposta “de membro” da qual a do forasteiro pudesse ser distinguida.

Revogar convite pendente da organização

org_id entra no WHERE — adivinhar o id de um convite de outra empresa não revoga nada (defesa de IDOR).

Mesma autorização em dois degraus da listagem e do reenvio: 404 para quem não é membro da org, 403 para o membro sem members:manage.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
org_id path sim —
id path sim —
Status Descrição
204 revogado
400 error
401 error
403 error
404 NOT_FOUND — o chamador não é membro da org ou não existe convite pendente com aquele id naquela org. Os dois casos compartilham o status de propósito.
500 error
501 Os mesmos dois códigos da listagem, nas mesmas posições: INVITES_DISABLED (módulo de convites desligado) depois da autorização — antes deste conserto esta rota mentia com 404, e a UI concluía que aquele convite específico havia sumido; e AUTHZ_UNAVAILABLE (memberships não cabeados) antes do gate, que é seguro por não depender do org_id nem de quem chama.

Reenviar o e-mail de um convite da organização

O token é ROTACIONADO: o link anterior deixa de valer no mesmo UPDATE em que o novo nasce. Reenviar mantendo o token velho deixaria dois links vivos com o mesmo segredo — e o motivo mais comum de reenviar é desconfiar do primeiro. A expiração reinicia a partir de agora (InvitationTTL = 7 dias), o que também torna esta a forma de ressuscitar um convite vencido.

A resposta NUNCA devolve o token. Ele vai só para a caixa do convidado.

Alcança tanto o convite de organização quanto os de sites daquela org (o mesmo conjunto que GET /auth/orgs/{org_id}/invitations lista).

Autoriza: membro da org com members:manage. Quem não é da org recebe 404 (não confirma a existência da empresa); quem é da org mas não administra membros recebe 403.

Freado por IP e pela caixa do convidado (429 RATE_LIMITED).

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
org_id path sim —
id path sim —
Status Descrição
202 Reenviado. email_sent:false = o token JÁ rotacionou (o link antigo morreu) e só o envio falhou — reenvie de novo.
400 error
403 error
404 error
409 INVITATION_ALREADY_ACCEPTED — a pessoa já é membro.
429 error
501 error

POST /auth/sites/{site_id}/invitations/{id}/resend

Seção intitulada “POST /auth/sites/{site_id}/invitations/{id}/resend”

Reenviar o e-mail de um convite daquele site

Mesma semântica da rota de organização (token rotacionado, expiração reiniciada, token nunca na resposta), com o escopo do SITE no WHERE: o id de um convite de outro site simplesmente não é encontrado.

Autoriza: site_owner do site, ou members:manage na organização dona dele. Sem acesso ao site → 404 (403 confirmaria que o site existe).

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
site_id path sim —
id path sim —
Status Descrição
202 reenviado
400 error
404 error
409 INVITATION_ALREADY_ACCEPTED — a pessoa já é membro.
429 error
501 error

Equipe daquele site (quem tem site_membership)

Lista quem tem vínculo GRAVADO naquele site. Não inclui quem alcança o site por papel de ORGANIZAÇÃO (projeção): essas pessoas não têm linha em site_memberships e removê-las é operação de empresa, não de site.

Autoriza: site_owner do site, ou members:manage na organização dona dele. Sem acesso ao site → 404.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
site_id path sim —
Status Descrição
200 equipe do site
400 error
404 error
500 error

Trocar o papel de alguém da equipe do site

É o “promover a dono” que o 409 LAST_SITE_OWNER instrui — sem esta rota, aquela mensagem mandaria o operador por um caminho inexistente.

REBAIXAR o último dono cai na MESMA guarda (409 LAST_SITE_OWNER): trocar o papel do único dono para editor orfana o site exatamente como removê-lo. Rebaixar um dono havendo outro é permitido, e reatribuir o papel de dono ao próprio dono também (não é rebaixamento).

Papéis aceitos: qualquer papel de escopo site que exista na empresa — os presets (site_owner, site_editor, site_author, site_contributor, com os apelidos owner/editor/author/ contributor/dono/autor/colaborador) e os papéis PERSONALIZADOS que a empresa criou (pelo slug). Papel de ORGANIZAÇÃO nunca passa (admin, org_admin, … → 400): a resolução fixa o escopo site, então o vetor de escalada morre ali.

Só o papel ESTRUTURAL (is_system) conta como “continua dono” — um papel personalizado batizado de “dono” não fura a guarda.

Autoriza: site_owner do site, ou members:manage na organização dona dele. Sem acesso ao site → 404 opaco.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
site_id path sim —
user_id path sim —

Tipos de conteúdo: application/json (object)

Status Descrição
200 papel trocado
400 BAD_ID (user_id inválido), BAD_REQUEST (corpo inválido), BAD_ROLE (role vazio), ROLE_NOT_FOUND (papel de site inexistente nesta empresa — inclui todo papel de organização).
404 NOT_FOUND (sem acesso ao site — opaco) ou NOT_MEMBER (a pessoa não tem vínculo com este site).
409 LAST_SITE_OWNER — o rebaixamento deixaria o site órfão.
500 error
501 error

Remover alguém da equipe do site

Idempotente: remover quem não é membro devolve o MESMO 204 de quem era. Respostas diferentes transformariam o endpoint num oráculo de “essa pessoa tem acesso a este site?”.

Nunca remove o ÚLTIMO dono (409 LAST_SITE_OWNER): um site sem dono não pode mais ser administrado por ninguém. Vale inclusive para quem está tentando sair — auto-remoção é permitida, exceto se você for o último dono.

Falha ao contar os donos ⇒ 500 e nada é removido (falha fechada).

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
site_id path sim —
user_id path sim —
Status Descrição
204 removido (ou já não era membro)
400 error
404 error
409 LAST_SITE_OWNER — deixaria o site órfão.
500 error

Send OTP to email

Anti-enumeração: um e-mail sem conta recebe o MESMO 202 de um e-mail com conta (antes: 404 USER_NOT_FOUND para propósitos que exigem conta, o que revelava quem tem cadastro). O desfecho real fica no log do servidor. Freado por IP e por e-mail.

Autenticação: nenhuma declarada

Tipos de conteúdo: application/json (object)

Status Descrição
202 code dispatched
400 error
429 error

Verify OTP and issue a session

Autenticação: nenhuma declarada

Tipos de conteúdo: application/json (object)

Status Descrição
200 tokens issued
401 error
404 error
429 error

Staff login — password + TOTP

Autenticação: nenhuma declarada

Tipos de conteúdo: application/json (object)

Status Descrição
200 tokens issued
401 error
403 EMAIL_NOT_VERIFIED — conta de auto-cadastro que ainda não confirmou o e-mail. Só é devolvido DEPOIS de a senha bater, para não virar oráculo de enumeração.
423 error

Rotate refresh token

ROTACIONA: a sessao antiga e revogada e outra e emitida. Apresentar o MESMO refresh token duas vezes e tratado como token roubado e revoga TODAS as sessoes do usuario — quem chama precisa de single-flight (uma renovacao em voo por navegador).

O token pode vir no CORPO (BFF, server-side) ou no cookie camverly_refresh (navegador, onde o cookie e HttpOnly e o JavaScript nao consegue monta-lo). Corpo AUSENTE e tolerado; corpo QUEBRADO e 400.

Autenticação: nenhuma declarada

Tipos de conteúdo: application/json (object)

Status Descrição
200 new tokens
400 BAD_REQUEST — corpo PRESENTE e malformado. NAO significa sessao perdida: e defeito de serializacao do cliente.
401 REFRESH_FAILED — a sessao acabou de verdade (token desconhecido, revogado, reuso detectado, usuario inexistente). E o UNICO status que significa “mande o usuario ao login”.
403 error
423 error
503 REFRESH_UNAVAILABLE — nao foi possivel DETERMINAR se a sessao vale (banco fora, timeout). O cliente deve tentar de novo e nunca deslogar: “nao sei” nao e “nao vale”.

Login faseado, passo 1 — prova a senha e abre o desafio

Devolve um desafio opaco e a lista de segundos fatores DISPONIVEIS para aquela conta. methods so existe no 200: publicar o inventario de fatores para quem nao provou a senha entregaria parte do e-mail e do telefone da vitima.

O 401 e IDENTICO em corpo, status e TEMPO entre “e-mail nao existe” e “senha errada” (uma derivacao Argon2id equivalente e queimada nos caminhos que recusam antes da comparacao).

Autenticação: nenhuma declarada

Tipos de conteúdo: application/json (object)

Status Descrição
200 desafio aberto
400 error
401 INVALID_CREDENTIALS — resposta e tempo uniformes.
423 error
428 MFA_NOT_ENROLLED — staff sem fator TOTP confirmado (ADR-0019, politica preservada). So alcancavel DEPOIS de a senha bater.

Login faseado, passo 2 — entrega o desafio do 2o fator

email e sms DESPACHAM um codigo e respondem 202 com so a expiracao (nada sobre a conta, o endereco ou o numero). totp nao passa por aqui (o segredo ja esta no aparelho).

passkey NAO ENVIA NADA: ele ENTREGA as opcoes da cerimonia e responde 200 com corpo. O objeto publicKey e o argumento de navigator.credentials.get(); challenge e allowCredentials[].id vem em base64url sem padding e precisam virar ArrayBuffer no cliente (base64 comum NAO serve). O desafio fica no SERVIDOR e nao volta no passo 3.

Autenticação: nenhuma declarada

Tipos de conteúdo: application/json (object)

Status Descrição
200 So para method=passkey: opcoes da cerimonia de autenticacao.
202 codigo despachado (email / sms)
400 BAD_REQUEST ou METHOD_NOT_AVAILABLE (canal indisponivel para a conta).
401 CHALLENGE_INVALID — inexistente, expirado ou ja consumido (um codigo so).
501 PASSKEY_DISABLED — WebAuthn nao configurado neste ambiente.
502 error

Login faseado, passo 3 — troca o desafio por uma sessao

Consumo de USO UNICO por compare-and-set. trust_device pede a sessao de 90 dias; o campo trusted_device da resposta diz o que o SERVIDOR decidiu — a tela so promete 90 dias se ele vier true.

Autenticação: nenhuma declarada

Tipos de conteúdo: application/json (object)

Status Descrição
200 sessao emitida (+ cookies camverly_session e camverly_refresh)
400 error
401 MFA_INVALID_CODE (com details.attempts_remaining), CHALLENGE_INVALID, PASSKEY_CEREMONY_INVALID (cerimonia inexistente, expirada ou JA CONSUMIDA — peca outra em /auth/login/mfa/send) ou PASSKEY_CLONE_SUSPECTED (contador de assinaturas REGREDIU; a acao certa e trocar a chave, nao tentar de novo).
429 TOO_MANY_ATTEMPTS — 5a tentativa errada; o desafio MORRE aqui.

Capacidades de 2o fator da propria conta

Existe para a tela de seguranca nao ter de adivinhar se o SMS esta ligado — adivinhar erraria para o lado de mostrar um botao que da 501.

Autenticação: bearerAuth

Status Descrição
200 capacidades
401 error

Chave de acesso — abre a cerimonia de cadastro

O dono vem do Bearer verificado, NUNCA do corpo. O desafio e sorteado e guardado no servidor (uso unico); pedir de novo INVALIDA o pedido anterior. Corpo da requisicao vazio ({}).

Autenticação: bearerAuth

Status Descrição
200 opcoes da cerimonia
401 error
409 PASSKEY_LIMIT_REACHED — teto de 20 chaves por conta.
501 PASSKEY_DISABLED — WebAuthn nao configurado neste ambiente.

Chave de acesso — valida a attestation e persiste

Autenticação: bearerAuth

Tipos de conteúdo: application/json (object)

Status Descrição
201 chave cadastrada
400 BAD_REQUEST — corpo ilegivel ou credential ausente.
401 PASSKEY_CEREMONY_INVALID (nao havia cerimonia viva, ou ela ja foi queimada — recomece pelo /begin) ou PASSKEY_INVALID (attestation recusada: desafio, RP ID, origem, flags ou assinatura).
409 PASSKEY_ALREADY_REGISTERED — esse credential_id ja existe (um codigo so, seja sua ou de outra conta).
501 error

Chaves de acesso da propria conta

Autenticação: bearerAuth

Status Descrição
200 lista
401 error
501 error

Remove uma chave de acesso da propria conta (exige step-up)

Escopado ao dono no proprio DELETE ... AND user_id = $2. Id de outra conta devolve 404, o MESMO codigo de “nao existe”, para o endpoint nao virar oraculo.

STEP-UP OBRIGATORIO desde a onda 4 do IAM — ver o bloco “STEP-UP EM OPERACAO SENSIVEL” mais abaixo neste arquivo. Sem X-Step-Up-Code, responde 428 STEP_UP_REQUIRED e manda um OTP para o e-mail primario (nunca para a passkey que esta sendo removida: quem perdeu a chave fisica precisa exatamente desta operacao).

O step-up vem ANTES de qualquer consulta ao repositorio — emitir o codigo so depois de descobrir que a passkey existe faria a presenca do e-mail virar oraculo de “esse id existe nesta conta?”.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
id path sim —
X-Step-Up-Code header não —
Status Descrição
204 removida
401 CODE_INVALID (step-up errado) ou nao autenticado.
404 PASSKEY_NOT_FOUND — inexistente, id ilegivel OU de outra conta.
409 LAST_STRONG_FACTOR — a remocao deixaria a conta SEM nenhum fator forte (sem TOTP confirmado e sem outra passkey). O piso do IAM proibe que a troca de fator seja silenciosa.
428 STEP_UP_REQUIRED — details:{channel,sent_to} mascarado.
501 error
503 STEP_UP_UNAVAILABLE — a remocao NAO aconteceu (falha fechada).

E-mails da propria conta (+ nudge de secundario)

O dono vem SEMPRE do Bearer verificado. nudge e decidido no SERVIDOR; a tela nao recalcula a politica de recuperacao.

Autenticação: bearerAuth

Status Descrição
200 lista
401 error
501 error

Adiciona um e-mail e dispara o codigo que o prova

202, nao 201: o que interessa (o endereco virar identidade) ainda NAO aconteceu — a linha nasce PENDENTE e so o /verify a torna utilizavel.

Nao existe resposta “esse e-mail ja pertence a alguem”. Responde-la transformaria o endpoint num oraculo de “essa pessoa tem conta na Canverly?” para qualquer um com uma sessao. A recusa acontece na VERIFICACAO, onde quem a recebe ja provou controlar a caixa.

A linha PENDENTE pode coexistir em varias contas (anti-squatting): se declarar bastasse para reservar, eu bloquearia o endereco de qualquer pessoa so afirmando que e meu.

Freado por IP, por ALVO (3/h no mesmo endereco) e por AUTOR (10/h por conta) — sem isso, este e o caminho mais curto para transformar a plataforma numa maquina de spam com o nosso dominio de envio.

Autenticação: bearerAuth

Tipos de conteúdo: application/json (object)

Status Descrição
202 codigo enviado
400 INVALID_EMAIL / BAD_REQUEST.
401 error
409 EMAIL_ALREADY_ADDED — o endereco ja esta NESTA conta. EMAIL_LIMIT_REACHED — teto de 10 enderecos por conta.
429 RATE_LIMITED.
501 error

Prova o endereco e o transforma em identidade da conta

A partir do 200 o endereco serve para entrar e para recuperar — e a unica transicao desta onda que muda o que a conta consegue fazer.

O codigo tem proposito email_add, TTL de 10 min, 5 tentativas e e de USO UNICO (CAS no banco). Todo desfecho que dependa do codigo colapsa no MESMO 401 CODE_INVALID — inexistente, nao confere, ja usado e tentativas estouradas sao indistinguiveis de proposito.

Autenticação: bearerAuth

Tipos de conteúdo: application/json (object)

Status Descrição
200 verificado
401 CODE_INVALID — um codigo so para todos os desfechos do OTP.
404 EMAIL_NOT_FOUND — esse endereco nao esta nesta conta.
409 EMAIL_ALREADY_ADDED — ja estava verificado (nenhum codigo foi queimado). EMAIL_TAKEN — outra conta provou o mesmo endereco antes; quem prova primeiro fica com ele. Dizer o motivo aqui nao vaza nada: quem chega neste ponto acabou de conferir um codigo, isto e, controla a caixa.
429 RATE_LIMITED.
501 error

Torna um e-mail VERIFICADO o principal (exige step-up)

PROTOCOLO EM DUAS CHAMADAS. Sem code, o servidor emite um OTP (sensitive_action) para o e-mail primario ATUAL e responde 428 STEP_UP_REQUIRED com details.sent_to MASCARADO; nada muda. Com o code conferido, a troca acontece e responde 200.

Por que step-up: quem troca o primario passa a receber a recuperacao da conta. O Bearer sozinho prova “esta sessao esta viva”, que e exatamente o que um sequestrador com sessao roubada tem. O que ele NAO tem e a caixa primaria ATUAL — por isso o codigo vai para o endereco que esta PERDENDO o posto, nunca para o que esta ganhando.

Endereco NAO verificado e recusado com 409 antes do step-up: emitir codigo para uma operacao ja impossivel gastaria OTP a toa.

Idempotente: promover quem ja e primario responde 200 sem queimar codigo.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
id path sim —

Tipos de conteúdo: application/json (object)

Status Descrição
200 promovido
401 CODE_INVALID (codigo de step-up errado) ou nao autenticado.
404 EMAIL_NOT_FOUND — inexistente, id ilegivel OU de outra conta.
409 EMAIL_NOT_VERIFIED — confirme o endereco antes de torna-lo principal.
428 STEP_UP_REQUIRED — codigo enviado ao primario ATUAL. details traz channel: "email" e sent_to MASCARADO, para a tela dizer para onde o codigo foi sem publicar o endereco.
501 error

Remove um e-mail da propria conta

Escopado ao dono no proprio SQL. Id de outra conta devolve 404, o MESMO codigo de “nao existe”, para nao virar oraculo.

Duas recusas, e cada uma protege uma coisa diferente: o PRIMARIO (a conta ficaria sem canal de recuperacao definido — promova outro antes) e o ULTIMO VERIFICADO (a conta ficaria IRRECUPERAVEL).

Sem step-up, e isso e uma decisao: remover nao move o canal de recuperacao para lugar nenhum, e exigir a caixa primaria travaria a limpeza de quem esta justamente perdendo o acesso a um endereco antigo.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
id path sim —
Status Descrição
204 removido
401 error
404 EMAIL_NOT_FOUND — inexistente, id ilegivel OU de outra conta.
409 EMAIL_IS_PRIMARY — promova outro antes. EMAIL_LAST_VERIFIED — a conta ficaria irrecuperavel.
501 error

Troca a senha COM SESSAO VIVA (exige senha atual + step-up)

ROTA NOVA. Ate esta onda so existiam duas escritas de senha para quem ja tem conta — o reset por OTP e o cadastro — e nenhuma para “estou logado e quero trocar minha senha”. Nao confundir com POST /auth/password/reset/confirm, que e publico.

TRES PROVAS, nenhuma redundante:

  1. Bearer valido — prova que ha uma sessao. E o que o sequestrador TEM.
  2. current_password — prova conhecimento da credencial. Fecha o caso do token roubado por quem nao sabe a senha.
  3. Step-up por e-mail primario — prova controle da CAIXA. Fecha o caso em que o atacante sabe a senha (reuso, vazamento de outro servico) E tem a sessao, que e quando 1 e 2 sao inuteis.

ORDEM DAS CHECAGENS: politica da senha nova -> senha atual -> step-up. Reprovar depois de queimar o OTP deixaria a pessoa sem codigo e sem senha nova; e exigir a senha atual antes do step-up impede que um token roubado bombardeie a caixa da vitima com e-mails de confirmacao.

A senha nova passa pela MESMA politica do cadastro: comprimento (12) + corpus de senhas vazadas (PASSWORD_BREACHED).

Efeito colateral: no sucesso, TODAS as outras sessoes da conta sao revogadas; a do chamador e poupada. Trocar a senha e quase sempre a reacao a uma suspeita de comprometimento, e uma senha nova que deixa a sessao do invasor viva nao resolve nada.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
X-Step-Up-Code header não —

Tipos de conteúdo: application/json (object)

Status Descrição
204 senha trocada; as OUTRAS sessoes foram revogadas
400 PASSWORD_TOO_WEAK — menos de 12 caracteres. PASSWORD_BREACHED — a senha escolhida consta de vazamentos publicos.
401 INVALID_CREDENTIALS (senha atual errada) ou CODE_INVALID (step-up).
409 PASSWORD_NOT_SET — conta sem senha (leitor de magic link).
428 STEP_UP_REQUIRED — ver o bloco de step-up acima.
501 error
503 STEP_UP_UNAVAILABLE — o codigo nao pode ser enviado; a troca NAO aconteceu. PASSWORD_CHECK_UNAVAILABLE so ocorre com HIBP_FAIL_CLOSED=true (nao e o padrao).

Remove o fator TOTP da propria conta (exige step-up)

STEP-UP OBRIGATORIO (ver o bloco acima). Derrubar o segundo fator da vitima e o primeiro movimento de quem roubou uma sessao e quer manter o acesso depois que o roubo for descoberto.

O codigo vai para o e-mail primario, nunca para o TOTP que esta sendo removido — quem perdeu o celular precisa exatamente desta operacao.

Idempotente: sem fator cadastrado, responde 204 mesmo assim.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
X-Step-Up-Code header não —
Status Descrição
204 fator removido
401 CODE_INVALID (step-up) ou nao autenticado.
428 STEP_UP_REQUIRED.
503 STEP_UP_UNAVAILABLE — a remocao NAO aconteceu.

Gera um pool NOVO de codigos de recuperacao (exige step-up)

STEP-UP OBRIGATORIO (ver o bloco acima), e esta e a mais sorrateira das operacoes protegidas: gerar codigos novos INVALIDA os antigos e devolve os novos em texto puro na resposta. Uma unica chamada com sessao roubada entrega ao atacante dez credenciais de recuperacao permanentes (que sobrevivem a troca de senha e a revogacao de sessao) e queima as da vitima, que so descobre no dia em que precisar de uma.

Os codigos aparecem UMA vez; o servidor guarda so o SHA-256.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
X-Step-Up-Code header não —

Tipos de conteúdo: application/json (object)

Status Descrição
200 pool novo (os codigos NAO sao recuperaveis depois)
401 CODE_INVALID (step-up) ou nao autenticado.
428 STEP_UP_REQUIRED.
501 error
503 STEP_UP_UNAVAILABLE — nenhum codigo foi gerado nem invalidado.

Sair de todas as sessoes (exige step-up)

STEP-UP OBRIGATORIO nas DUAS variantes (ver o bloco acima). E destrutivo e e literalmente o movimento de um invasor que quer expulsar o dono da conta. A variante ?keep_current=1 e a PIOR das duas para esse fim: ela mata todo mundo MENOS quem pediu — ele fica, a vitima sai, e a vitima nem consegue voltar para revogar a sessao dele.

DELETE /auth/sessions/{id} (revogar UMA) continua SEM step-up, e isso e uma decisao com risco residual assumido: quem acabou de perder o celular precisa matar aquela sessao AGORA, e um invasor que ja leu a lista consegue o mesmo efeito em N chamadas. O gate encarece o botao de uma tacada so, que e o que a tela oferece e o que um script usa.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
keep_current query não mantem a sessao do chamador; TAMBEM exige step-up
X-Step-Up-Code header não —
Status Descrição
204 sessoes revogadas (cookies limpos quando a atual caiu)
401 CODE_INVALID (step-up) ou nao autenticado.
428 STEP_UP_REQUIRED.
503 STEP_UP_UNAVAILABLE — nenhuma sessao foi revogada.

Cadastra um telefone e dispara o codigo de confirmacao

O numero NAO entra na conta aqui: fica pendente ate ser provado, porque telefone nao confirmado nao pode virar segundo fator. Validacao E.164 acontece ANTES de qualquer chamada ao provedor.

Autenticação: bearerAuth

Tipos de conteúdo: application/json (object)

Status Descrição
202 codigo despachado
400 INVALID_PHONE — nao e E.164 aceitavel.
401 error
501 SMS_DISABLED — sem provedor configurado (estado atual).
502 error

Confirma o telefone com o codigo recebido

Autenticação: bearerAuth

Tipos de conteúdo: application/json (object)

Status Descrição
200 telefone verificado
401 MFA_INVALID_CODE com details.attempts_remaining.
404 PHONE_VERIFICATION_NOT_FOUND — nada pendente ou ja expirou.
429 error
501 error

Remove o telefone da conta (idempotente)

Autenticação: bearerAuth

Status Descrição
204 removido (ou nao havia nada a remover)
401 error
501 error

Quantas transferências aguardam decisão do usuário, por organização

Contador de menu do painel: para cada org em que o chamador pode decidir (members:manage), quantas transferências estão ENTRANDO. Serviço sem o módulo cabeado responde {"orgs": [], "total": 0} com 200, e não erro: derrubar a lateral inteira do painel por um enfeite de menu trocaria a ausência de aviso por uma tela quebrada.

Erro ao CONTAR, porém, nunca vira zero — “não consegui contar” e “não há nada” são coisas diferentes, e o painel precisa poder dizer a primeira.

Autenticação: bearerAuth

Status Descrição
200 contagem por organização e total
401 error
500 error

Transferências pendentes da organização (entrando e saindo)

Separa o que ENTRA do que SAI e devolve o NOME das organizações dos dois lados, por extenso. O id não diz nada a quem lê a tela — “aceitar a transferência de 01KVXQ1C…” é um pedido que ninguém deveria aprovar. Traz junto os compartilhamentos (share) cedidos e recebidos.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
org_id path sim —
Status Descrição
200 transferências entrando/saindo e compartilhamentos
400 error
401 error
403 error
404 error
500 error
501 error

Iniciar transferência (ou compartilhamento) de um site

A org da URL é a ORIGEM; o destino vem por to_org_slug. mode distingue transfer (o site muda de dono) de share (a outra empresa ganha acesso com o papel de share_role, e o dono não muda).

A transferência criada fica pendente até o destino aceitar; ela expira sozinha (expires_at).

CUIDADO OPERACIONAL, medido em 2026-09-05: aceitar a transferência muda o dono no cadastro do site (tenancy), e o ACERVO (posts, mídia, termos, licença) só acompanha porque o tenancy converge os demais serviços numa fila. Se essa convergência estiver parada, o site aparece na empresa nova com o conteúdo ainda carimbado para a antiga.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
org_id path sim —

Tipos de conteúdo: application/json (object)

Status Descrição
200 transferência criada, pendente de decisão do destino
400 BAD_REQUEST (JSON ou site_id inválido), SAME_ORG (destino igual à origem) ou MODE_INVALID (modo/papel fora do contrato).
401 error
403 FORBIDDEN (membro sem members:manage) ou NOT_OWNER — o site informado não pertence à organização da URL. Adivinhar o ULID do site de outra empresa não transfere nada.
404 NOT_FOUND (não é membro da org) ou TARGET_NOT_FOUND (slug de destino inexistente).
409 TRANSFER_EXISTS — já há uma pendente para essa organização.
500 error
501 error

Aceitar uma transferência recebida

A org da URL é o DESTINO. Aceitar muda o dono do site no tenancy e dispara a convergência do acervo nos demais serviços.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
org_id path sim —
id path sim —
Status Descrição
200 transferência aceita
400 error
401 error
403 FORBIDDEN — sua organização não é parte desta transferência.
404 NOT_FOUND (não é membro da org) ou TRANSFER_NOT_FOUND.
409 NOT_PENDING — já decidida ou expirada.
500 error
501 error

Recusar uma transferência recebida

A org da URL é o DESTINO. Nada muda no cadastro do site.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
org_id path sim —
id path sim —
Status Descrição
204 recusada
400 error
401 error
403 FORBIDDEN — sua organização não é parte desta transferência.
404 NOT_FOUND (não é membro da org) ou TRANSFER_NOT_FOUND.
409 NOT_PENDING — já decidida ou expirada.
500 error
501 error

Cancelar uma transferência que a organização iniciou

A org da URL é a ORIGEM. Só cabe enquanto a transferência está pendente — depois de aceita, desfazer é uma transferência nova no sentido contrário.

Autenticação: bearerAuth

Nome Onde Obrigatório Descrição
org_id path sim —
id path sim —
Status Descrição
204 cancelada
400 error
401 error
403 FORBIDDEN — sua organização não é parte desta transferência.
404 NOT_FOUND (não é membro da org) ou TRANSFER_NOT_FOUND.
409 NOT_PENDING — já decidida ou expirada.
500 error
501 error