Skip to content

Commit 0015a1d

Browse files
committed
- added ParseToken middleware that allows to parse JWT token and set a sub and scope to the request context
- silenced /ping, /liveness, /readiness, /health endpoints from the Logger middleware - fixed TextResponse Content-Type
1 parent b7b8ccf commit 0015a1d

12 files changed

Lines changed: 807 additions & 48 deletions

README.md

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,60 +11,124 @@ go get github.com/pkgz/rest
1111
```
1212

1313
## Server
14-
Create a simple http server with timeouts.
14+
Create an http server with sensible default timeouts.
15+
16+
```golang
17+
srv := rest.NewServer(8080)
18+
if err := srv.Run(router); err != nil {
19+
log.Fatal(err)
20+
}
21+
```
22+
23+
Timeouts default when left at zero and can be overridden on the `Server` struct:
24+
25+
| Field | Default |
26+
|---------------------|--------------|
27+
| `ReadHeaderTimeout` | `10s` |
28+
| `ReadTimeout` | `30s` |
29+
| `WriteTimeout` | `30s` |
30+
| `IdleTimeout` | `60s` |
31+
32+
When no router is provided, a default one is used exposing `/ping`, `/liveness`
33+
and a `/readiness` probe. Set `SSL` to serve HTTPS (with optional HTTP→HTTPS
34+
redirect). Call `Shutdown()` for a graceful stop.
1535

1636
## Middleware
1737

1838
### Logger
1939
Log all requests with level DEBUG.
2040

21-
Log contains next parameters:
41+
The log line contains:
2242

2343
- Method
24-
- requested url
25-
- ip address (please use hide real user ip on prod)
26-
- request duration
44+
- requested url (sensitive query params are masked, see below)
45+
- client ip address (see `GetAddr`)
2746
- response code
47+
- request duration
2848

2949
```bash
30-
[DEBUG] GET - /test - 127.0.0.1 - 10.423µs - 200
50+
[DEBUG] GET - /test - 127.0.0.1 - 200 - 10.423µs
3151
```
3252

53+
Sensitive query parameters (`jwt`, `token`, `access_token`, `api_key`) are masked
54+
as `***` before logging, so secrets passed in the URL never reach the logs.
55+
56+
Requests to known health-check paths (`rest.HealthPaths`: `/ping`, `/liveness`,
57+
`/readiness`) are not logged, to keep probe noise out of the logs. The list is a
58+
package variable you can extend.
59+
60+
### Readiness
61+
Middleware for a readiness probe. Returns `503 Service Unavailable` until the
62+
provided `*atomic.Value` holds `true`.
63+
64+
```golang
65+
isReady := &atomic.Value{}
66+
isReady.Store(false)
67+
router.Use(rest.Readiness("/readiness", isReady))
68+
```
69+
70+
### ParseToken
71+
Verifies the request JWT (`RS256`) against the public key published at
72+
`${AUTH_HOST}/.well-known/jwks.json`, optionally enforcing scopes, and adds the
73+
`sub` and `scope` claims to the request context.
74+
75+
```golang
76+
router.Use(rest.ParseToken("admin", "user"))
77+
```
78+
79+
Requires the `AUTH_HOST` environment variable. `JWKS_KEY_ID` may be set to select
80+
a specific key from the key set.
81+
3382
## Helpers
3483

3584
### ReadBody
36-
Read the body from request and trying to unmarshal to the provided struct.
85+
Read the body from a request and unmarshal it into the provided struct pointer.
86+
Reads are capped at `rest.MaxBodyBytes` (default 1 MiB) to bound memory use.
87+
88+
### GetAddr
89+
Resolve the client address. Proxy-supplied headers take precedence (when present)
90+
over `RemoteAddr`, in the order: `CF-Connecting-IP`, `X-Forwarded-For` (first
91+
entry), `X-Real-Ip`, then `RemoteAddr`. These headers are client-spoofable, so
92+
only rely on them behind a trusted proxy.
3793

3894
### JsonResponse
39-
Write a response with application/json Content-Type header.
40-
Except only bytes or struct.
95+
Write a response with `application/json` Content-Type header.
96+
97+
### TextResponse
98+
Write a plain string response with `text/html` Content-Type header.
99+
100+
### OkResponse
101+
Write `{"ok":true}` with an `application/json` Content-Type header.
41102

42103
### ErrorResponse
43-
Makes error response easiest.
104+
Write an error response.
44105

45106
```golang
46-
JsonError(w, http.StatusBadRequest, err, "Missed value in request")
107+
rest.ErrorResponse(w, r, http.StatusBadRequest, err, "Missed value in request")
47108
```
48109

49-
Error in response has the next structure:
110+
The error response has the following structure:
50111

51-
```
112+
```golang
52113
type HttpError struct {
53114
Err string `json:"error"`
54115
Message string `json:"message,omitempty"`
116+
TraceID string `json:"trace_id,omitempty"`
55117
}
56118
```
57119

120+
`TraceID` is populated from the request's `Uber-Trace-Id` header when present.
121+
58122
### NotFound
59-
Handler for not found endpoint.
60-
Return next response:
123+
Handler for a not found endpoint. Returns:
61124

