add rate limiting and HSTS for security hardening
Some checks failed
deploy / deploy (push) Failing after 8m33s
Some checks failed
deploy / deploy (push) Failing after 8m33s
- Add Hammer library for rate limiting with ETS backend - Rate limit login (5/min), magic link (3/min), newsletter (10/min), API (60/min) - Add themed 429 error page using bare shop styling - Enable HSTS in production with rewrite_on for Fly proxy - Add security hardening plan to docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,8 @@ defmodule BerrypodWeb.CartController do
|
||||
|
||||
alias Berrypod.Cart
|
||||
|
||||
plug BerrypodWeb.Plugs.RateLimit, [type: :api] when action == :update
|
||||
|
||||
@doc """
|
||||
Updates the cart in session (JSON API for JS hook).
|
||||
"""
|
||||
|
||||
@@ -3,6 +3,8 @@ defmodule BerrypodWeb.ContactController do
|
||||
|
||||
alias Berrypod.ContactNotifier
|
||||
|
||||
plug BerrypodWeb.Plugs.RateLimit, [type: :api] when action == :create
|
||||
|
||||
@doc """
|
||||
Handles contact form submission (no-JS fallback).
|
||||
"""
|
||||
|
||||
@@ -32,6 +32,11 @@ defmodule BerrypodWeb.ErrorHTML do
|
||||
)
|
||||
end
|
||||
|
||||
def render("429.html", assigns) do
|
||||
# 429 gets a bare themed page - just the message, no nav/footer/hero
|
||||
render_bare_error(assigns, "429", "Too many requests", "Please wait a moment and try again.")
|
||||
end
|
||||
|
||||
def render(template, _assigns) do
|
||||
Phoenix.Controller.status_message_from_template(template)
|
||||
end
|
||||
@@ -53,6 +58,54 @@ defmodule BerrypodWeb.ErrorHTML do
|
||||
end
|
||||
end
|
||||
|
||||
defp render_bare_error(assigns, error_code, error_title, error_description) do
|
||||
{theme_settings, generated_css} = load_theme_data()
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|> Map.put(:error_code, error_code)
|
||||
|> Map.put(:error_title, error_title)
|
||||
|> Map.put(:error_description, error_description)
|
||||
|> Map.put(:theme_settings, theme_settings)
|
||||
|> Map.put(:generated_css, generated_css)
|
||||
|
||||
~H"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{@error_code} - {@error_title}</title>
|
||||
<link phx-track-static rel="stylesheet" href={~p"/assets/css/shop.css"} />
|
||||
<style id="theme-css">
|
||||
<%= Phoenix.HTML.raw(@generated_css) %>
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
class="shop-root themed"
|
||||
data-mood={@theme_settings.mood}
|
||||
data-typography={@theme_settings.typography}
|
||||
data-shape={@theme_settings.shape}
|
||||
data-density={@theme_settings.density}
|
||||
>
|
||||
<main style="min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem;">
|
||||
<p style="font-size: 4rem; font-weight: 700; margin: 0; color: var(--color-text);">
|
||||
{@error_code}
|
||||
</p>
|
||||
<h1 style="font-size: 1.5rem; font-weight: 600; margin: 0.5rem 0; color: var(--color-text);">
|
||||
{@error_title}
|
||||
</h1>
|
||||
<p style="font-size: 1rem; opacity: 0.6; color: var(--color-text);">
|
||||
{@error_description}
|
||||
</p>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_minimal_error(assigns) do
|
||||
~H"""
|
||||
<!DOCTYPE html>
|
||||
|
||||
@@ -3,6 +3,8 @@ defmodule BerrypodWeb.NewsletterController do
|
||||
|
||||
alias Berrypod.Newsletter
|
||||
|
||||
plug BerrypodWeb.Plugs.RateLimit, [type: :newsletter] when action == :subscribe
|
||||
|
||||
@doc "No-JS fallback for newsletter signup form."
|
||||
def subscribe(conn, %{"email" => email}) do
|
||||
ip_hash = hash_ip(conn)
|
||||
|
||||
@@ -4,6 +4,8 @@ defmodule BerrypodWeb.UserSessionController do
|
||||
alias Berrypod.Accounts
|
||||
alias BerrypodWeb.UserAuth
|
||||
|
||||
plug BerrypodWeb.Plugs.RateLimit, [type: :login] when action == :create
|
||||
|
||||
def create(conn, %{"_action" => "confirmed"} = params) do
|
||||
create(conn, params, "User confirmed successfully.")
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user