API v1

Autenticação

Backend: msystem_api_go · Fiber v2 Mobile: Android (Kotlin) · iOS (Swift) JWT HS256 · Cookie HttpOnly

A autenticação é fim-a-fim: o cliente (Android / iOS) coleta as credenciais, chama /v1/auth/login, recebe um access token (JWT) e um refresh token via cookie, e usa o JWT no header Authorization de todas as chamadas seguintes. O backend Go valida o token a cada requisição autenticada (assinatura + versão da senha + permissões da rota).

Visão geral do fluxo

┌─────────────┐   1. POST /v1/auth/login (username, password, company_id?)
│  Mobile App │──────────────────────────────────────────────►  ┌──────────────────┐
│ (Android /  │                                                  │  Go API (Fiber)  │
│   iOS)      │◄──────── 2. { token, expiration } + cookie ──────│  modules/auth    │
└──────┬──────┘                Set-Cookie: refresh_token         └──────────┬───────┘
       │                                                                    │
       │ 3. Persiste: jwt_token, perms_user, company_id, user_id            │
       │                                                                    │
       │ 4. GET /v1/<qualquer> com Authorization: Bearer <jwt>              │
       ├───────────────────────────────────────────────────────────────────►│
       │                                                                    │
       │             middleware: Authenticator → permissões → handler       │
       │◄───────────────────────────────────────────────────── 200 { data } │
       │                                                                    │
       │ 5. Quando JWT expira (~20 min) → POST /v1/auth/refresh             │
       │    (envia cookie refresh_token + body opcional)                    │
       └───────────────────────────────────────────────────────────────────►│
                                                                            │
                                          ◄── { token, expiration } novos ──┘

1 · Endpoints e regras de token

Declarações em src/modules/auth/routers/routers.go:

POST/v1/auth/login
Autenticação principal. Sem auth. RateLimit 5-M. Audit crítico LOGIN. Aceita AuthLogin{ username, password, company_id?, remember? }.
POST/v1/auth/refresh
Renova o access token. Lê cookie refresh_token (ou header). Audit crítico REFRESH.
POST/v1/auth/logout
Encerra a sessão e limpa o cookie. Audit crítico LOGOUT.
POST/v1/auth/generate_access_token
Gera código OTP por SMS — busca usuário por CPF e valida que o phone está cadastrado. Sem auth.
POST/v1/auth/validate_access_token
Confirma OTP (cpf + code). Caminho de login alternativo.
PATCH/v1/auth/company/change
Troca a empresa ativa da sessão. Reaproveita o Login internamente (modo Refresh: true) para reemitir o JWT com novo companie_id.

2 · TTLs e ambientes

TokenProduçãoTest modeDev (APP_ENV=dev)
Access (JWT)20 min7 diassem exp (nunca expira)
Refresh4 dias4 dias10 anos (cookie) / sem exp (claim)
Em APP_ENV=dev, o campo expiration retornado no payload de login vem como 0. Constantes em src/middleware/jwt.go: defaultAccessTokenTTL=20m, defaultRefreshTokenTTL=4d, testModeAccessTokenTTL=7d.

3 · Claims do JWT

Geradas em middleware.GenerateToken e validadas em middleware.ValidadeToken:

ClaimOrigemUso no backend
idUUID do usuárioauth.ID — filtros por usuário, audit log
usernameCPF (normalmente)Identificação humana, logs
companie_idEmpresa ativa selecionadaMulti-tenant — filtra TODAS as queries Elastic/Postgres
id_usuarioID do vínculo usuário↔empresaPermissões específicas por empresa
typeTipo da empresa (UUID)Casa com o campo Sectors da rota
name_completeNome completoExibição em UI, logs de auditoria
pwdvHash da versão da senhaInvalida o JWT se a senha foi trocada (anti-replay)
authorizedtrue fixoSentinela
expUnix timestampExpiração — ausente em APP_ENV=dev
pwdv (password version): passwordSecurity.BuildPasswordVersion(userID, username, passwordHash, JWT_Secret). Em todo request o middleware refaz esse cálculo a partir do hash salvo no Elasticsearch e compara em tempo constante. Se você redefine a senha do usuário, todos os JWTs antigos viram inválidos automaticamente — sem precisar manter blocklist.

4 · Extração do token (ordem)

Função middleware.ExtractToken aceita o token de mais de uma origem:

  1. Authorization: Bearer <jwt> (preferencial — usado pelo mobile em todas as chamadas).
  2. Authorization: <jwt> sem prefixo (tolerado, mesmo normalizador).
  3. Cookie refresh_tokenapenas para /v1/auth/refresh e /v1/auth/company/change.

O normalizador remove aspas, ignora "null"/"undefined" literais e faz TrimSpace. Caso típico iOS: garantir que o header não venha como "Bearer null" quando o Keychain está vazio.

5 · Login passo-a-passo (backend)

Implementação em src/modules/auth/services/login.go:

1

Canonicaliza username

canonicalizeLoginUsername(auth.Username) — remove máscara do CPF, trim, etc.

2

Busca usuário no Elastic

repository.GetUserElasticWhithPass(...). Se não achar → 401 user_not_found.

3

Verifica senha (multi-esquema)

Detecta automaticamente o hash: moderno (bcrypt/argon) ou legacy MD5. Se MD5, valida e regrava com hash moderno no Elastic (upgrade transparente).

4

Resolve empresa

Filtra empresas expiradas (RemoveExpiredUserCompanies). Se nenhuma → 401 no_active_company. Se company_id foi enviado, valida vínculo; senão usa a primeira.

5

Carrega tipo da empresa

