Skip to content

Commit 6b84493

Browse files
authored
api: Use Huma to generate swagger docs and parse JSON automatically (#262)
## Motivation and Context Currently there's a lot of unsafe manual JSON unmarshalling/marshalling, and we have to manually keep the openapi schema in sync. This PR migrates the API to [Huma](https://github.com/danielgtaylor/huma), which appears like a popular, well-maintained way to build APIs in Golang. I'm not a Golang expert, but seemed highly recommended by Reddit + Claude and has a decent star count. It provides automatic openapi spec generation and docs hosting, JSON parsing, as well as the ability to add auth middlewares etc. ## How Has This Been Tested? Running locally, and poking at the endpoints ## Breaking Changes Docs are served at `/docs` rather than `/v0/swagger/index.html` now ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context <!-- Add any other context, implementation notes, or design decisions -->
1 parent 122dbf6 commit 6b84493

26 files changed

+1622
-2267
lines changed

Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ FROM alpine:latest
88
WORKDIR /app
99
COPY --from=builder /build/registry .
1010
COPY --from=builder /app/data/seed.json /app/data/seed.json
11-
COPY --from=builder /app/internal/docs/swagger.yaml /app/internal/docs/swagger.yaml
1211

1312
# Create a non-privileged user that the app will run under.
1413
# See https://docs.docker.com/go/dockerfile-user-best-practices/

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ Publishing requires GitHub OAuth validation:
197197
### API Documentation
198198

199199
```
200-
GET /v0/swagger/index.html
200+
GET /docs
201201
```
202202

203203
The API is documented using Swagger/OpenAPI. This page provides a complete reference of all endpoints with request/response schemas and examples, and allows you to test the API directly from your browser.

docs/faq.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The MCP Registry is the official centralized metadata repository for publicly-ac
1818
There are four underlying concepts:
1919
- "MCP Server Registry API" (or "MCP Registry API"): The OpenAPI specification defined in [openapi.yaml](./server-registry-api/openapi.yaml). This is a reusable API specification that anyone building any sort of "MCP server registry" should consider adopting / aligning with.
2020
- "Official MCP Registry" (or "MCP Registry"): The application that lives at `https://registry.modelcontextprotocol.io`. This registry currently only catalogs MCP servers, but may be extended in the future to also catalog MCP client/host apps and frameworks.
21-
- "Official MCP Registry API": The REST API that lives at `https://registry.modelcontextprotocol.io/api`, with an OpenAPI specification defined at [swagger.yaml](../internal/docs/swagger.yaml)
21+
- "Official MCP Registry API": The REST API served at `https://registry.modelcontextprotocol.io`, which is a superset of the MCP Registry API. Its OpenAPI specification can be downloaded from [https://registry.modelcontextprotocol.io/openapi.yaml](https://registry.modelcontextprotocol.io/openapi.yaml)
2222
- "MCP server registry" (or "MCP registry"): A third party, likely commercial, implementation of the MCP Server Registry API or derivative specification.
2323

2424
### Is the MCP Registry a package registry?

docs/server-registry-api/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ The centralized, publicly available catalog ("Official MCP Registry") needs addi
1010

1111
References:
1212
- [openapi.yaml](./openapi.yaml) - A reusable API specification (MCP Server Registry API) that anyone building any sort of "MCP server registry" should consider adopting / aligning with.
13-
- [swagger.yaml](../../internal/docs/swagger.yaml) - The specification backing the Official MCP Registry; a derivative of the MCP Server Registry API specification.
13+
- [https://registry.modelcontextprotocol.io/openapi.yaml](https://registry.modelcontextprotocol.io/openapi.yaml) - The specification backing the Official MCP Registry; a derivative of the MCP Server Registry API specification.

go.mod

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,29 @@ go 1.24.0
44

55
require (
66
github.com/caarlos0/env/v11 v11.3.1
7+
github.com/danielgtaylor/huma/v2 v2.34.1
78
github.com/google/uuid v1.6.0
89
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
910
github.com/stretchr/testify v1.10.0
10-
github.com/swaggo/files v1.0.1
11-
github.com/swaggo/http-swagger v1.3.4
1211
go.mongodb.org/mongo-driver v1.17.3
13-
golang.org/x/net v0.42.0
1412
)
1513

1614
require (
17-
github.com/KyleBanks/depth v1.2.1 // indirect
1815
github.com/davecgh/go-spew v1.1.1 // indirect
19-
github.com/go-openapi/jsonpointer v0.21.1 // indirect
20-
github.com/go-openapi/jsonreference v0.21.0 // indirect
21-
github.com/go-openapi/spec v0.21.0 // indirect
22-
github.com/go-openapi/swag v0.23.1 // indirect
2316
github.com/golang/snappy v0.0.4 // indirect
24-
github.com/josharian/intern v1.0.0 // indirect
25-
github.com/klauspost/compress v1.16.7 // indirect
26-
github.com/mailru/easyjson v0.9.0 // indirect
17+
github.com/klauspost/compress v1.18.0 // indirect
18+
github.com/kr/text v0.2.0 // indirect
2719
github.com/montanaflynn/stats v0.7.1 // indirect
2820
github.com/pmezard/go-difflib v1.0.0 // indirect
21+
github.com/rogpeppe/go-internal v1.11.0 // indirect
2922
github.com/stretchr/objx v0.5.2 // indirect
30-
github.com/swaggo/swag v1.16.4 // indirect
3123
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
3224
github.com/xdg-go/scram v1.1.2 // indirect
3325
github.com/xdg-go/stringprep v1.0.4 // indirect
3426
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
3527
golang.org/x/crypto v0.41.0 // indirect
3628
golang.org/x/sync v0.16.0 // indirect
3729
golang.org/x/text v0.28.0 // indirect
38-
golang.org/x/tools v0.35.0 // indirect
30+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
3931
gopkg.in/yaml.v3 v3.0.1 // indirect
4032
)

go.sum

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,25 @@
1-
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
2-
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
31
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
42
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
3+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
4+
github.com/danielgtaylor/huma/v2 v2.34.1 h1:EmOJAbzEGfy0wAq/QMQ1YKfEMBEfE94xdBRLPBP0gwQ=
5+
github.com/danielgtaylor/huma/v2 v2.34.1/go.mod h1:ynwJgLk8iGVgoaipi5tgwIQ5yoFNmiu+QdhU7CEEmhk=
56
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
67
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7-
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
8-
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
9-
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
10-
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
11-
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
12-
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
13-
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
14-
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
158
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
169
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
17-
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
18-
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
10+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
11+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
1912
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
2013
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
21-
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
22-
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
23-
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
24-
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
14+
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
15+
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
16+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
2517
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
2618
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
19+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
20+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
2721
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
2822
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
29-
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
30-
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
3123
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
3224
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
3325
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -40,12 +32,6 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
4032
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
4133
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
4234
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
43-
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
44-
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
45-
github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
46-
github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
47-
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
48-
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
4935
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
5036
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
5137
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
@@ -62,14 +48,9 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
6248
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
6349
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
6450
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
65-
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
66-
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
6751
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
6852
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
6953
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
70-
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
71-
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
72-
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
7354
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7455
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7556
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
@@ -79,22 +60,17 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
7960
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
8061
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
8162
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
82-
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
8363
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
8464
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
85-
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
8665
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
8766
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
8867
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
8968
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
90-
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
9169
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
9270
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
9371
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
9472
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
9573
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
96-
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
97-
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
9874
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
9975
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
10076
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

internal/api/handlers/v0/auth.go

Lines changed: 75 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,101 @@
1-
// Package v0 contains API handlers for version 0 of the API
21
package v0
32

43
import (
5-
"encoding/json"
6-
"io"
4+
"context"
75
"net/http"
86

7+
"github.com/danielgtaylor/huma/v2"
98
"github.com/modelcontextprotocol/registry/internal/auth"
109
"github.com/modelcontextprotocol/registry/internal/model"
1110
)
1211

13-
// StartAuthHandler handles requests to start an authentication flow
14-
func StartAuthHandler(authService auth.Service) http.HandlerFunc {
15-
return func(w http.ResponseWriter, r *http.Request) {
16-
// Only allow POST method
17-
if r.Method != http.MethodPost {
18-
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
19-
return
20-
}
12+
// StartAuthInput represents the input for starting an auth flow
13+
type StartAuthInput struct {
14+
Body struct {
15+
Method string `json:"method" doc:"Authentication method" example:"github"`
16+
RepoRef string `json:"repo_ref" doc:"Repository reference" example:"owner/repo"`
17+
}
18+
}
2119

22-
// Read the request body
23-
body, err := io.ReadAll(r.Body)
24-
if err != nil {
25-
http.Error(w, "Error reading request body", http.StatusBadRequest)
26-
return
27-
}
28-
defer r.Body.Close()
20+
// StartAuthBody represents the auth flow start response body
21+
type StartAuthBody struct {
22+
AuthFlowInfo map[string]string `json:"auth_flow_info" doc:"Authentication flow information"`
23+
StatusToken string `json:"status_token" doc:"Token to check auth status"`
24+
}
2925

30-
// Parse request body into AuthRequest struct
31-
var authReq struct {
32-
Method string `json:"method"`
33-
RepoRef string `json:"repo_ref"`
34-
}
35-
err = json.Unmarshal(body, &authReq)
36-
if err != nil {
37-
http.Error(w, "Invalid request payload: "+err.Error(), http.StatusBadRequest)
38-
return
39-
}
26+
// CheckAuthStatusInput represents the input for checking auth status
27+
type CheckAuthStatusInput struct {
28+
StatusToken string `path:"token" doc:"Status token from auth flow start"`
29+
}
30+
31+
// CheckAuthStatusBody represents the auth status response body
32+
type CheckAuthStatusBody struct {
33+
Status string `json:"status" doc:"Authentication status" example:"pending"`
34+
}
4035

36+
// RegisterAuthEndpoints registers all auth-related endpoints
37+
func RegisterAuthEndpoints(api huma.API, authService auth.Service) {
38+
// Start auth flow endpoint
39+
huma.Register(api, huma.Operation{
40+
OperationID: "start-auth",
41+
Method: http.MethodPost,
42+
Path: "/v0/auth/start",
43+
Summary: "Start authentication flow",
44+
Description: "Start an authentication flow for publishing servers",
45+
Tags: []string{"auth"},
46+
}, func(ctx context.Context, input *StartAuthInput) (*Response[StartAuthBody], error) {
4147
// Validate required fields
42-
if authReq.Method == "" {
43-
http.Error(w, "Auth method is required", http.StatusBadRequest)
44-
return
48+
if input.Body.Method == "" {
49+
return nil, huma.Error400BadRequest("Auth method is required")
50+
}
51+
if input.Body.RepoRef == "" {
52+
return nil, huma.Error400BadRequest("Repository reference is required")
4553
}
4654

47-
// Convert string method to enum type
48-
var method model.AuthMethod
49-
switch authReq.Method {
55+
// Convert string method to AuthMethod type
56+
var authMethod model.AuthMethod
57+
switch input.Body.Method {
5058
case "github":
51-
method = model.AuthMethodGitHub
59+
authMethod = model.AuthMethodGitHub
60+
case "none":
61+
authMethod = model.AuthMethodNone
5262
default:
53-
http.Error(w, "Unsupported authentication method", http.StatusBadRequest)
54-
return
63+
return nil, huma.Error400BadRequest("Invalid auth method: " + input.Body.Method)
5564
}
5665

57-
// Start auth flow
58-
flowInfo, statusToken, err := authService.StartAuthFlow(r.Context(), method, authReq.RepoRef)
66+
// Start the auth flow
67+
flowInfo, statusToken, err := authService.StartAuthFlow(ctx, authMethod, input.Body.RepoRef)
5968
if err != nil {
60-
http.Error(w, "Failed to start auth flow: "+err.Error(), http.StatusInternalServerError)
61-
return
62-
}
63-
64-
// Return successful response
65-
w.Header().Set("Content-Type", "application/json")
66-
w.WriteHeader(http.StatusOK)
67-
if err := json.NewEncoder(w).Encode(map[string]any{
68-
"flow_info": flowInfo,
69-
"status_token": statusToken,
70-
"expires_in": 300, // 5 minutes
71-
}); err != nil {
72-
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
73-
return
74-
}
75-
}
76-
}
77-
78-
// CheckAuthStatusHandler handles requests to check the status of an authentication flow
79-
func CheckAuthStatusHandler(authService auth.Service) http.HandlerFunc {
80-
return func(w http.ResponseWriter, r *http.Request) {
81-
// Only allow GET method
82-
if r.Method != http.MethodGet {
83-
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
84-
return
69+
return nil, huma.Error500InternalServerError("Failed to start auth flow", err)
8570
}
8671

87-
// Get status token from query parameter
88-
statusToken := r.URL.Query().Get("token")
89-
if statusToken == "" {
90-
http.Error(w, "Status token is required", http.StatusBadRequest)
91-
return
92-
}
72+
return &Response[StartAuthBody]{
73+
Body: StartAuthBody{
74+
AuthFlowInfo: flowInfo,
75+
StatusToken: statusToken,
76+
},
77+
}, nil
78+
})
9379

94-
// Check auth status
95-
token, err := authService.CheckAuthStatus(r.Context(), statusToken)
80+
// Check auth status endpoint
81+
huma.Register(api, huma.Operation{
82+
OperationID: "check-auth-status",
83+
Method: http.MethodGet,
84+
Path: "/v0/auth/status/{token}",
85+
Summary: "Check authentication status",
86+
Description: "Check the status of an ongoing authentication flow",
87+
Tags: []string{"auth"},
88+
}, func(ctx context.Context, input *CheckAuthStatusInput) (*Response[CheckAuthStatusBody], error) {
89+
// Check the auth status
90+
status, err := authService.CheckAuthStatus(ctx, input.StatusToken)
9691
if err != nil {
97-
if err.Error() == "pending" {
98-
// Auth is still pending
99-
w.Header().Set("Content-Type", "application/json")
100-
w.WriteHeader(http.StatusOK)
101-
if err := json.NewEncoder(w).Encode(map[string]any{
102-
"status": "pending",
103-
}); err != nil {
104-
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
105-
return
106-
}
107-
return
108-
}
109-
110-
// Other error
111-
http.Error(w, "Failed to check auth status: "+err.Error(), http.StatusInternalServerError)
112-
return
92+
return nil, huma.Error404NotFound("Auth flow not found or expired")
11393
}
11494

115-
// Authentication completed successfully
116-
w.Header().Set("Content-Type", "application/json")
117-
w.WriteHeader(http.StatusOK)
118-
if err := json.NewEncoder(w).Encode(map[string]any{
119-
"status": "complete",
120-
"token": token,
121-
}); err != nil {
122-
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
123-
return
124-
}
125-
}
126-
}
95+
return &Response[CheckAuthStatusBody]{
96+
Body: CheckAuthStatusBody{
97+
Status: status,
98+
},
99+
}, nil
100+
})
101+
}

0 commit comments

Comments
 (0)