MishkaCmsCore.Runtime.Plugs.AuthRateLimitPlug (Mishka CMS Core v0.0.1)

Copy Markdown View Source

The JSON:API side of MishkaCmsCore.Accounts.AuthRateLimit: hands every action the address conn.remote_ip names (the endpoint's plug RemoteIp has read it) as the ash_authentication_request context, marked api?: true so the actions ask no captcha (MishkaCmsCore.Accounts.CaptchaCheck), and gives a 429 AshJsonApi wrote for a refused sign-in or request its headers.

pipeline :api do
  plug :accepts, ["json"]
  plug MishkaCmsCore.Runtime.Plugs.ApiTenantLookupPlug
  plug MishkaCmsCore.Runtime.Plugs.ApiCorsPlug
  plug MishkaCmsCore.Runtime.Plugs.AuthRateLimitPlug
  ...
end

The refusal

retry-after in seconds, x-error-code: rate_limited and cache-control: no-store, both headers exposed to a frontend on an origin ApiCorsPlug allowed, on the JSON:API errors document:

{"errors": [{"status": "429", "code": "rate_limited", "title": "Too many sign-in attempts",
             "detail": "Too many sign-in attempts. Try again in 15 minutes.",
             "meta": {"retry_after": 900, "action": "sign_in", "limited_by": "account"}}],
 "jsonapi": {"version": "1.0"}}

Events

  • auth:request:rate_limited — the refused conn, with %{action: action, key: :ip | :account, retry_after: seconds, site: site} as its meta. The response is written by then: a plugin may change it with Plug.Conn.resp/3 and its headers, not send one. MishkaCmsCoreWeb.AuthController fires it for the browser's routes, before anything is sent.

Summary

Functions

Puts the headers of a refusal that says to wait seconds on conn.

Functions

call(conn, opts)

init(opts)

put_refusal_headers(conn, seconds)

@spec put_refusal_headers(Plug.Conn.t(), pos_integer()) :: Plug.Conn.t()

Puts the headers of a refusal that says to wait seconds on conn.