GetCompanieElastic(...) retorna o type (vai para a claim type) — usado depois pelo middleware de autorização para casar com o Sectors da rota.

6

Gera tokens + cookie

GenerateToken(authDb) emite access + refresh. O refresh vai como Set-Cookie: refresh_token=...; HttpOnly; SameSite=None|Lax; Secure (Secure só em produção). Resposta JSON: { token, expiration }.

6 · Resposta de sucesso

HTTP/1.1 200 OK
Set-Cookie: refresh_token=eyJhbGc...; Path=/; HttpOnly; SameSite=Lax
Content-Type: application/json

{
  "token": "eyJhbGc...<access JWT>",
  "expiration": 1735689600
}
Diferente da maioria das respostas v1 (que envolvem em { "data": ... }), o login retorna direto o objeto. O wrapper data só aparece a partir das chamadas autenticadas.

7 · Refresh e troca de empresa

Tanto POST /v1/auth/refresh quanto PATCH /v1/auth/company/change reaproveitam o serviço Login com Refresh: true:

  • Valida o token recebido (do header ou do cookie refresh_token).
  • Reconstrói AuthLogin{ Username, CompanyId, Refresh: true, ... } a partir das claims.
  • No refresh, mantém o companie_id da claim atual.
  • Na troca de empresa, usa o company_id do body — o backend revalida o vínculo no Elastic antes de reemitir.
  • Por Refresh: true, o passo 3 (verificação de senha) é pulado — não há credencial nesses fluxos.

8 · Pipeline de autorização (após login)

Toda rota com Autentication: true passa por:

techlog → audit → FormatModel → Authenticator → permissões → RateLimit → Handler

O Authenticator (middleware.Authenticator):

  1. Extrai o token (ordem acima).
  2. Parseia com JWT_Secret (HMAC-SHA256).
  3. Revalida o pwdv contra o hash atual no Elastic.
  4. Popula c.Locals("tokenClaims") com *authModel.Auth.
  5. Em caso de falha: 401 { "erro": "..." }.

Em seguida o middleware de permissão (src/services/auth.go) valida:

  • Sectorsauth.Typeroute.Sectors (ex.: "T").
  • Serviços contratados da empresa cobrem route.Permissions.
  • Permissões usuário/setor com Operator AND ou OR.
  • PermissionRestricted — bloqueio explícito.

9 · Como o mobile usa tudo isso

OperaçãoAndroidiOS
Login LoginFragmentPOST /v1/auth/login LoginViewControllerPOST /v1/auth/login
Persistir JWT SharedPreferences "jwt_tokens" · chave "jwt_token" Keychain · chave "jwt_token"
Persistir company_id SharedPreferences "selected_company" · chave "company_id" UserDefaults · chave "selected_company"
Persistir perms_user SharedPreferences "DATA" · chave "perms_user" UserDefaults · chave "perms_user"
Header em chamadas Interceptor OkHttp adiciona Authorization: Bearer <jwt> URLRequest.setValue("Bearer \(jwt)", forHTTPHeaderField: "Authorization")
Refresh quando expira JWTTokenMonitor.shared.ensureValidTokenForAPICall() antes de cada chamada crítica Mesma estratégia — verifica expiration salvo e chama /auth/refresh proativamente
OTP / Biometria CodeConfirmationFragment usa generate_access_token + validate_access_token Fluxo equivalente em login/options/ · biometria via LAContext
Troca de empresa ConfigurationFragment → PATCH /v1/auth/company/change → reroda analyzePermissionIds() ConfigViewController → mesmo endpoint → reescreve perms_user
Race condition conhecida (mobile): o company_id precisa estar persistido antes de iniciar o ciclo de refresh. Senão o novo JWT é reemitido apontando para a empresa errada. Tanto o Android quanto o iOS gravam o selected_company de forma síncrona dentro do callback de sucesso do login, antes de navegar para a Home.

10 · API legada (em descomissionamento)

O backend antigo (api.monisat.online) usava três headers fixos por requisição:

Client-Id: <client_id>
User-Id:   <user_id>
Token:     <token estático>

Esse modelo persiste apenas em chamadas listadas no doc Migração legacy → v1. Todos os módulos novos do mobile já usam exclusivamente o Bearer JWT da v1.

Erros comuns

SintomaCausa provávelOnde investigar
401 token invalidoJWT expirado ou pwdv divergente (senha trocada)Forçar refresh; se persistir, exigir novo login.
401 token ausenteHeader não chegou (interceptor desativado, Keychain vazio)Logar o header antes do envio; ver ExtractToken.
401 Credenciais incorretasLogin: usuário não encontrado, hash inválido ou empresa expiradaBuscar reason no audit log (campos password_scheme, company_id_requested).
401 requested_company_not_linkedO company_id enviado não está na lista de empresas ativas do usuárioRecarregar a lista no app; usuário pode ter sido removido da empresa.
Token aceito mas 403 em rotasPermissão insuficiente (sector, serviço contratado ou ID de perm)src/services/auth.go + lista Permissions/Sectors da rota.

Arquivos-fonte

CamadaArquivo
Geração / validação JWTmsystem_api_go/src/middleware/jwt.go
Login + Refresh + Company Changemsystem_api_go/src/modules/auth/services/{login,refresh,company_change}.go
OTP por SMSmsystem_api_go/src/modules/auth/services/{generate,validate}_access_token.go
Cookie do refreshmsystem_api_go/src/modules/auth/services/refresh_cookie_policy.go
Versão da senha (pwdv)msystem_api_go/src/security/password/password.go
Autorização por rotamsystem_api_go/src/services/auth.go
Mobile · AndroidVer Login (Android)
Mobile · iOSVer Login (iOS)