Skip to content

Commit 8d23030

Browse files
committed
Add self-contained Antora documentation
Migrate from docs-repo-driven doc generation to self-contained docs: - Add bb-tasks git dep for gen-api-docs task - Add sync-readme, gen-api-docs, gen-docs babashka tasks - Switch README from markdown to AsciiDoc - Switch usage guide from doc/usage.md to Antora pages - Set Antora version to 'next' (prerelease) for main branch - Commit generated API doc pages and nav partial
1 parent cbd7b68 commit 8d23030

15 files changed

+1506
-434
lines changed

README.adoc

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
= client-ip
2+
3+
A 0-dependency ring middleware for determining a request's real client IP address from HTTP headers
4+
5+
image:https://github.com/outskirtslabs/client-ip/actions/workflows/ci.yml/badge.svg[Build Status,link=https://github.com/outskirtslabs/client-ip/actions]
6+
image:https://cljdoc.org/badge/com.outskirtslabs/client-ip[cljdoc,link=https://cljdoc.org/d/com.outskirtslabs/client-ip]
7+
image:https://img.shields.io/clojars/v/com.outskirtslabs/client-ip.svg[Clojars Project,link=https://clojars.org/com.outskirtslabs/client-ip]
8+
9+
`X-Forwarded-For` and other client IP headers are https://adam-p.ca/blog/2022/03/x-forwarded-for/[often used incorrectly], resulting in bugs and security vulnerabilities. This library provides strategies for extracting the correct client IP based on your network configuration.
10+
11+
It is based on the golang reference implementation https://github.com/realclientip/realclientip-go[realclientip/realclientip-go].
12+
13+
Quick feature list:
14+
15+
* ring middleware determining the client's IP address
16+
* 0 dependency IP address string parsing with guaranteed no trips to the hosts' DNS services, which can block and timeout (unlike Java's `InetAddress/getByName`)
17+
* rightmost-ish strategies support the `X-Forwarded-For` and `Forwarded` (RFC 7239) headers
18+
* IPv6 zone identifiers support
19+
20+
Note that there is no dependency on ring, the public API could also be used for pedestal or sieppari-style interceptors.
21+
22+
== Installation
23+
24+
[source,clojure]
25+
----
26+
;; deps.edn
27+
{:deps {com.outskirtslabs/client-ip {:mvn/version "0.1.1"}}}
28+
29+
;; Leiningen
30+
[com.outskirtslabs/client-ip "0.1.1"]
31+
----
32+
33+
== Quick Start
34+
35+
[source,clojure]
36+
----
37+
(ns myapp.core
38+
(:require [ol.client-ip.core :as client-ip]
39+
[ol.client-ip.strategy :as strategy]))
40+
41+
;; Simple case: behind a trusted proxy that sets X-Real-IP
42+
(def app
43+
(-> handler
44+
(client-ip/wrap-client-ip
45+
{:strategy (strategy/single-ip-header-strategy "x-real-ip")})))
46+
47+
;; The client IP is now available in the request
48+
(defn handler [request]
49+
(let [client-ip (:ol/client-ip request)]
50+
{:status 200
51+
:body (str "Your IP is: " client-ip)}))
52+
----
53+
54+
For detailed guidance on choosing strategies, see link:doc/modules/ROOT/pages/usage.adoc[Usage Guide].
55+
56+
Choosing the wrong strategy can result in IP address spoofing security vulnerabilities.
57+
58+
== Recommended Reading
59+
60+
You think it is an easy question:
61+
62+
____
63+
I have an HTTP application, I just want to know the IP address of my client.
64+
____
65+
66+
But who is the client?
67+
68+
____
69+
The computer on the other end of the network connection?
70+
____
71+
72+
But which network connection? The one connected to your HTTP application is probably a reverse proxy or load balancer.
73+
74+
____
75+
Well I mean the "user's IP address"
76+
____
77+
78+
It ain't so easy kid.
79+
80+
There are many good articles on the internet that discuss the perils and pitfalls of trying to answer this deceptively simple question.
81+
82+
You _should_ read one or two of them to get an idea of the complexity in this space. Libraries, like this one, _cannot_ hide the complexity from you, there is no abstraction nor encapsulation nor "default best practice".
83+
84+
Below are some of those good articles:
85+
86+
* MDN: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For#security_and_privacy_concerns[X-Forwarded-For: Security and privacy concerns]
87+
* https://adam-p.ca/blog/2022/03/x-forwarded-for/[The perils of the "real" client IP] (https://web.archive.org/web/20250416042714/https://adam-p.ca/blog/2022/03/x-forwarded-for/[archive link])
88+
* https://www.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/[Rails IP Spoofing Vulnerabilities and Protection] (https://web.archive.org/web/20250421121810/https://www.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/[archive link])
89+
* https://casey.link/blog/client-ip-ring-middleware/[ol.client-ip: A Clojure Library to Prevent IP Spoofing]
90+
91+
== Security
92+
93+
See https://github.com/outskirtslabs/client-ip/security/advisories[here] for security advisories or to report a security vulnerability.
94+
95+
== License
96+
97+
Copyright (C) 2025 Casey Link <casey@outskirtslabs.com>
98+
99+
Distributed under the https://github.com/outskirtslabs/client-ip/blob/main/LICENSE[MIT License].

