{"openapi":"3.1.0","info":{"title":"GuardList API","version":"1.0.0","description":"Acesso à API do GuardList, ferramenta interna de blocklists de firewall por cliente. A API usa sessão por cookie e é restrita à equipe: não há chave de API nem cadastro público. Esta especificação cobre só o acesso; as operações internas não são publicadas."},"servers":[{"url":"https://guardlist.onrender.com"}],"externalDocs":{"description":"Documentação do GuardList","url":"https://guardlist.onrender.com/docs"},"tags":[{"name":"Serviço"},{"name":"Autenticação"}],"paths":{"/healthz":{"get":{"operationId":"getHealth","summary":"Situação do serviço","description":"Informa se o servidor está no ar. Não exige login.","tags":["Serviço"],"security":[],"responses":{"200":{"description":"Serviço no ar","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"}}}}}}}}},"/api/auth/me":{"get":{"operationId":"getCurrentUser","summary":"Usuário da sessão atual","description":"Mostra quem está na sessão. Sem sessão válida responde 200 com `user` nulo; serve para descobrir se é preciso entrar.","tags":["Autenticação"],"security":[{},{"sessionCookie":[]}],"responses":{"200":{"description":"Situação da sessão; `user` é nulo sem sessão válida","content":{"application/json":{"schema":{"type":"object","required":["authEnabled","user"],"properties":{"authEnabled":{"type":"boolean"},"user":{"oneOf":[{"$ref":"#/components/schemas/User"},{"type":"null"}]},"idleTimeoutMs":{"type":"integer","description":"Tempo sem uso até a sessão encerrar"}}}}}}}}},"/api/auth/login":{"post":{"operationId":"login","summary":"Entrar com usuário e senha","description":"Abre a sessão no cookie `gl_session`. Com a verificação em duas etapas exigida, devolve `step` e cria o cookie `gl_mfa`; continue em `verifyMfa`.","tags":["Autenticação"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["username","password"],"properties":{"username":{"type":"string","maxLength":64},"password":{"type":"string","maxLength":256,"format":"password"}}}}}},"responses":{"200":{"description":"Sessão aberta (`user`) ou próxima etapa da verificação em duas etapas (`step`)","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"},"step":{"type":"string","enum":["mfa","mfa-setup"]}}}}}},"400":{"description":"Faltam usuário ou senha","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Usuário ou senha incorretos","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"423":{"description":"Conta bloqueada por tentativas erradas","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Limite de tentativas; espere o tempo de Retry-After","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Verificação em duas etapas indisponível no servidor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/mfa":{"post":{"operationId":"verifyMfa","summary":"Confirmar o código da verificação em duas etapas","description":"Usa o cookie `gl_mfa`, criado pelo login. Envie `code` ou `recoveryCode`.","tags":["Autenticação"],"security":[{"mfaStepCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","maxLength":20},"recoveryCode":{"type":"string","maxLength":40}}}}}},"responses":{"200":{"description":"Sessão aberta","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["user"],"properties":{"user":{"$ref":"#/components/schemas/User"}}}}}},"401":{"description":"Código incorreto ou etapa expirada","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"423":{"description":"Conta bloqueada por tentativas erradas","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Limite de tentativas; espere o tempo de Retry-After","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/logout":{"post":{"operationId":"logout","summary":"Encerrar a sessão atual","description":"Invalida a sessão no servidor e apaga o cookie.","tags":["Autenticação"],"security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Sessão encerrada","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"}}}}}}}}}},"components":{"headers":{"RateLimitLimit":{"description":"Máximo de requisições na janela","schema":{"type":"integer"}},"RateLimitRemaining":{"description":"Requisições restantes na janela","schema":{"type":"integer"}},"RateLimitReset":{"description":"Segundos até a janela recomeçar","schema":{"type":"integer"}},"RetryAfter":{"description":"Segundos para esperar antes de tentar de novo","schema":{"type":"integer"}}},"securitySchemes":{"sessionCookie":{"type":"apiKey","in":"cookie","name":"gl_session","description":"Cookie de sessão criado pelo login"},"mfaStepCookie":{"type":"apiKey","in":"cookie","name":"gl_mfa","description":"Cookie temporário entre a senha e o código"}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"User":{"type":"object","required":["id","username","displayName","role","active","mustChangePassword","mfaEnabled"],"properties":{"id":{"type":"integer"},"username":{"type":"string"},"displayName":{"type":"string"},"role":{"type":"string","enum":["admin","analyst"]},"active":{"type":"boolean"},"mustChangePassword":{"type":"boolean","description":"A pessoa precisa trocar a senha antes de continuar"},"createdAt":{"type":"string","format":"date-time"},"lastLoginAt":{"type":["string","null"],"format":"date-time"},"lockedUntil":{"type":["string","null"],"format":"date-time"},"mfaEnabled":{"type":"boolean"}}}}}}