defmodule ApiBrasil.Platform.Auth do @moduledoc """ Autenticação e conta (`/auth/*`, `/profile*`, `/password/*`). O cliente da SDK é imutável, então nenhuma rota guarda o Bearer Token sozinha: `login/3`, `verify_2fa/3` e `refresh/2` devolvem a sessão retornada pela plataforma e cabe a você aplicar o token com `authenticate/2` — ou usar `ApiBrasil.login/2`, que já devolve o cliente autenticado. alias ApiBrasil.Platform.Auth {:ok, sessao} = Auth.login(client, %{"email" => "voce@empresa.com.br", "password" => "******"}) client = if Auth.requires_2fa?(sessao) do desafio = sessao["challenge"] {:ok, _} = Auth.send_2fa(client, %{"challenge" => desafio, "method" => "email"}) {:ok, sessao} = Auth.verify_2fa(client, %{"challenge" => desafio, "code" => "000000"}) Auth.authenticate(client, sessao) else Auth.authenticate(client, sessao) end """ use ApiBrasil.Core.Service route_post(:login, "auth/login", """ Autentica com email/senha: `POST /auth/login`. O body aceita `email`, `password` e `turnstile_token`. Se a conta tiver 2FA, a resposta traz `requires_2fa` e `challenge` — use `send_2fa/3` e `verify_2fa/3` para concluir. Devolve apenas a sessão: aplique o token no cliente com `authenticate/2` (ou use `ApiBrasil.login/2`, que já entrega o cliente autenticado). """) route_post(:verify_2fa, "auth/login/verify-2fa", """ Conclui o login com o código 2FA: `POST /auth/login/verify-2fa`. Devolve a sessão autenticada: aplique o token no cliente com `authenticate/2`. """) route_empty(:refresh, :post, "refresh", """ Renova o JWT: `POST /refresh`. Devolve a nova sessão: aplique o token no cliente com `authenticate/2`. """) route_empty(:logout, :post, "auth/logout", """ Encerra a sessão: `POST /auth/logout`. O cliente é imutável e continua carregando o token antigo — descarte-o ou use `ApiBrasil.put_bearer_token(client, nil)`. """) route_post(:send_2fa, "auth/2fa/send", """ Envia o código 2FA pelo método escolhido (`email`, `sms`, `whatsapp`, `call`): `POST /auth/2fa/send`. """) route_post(:register, "auth/register", "Cria uma conta: `POST /auth/register`.") route_post( :register_simple, "auth/register/simple", "Cadastro simplificado: `POST /auth/register/simple`." ) route_post( :verification_send, "auth/verification/send", "Dispara a verificação de email/celular: `POST /auth/verification/send`." ) route_post( :verification_verify, "auth/verification/verify", "Confirma o código de verificação: `POST /auth/verification/verify`." ) route_post( :password_forgot, "auth/password/forgot", "Inicia a recuperação de senha: `POST /auth/password/forgot`." ) route_post( :password_verify_code, "auth/password/verify-code", "Valida o código de recuperação: `POST /auth/password/verify-code`." ) route_post( :password_reset, "auth/password/reset", "Redefine a senha: `POST /auth/password/reset`." ) route_post( :password_resend, "auth/password/resend", "Reenvia o código de recuperação: `POST /auth/password/resend`." ) route_post( :change_password, "password/change", "Troca a senha com a sessão ativa: `POST /password/change`." ) route_put(:update_me, "profile/me", "Atualiza o perfil: `PUT /profile/me`.") route_get( :two_factor_methods, "auth/2fa/methods", "Lista os métodos 2FA ativos da conta: `GET /auth/2fa/methods`." ) route_get(:me, "profile/me", "Perfil atual: `GET /profile/me`.") route_get(:verify, "auth/verify", "Valida o token atual: `GET /auth/verify`.") route_empty(:profile, :post, "profile", "Perfil completo, com estatísticas: `POST /profile`.") route_empty( :token_rotate, :post, "auth/token/rotate", "Rotaciona o token: `POST /auth/token/rotate`." ) route_empty( :token_revoke, :post, "auth/token/revoke", "Revoga o token atual: `POST /auth/token/revoke`." ) @doc """ Lê o Bearer Token de uma resposta de autenticação. Procura em `authorization.token` e, na sequência, em `token` — conforme a rota. Devolve `nil` quando não há token (strings vazias contam como ausentes). """ @spec extract_token(map()) :: String.t() | nil def extract_token(%{} = session) do authorization_token(session) || presence(Map.get(session, "token")) end @doc "Informa se a resposta de login exige segundo fator." @spec requires_2fa?(map()) :: boolean() def requires_2fa?(%{} = session), do: Map.get(session, "requires_2fa") == true @doc """ Devolve um novo cliente com o Bearer Token da sessão aplicado. Quando a sessão não traz token, devolve o cliente inalterado. {:ok, sessao} = ApiBrasil.Platform.Auth.login(client, credenciais) client = ApiBrasil.Platform.Auth.authenticate(client, sessao) """ @spec authenticate(Client.t(), map()) :: Client.t() def authenticate(%Client{} = client, %{} = session) do case extract_token(session) do nil -> client token -> ApiBrasil.put_bearer_token(client, token) end end defp authorization_token(session) do case Map.get(session, "authorization") do %{} = authorization -> presence(Map.get(authorization, "token")) _outro -> nil end end defp presence(token) when is_binary(token) and token != "", do: token defp presence(_token), do: nil end