62125
```bash
63126
Content-Type: text/plain
64127
Status Code: 404 (Not Found)
65128
Body: Not found.
66129
```
67130

68-
69131
## Licence
70132
[MIT License](https://github.com/pkgz/rest/blob/master/LICENSE)
133+
</content>
134+
</invoke>

go.mod

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
11
module github.com/pkgz/rest
22

3-
go 1.19
3+
go 1.26
44

55
require (
6-
github.com/go-chi/chi/v5 v5.0.3
7-
github.com/stretchr/testify v1.3.0
6+
github.com/go-chi/chi/v5 v5.3.0
7+
github.com/go-chi/jwtauth/v5 v5.4.0
8+
github.com/go-jose/go-jose/v4 v4.1.4
9+
github.com/stretchr/testify v1.11.1
810
)
911

1012
require (
1113
github.com/davecgh/go-spew v1.1.1 // indirect
14+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect
15+
github.com/goccy/go-json v0.10.6 // indirect
16+
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
17+
github.com/lestrrat-go/dsig v1.3.0 // indirect
18+
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect
19+
github.com/lestrrat-go/httpcc v1.0.1 // indirect
20+
github.com/lestrrat-go/httprc/v3 v3.0.6 // indirect
21+
github.com/lestrrat-go/jwx/v3 v3.1.1 // indirect
22+
github.com/lestrrat-go/option v1.0.1 // indirect
23+
github.com/lestrrat-go/option/v2 v2.0.0 // indirect
1224
github.com/pmezard/go-difflib v1.0.0 // indirect
25+
github.com/segmentio/asm v1.2.1 // indirect
26+
github.com/valyala/fastjson v1.6.10 // indirect
27+
golang.org/x/crypto v0.53.0 // indirect
28+
golang.org/x/sys v0.46.0 // indirect
29+
gopkg.in/yaml.v3 v3.0.1 // indirect
1330
)

go.sum

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,67 @@
11
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
22
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
33
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4-
github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4=
5-
github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
4+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
5+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
6+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo=
7+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
8+
github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM=
9+
github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
10+
github.com/go-chi/jwtauth/v5 v5.4.0 h1:Ieh0xMJsFvqylqJ02/mQHKzbbKO9DYNBh4DPKCwTwYI=
11+
github.com/go-chi/jwtauth/v5 v5.4.0/go.mod h1:w6yjqUUXz1b8+oiJel64Sz1KJwduQM6qUA5QNzO5+bQ=
12+
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
13+
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
14+
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
15+
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
16+
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
17+
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
18+
github.com/lestrrat-go/blackmagic v1.0.3 h1:94HXkVLxkZO9vJI/w2u1T0DAoprShFd13xtnSINtDWs=
19+
github.com/lestrrat-go/blackmagic v1.0.3/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
20+
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
21+
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
22+
github.com/lestrrat-go/dsig v1.3.0 h1:phjMOCXvYzhuIgn7Voe2rex8z166vGfxRxmqM25P9/Q=
23+
github.com/lestrrat-go/dsig v1.3.0/go.mod h1:RD2eOaidyPvpc7IJQoO3Qq52RWdy8ZcJs8lrOnoa1Kc=
24+
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY=
25+
github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU=
26+
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
27+
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
28+
github.com/lestrrat-go/httprc/v3 v3.0.0-beta2 h1:SDxjGoH7qj0nBXVrcrxX8eD94wEnjR+EEuqqmeqQYlY=
29+
github.com/lestrrat-go/httprc/v3 v3.0.0-beta2/go.mod h1:Nwo81sMxE0DcvTB+rJyynNhv/DUu2yZErV7sscw9pHE=
30+
github.com/lestrrat-go/httprc/v3 v3.0.6 h1:4FpLQ18KK/ypPbVU3NLWJNRvH3kcYiqKqWfKGqNWxxI=
31+
github.com/lestrrat-go/httprc/v3 v3.0.6/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0=
32+
github.com/lestrrat-go/jwx/v3 v3.0.2 h1:N+XLjTJEzDZRP3S0SezclXFAfopwL+o5vaL+qg6rX1I=
33+
github.com/lestrrat-go/jwx/v3 v3.0.2/go.mod h1:qO9w1qkQH77a0r9OXNM33YQPnV/evetKYRg58h1rBNE=
34+
github.com/lestrrat-go/jwx/v3 v3.1.1 h1:yd9AdPmZ4INnQ7k42IrzXYpnEG803+SrQ6hdMvzHJzw=
35+
github.com/lestrrat-go/jwx/v3 v3.1.1/go.mod h1:uw/MN2M/Xiu4FhwcIwH11Zsh9JWx9SWzgALl7/uIEkU=
36+
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
37+
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
38+
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
39+
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
640
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
741
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
42+
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
43+
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
44+
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
45+
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
846
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
9-
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
10-
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
47+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
48+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
49+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
50+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
51+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
52+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
53+
github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=
54+
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
55+
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
56+
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
57+
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
58+
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
59+
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
60+
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
61+
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
62+
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
63+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
64+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
65+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
66+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
67+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)