README.md

Lines changed: 0 additions & 96 deletions
This file was deleted.

bb.edn

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11

22
{:min-bb-version "0.8.156"
3-
:deps {}
3+
:deps {outskirtslabs/bb-tasks
4+
{:git/url "https://github.com/outskirtslabs/bb-tasks"
5+
:git/sha "103fafddb29ea04b08083742684ab1bb39d87c27"}}
6+
:pods {clj-kondo/clj-kondo {:version "2024.11.14"}}
47
:paths ["resources" "scripts"]
58
:tasks {:requires ([babashka.fs :as fs]
69
[clojure.string :as str]
@@ -26,4 +29,21 @@
2629
publish {:doc "Publish the clojure sdk libs to clojars"
2730
:depends [jar]
2831
:task (clojure "-T:build deploy")}
29-
ci {:depends [test fmt.check lint]}}}
32+
ci {:depends [test fmt.check lint]}
33+
sync-readme {:doc "Sync README.adoc as Antora index page"
34+
:task (let [readme (slurp "README.adoc")
35+
index (str/replace readme
36+
(re-pattern "link:doc/modules/ROOT/pages/([^\\[]+)\\[")
37+
"xref:$1[")]
38+
(spit "doc/modules/ROOT/pages/index.adoc" index)
39+
(println "Synced README.adoc -> doc/modules/ROOT/pages/index.adoc"))}
40+
gen-api-docs {:doc "Generate AsciiDoc API reference pages"
41+
:task (let [gen (requiring-resolve 'ol.bb-tasks.gen-api-docs/generate!)
42+
branch (str/trim (:out (shell {:out :string} "git rev-parse --abbrev-ref HEAD")))]
43+
(gen {:project-root "."
44+
:source-paths ["src"]
45+
:antora-start-path "doc"
46+
:github-repo "https://github.com/outskirtslabs/client-ip"
47+
:git-branch branch}))}
48+
gen-docs {:doc "Generate all Antora documentation"
49+
:depends [sync-readme gen-api-docs]}}}

doc/antora.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
name: ol.client-ip
3+
title: Client IP
4+
version: '0.1'
5+
nav:
6+
- modules/ROOT/nav.adoc

doc/modules/ROOT/nav.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
* xref:index.adoc[Home]
2+
* xref:usage.adoc[Usage Guide]
3+
4+
include::partial$api-nav.adoc[]
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
= ol.client-ip.cidr
2+
3+
[#cidr-parts]
4+
== cidr-parts
5+
6+
[source,clojure]
7+
----
8+
(cidr-parts cidr)
9+
----
10+
11+
Parse an ip network string into its prefix and signifcant bits
12+
13+
`cidr` must be given in CIDR notation, as defined in [RFC 4632 section 3.1](https://tools.ietf.org/html/rfc4632#section-3.1)
14+
15+
Returns a vector of [^InetAddress prefix ^Integer bits]
16+
17+
18+
[.api-source]
19+
link:https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/cidr.clj#L26-L39[source,window=_blank]
20+
21+
'''
22+
23+
[#contains-QMARK-]
24+
== contains?
25+
26+
[source,clojure]
27+
----
28+
(contains? cidr ip)
29+
(contains? cidr-ip cidr-mask ip)
30+
----
31+
32+
Check if the given CIDR contains the given IP address.
33+
34+
Arguments:
35+
cidr - CIDR notation string (e.g. "192.168.0.0/24" or "2001:db8::/32")
36+
ip - IP address to check (can be InetAddress or string)
37+
38+
39+
[.api-source]
40+
link:https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/cidr.clj#L41-L61[source,window=_blank]
41+
42+
'''
43+
44+
[#reserved-ranges]
45+
== reserved-ranges
46+
47+
48+
49+
50+
[.api-source]
51+
link:https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/cidr.clj#L66-L93[source,window=_blank]
52+
53+
'''
54+
55+
[#reserved-QMARK-]
56+
== reserved?
57+
58+
[source,clojure]
59+
----
60+
(reserved? ip)
61+
----
62+
63+
Check if an IP is in a reserved range (loopback or private network).
64+
65+
66+
[.api-source]
67+
link:https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/cidr.clj#L95-L98[source,window=_blank]

0 commit comments

Comments
 (0)