Current section
Files
Jump to
Current section
Files
lib/api_brasil/platform/auth.ex
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